From 12069a35f9145faede1b9266a1c4a9144e0630c3 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg <42micro@gmail.com> Date: Tue, 24 Dec 2013 14:51:43 -0500 Subject: [PATCH 1/3] Ensure we check Snapchat's SSL cert when POSTing --- snapchat_core/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapchat_core/api.py b/snapchat_core/api.py index ad0b6d7..c23fbe1 100755 --- a/snapchat_core/api.py +++ b/snapchat_core/api.py @@ -235,7 +235,7 @@ def _post_or_fail(resource, request_params, files=None): the POST request. Formatted like: {'data': ('file', data)} """ uri = "%s%s" % (constants.ROOT_URL, resource) - result = requests.post(uri, request_params, files = files) + result = requests.post(uri, request_params, files = files, verify=True) if result.status_code != 200: raise Exception("POST request failed with status code %d" % (result.status_code)) From 7848e51b86eb08d158a03f027d8c3934ff9d9363 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg <42micro@gmail.com> Date: Fri, 27 Dec 2013 18:22:47 -0500 Subject: [PATCH 2/3] Added single-snap deletion feature --- sfs | 4 ++++ snapchat_core/api.py | 49 ++++++++++++++++++++++++++++++++++++++ snapchat_core/constants.py | 1 + snapchat_fs/__init__.py | 2 +- snapchat_fs/snapchatfs.py | 10 ++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/sfs b/sfs index 0d98c05..e00343d 100755 --- a/sfs +++ b/sfs @@ -6,6 +6,7 @@ Usage: sfs download_all sfs download ... sfs upload ... + sfs delete ... sfs (-h | --help) sfs (-v | --version) @@ -139,6 +140,9 @@ def cli(arguments): if arguments['upload']: for filename in arguments['']: upload_sfs_file(session, filename) + if arguments['delete']: + for id in arguments['']: + delete_sfs_file(session, id) def main(): global SETTINGS diff --git a/snapchat_core/api.py b/snapchat_core/api.py index c23fbe1..9b57c6d 100755 --- a/snapchat_core/api.py +++ b/snapchat_core/api.py @@ -10,6 +10,7 @@ import constants from friend import Friend from snaps import Snap, SentSnap, ReceivedSnap +import json __author__ = "Alex Clemmer, Chad Brubaker" __copyright__ = "Copyright 2013, Alex Clemmer and Chad Brubaker" @@ -28,7 +29,9 @@ CLIENT_ID = 'id' COUNTRY_CODE = 'country_code' DISPLAY = 'display' +EVENTS = 'events' FRIENDS = 'friends' +JSON = 'json' MEDIA_ID = 'media_id' NAME = 'name' PASSWORD = 'password' @@ -141,6 +144,52 @@ def send_image_to(self, recipient, media_id, time = 10 result = SnapchatSession._post_or_fail(constants.SEND_RESOURCE , params) + # Thank you very much to gibsonsec for their API docs[1] + # and thank you to php-snapchat[2] for their reference implementation + # [1] http://gibsonsec.org/snapchat/fulldisclosure/#sending-updates-bqupdate_snaps + # [2] https://github.com/dstelljes/php-snapchat/blob/master/src/snapchat.php#L750 + def delete_image(self, snap_id): + """ + Deletes an image from Snapchat + @snap_id The ID of the snap to be deleted + """ + # time.time() is the number of seconds since epoch represented as a float: + # . + snap_info = { + snap_id: { + 't' : int(time.time()) + , 'c' : 0 + , 'replayed' : 0 + } + } + # Say we viewed the snap 10 seconds ago and it expired just now, + # i.e., we viewed the snap for 10 seconds + event_one = { + 'mEventName' : 'SNAP_VIEW' + , 'mTimestamp' : int(time.time())-10 + , 'mParams' : {'id': snap_id} + } + event_two = { + 'mEventName' : 'SNAP_EXPIRED' + , 'mTimestamp' : int(time.time()) + , 'mParams' : {'id': snap_id} + } + + events = [event_one, event_two] + timestamp = SnapchatSession._generate_timestamp() + # We must use json.dumps() because the API take strings as values, + # not JSON dicts + req_params = { + EVENTS : json.dumps(events) + , JSON : json.dumps(snap_info) + , TIMESTAMP : timestamp + , USERNAME : self.username + , REQ_TOKEN : SnapchatSession._generate_req_token( + self.session_token, timestamp) + } + result = SnapchatSession._post_or_fail(constants.UPDATE_SNAPS_RESOURCE + , req_params) + def get_snaps(self, filter_func=lambda snap: True): """ Returns array of Snaps sent to current user, represented as a list diff --git a/snapchat_core/constants.py b/snapchat_core/constants.py index 1a7d91a..65a74ad 100755 --- a/snapchat_core/constants.py +++ b/snapchat_core/constants.py @@ -9,6 +9,7 @@ SEND_RESOURCE = "/bq/send" UPLOAD_RESOURCE = "/bq/upload" BLOB_RESOURCE = "/bq/blob" +UPDATE_SNAPS_RESOURCE = "/bq/update_snaps" # we pass this token to the service to "authenticate" that we're supposed # to be able to log in diff --git a/snapchat_fs/__init__.py b/snapchat_fs/__init__.py index abbfe4c..a10b9ac 100755 --- a/snapchat_fs/__init__.py +++ b/snapchat_fs/__init__.py @@ -1 +1 @@ -from snapchatfs import list_all_downloadable_sfs_files, download_all_sfs, download_by_id, upload_sfs_file +from snapchatfs import list_all_downloadable_sfs_files, download_all_sfs, download_by_id, upload_sfs_file, delete_sfs_file diff --git a/snapchat_fs/snapchatfs.py b/snapchat_fs/snapchatfs.py index be2ad22..a466925 100755 --- a/snapchat_fs/snapchatfs.py +++ b/snapchat_fs/snapchatfs.py @@ -167,3 +167,13 @@ def upload_sfs_file(session, filename): sfs_id = session.generate_sfs_id(basename, data) session.upload_image(data, sfs_id) session.send_image_to(session.username, sfs_id) + +def delete_sfs_file(session, snap_id): + """ + Deletes a file from Snapchat FS. + + @session An SfsSession object that has been logged in. + @snap_id The ID of the snap to delete + """ + session.delete_image(snap_id) + print util.green('Deleted file with ID ') + (snap_id) From 3e3ec42d8221f7667b68413e37500596269b8855 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg <42micro@gmail.com> Date: Fri, 27 Dec 2013 22:30:47 -0500 Subject: [PATCH 3/3] sfs list output now aligns well and centers headers; also uses newer Python formatting syntax --- snapchat_fs/snapchatfs.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/snapchat_fs/snapchatfs.py b/snapchat_fs/snapchatfs.py index a466925..4276b06 100755 --- a/snapchat_fs/snapchatfs.py +++ b/snapchat_fs/snapchatfs.py @@ -147,10 +147,23 @@ def list_all_downloadable_sfs_files(session): """ files = all_downloadable_sfs_files(session) - print '\t'.join([util.bold('Filename'), util.bold('Content hash'), util.bold('Snap ID')]) + max_filename_len = 0 + # Apparently snap IDs can have different lengths + max_id_len = 0 + for filename, _, _, snap in files: + max_filename_len = max(max_filename_len, len(filename)) + max_id_len = max(max_id_len, len(snap.id)) + # Center the headers + print util.bold('{:^{width}} ').format('Filename' + , width=max_filename_len) +\ + util.bold('{:^{width}} ').format('Content hash' + , width=23) +\ + util.bold('{:^{width}} ').format('Snap ID' + , width=max_id_len) for filename, content_hash, received_id, snap in files: - print '%s\t%s...%s\t%s' % (filename, content_hash[:17] - , content_hash[-3:], snap.id) + print '{:<{f_width}} {}...{} {}'.format(filename, content_hash[:17] + , content_hash[-3:], snap.id + , f_width=max_filename_len) def upload_sfs_file(session, filename): """