Skip to content

Instantly share code, notes, and snippets.

@ewpratten
Last active October 23, 2021 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ewpratten/5d5d21cd71c3543058ae10535d1784b8 to your computer and use it in GitHub Desktop.
Save ewpratten/5d5d21cd71c3543058ae10535d1784b8 to your computer and use it in GitHub Desktop.
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
import argparse
import sys
from datetime import datetime
import json
import requests
from multiprocessing.pool import ThreadPool
import os
def download_memory(data):
filename, url = data
print(f"Downloading {filename}")
# Ask the snap servers for the real url of the file
url = requests.post(url).text
# Create the directories needed for the file
if not os.path.isdir(os.path.dirname(filename)):
os.makedirs(os.path.dirname(filename))
# Download and save the raw file
r = requests.get(url, stream=True)
with open(filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
def main() -> int:
# Handle program arguments
ap = argparse.ArgumentParser(
prog='snapchat_memories_dl', description='Download snapchat memories from a specific time period')
ap.add_argument('-t', '--threads', type=int, required=False, default=10,
help='Number of threads to use when downloading media')
ap.add_argument('-s', '--start', type=int, required=True,
help='Start date of the time period (as unix timestamp)')
ap.add_argument('-e', '--end', type=int, required=True,
help='End date of the time period (as unix timestamp)')
ap.add_argument('-o', '--output', type=str,
required=True, help='Output directory')
ap.add_argument("memories", type=str,
help="Path to `memories_history.json` file")
args = ap.parse_args()
# Convert the arguments to datetime objects
start = datetime.fromtimestamp(int(args.start))
end = datetime.fromtimestamp(int(args.end))
print(f"Start timestamp: {start}")
print(f"End timestamp: {end}")
# Read a list of memories into memory for later processing
memories = {}
print("Processing memories")
with open(args.memories, 'r') as f:
memories_json = json.load(f)
for media in memories_json['Saved Media']:
# Convert the timestamp to a datetime object
timestamp = datetime.fromisoformat(
media['Date'].replace(' UTC', ''))
# Check if the timestamp is within the specified time period
if start <= timestamp <= end:
# Build a filename
filename = os.path.join(
args.output,
"memory-{}.{}".format(
timestamp.strftime("%Y%m%d-%H%M%S"),
"jfif" if media['Media Type'] == "PHOTO" else "mp4"
)
)
memories[filename] = media['Download Link']
print(f"Found {len(memories)} memories in the specified time period")
# Build a thread pool for work
pool = ThreadPool(args.threads)
pool.map(download_memory, memories.items())
print(f"Downloaded {len(memories)} items")
print(f"You should run: cd {args.output}; mogrify -format jpg *.jfif && rm *.jfif")
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment