Last active
October 23, 2021 22:08
-
-
Save ewpratten/5d5d21cd71c3543058ae10535d1784b8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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