├── app ├── base │ ├── __init__.py │ ├── constants.py │ └── util.py ├── django_settings.py ├── birdfeeder │ ├── __init__.py │ └── handlers │ │ ├── __init__.py │ │ ├── pingersecret.py.template │ │ ├── main.py │ │ ├── tools.py │ │ └── pinger.py ├── datasources │ ├── __init__.py │ ├── oauth2 │ │ ├── clients │ │ │ ├── __init__.py │ │ │ ├── imap.py │ │ │ └── smtp.py │ │ └── _version.py │ ├── test_oauth_access_token.py │ └── get_oauth_access_token.py ├── feedplayback │ └── __init__.py ├── mastofeeder │ ├── __init__.py │ └── handlers │ │ ├── __init__.py │ │ └── main.py ├── tweetdigest │ └── __init__.py ├── third_party │ ├── urllib3 │ │ ├── contrib │ │ │ ├── __init__.py │ │ │ ├── _securetransport │ │ │ │ └── __init__.py │ │ │ └── _appengine_environ.py │ │ ├── packages │ │ │ ├── __init__.py │ │ │ └── backports │ │ │ │ ├── __init__.py │ │ │ │ └── makefile.py │ │ ├── _version.py │ │ ├── util │ │ │ ├── queue.py │ │ │ ├── __init__.py │ │ │ └── proxy.py │ │ └── filepost.py │ ├── chardet │ │ ├── cli │ │ │ ├── __init__.py │ │ │ └── chardetect.py │ │ ├── metadata │ │ │ └── __init__.py │ │ ├── version.py │ │ ├── compat.py │ │ ├── euctwprober.py │ │ ├── euckrprober.py │ │ ├── gb2312prober.py │ │ ├── big5prober.py │ │ ├── enums.py │ │ ├── cp949prober.py │ │ ├── mbcsgroupprober.py │ │ └── utf8prober.py │ ├── idna-2.10.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ ├── RECORD │ │ └── LICENSE.rst │ ├── requests_toolbelt │ │ ├── auth │ │ │ ├── __init__.py │ │ │ └── _digest_auth_compat.py │ │ ├── cookies │ │ │ ├── __init__.py │ │ │ └── forgetful.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── deprecated.py │ │ ├── downloadutils │ │ │ └── __init__.py │ │ ├── adapters │ │ │ ├── __init__.py │ │ │ ├── host_header_ssl.py │ │ │ ├── fingerprint.py │ │ │ ├── ssl.py │ │ │ └── source.py │ │ ├── multipart │ │ │ └── __init__.py │ │ ├── exceptions.py │ │ ├── __init__.py │ │ ├── threaded │ │ │ └── thread.py │ │ └── sessions.py │ ├── chardet-4.0.0.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── entry_points.txt │ │ └── WHEEL │ ├── six-1.16.0.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ ├── RECORD │ │ ├── LICENSE │ │ └── METADATA │ ├── certifi-2021.10.8.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ ├── RECORD │ │ └── LICENSE │ ├── decorator-4.4.2.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── pbr.json │ │ ├── WHEEL │ │ ├── RECORD │ │ └── LICENSE.txt │ ├── python_dateutil-2.8.2.dist-info │ │ ├── zip-safe │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ ├── RECORD │ │ └── LICENSE │ ├── requests-2.27.1.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ └── RECORD │ ├── urllib3-1.26.13.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ └── LICENSE.txt │ ├── idna │ │ ├── package_data.py │ │ ├── __init__.py │ │ ├── compat.py │ │ └── intranges.py │ ├── requests_toolbelt-0.9.1.dist-info │ │ ├── INSTALLER │ │ ├── top_level.txt │ │ ├── WHEEL │ │ ├── LICENSE │ │ └── AUTHORS.rst │ ├── dateutil │ │ ├── tzwin.py │ │ ├── zoneinfo │ │ │ ├── dateutil-zoneinfo.tar.gz │ │ │ └── rebuild.py │ │ ├── _version.py │ │ ├── __init__.py │ │ ├── tz │ │ │ ├── __init__.py │ │ │ └── _factories.py │ │ ├── _common.py │ │ ├── parser │ │ │ └── __init__.py │ │ ├── utils.py │ │ └── easter.py │ ├── certifi │ │ ├── __init__.py │ │ ├── __main__.py │ │ └── core.py │ ├── pytz │ │ └── zoneinfo.zip │ ├── bin │ │ └── chardetect │ └── requests │ │ ├── __version__.py │ │ ├── certs.py │ │ ├── hooks.py │ │ ├── packages.py │ │ ├── _internal_utils.py │ │ └── compat.py ├── queue.yaml ├── static │ ├── googled756c8e5e6bbce3a.html │ ├── header.png │ ├── favicon.ico │ ├── feed-icon.png │ ├── twitter-icon.png │ ├── apple-touch-icon.png │ ├── twitter-sign-in.png │ ├── header-background.png │ ├── robots.txt │ ├── birdfeeder.js │ ├── mastofeeder.js │ ├── main.js │ └── util.js ├── cron.yaml ├── templates │ ├── tweetdigest │ │ ├── usernames.snippet │ │ ├── list.snippet │ │ ├── digest-contents.snippet │ │ ├── retired-digest.html │ │ ├── legacy-digest.html │ │ ├── retired-digest.atom │ │ ├── digest.atom │ │ ├── legacy-digest.atom │ │ ├── digest.html │ │ └── index.html │ ├── mastofeeder │ │ ├── footer.snippet │ │ ├── account-link.snippet │ │ ├── digest.html │ │ ├── intro.snippet │ │ ├── digest-contents.snippet │ │ ├── digest.atom │ │ ├── index-signed-out.html │ │ ├── feed.html │ │ ├── status-footer.snippet │ │ ├── feed.atom │ │ ├── status-group.snippet │ │ ├── index-signed-in.html │ │ └── status.snippet │ ├── birdfeeder │ │ ├── footer.snippet │ │ ├── intro.snippet │ │ ├── index-signed-out.html │ │ ├── backup.atom │ │ ├── index-signed-in.html │ │ ├── feed.atom │ │ └── backup.html │ ├── not-found.html │ ├── feedplayback │ │ ├── intro-body.snippet │ │ └── subscription.html │ ├── base │ │ ├── page.html │ │ └── status-group.snippet │ └── index.html ├── queue.yaml.disabled ├── dos.yaml ├── index.yaml ├── cron.yaml.disabled ├── app.yaml ├── appengine_config.py └── cron_tasks.py ├── worker ├── .vscode │ └── settings.json ├── src │ ├── lib │ │ ├── index.ts │ │ ├── assets │ │ │ ├── header.png │ │ │ ├── feed-icon.png │ │ │ ├── feed-icon@2x.png │ │ │ └── header-background.png │ │ ├── constants.ts │ │ ├── components │ │ │ ├── AccountLink.svelte │ │ │ ├── MastodonDebugHtml.svelte │ │ │ └── MastodonStatusFooter.svelte │ │ ├── masto-feeder │ │ │ └── types.ts │ │ ├── masto.ts │ │ └── kv.ts │ ├── routes │ │ └── masto-feeder │ │ │ ├── +layout.server.ts │ │ │ ├── sign-in-callback │ │ │ └── +server.ts │ │ │ ├── feed │ │ │ └── [feedId] │ │ │ │ ├── parent │ │ │ │ └── [statusId] │ │ │ │ │ └── +server.ts │ │ │ │ └── timeline │ │ │ │ └── +server.ts │ │ │ └── +page.server.ts │ ├── app.html │ └── app.d.ts ├── static │ └── favicon.ico ├── .prettierignore ├── .gitignore ├── vite.config.ts ├── svelte.config.js ├── wrangler.toml ├── tsconfig.json ├── eslint.config.js ├── README.md └── package.json ├── assets ├── favicon.psd ├── header.psd ├── profile icon.psd └── profile-icon.gif ├── .gitignore ├── twitter-digest-stub ├── app.yaml ├── main.py └── index.yaml ├── Makefile └── birdpinger └── README /app/base/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/django_settings.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/birdfeeder/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/datasources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/feedplayback/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mastofeeder/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/tweetdigest/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/birdfeeder/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mastofeeder/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/datasources/oauth2/clients/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/urllib3/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/chardet/cli/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/third_party/chardet/metadata/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/urllib3/packages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/idna-2.10.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/cookies/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/chardet-4.0.0.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/idna-2.10.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | idna 2 | -------------------------------------------------------------------------------- /app/third_party/six-1.16.0.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/six-1.16.0.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | six 2 | -------------------------------------------------------------------------------- /app/third_party/urllib3/packages/backports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/certifi-2021.10.8.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/decorator-4.4.2.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/python_dateutil-2.8.2.dist-info/zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/third_party/requests-2.27.1.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/downloadutils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/urllib3-1.26.13.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/urllib3/contrib/_securetransport/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/third_party/chardet-4.0.0.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | chardet 2 | -------------------------------------------------------------------------------- /app/third_party/idna/package_data.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.10' 2 | 3 | -------------------------------------------------------------------------------- /app/third_party/python_dateutil-2.8.2.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/requests-2.27.1.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt-0.9.1.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /app/third_party/urllib3-1.26.13.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | urllib3 2 | -------------------------------------------------------------------------------- /app/third_party/certifi-2021.10.8.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | certifi 2 | -------------------------------------------------------------------------------- /app/third_party/decorator-4.4.2.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | decorator 2 | -------------------------------------------------------------------------------- /app/third_party/python_dateutil-2.8.2.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | dateutil 2 | -------------------------------------------------------------------------------- /worker/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt-0.9.1.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | requests_toolbelt 2 | -------------------------------------------------------------------------------- /app/queue.yaml: -------------------------------------------------------------------------------- 1 | queue: 2 | - name: birdfeeder-update 3 | rate: 5/s 4 | bucket_size: 5 5 | -------------------------------------------------------------------------------- /app/static/googled756c8e5e6bbce3a.html: -------------------------------------------------------------------------------- 1 | google-site-verification: googled756c8e5e6bbce3a.html -------------------------------------------------------------------------------- /assets/favicon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/assets/favicon.psd -------------------------------------------------------------------------------- /assets/header.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/assets/header.psd -------------------------------------------------------------------------------- /app/static/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/header.png -------------------------------------------------------------------------------- /app/third_party/decorator-4.4.2.dist-info/pbr.json: -------------------------------------------------------------------------------- 1 | {"is_release": false, "git_version": "8608a46"} -------------------------------------------------------------------------------- /app/third_party/idna/__init__.py: -------------------------------------------------------------------------------- 1 | from .package_data import __version__ 2 | from .core import * 3 | -------------------------------------------------------------------------------- /app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/favicon.ico -------------------------------------------------------------------------------- /app/static/feed-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/feed-icon.png -------------------------------------------------------------------------------- /app/third_party/dateutil/tzwin.py: -------------------------------------------------------------------------------- 1 | # tzwin has moved to dateutil.tz.win 2 | from .tz.win import * 3 | -------------------------------------------------------------------------------- /assets/profile icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/assets/profile icon.psd -------------------------------------------------------------------------------- /assets/profile-icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/assets/profile-icon.gif -------------------------------------------------------------------------------- /worker/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /app/third_party/urllib3/_version.py: -------------------------------------------------------------------------------- 1 | # This file is protected via CODEOWNERS 2 | __version__ = "1.26.13" 3 | -------------------------------------------------------------------------------- /worker/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/worker/static/favicon.ico -------------------------------------------------------------------------------- /app/static/twitter-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/twitter-icon.png -------------------------------------------------------------------------------- /app/third_party/certifi/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import contents, where 2 | 3 | __version__ = "2021.10.08" 4 | -------------------------------------------------------------------------------- /app/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/apple-touch-icon.png -------------------------------------------------------------------------------- /app/static/twitter-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/twitter-sign-in.png -------------------------------------------------------------------------------- /app/static/header-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/static/header-background.png -------------------------------------------------------------------------------- /app/third_party/pytz/zoneinfo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/third_party/pytz/zoneinfo.zip -------------------------------------------------------------------------------- /worker/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /worker/src/lib/assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/worker/src/lib/assets/header.png -------------------------------------------------------------------------------- /app/third_party/chardet-4.0.0.dist-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | chardetect = chardet.cli.chardetect:main 3 | 4 | -------------------------------------------------------------------------------- /worker/src/lib/assets/feed-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/worker/src/lib/assets/feed-icon.png -------------------------------------------------------------------------------- /worker/src/lib/assets/feed-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/worker/src/lib/assets/feed-icon@2x.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | app/datasources/oauth_keys.py 3 | *.[68] 4 | birdpinger/birdpinger 5 | app/birdfeeder/handlers/pingersecret.py 6 | -------------------------------------------------------------------------------- /worker/src/lib/assets/header-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/worker/src/lib/assets/header-background.png -------------------------------------------------------------------------------- /app/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /tweet-digest/digest 3 | Disallow: /feed-playback/subscription 4 | Disallow: /bird-feeder/feed/ 5 | -------------------------------------------------------------------------------- /app/cron.yaml: -------------------------------------------------------------------------------- 1 | cron: 2 | - description: Daily exception report 3 | url: /_ereporter?sender=mihai.parparita@gmail.com&delete=true 4 | schedule: every day 00:00 5 | -------------------------------------------------------------------------------- /app/third_party/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaip/streamspigot/HEAD/app/third_party/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz -------------------------------------------------------------------------------- /app/third_party/idna-2.10.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.33.6) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/six-1.16.0.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.36.2) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/certifi-2021.10.8.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.35.1) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/chardet-4.0.0.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.35.1) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/decorator-4.4.2.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.33.4) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/requests-2.27.1.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.37.1) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/urllib3-1.26.13.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.38.4) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/usernames.snippet: -------------------------------------------------------------------------------- 1 | {% for username in usernames %}{{ username }}{% if not forloop.last %}, {% endif %}{% endfor %} -------------------------------------------------------------------------------- /app/third_party/python_dateutil-2.8.2.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.36.2) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/footer.snippet: -------------------------------------------------------------------------------- 1 | Feeds are exported under randomly-generated URLs. Though they should not be guessable, they may end up "leaking" if accidentally sent to someone. 2 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt-0.9.1.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.32.3) 3 | Root-Is-Purelib: true 4 | Tag: py2-none-any 5 | Tag: py3-none-any 6 | 7 | -------------------------------------------------------------------------------- /app/third_party/dateutil/_version.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # file generated by setuptools_scm 3 | # don't change, don't track in version control 4 | version = '2.8.2' 5 | version_tuple = (2, 8, 2) 6 | -------------------------------------------------------------------------------- /app/queue.yaml.disabled: -------------------------------------------------------------------------------- 1 | queue: 2 | - name: feedplayback-advance 3 | rate: 1/s 4 | bucket_size: 1 5 | 6 | - name: birdfeeder-crawl-on-demand 7 | rate: 5/s 8 | bucket_size: 5 9 | 10 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/list.snippet: -------------------------------------------------------------------------------- 1 | the {{ list_id }} 2 | list created by {{ list_owner }} -------------------------------------------------------------------------------- /twitter-digest-stub/app.yaml: -------------------------------------------------------------------------------- 1 | application: twitter-digest-hrd 2 | version: 2 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | handlers: 8 | - url: /.* 9 | script: main.app 10 | 11 | -------------------------------------------------------------------------------- /worker/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # SvelteKit generated code 4 | /.svelte-kit 5 | 6 | # Build artifacts when building the worker 7 | .cloudflare 8 | 9 | # wrangler local state when running `wrangler dev` 10 | .wrangler 11 | -------------------------------------------------------------------------------- /worker/src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const APP_NAME = "Stream Spigot"; 2 | 3 | export const ANCHOR_COLOR = "#2db300"; 4 | export const BUBBLE_COLOR = "#00000009"; 5 | export const BUBBLE_TEXT_COLOR = "#41419b"; 6 | export const USER_LINK_COLOR = "#666"; 7 | -------------------------------------------------------------------------------- /app/templates/birdfeeder/footer.snippet: -------------------------------------------------------------------------------- 1 | Though the feed URL that your timeline is available under is not guessable, you 2 | may inadvertedly "leak" it if you are not careful. For example, emailing an item 3 | from your timeline feed in Google Reader exposes the feed URL. -------------------------------------------------------------------------------- /app/third_party/dateutil/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | try: 3 | from ._version import version as __version__ 4 | except ImportError: 5 | __version__ = 'unknown' 6 | 7 | __all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', 8 | 'utils', 'zoneinfo'] 9 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/cookies/forgetful.py: -------------------------------------------------------------------------------- 1 | """The module containing the code for ForgetfulCookieJar.""" 2 | from requests.cookies import RequestsCookieJar 3 | 4 | 5 | class ForgetfulCookieJar(RequestsCookieJar): 6 | def set_cookie(self, *args, **kwargs): 7 | return 8 | -------------------------------------------------------------------------------- /app/third_party/chardet/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module exists only to simplify retrieving the version number of chardet 3 | from within setup.py and from chardet subpackages. 4 | 5 | :author: Dan Blanchard (dan.blanchard@gmail.com) 6 | """ 7 | 8 | __version__ = "4.0.0" 9 | VERSION = __version__.split('.') 10 | -------------------------------------------------------------------------------- /app/third_party/idna/compat.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | from .codec import * 3 | 4 | def ToASCII(label): 5 | return encode(label) 6 | 7 | def ToUnicode(label): 8 | return decode(label) 9 | 10 | def nameprep(s): 11 | raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") 12 | 13 | -------------------------------------------------------------------------------- /worker/src/routes/masto-feeder/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import {MastoFeederController} from "$lib/masto-feeder/controller"; 2 | 3 | export async function load(event) { 4 | const controller = new MastoFeederController(event); 5 | return { 6 | session: (await controller.getSession()) ?? undefined, 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /app/birdfeeder/handlers/pingersecret.py.template: -------------------------------------------------------------------------------- 1 | # To not make it too easy for drive-by users to trigger birdfeeder pings, 2 | # rename this file to pingersecret.py, fillin in a value for the secret key, 3 | # and pass it to the birdfeeder binary with the --stream_spigot_secret command 4 | # line flag. 5 | 6 | SECRET = 'SECRET_KEY_GOES_HERE' -------------------------------------------------------------------------------- /app/third_party/certifi/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from certifi import contents, where 4 | 5 | parser = argparse.ArgumentParser() 6 | parser.add_argument("-c", "--contents", action="store_true") 7 | args = parser.parse_args() 8 | 9 | if args.contents: 10 | print(contents()) 11 | else: 12 | print(where()) 13 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/account-link.snippet: -------------------------------------------------------------------------------- 1 | {% with display_status.status.account as account %} 2 | 3 | {{ display_status.account_display_name }} 4 | 6 | @{{ account.username }} 7 | 8 | 9 | {% endwith %} 10 | -------------------------------------------------------------------------------- /app/templates/not-found.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block intro %} 4 |

5 | Ooops, can't find that page. Go to the {{ APP_NAME }} 6 | homepage and see if you can find what you're looking for there. 7 |

8 | {% endblock %} 9 | 10 | {% block footer %} 11 | π 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /worker/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {sveltekit} from "@sveltejs/kit/vite"; 2 | import {defineConfig} from "vite"; 3 | 4 | export default defineConfig({ 5 | server: { 6 | host: true, 7 | port: 3413, 8 | }, 9 | preview: { 10 | host: true, 11 | port: 4413, 12 | }, 13 | plugins: [sveltekit()], 14 | }); 15 | -------------------------------------------------------------------------------- /app/third_party/bin/chardetect: -------------------------------------------------------------------------------- 1 | #!/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python 2 | # -*- coding: utf-8 -*- 3 | import re 4 | import sys 5 | 6 | from chardet.cli.chardetect import main 7 | 8 | if __name__ == '__main__': 9 | sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 10 | sys.exit(main()) 11 | -------------------------------------------------------------------------------- /app/templates/feedplayback/intro-body.snippet: -------------------------------------------------------------------------------- 1 |

This is a feed playback for the 2 | {{ feed_title }} feed.

3 | 4 |

The first item appears below, and after this a new item will appear 5 | {{ frequency }} (there will be {{ item_count }} items total).

6 | 7 |

See this page more 8 | information about feed playback.

9 | -------------------------------------------------------------------------------- /app/datasources/oauth2/_version.py: -------------------------------------------------------------------------------- 1 | # This is the version of this source code. 2 | 3 | verstr = "1.3.128" 4 | try: 5 | from pyutil.version_class import Version as pyutil_Version 6 | __version__ = pyutil_Version(verstr) 7 | except (ImportError, ValueError): 8 | # Maybe there is no pyutil installed. 9 | from distutils.version import LooseVersion as distutils_Version 10 | __version__ = distutils_Version(verstr) 11 | -------------------------------------------------------------------------------- /worker/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dev: 2 | @PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python dev_appserver.py --port=8081 app 3 | 4 | deploy: 5 | gcloud app deploy --project streamspigot-hrd app/app.yaml 6 | 7 | twitter-digest-stub-dev: 8 | @PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python dev_appserver.py --port=8082 twitter-digest-stub 9 | 10 | twitter-digest-stub-deploy: 11 | @PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python /usr/local/bin/appcfg.py update twitter-digest-stub 12 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/digest-contents.snippet: -------------------------------------------------------------------------------- 1 |
2 | {% if grouped_statuses %} 3 | {% for status_group in grouped_statuses %} 4 | {% with '1.5em' as bottom_margin %} 5 | {% include 'base/status-group.snippet' %} 6 | {% endwith %} 7 | {% endfor %} 8 | {% else %} 9 |
10 | No updates for this day. 11 |
12 | {% endif %} 13 |
14 | -------------------------------------------------------------------------------- /worker/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-cloudflare"; 2 | import {vitePreprocess} from "@sveltejs/vite-plugin-svelte"; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: {adapter: adapter()}, 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /app/dos.yaml: -------------------------------------------------------------------------------- 1 | blacklist: 2 | - subnet: 80.37.163.114 3 | description: High traffic claiming to be from Lynx 4 | - subnet: 137.254.4.13 5 | description: Overly aggressive tweet digest fetcher (claims to be IE) 6 | - subnet: 144.76.37.210 7 | description: Overly aggressive tweet digest fetcher (claims to be SilverReader) 8 | - subnet: 72.14.199.0/24 9 | description: Google's Feedfetcher 10 | - subnet: 209.85.238.0/24 11 | description: Google's Feedfetcher 12 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/digest.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }}: {{ feed_title }}{% endblock %} 4 | 5 | {% block subtitle %}{{ feed_title }}{% endblock %} 6 | {% block subsubtitle %}{{ title_date }}{% endblock %} 7 | 8 | {% block intro %} 9 |

Debug HTML output.

10 | {% endblock %} 11 | 12 | {% block body %} 13 | {{ digest_contents|safe }} 14 | {% endblock %} 15 | 16 | {% block footer %} 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/adapters/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | requests-toolbelt.adapters 4 | ========================== 5 | 6 | See http://toolbelt.rtfd.org/ for documentation 7 | 8 | :copyright: (c) 2014 by Ian Cordasco and Cory Benfield 9 | :license: Apache v2.0, see LICENSE for more details 10 | """ 11 | 12 | from .ssl import SSLAdapter 13 | from .source import SourceAddressAdapter 14 | 15 | __all__ = ['SSLAdapter', 'SourceAddressAdapter'] 16 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/retired-digest.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }} : Retired Tweet Digest{% endblock %} 4 | {% block subtitle %}Retired Tweet Digest{% endblock %} 5 | 6 | {% block intro %} 7 |

Tweet Digest can no longer operate (due to Twitter restricting API access) and has thus been retired.

8 | {% endblock %} 9 | 10 | {% block footer %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /birdpinger/README: -------------------------------------------------------------------------------- 1 | To build and run 2 | 3 | 1. Install Go per http://golang.org/doc/install.html 4 | 2. go get github.com/araddon/httpstream 5 | 3. go get github.com/araddon/goauth 6 | 4. cd birdpinger 7 | 5. go build 8 | 6. ./birdpinger \ 9 | --twitter_username=[username] \ 10 | --twitter_password=[password] \ 11 | --stream_spigot_hostname= \ 12 | --stream_spigot_secret= 2 | import {USER_LINK_COLOR} from "$lib/constants"; 3 | import {displayName} from "$lib/masto-feeder/display-status"; 4 | import type {mastodon} from "masto"; 5 | 6 | let { 7 | account, 8 | }: { 9 | account: mastodon.v1.Account; 10 | } = $props(); 11 | 12 | 13 | {displayName(account)} 14 | 15 | @{account.username} 16 | 17 | -------------------------------------------------------------------------------- /worker/src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import type {MastoFeederSession} from "$lib/masto-feeder/types"; 5 | 6 | declare global { 7 | namespace App { 8 | interface Platform { 9 | env?: { 10 | MASTOFEEDER: KVNamespace; 11 | }; 12 | } 13 | interface Locals { 14 | mastoFeederSession?: MastoFeederSession; 15 | } 16 | } 17 | } 18 | 19 | export {}; 20 | -------------------------------------------------------------------------------- /app/third_party/requests/certs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | requests.certs 6 | ~~~~~~~~~~~~~~ 7 | 8 | This module returns the preferred default CA certificate bundle. There is 9 | only one — the one from the certifi package. 10 | 11 | If you are packaging Requests, e.g., for a Linux distribution or a managed 12 | environment, you can change the definition of where() to return a separately 13 | packaged CA bundle. 14 | """ 15 | from certifi import where 16 | 17 | if __name__ == '__main__': 18 | print(where()) 19 | -------------------------------------------------------------------------------- /app/third_party/six-1.16.0.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | six-1.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 2 | six-1.16.0.dist-info/LICENSE,sha256=i7hQxWWqOJ_cFvOkaWWtI9gq3_YPI5P8J2K2MYXo5sk,1066 3 | six-1.16.0.dist-info/METADATA,sha256=VQcGIFCAEmfZcl77E5riPCN4v2TIsc_qtacnjxKHJoI,1795 4 | six-1.16.0.dist-info/RECORD,, 5 | six-1.16.0.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 6 | six-1.16.0.dist-info/top_level.txt,sha256=_iVH_iYEtEXnD8nYGQYpYFUvkUW9sEO1GYbkeKSAais,4 7 | six.py,sha256=TOOfQi7nFGfMrIvtdr6wX4wyHH8M7aknmuLfo2cBBrM,34549 8 | six.pyc,, 9 | -------------------------------------------------------------------------------- /worker/src/routes/masto-feeder/sign-in-callback/+server.ts: -------------------------------------------------------------------------------- 1 | import {MastoFeederController} from "$lib/masto-feeder/controller"; 2 | import {error, type RequestHandler} from "@sveltejs/kit"; 3 | 4 | export const GET: RequestHandler = async event => { 5 | const code = event.url.searchParams.get("code"); 6 | if (!code) { 7 | return error(400, "No auth code found"); 8 | } 9 | const state = event.url.searchParams.get("state"); 10 | 11 | const controller = new MastoFeederController(event); 12 | 13 | return await controller.handleSignInCallback(code, state); 14 | }; 15 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/intro.snippet: -------------------------------------------------------------------------------- 1 |

This {{ APP_NAME }} tool lets you subscribe to your 2 | Mastodon timeline and lists in a feedreader such as 3 | NetNewsWire, 4 | NewsBlur, 5 | Reeder or 6 | Feedly.

7 | 8 |

By signining in with your Mastodon account, you enable Masto Feeder to 9 | generate feeds under "secret" URLs that will contain the statuses of 10 | accounts that you follow or have in a list.

11 | -------------------------------------------------------------------------------- /worker/src/lib/masto-feeder/types.ts: -------------------------------------------------------------------------------- 1 | export type MastoFeederApp = { 2 | instanceUrl: string; 3 | clientId: string; 4 | clientSecret: string; 5 | }; 6 | 7 | export type MastoFeederAuthRequest = { 8 | id: string; 9 | instanceUrl: string; 10 | }; 11 | 12 | export type MastoFeederSession = { 13 | sessionId: string; 14 | mastodonId: string; 15 | instanceUrl: string; 16 | feedId: string; 17 | accessToken: string; 18 | prefs?: MastoFeederPrefs; 19 | }; 20 | 21 | export type MastoFeederPrefs = { 22 | timeZone?: string; 23 | useLocalUrls?: boolean; 24 | }; 25 | -------------------------------------------------------------------------------- /worker/src/routes/masto-feeder/feed/[feedId]/parent/[statusId]/+server.ts: -------------------------------------------------------------------------------- 1 | import {MastoFeederController} from "$lib/masto-feeder/controller"; 2 | import {error, type RequestHandler} from "@sveltejs/kit"; 3 | 4 | export const GET: RequestHandler = async event => { 5 | const controller = new MastoFeederController(event); 6 | const {feedId, statusId} = event.params; 7 | if (!feedId) { 8 | return error(400, "Invalid feed ID"); 9 | } 10 | if (!statusId) { 11 | return error(400, "Invalid status ID"); 12 | } 13 | 14 | return controller.handleStatusParent(feedId, statusId); 15 | }; 16 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/digest-contents.snippet: -------------------------------------------------------------------------------- 1 |
2 | {% if status_groups %} 3 |
4 | {% for status_group in status_groups %} 5 | {% include 'status-group.snippet' %} 6 | {% endfor %} 7 |
8 | {% else %} 9 |
10 | No updates for this day. 11 |
12 | {% endif %} 13 |
14 | -------------------------------------------------------------------------------- /app/third_party/urllib3/util/queue.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from ..packages import six 4 | from ..packages.six.moves import queue 5 | 6 | if six.PY2: 7 | # Queue is imported for side effects on MS Windows. See issue #229. 8 | import Queue as _unused_module_Queue # noqa: F401 9 | 10 | 11 | class LifoQueue(queue.Queue): 12 | def _init(self, _): 13 | self.queue = collections.deque() 14 | 15 | def _qsize(self, len=len): 16 | return len(self.queue) 17 | 18 | def _put(self, item): 19 | self.queue.append(item) 20 | 21 | def _get(self): 22 | return self.queue.pop() 23 | -------------------------------------------------------------------------------- /app/static/birdfeeder.js: -------------------------------------------------------------------------------- 1 | import { $ } from "./util.js"; 2 | 3 | export function init() { 4 | $("#birdfeeder-reset-feed-id").addEventListener("click", resetFeedId); 5 | } 6 | 7 | function resetFeedId() { 8 | var feedContainerNode = $("#birdfeeder-feed-container"); 9 | feedContainerNode.classList.add("disabled"); 10 | 11 | fetch("/bird-feeder/reset-feed-id", { method: "POST" }) 12 | .then((response) => response.text()) 13 | .then(() => window.location.reload()) 14 | .catch((error) => { 15 | feedContainerNode.classList.remove("disabled"); 16 | alert("Could not reset the feed URL: " + error); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /app/static/mastofeeder.js: -------------------------------------------------------------------------------- 1 | import { $ } from "./util.js"; 2 | 3 | export function init() { 4 | $("#mastofeeder-reset-feed-id").addEventListener("click", resetFeedId); 5 | } 6 | 7 | function resetFeedId() { 8 | var feedContainerNode = $("#mastofeeder-feed-container"); 9 | feedContainerNode.classList.add("disabled"); 10 | 11 | fetch("/masto-feeder/reset-feed-id", { method: "POST" }) 12 | .then((response) => response.text()) 13 | .then(() => window.location.reload()) 14 | .catch((error) => { 15 | feedContainerNode.classList.remove("disabled"); 16 | alert("Could not reset the feed URL: " + error); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /app/datasources/test_oauth_access_token.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from oauth_keys import SERVICE_PROVIDERS 4 | 5 | if len(sys.argv) != 2: 6 | print 'Usage: python get_oauth_access_token.py [%s]' % ( 7 | '|'.join(SERVICE_PROVIDERS.keys())) 8 | exit(1) 9 | 10 | service_provider = SERVICE_PROVIDERS[sys.argv[1]] 11 | for i in xrange(0, len(service_provider.access_tokens)): 12 | oauth_client = service_provider.get_oauth_client(token_index=i) 13 | resp, content = oauth_client.request(service_provider.test_url, 'GET') 14 | print 'Token %d' % i 15 | print ' Response: %s' % str(resp) 16 | print ' Content: %s' % str(content) 17 | -------------------------------------------------------------------------------- /worker/src/lib/components/MastodonDebugHtml.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | {#each displayStatuses as displayStatus} 15 |
16 |

{displayStatus.titleAsText}

17 | 18 |
19 | {/each} 20 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt-0.9.1.dist-info/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Ian Cordasco, Cory Benfield 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /app/templates/birdfeeder/intro.snippet: -------------------------------------------------------------------------------- 1 |

This {{ APP_NAME }} tool lets you subscribe to your Twitter 2 | timeline in a feed reader such as NetNewsWire. By signing in with your 3 | Twitter account, you enable Bird Feeder to generate a feed under a "secret" 4 | URL that will contain the tweets of all the people that you follow.

5 | 6 |

Note: feeds are expensive to provide and are only available to a 7 | few accounts.

8 | 9 |

As a bonus for 10 | these 11 | uncertain times, this tool can also generate a backup of your 12 | tweets and favorites. This is available for all accounts.

13 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/legacy-digest.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }} : Legacy Tweet Digest{% endblock %} 4 | {% block subtitle %}Legacy Tweet Digest{% endblock %} 5 | 6 | {% block intro %} 7 |

You have been redirected from a twitter-digest.appspot.com URL. 8 | That application is no longer running.

9 | 10 |

If you still wish to get a digest of 11 | tweets, you can do at this URL:
12 | {{ digest_url }}

13 | {% endblock %} 14 | 15 | 16 | {% block footer %} 17 |

To create a new digest, see the 18 | Tweet Digest tool on 19 | {{ APP_NAME }}.

20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /app/third_party/decorator-4.4.2.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | decorator-4.4.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 2 | decorator-4.4.2.dist-info/LICENSE.txt,sha256=_RFmDKvwUyCCxFcGhi-vwpSQfsf44heBgkCkmZgGeC4,1309 3 | decorator-4.4.2.dist-info/METADATA,sha256=RYLh5Qy8XzYOcgCT6RsI_cTXG_PE1QvoAVT-u2vus80,4168 4 | decorator-4.4.2.dist-info/RECORD,, 5 | decorator-4.4.2.dist-info/WHEEL,sha256=h_aVn5OB2IERUjMbi2pucmR_zzWJtk303YXvhh60NJ8,110 6 | decorator-4.4.2.dist-info/pbr.json,sha256=AL84oUUWQHwkd8OCPhLRo2NJjU5MDdmXMqRHv-posqs,47 7 | decorator-4.4.2.dist-info/top_level.txt,sha256=Kn6eQjo83ctWxXVyBMOYt0_YpjRjBznKYVuNyuC_DSI,10 8 | decorator.py,sha256=aQ8Ozc-EK26xBTOXVR5A-8Szgx99_bhaexZSGNn38Yc,17222 9 | decorator.pyc,, 10 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/digest.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ feed_url }} 4 | 5 | 6 | {{ feed_title }} 7 | {{ end_date_iso }}Z 8 | 9 | {{ APP_NAME }} : Masto Feeder 10 | 11 | 12 | {{ digest_html_url }} 13 | Digest for {{ title_date }} 14 | {{ end_date_iso }}Z 15 | 16 | 17 | {{ digest_contents }} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/index-signed-out.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }} : Masto Feeder{% endblock %} 4 | {% block subtitle %}Masto Feeder{% endblock %} 5 | 6 | {% block intro %} 7 | 8 | {% include "intro.snippet" %} 9 | 10 | {% endblock %} 11 | 12 | {% block body %} 13 | 14 |

To get started, sign in to your Mastodon instance to allow Masto Feeder 15 | access to your timeline and lists.

16 | 17 |
18 | 19 | 20 |
21 | 22 | {% endblock %} 23 | 24 | {% block footer %} 25 | {% include "footer.snippet" %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /app/templates/birdfeeder/index-signed-out.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }} : Bird Feeder{% endblock %} 4 | {% block subtitle %}Bird Feeder{% endblock %} 5 | 6 | {% block bodystart %} 7 |
8 | {% endblock %} 9 | 10 | {% block intro %} 11 | {% include "intro.snippet" %} 12 | {% endblock %} 13 | 14 | {% block body %} 15 | 16 |

To get started, sign in with Twitter to allow Bird Feeder access to your 17 | tweets.

18 | 19 |
20 | 21 | Sign in with Twitter 22 | 23 |
24 | {% endblock %} 25 | 26 | 27 | {% block footer %} 28 |

{% include "footer.snippet" %}

29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /worker/src/routes/masto-feeder/feed/[feedId]/timeline/+server.ts: -------------------------------------------------------------------------------- 1 | import {MastoFeederController} from "$lib/masto-feeder/controller"; 2 | import {error, type RequestHandler} from "@sveltejs/kit"; 3 | 4 | export const GET: RequestHandler = async event => { 5 | const controller = new MastoFeederController(event); 6 | const {feedId} = event.params; 7 | if (!feedId) { 8 | return error(400, "Invalid feed ID"); 9 | } 10 | const {searchParams} = event.url; 11 | const debug = searchParams.get("debug") === "true"; 12 | const html = searchParams.get("html") === "true"; 13 | const includeStatusJson = searchParams.get("includeStatusJson") === "true"; 14 | 15 | return controller.handleTimelineFeed(feedId, { 16 | debug, 17 | html, 18 | includeStatusJson, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /app/birdfeeder/handlers/main.py: -------------------------------------------------------------------------------- 1 | import session 2 | 3 | class IndexHandler(session.SessionApiHandler): 4 | def _get_signed_in(self): 5 | twitter_user = self._api.GetUser(self._session.twitter_id) 6 | 7 | timeline_feed_url = self._session.get_timeline_feed_url() 8 | 9 | self._write_template('birdfeeder/index-signed-in.html', { 10 | 'twitter_user': twitter_user, 11 | 'sign_out_path': self._get_path('sign-out'), 12 | 'timeline_feed_url': timeline_feed_url, 13 | 'backup_path': self._get_path('backup'), 14 | 'allows_feed_updates': self._session.allows_feed_updates(), 15 | }) 16 | 17 | def _get_signed_out(self): 18 | self._write_template('birdfeeder/index-signed-out.html', { 19 | 'sign_in_path': self._get_path('sign-in'), 20 | }) 21 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/feed.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }}: {{ feed_title }}{% endblock %} 4 | 5 | {% block subtitle %}{{ feed_title }}{% endblock %} 6 | 7 | {% block intro %} 8 |

Debug HTML output.

9 | 14 | {% endblock %} 15 | 16 | {% block body %} 17 | {% for display_status in display_statuses %} 18 |
19 | {% include 'status.snippet' %} 20 |
21 | {% endfor %} 22 | {% endblock %} 23 | 24 | {% block footer %} 25 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/retired-digest.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | tag:persistent.info,2007:/twitter-digest/retired 4 | Retired Tweet Digest 5 | 2023-06-05T00:00:00Z 6 | 7 | {{ APP_NAME }} 8 | 9 | 10 | tag:persistent.info,2007:/twitter-digest/retired 11 | Tweet Digest Has Been Retired 12 | 2023-06-05T00:00:00Z 13 | 14 |
15 |

Tweet Digest can no longer operate (due to Twitter restricting API access) and has thus been retired.

16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /app/base/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | class _ConstantsDict(dict): 4 | def __getattr__(self, m): 5 | return self[m] 6 | 7 | CONSTANTS = _ConstantsDict( 8 | BACKGROUND_COLOR='#8aa7ff', 9 | BACKGROUND_DARKER_COLOR='#7094ff', 10 | ANCHOR_COLOR='#2db300', 11 | 12 | USER_LINK_COLOR='#666', 13 | BUBBLE_COLOR='#f6f6f6', 14 | BUBBLE_QUOTED_COLOR='#eaeef9', 15 | BUBBLE_QUOTED_BORDER_COLOR='#eaeef988', 16 | BUBBLE_REPLY_COLOR='#e6e6e6', 17 | BUBBLE_TEXT_COLOR='#41419b', 18 | BUBBLE_SEPARATOR_COLOR='#d6e0ff', 19 | HEADER_COLOR='#666', 20 | 21 | APP_NAME='Stream Spigot', 22 | APP_URL='HTTP_HOST' in os.environ and \ 23 | ('http://' + os.environ['HTTP_HOST']) or 'http://www.streamspigot.com', 24 | IS_DEV_SERVER=os.environ.get('HTTP_HOST', '').startswith('localhost'), 25 | 26 | HUB_URL = 'http://pubsubhubbub.appspot.com/' 27 | ) 28 | -------------------------------------------------------------------------------- /app/mastofeeder/handlers/main.py: -------------------------------------------------------------------------------- 1 | import session 2 | 3 | class IndexHandler(session.SessionApiHandler): 4 | def _get_signed_in(self): 5 | mastodon_user = self._api.me() 6 | 7 | lists = self._api.lists() 8 | lists_and_feed_paths = [ 9 | (l, self._get_feed_path('list', str(l.id), 'timeline')) 10 | for l in sorted(lists, key=lambda l: l.title) 11 | ] 12 | 13 | self._write_template('mastofeeder/index-signed-in.html', { 14 | 'mastodon_user': mastodon_user, 15 | 'timeline_feed_path': self._get_feed_path('timeline'), 16 | 'lists_and_feed_paths': lists_and_feed_paths, 17 | 'sign_out_path': self._get_path('sign-out'), 18 | }) 19 | 20 | def _get_signed_out(self): 21 | self._write_template('mastofeeder/index-signed-out.html', { 22 | 'sign_in_path': self._get_path('sign-in'), 23 | }) 24 | -------------------------------------------------------------------------------- /app/third_party/requests/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.hooks 5 | ~~~~~~~~~~~~~~ 6 | 7 | This module provides the capabilities for the Requests hooks system. 8 | 9 | Available hooks: 10 | 11 | ``response``: 12 | The response generated from a Request. 13 | """ 14 | HOOKS = ['response'] 15 | 16 | 17 | def default_hooks(): 18 | return {event: [] for event in HOOKS} 19 | 20 | # TODO: response is the only one 21 | 22 | 23 | def dispatch_hook(key, hooks, hook_data, **kwargs): 24 | """Dispatches a hook dictionary on a given piece of data.""" 25 | hooks = hooks or {} 26 | hooks = hooks.get(key) 27 | if hooks: 28 | if hasattr(hooks, '__call__'): 29 | hooks = [hooks] 30 | for hook in hooks: 31 | _hook_data = hook(hook_data, **kwargs) 32 | if _hook_data is not None: 33 | hook_data = _hook_data 34 | return hook_data 35 | -------------------------------------------------------------------------------- /app/third_party/certifi-2021.10.8.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | certifi-2021.10.8.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 2 | certifi-2021.10.8.dist-info/LICENSE,sha256=vp2C82ES-Hp_HXTs1Ih-FGe7roh4qEAEoAEXseR1o-I,1049 3 | certifi-2021.10.8.dist-info/METADATA,sha256=iB_zbT1uX_8_NC7iGv0YEB-9b3idhQwHrFTSq8R1kD8,2994 4 | certifi-2021.10.8.dist-info/RECORD,, 5 | certifi-2021.10.8.dist-info/WHEEL,sha256=ADKeyaGyKF5DwBNE0sRE5pvW-bSkFMJfBuhzZ3rceP4,110 6 | certifi-2021.10.8.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 7 | certifi/__init__.py,sha256=xWdRgntT3j1V95zkRipGOg_A1UfEju2FcpujhysZLRI,62 8 | certifi/__init__.pyc,, 9 | certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 10 | certifi/__main__.pyc,, 11 | certifi/cacert.pem,sha256=-og4Keu4zSpgL5shwfhd4kz0eUnVILzrGCi0zRy2kGw,265969 12 | certifi/core.py,sha256=V0uyxKOYdz6ulDSusclrLmjbPgOXsD0BnEf0SQ7OnoE,2303 13 | certifi/core.pyc,, 14 | -------------------------------------------------------------------------------- /app/static/main.js: -------------------------------------------------------------------------------- 1 | import { init as birdfeederInit } from "./birdfeeder.js"; 2 | import { init as feedplaybackInit } from "./feedplayback.js"; 3 | import { init as mastofeederInit } from "./mastofeeder.js"; 4 | import { init as tweetdigestInit } from "./tweetdigest.js"; 5 | import { exportFunction, $ } from "./util.js"; 6 | 7 | function init() { 8 | var a = [ 9 | 109, 105, 104, 97, 105, 64, 112, 101, 114, 115, 105, 115, 116, 101, 110, 10 | 116, 46, 105, 110, 102, 111, 11 | ]; 12 | var b = a.map(i => String.fromCharCode(i)).join(""); 13 | const emailNode = $("#email"); 14 | emailNode.href = "mailto:" + b; 15 | emailNode.textContent = b; 16 | } 17 | 18 | exportFunction("streamspigot.birdfeeder.init", birdfeederInit); 19 | exportFunction("streamspigot.feedplayback.init", feedplaybackInit); 20 | exportFunction("streamspigot.mastofeeder.init", mastofeederInit); 21 | exportFunction("streamspigot.tweetdigest.init", tweetdigestInit); 22 | exportFunction("streamspigot.main.init", init); 23 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/multipart/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | requests_toolbelt.multipart 3 | =========================== 4 | 5 | See http://toolbelt.rtfd.org/ for documentation 6 | 7 | :copyright: (c) 2014 by Ian Cordasco and Cory Benfield 8 | :license: Apache v2.0, see LICENSE for more details 9 | """ 10 | 11 | from .encoder import MultipartEncoder, MultipartEncoderMonitor 12 | from .decoder import MultipartDecoder 13 | from .decoder import ImproperBodyPartContentException 14 | from .decoder import NonMultipartContentTypeException 15 | 16 | __title__ = 'requests-toolbelt' 17 | __authors__ = 'Ian Cordasco, Cory Benfield' 18 | __license__ = 'Apache v2.0' 19 | __copyright__ = 'Copyright 2014 Ian Cordasco, Cory Benfield' 20 | 21 | __all__ = [ 22 | 'MultipartEncoder', 23 | 'MultipartEncoderMonitor', 24 | 'MultipartDecoder', 25 | 'ImproperBodyPartContentException', 26 | 'NonMultipartContentTypeException', 27 | '__title__', 28 | '__authors__', 29 | '__license__', 30 | '__copyright__', 31 | ] 32 | -------------------------------------------------------------------------------- /worker/src/lib/masto.ts: -------------------------------------------------------------------------------- 1 | import * as masto from "masto"; 2 | 3 | export function createOAuthAPIClient( 4 | props: Parameters[0] 5 | ) { 6 | return masto.createOAuthAPIClient({ 7 | ...props, 8 | requestInit: { 9 | ...props.requestInit, 10 | headers: { 11 | "User-Agent": 12 | "Masto-Feeder; (+https://www.streamspigot.com/masto-feeder)", 13 | ...props.requestInit?.headers, 14 | }, 15 | }, 16 | }); 17 | } 18 | 19 | export function createRestAPIClient( 20 | props: Parameters[0] 21 | ) { 22 | return masto.createRestAPIClient({ 23 | ...props, 24 | requestInit: { 25 | ...props.requestInit, 26 | headers: { 27 | "User-Agent": 28 | "Masto-Feeder; (+https://www.streamspigot.com/masto-feeder)", 29 | ...props.requestInit?.headers, 30 | }, 31 | }, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /app/third_party/requests/packages.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | try: 4 | import chardet 5 | except ImportError: 6 | import charset_normalizer as chardet 7 | import warnings 8 | 9 | warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') 10 | 11 | # This code exists for backwards compatibility reasons. 12 | # I don't like it either. Just look the other way. :) 13 | 14 | for package in ('urllib3', 'idna'): 15 | locals()[package] = __import__(package) 16 | # This traversal is apparently necessary such that the identities are 17 | # preserved (requests.packages.urllib3.* is urllib3.*) 18 | for mod in list(sys.modules): 19 | if mod == package or mod.startswith(package + '.'): 20 | sys.modules['requests.packages.' + mod] = sys.modules[mod] 21 | 22 | target = chardet.__name__ 23 | for mod in list(sys.modules): 24 | if mod == target or mod.startswith(target + '.'): 25 | sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] 26 | # Kinda cool, though, right? 27 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/auth/_digest_auth_compat.py: -------------------------------------------------------------------------------- 1 | """Provide a compatibility layer for requests.auth.HTTPDigestAuth.""" 2 | import requests 3 | 4 | 5 | class _ThreadingDescriptor(object): 6 | def __init__(self, prop, default): 7 | self.prop = prop 8 | self.default = default 9 | 10 | def __get__(self, obj, objtype=None): 11 | return getattr(obj._thread_local, self.prop, self.default) 12 | 13 | def __set__(self, obj, value): 14 | setattr(obj._thread_local, self.prop, value) 15 | 16 | 17 | class _HTTPDigestAuth(requests.auth.HTTPDigestAuth): 18 | init = _ThreadingDescriptor('init', True) 19 | last_nonce = _ThreadingDescriptor('last_nonce', '') 20 | nonce_count = _ThreadingDescriptor('nonce_count', 0) 21 | chal = _ThreadingDescriptor('chal', {}) 22 | pos = _ThreadingDescriptor('pos', None) 23 | num_401_calls = _ThreadingDescriptor('num_401_calls', 1) 24 | 25 | 26 | if requests.__build__ < 0x020800: 27 | HTTPDigestAuth = requests.auth.HTTPDigestAuth 28 | else: 29 | HTTPDigestAuth = _HTTPDigestAuth 30 | -------------------------------------------------------------------------------- /worker/src/lib/components/MastodonStatusFooter.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 18 | {displayStatus.createdAtFormatted} 19 | {#if permalinkStatus.application} 20 | {" "}from {permalinkStatus.application.name} 21 | {/if} 22 | {#if permalinkStatus.inReplyToId} 23 | {" "}(in reply to) 27 | {/if} 28 | 29 |
30 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/digest.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | tag:persistent.info,2007:/twitter-digest/{{ digest_id }} 4 | 5 | 6 | Tweet Digest of {{ digest_source|striptags }} - {{ APP_NAME }} 7 | {{ end_date_iso }}Z 8 | 9 | {{ APP_NAME }} 10 | 11 | 12 | tag:persistent.info,2007:/twitter-digest/{{ digest_entry_id }} 13 | Tweet Digest for {{ title_date }} 14 | {{ end_date_iso }}Z 15 | 16 | 17 | {{ digest_contents }} 18 | {% if digest_errors %} 19 | {% filter force_escape %} 20 |

{{ digest_errors }}

21 | {% endfilter %} 22 | {% endif %} 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /worker/src/lib/kv.ts: -------------------------------------------------------------------------------- 1 | export interface KV { 2 | get(key: string): Promise; 3 | put(key: string, value: string): Promise; 4 | delete(key: string): Promise; 5 | 6 | getJSON(key: string): Promise; 7 | putJSON(key: string, value: T): Promise; 8 | } 9 | 10 | export class WorkerKV implements KV { 11 | #kv: KVNamespace; 12 | 13 | constructor(kv: KVNamespace) { 14 | this.#kv = kv; 15 | } 16 | 17 | async get(key: string): Promise { 18 | return await this.#kv.get(key); 19 | } 20 | 21 | async put(key: string, value: string): Promise { 22 | return await this.#kv.put(key, value); 23 | } 24 | 25 | async delete(key: string): Promise { 26 | return await this.#kv.delete(key); 27 | } 28 | 29 | async getJSON(key: string): Promise { 30 | return await this.#kv.get(key, {type: "json"}); 31 | } 32 | 33 | async putJSON(key: string, value: T): Promise { 34 | return await this.#kv.put(key, JSON.stringify(value)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/third_party/dateutil/_common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common code used in multiple modules. 3 | """ 4 | 5 | 6 | class weekday(object): 7 | __slots__ = ["weekday", "n"] 8 | 9 | def __init__(self, weekday, n=None): 10 | self.weekday = weekday 11 | self.n = n 12 | 13 | def __call__(self, n): 14 | if n == self.n: 15 | return self 16 | else: 17 | return self.__class__(self.weekday, n) 18 | 19 | def __eq__(self, other): 20 | try: 21 | if self.weekday != other.weekday or self.n != other.n: 22 | return False 23 | except AttributeError: 24 | return False 25 | return True 26 | 27 | def __hash__(self): 28 | return hash(( 29 | self.weekday, 30 | self.n, 31 | )) 32 | 33 | def __ne__(self, other): 34 | return not (self == other) 35 | 36 | def __repr__(self): 37 | s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] 38 | if not self.n: 39 | return s 40 | else: 41 | return "%s(%+d)" % (s, self.n) 42 | 43 | # vim:ts=4:sw=4:et 44 | -------------------------------------------------------------------------------- /app/third_party/urllib3/contrib/_appengine_environ.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides means to detect the App Engine environment. 3 | """ 4 | 5 | import os 6 | 7 | 8 | def is_appengine(): 9 | return is_local_appengine() or is_prod_appengine() 10 | 11 | 12 | def is_appengine_sandbox(): 13 | """Reports if the app is running in the first generation sandbox. 14 | 15 | The second generation runtimes are technically still in a sandbox, but it 16 | is much less restrictive, so generally you shouldn't need to check for it. 17 | see https://cloud.google.com/appengine/docs/standard/runtimes 18 | """ 19 | return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" 20 | 21 | 22 | def is_local_appengine(): 23 | return "APPENGINE_RUNTIME" in os.environ and os.environ.get( 24 | "SERVER_SOFTWARE", "" 25 | ).startswith("Development/") 26 | 27 | 28 | def is_prod_appengine(): 29 | return "APPENGINE_RUNTIME" in os.environ and os.environ.get( 30 | "SERVER_SOFTWARE", "" 31 | ).startswith("Google App Engine/") 32 | 33 | 34 | def is_prod_appengine_mvms(): 35 | """Deprecated.""" 36 | return False 37 | -------------------------------------------------------------------------------- /app/third_party/six-1.16.0.dist-info/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2020 Benjamin Peterson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /app/third_party/certifi-2021.10.8.dist-info/LICENSE: -------------------------------------------------------------------------------- 1 | This package contains a modified version of ca-bundle.crt: 2 | 3 | ca-bundle.crt -- Bundle of CA Root Certificates 4 | 5 | Certificate data from Mozilla as of: Thu Nov 3 19:04:19 2011# 6 | This is a bundle of X.509 certificates of public Certificate Authorities 7 | (CA). These were automatically extracted from Mozilla's root certificates 8 | file (certdata.txt). This file can be found in the mozilla source tree: 9 | http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1# 10 | It contains the certificates in PEM format and therefore 11 | can be directly used with curl / libcurl / php_curl, or with 12 | an Apache+mod_ssl webserver for SSL client authentication. 13 | Just configure this file as the SSLCACertificateFile.# 14 | 15 | ***** BEGIN LICENSE BLOCK ***** 16 | This Source Code Form is subject to the terms of the Mozilla Public License, 17 | v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain 18 | one at http://mozilla.org/MPL/2.0/. 19 | 20 | ***** END LICENSE BLOCK ***** 21 | @(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ 22 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt-0.9.1.dist-info/AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Requests-toolbelt is written and maintained by Ian Cordasco, Cory Benfield and 2 | various contributors: 3 | 4 | Development Lead 5 | ```````````````` 6 | 7 | - Ian Cordasco 8 | 9 | - Cory Benfield 10 | 11 | 12 | Requests 13 | ```````` 14 | 15 | - Kenneth Reitz and various contributors 16 | 17 | 18 | Urllib3 19 | ``````` 20 | 21 | - Andrey Petrov 22 | 23 | 24 | Patches and Suggestions 25 | ``````````````````````` 26 | 27 | - Jay De Lanoy 28 | 29 | - Zhaoyu Luo 30 | 31 | - Markus Unterwaditzer 32 | 33 | - Bryce Boe (@bboe) 34 | 35 | - Dan Lipsitt (https://github.com/DanLipsitt) 36 | 37 | - Cea Stapleton (http://www.ceastapleton.com) 38 | 39 | - Patrick Creech 40 | 41 | - Mike Lambert (@mikelambert) 42 | 43 | - Ryan Barrett (https://snarfed.org/) 44 | 45 | - Victor Grau Serrat (@lacabra) 46 | 47 | - Yorgos Pagles 48 | 49 | - Thomas Hauk 50 | 51 | - Achim Herwig 52 | 53 | - Ryan Ashley -------------------------------------------------------------------------------- /app/templates/tweetdigest/legacy-digest.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | tag:persistent.info,2007:/twitter-digest/legacy 4 | Legacy Tweet Digest 5 | 2015-03-11T00:00:00Z 6 | 7 | {{ APP_NAME }} 8 | 9 | 10 | tag:persistent.info,2007:/twitter-digest/legacy 11 | Legacy Tweet Digest, Action Required 12 | 2015-03-11T00:00:00Z 13 | 14 |
15 |

You are subscribed to a twitter-digest.appspot.com feed 16 | URL. That application is no longer running.

17 | 18 |

If you still wish to get a digest of tweets, you can instead 19 | subscribe to this URL:
20 | {{ digest_url }}

21 | 22 |

To create a new digest, see the 23 | Tweet Digest tool on 24 | {{ APP_NAME }}.

25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /app/third_party/urllib3-1.26.13.dist-info/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/templates/feedplayback/subscription.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }} : Feed Playback for {{ feed_title }}{% endblock %} 4 | {% block subtitle %}Feed Playback{% endblock %} 5 | {% block subsubtitle %}for {{ feed_title }}{% endblock %} 6 | 7 | {% block bodystart %} 8 |
9 | {% endblock %} 10 | 11 | {% block intro %} 12 |

This is a feed playback for the 13 | {{ feed_title }} feed. It is set 14 | to update {{ frequency }}. It is currently at item 15 | {{ position }} of {{ item_count }}.

16 | 17 |
18 | 19 | 20 |
21 | 22 |

You can subscribe to 23 | the playback feed 24 | (view in Google Reader). 25 |

26 | {% endblock %} 27 | 28 | {% block footer %} 29 |

To play back another feed, see the 30 | Feed Playback tool on 31 | {{ APP_NAME }}.

32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /worker/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import ts from "typescript-eslint"; 3 | import svelte from "eslint-plugin-svelte"; 4 | import prettier from "eslint-config-prettier"; 5 | import globals from "globals"; 6 | 7 | /** @type {import('eslint').Linter.FlatConfig[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs["flat/recommended"], 12 | prettier, 13 | ...svelte.configs["flat/prettier"], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node, 19 | }, 20 | }, 21 | rules: { 22 | "no-constant-condition": "off", 23 | }, 24 | }, 25 | { 26 | files: ["**/*.svelte"], 27 | languageOptions: { 28 | parserOptions: { 29 | parser: ts.parser, 30 | }, 31 | }, 32 | rules: { 33 | "svelte/no-at-html-tags": "off", 34 | }, 35 | }, 36 | { 37 | ignores: [ 38 | "build/", 39 | ".svelte-kit/", 40 | "package/", 41 | ".cloudflare/", 42 | ".wrangler/", 43 | ], 44 | }, 45 | ]; 46 | -------------------------------------------------------------------------------- /app/templates/tweetdigest/digest.html: -------------------------------------------------------------------------------- 1 | {% extends "base/page.html" %} 2 | 3 | {% block title %}{{ APP_NAME }}: Tweet Digest for {{ title_date }}{% endblock %} 4 | 5 | {% block head %} 6 | 10 | 20 | {% endblock %} 21 | 22 | {% block subtitle %}Tweet Digest{% endblock %} 23 | {% block subsubtitle %}for {{ title_date }}{% endblock %} 24 | 25 | {% block intro %} 26 |

This is a digest of {{ digest_source|safe }}. It is also available 27 | as a feed.

28 | {% endblock %} 29 | 30 | {% block body %} 31 |
32 | {{ digest_contents|safe }} 33 |
34 | {% endblock %} 35 | 36 | {% block footer %} 37 | {% if digest_errors %} 38 |

{{ digest_errors|safe }}

39 | {% endif %} 40 |

To create a new digest, see the 41 | Tweet Digest tool on 42 | {{ APP_NAME }}.

43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /app/third_party/requests/_internal_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests._internal_utils 5 | ~~~~~~~~~~~~~~ 6 | 7 | Provides utility functions that are consumed internally by Requests 8 | which depend on extremely few external helpers (such as compat) 9 | """ 10 | 11 | from .compat import is_py2, builtin_str, str 12 | 13 | 14 | def to_native_string(string, encoding='ascii'): 15 | """Given a string object, regardless of type, returns a representation of 16 | that string in the native string type, encoding and decoding where 17 | necessary. This assumes ASCII unless told otherwise. 18 | """ 19 | if isinstance(string, builtin_str): 20 | out = string 21 | else: 22 | if is_py2: 23 | out = string.encode(encoding) 24 | else: 25 | out = string.decode(encoding) 26 | 27 | return out 28 | 29 | 30 | def unicode_is_ascii(u_string): 31 | """Determine if unicode string only contains ASCII characters. 32 | 33 | :param str u_string: unicode string to check. Must be unicode 34 | and not Python 2 `str`. 35 | :rtype: bool 36 | """ 37 | assert isinstance(u_string, str) 38 | try: 39 | u_string.encode('ascii') 40 | return True 41 | except UnicodeEncodeError: 42 | return False 43 | -------------------------------------------------------------------------------- /app/third_party/idna-2.10.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | idna-2.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 2 | idna-2.10.dist-info/LICENSE.rst,sha256=QSAUQg0kc9ugYRfD1Nng7sqm3eDKMM2VH07CvjlCbzI,1565 3 | idna-2.10.dist-info/METADATA,sha256=ZWCaQDBjdmSvx5EU7Cv6ORC-9NUQ6nXh1eXx38ySe40,9104 4 | idna-2.10.dist-info/RECORD,, 5 | idna-2.10.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110 6 | idna-2.10.dist-info/top_level.txt,sha256=jSag9sEDqvSPftxOQy-ABfGV_RSy7oFh4zZJpODV8k0,5 7 | idna/__init__.py,sha256=9Nt7xpyet3DmOrPUGooDdAwmHZZu1qUAy2EaJ93kGiQ,58 8 | idna/__init__.pyc,, 9 | idna/codec.py,sha256=lvYb7yu7PhAqFaAIAdWcwgaWI2UmgseUua-1c0AsG0A,3299 10 | idna/codec.pyc,, 11 | idna/compat.py,sha256=R-h29D-6mrnJzbXxymrWUW7iZUvy-26TQwZ0ij57i4U,232 12 | idna/compat.pyc,, 13 | idna/core.py,sha256=jCoaLb3bA2tS_DDx9PpGuNTEZZN2jAzB369aP-IHYRE,11951 14 | idna/core.pyc,, 15 | idna/idnadata.py,sha256=gmzFwZWjdms3kKZ_M_vwz7-LP_SCgYfSeE03B21Qpsk,42350 16 | idna/idnadata.pyc,, 17 | idna/intranges.py,sha256=TY1lpxZIQWEP6tNqjZkFA5hgoMWOj1OBmnUG8ihT87E,1749 18 | idna/intranges.pyc,, 19 | idna/package_data.py,sha256=bxBjpLnE06_1jSYKEy5svOMu1zM3OMztXVUb1tPlcp0,22 20 | idna/package_data.pyc,, 21 | idna/uts46data.py,sha256=lMdw2zdjkH1JUWXPPEfFUSYT3Fyj60bBmfLvvy5m7ko,202084 22 | idna/uts46data.pyc,, 23 | -------------------------------------------------------------------------------- /app/third_party/requests_toolbelt/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Collection of exceptions raised by requests-toolbelt.""" 3 | 4 | 5 | class StreamingError(Exception): 6 | """Used in :mod:`requests_toolbelt.downloadutils.stream`.""" 7 | pass 8 | 9 | 10 | class VersionMismatchError(Exception): 11 | """Used to indicate a version mismatch in the version of requests required. 12 | 13 | The feature in use requires a newer version of Requests to function 14 | appropriately but the version installed is not sufficient. 15 | """ 16 | pass 17 | 18 | 19 | class RequestsVersionTooOld(Warning): 20 | """Used to indicate that the Requests version is too old. 21 | 22 | If the version of Requests is too old to support a feature, we will issue 23 | this warning to the user. 24 | """ 25 | pass 26 | 27 | 28 | class IgnoringGAECertificateValidation(Warning): 29 | """Used to indicate that given GAE validation behavior will be ignored. 30 | 31 | If the user has tried to specify certificate validation when using the 32 | insecure AppEngine adapter, it will be ignored (certificate validation will 33 | remain off), so we will issue this warning to the user. 34 | 35 | In :class:`requests_toolbelt.adapters.appengine.InsecureAppEngineAdapter`. 36 | """ 37 | pass 38 | -------------------------------------------------------------------------------- /app/cron.yaml.disabled: -------------------------------------------------------------------------------- 1 | cron: 2 | - description: 1d/0 3 | url: /cron/feed-playback/advance?frequency=1d&frequency_modulo=0 4 | schedule: every day 00:00 5 | 6 | - description: 2d/0 7 | url: /cron/feed-playback/advance?frequency=2d&frequency_modulo=0 8 | schedule: every day 01:00 9 | 10 | - description: 2d/1 11 | url: /cron/feed-playback/advance?frequency=2d&frequency_modulo=1 12 | schedule: every day 02:00 13 | 14 | - description: 7d/0 15 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=0 16 | schedule: every day 03:00 17 | 18 | - description: 7d/1 19 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=1 20 | schedule: every day 04:00 21 | 22 | - description: 7d/2 23 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=2 24 | schedule: every day 05:00 25 | 26 | - description: 7d/3 27 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=3 28 | schedule: every day 06:00 29 | 30 | - description: 7d/4 31 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=4 32 | schedule: every day 07:00 33 | 34 | - description: 7d/5 35 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=5 36 | schedule: every day 08:00 37 | 38 | - description: 7d/6 39 | url: /cron/feed-playback/advance?frequency=7d&frequency_modulo=6 40 | schedule: every day 09:00 41 | -------------------------------------------------------------------------------- /app/templates/mastofeeder/status-footer.snippet: -------------------------------------------------------------------------------- 1 | {% with display_status.status as status %} 2 | 3 | {% spaceless %} 4 |
5 | 6 | 12 | {% if use_relative_dates %}{{ status.relative_created_at }}{% else %}at {{ display_status.created_at_formatted }}{% endif %} 13 | {% with display_status.permalink_status as permalink_status %} 14 | {%if permalink_status.application %} 15 | from {{ permalink_status.application.name }} 16 | {% endif %} 17 | {%if permalink_status.in_reply_to_id %} 18 | (in reply to) 19 | {% endif %} 20 | {% endwith %} 21 | 22 |
23 | {% if include_status_json %} 24 |