initial checkin mid-refactoring of the threadodo bot.

This commit is contained in:
sneakers-the-rat 2022-10-03 18:46:42 -07:00
parent d82db83c5a
commit 1337621516
22 changed files with 2367 additions and 2 deletions

1
.gitignore vendored
View file

@ -160,3 +160,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
*creds.json

View file

@ -230,3 +230,4 @@ The hypothetical commands `show w' and `show c' should show the appropriate part
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

View file

@ -1,3 +1,6 @@
# twitter-wiki-bot
# wiki-postbot
Bot to add tweets using an extended wikilink syntax to the wiki
Starting with twitter, but then will add masto
Bot to add tweets using an extended wikilink syntax to the wiki!

838
poetry.lock generated Normal file
View file

@ -0,0 +1,838 @@
[[package]]
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "21.4.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "brotli"
version = "1.0.9"
description = "Python bindings for the Brotli compression library"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "brotlicffi"
version = "1.0.9.2"
description = "Python CFFI bindings to the Brotli library"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
cffi = ">=1.0.0"
[[package]]
name = "certifi"
version = "2022.6.15"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "cffi"
version = "1.15.0"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]]
name = "charset-normalizer"
version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "commonmark"
version = "0.9.1"
description = "Python parser for the CommonMark Markdown spec"
category = "main"
optional = false
python-versions = "*"
[package.extras]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "cssselect2"
version = "0.6.0"
description = "CSS selectors for Python ElementTree"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
tinycss2 = "*"
webencodings = "*"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
[[package]]
name = "fonttools"
version = "4.33.3"
description = "Tools to manipulate font files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
brotli = {version = ">=1.0.1", optional = true, markers = "platform_python_implementation == \"CPython\" and extra == \"woff\""}
brotlicffi = {version = ">=0.8.0", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"woff\""}
zopfli = {version = ">=0.1.4", optional = true, markers = "extra == \"woff\""}
[package.extras]
all = ["fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "zopfli (>=0.1.4)", "lz4 (>=1.7.4.2)", "matplotlib", "sympy", "skia-pathops (>=0.5.0)", "uharfbuzz (>=0.23.0)", "brotlicffi (>=0.8.0)", "scipy", "brotli (>=1.0.1)", "munkres", "unicodedata2 (>=14.0.0)", "xattr"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["scipy", "munkres"]
lxml = ["lxml (>=4.0,<5)"]
pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr"]
ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=14.0.0)"]
woff = ["zopfli (>=0.1.4)", "brotlicffi (>=0.8.0)", "brotli (>=1.0.1)"]
[[package]]
name = "html5lib"
version = "1.1"
description = "HTML parser based on the WHATWG HTML specification"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
six = ">=1.9"
webencodings = "*"
[package.extras]
all = ["genshi", "chardet (>=2.2)", "lxml"]
chardet = ["chardet (>=2.2)"]
genshi = ["genshi"]
lxml = ["lxml"]
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "oauthlib"
version = "3.2.0"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "parse"
version = "1.19.0"
description = "parse() is the opposite of format()"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pillow"
version = "9.1.1"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pycparser"
version = "2.21"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pydyf"
version = "0.2.0"
description = "A low-level PDF generator."
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-xdist", "pytest-flake8", "pytest-isort", "pytest-cov", "coverage", "pillow"]
[[package]]
name = "pygments"
version = "2.12.0"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "pypandoc"
version = "1.8.1"
description = "Thin wrapper for pandoc."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pyphen"
version = "0.12.0"
description = "Pure Python module to hyphenate text"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
[[package]]
name = "pytest"
version = "7.1.2"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
tomli = ">=1.0.0"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "requests"
version = "2.28.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2.0.0,<2.1.0"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "requests-oauthlib"
version = "1.3.1"
description = "OAuthlib authentication support for Requests."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
oauthlib = ">=3.0.0"
requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "rich"
version = "12.4.4"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main"
optional = false
python-versions = ">=3.6.3,<4.0.0"
[package.dependencies]
commonmark = ">=0.9.0,<0.10.0"
pygments = ">=2.6.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tinycss2"
version = "1.1.1"
description = "A tiny CSS parser"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
webencodings = ">=0.4"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "tweepy"
version = "4.10.0"
description = "Twitter library for Python"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
oauthlib = ">=3.2.0,<4"
requests = ">=2.27.0,<3"
requests-oauthlib = ">=1.2.0,<2"
[package.extras]
async = ["aiohttp (>=3.7.3,<4)", "async-lru (>=1.0.3,<2)"]
dev = ["coverage (>=4.4.2)", "coveralls (>=2.1.0)", "tox (>=3.21.0)"]
socks = ["requests[socks] (>=2.27.0,<3)"]
test = ["vcrpy (>=1.10.3)"]
[[package]]
name = "urllib3"
version = "1.26.9"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "weasyprint"
version = "55.0"
description = "The Awesome Document Factory"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cffi = ">=0.6"
cssselect2 = ">=0.1"
fonttools = {version = ">=4.0.0", extras = ["woff"]}
html5lib = ">=1.1"
Pillow = ">=4.0.0"
pydyf = ">=0.0.3"
Pyphen = ">=0.9.1"
tinycss2 = ">=1.0.0"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
[[package]]
name = "webencodings"
version = "0.5.1"
description = "Character encoding aliases for legacy web content"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "zopfli"
version = "0.2.1"
description = "Zopfli module for python"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
test = ["pytest"]
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "423cdca5ac87cd46ad86f1a5ac17ae17330ad736b226edfa90b6723e5b230674"
[metadata.files]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
brotli = [
{file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"},
{file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"},
{file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"},
{file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"},
{file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"},
{file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"},
{file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"},
{file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"},
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"},
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"},
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"},
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"},
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"},
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"},
{file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"},
{file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"},
{file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"},
{file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"},
{file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"},
{file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"},
{file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"},
{file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"},
{file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"},
{file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"},
{file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"},
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"},
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"},
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"},
{file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"},
{file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"},
{file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"},
{file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"},
{file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"},
{file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"},
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"},
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"},
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"},
{file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"},
{file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"},
{file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"},
{file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"},
{file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"},
{file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"},
{file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"},
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"},
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"},
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"},
{file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"},
{file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"},
{file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"},
{file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"},
{file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"},
{file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"},
{file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"},
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"},
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"},
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"},
{file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"},
{file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"},
{file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"},
{file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"},
{file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"},
]
brotlicffi = [
{file = "brotlicffi-1.0.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:408ec4359f9763280d5c4e0ad29c51d1240b25fdd18719067e972163b4125b98"},
{file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2e4629f7690ded66c8818715c6d4dd6a7ff6a4f10fad6186fe99850f781ce210"},
{file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:137c4635edcdf593de5ce9d0daa596bf499591b16b8fca5fd72a490deb54b2ee"},
{file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:af8a1b7bcfccf9c41a3c8654994d6a81821fdfe4caddcfe5045bfda936546ca3"},
{file = "brotlicffi-1.0.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9078432af4785f35ab3840587eed7fb131e3fc77eb2a739282b649b343c584dd"},
{file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7bb913d5bf3b4ce2ec59872711dc9faaff5f320c3c3827cada2d8a7b793a7753"},
{file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:16a0c9392a1059e2e62839fbd037d2e7e03c8ae5da65e9746f582464f7fab1bb"},
{file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:94d2810efc5723f1447b332223b197466190518a3eeca93b9f357efb5b22c6dc"},
{file = "brotlicffi-1.0.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9e70f3e20f317d70912b10dbec48b29114d3dbd0e9d88475cb328e6c086f0546"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:586f0ea3c2eed455d5f2330b9ab4a591514c8de0ee53d445645efcfbf053c69f"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4454c3baedc277fd6e65f983e3eb8e77f4bc15060f69370a0201746e2edeca81"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:52c1c12dad6eb1d44213a0a76acf5f18f64653bd801300bef5e2f983405bdde5"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:21cd400d24b344c218d8e32b394849e31b7c15784667575dbda9f65c46a64b0a"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:71061f8bc86335b652e442260c4367b782a92c6e295cf5a10eff84c7d19d8cf5"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:15e0db52c56056be6310fc116b3d7c6f34185594e261f23790b2fb6489998363"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-win32.whl", hash = "sha256:551305703d12a2dd1ae43d3dde35dee20b1cb49b5796279d4d34e2c6aec6be4d"},
{file = "brotlicffi-1.0.9.2-cp35-abi3-win_amd64.whl", hash = "sha256:2be4fb8a7cb482f226af686cd06d2a2cab164ccdf99e460f8e3a5ec9a5337da2"},
{file = "brotlicffi-1.0.9.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:8e7221d8a084d32d15c7b58e0ce0573972375c5038423dbe83f217cfe512e680"},
{file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:75a46bc5ed2753e1648cc211dcb2c1ac66116038766822dc104023f67ff4dfd8"},
{file = "brotlicffi-1.0.9.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1e27c43ef72a278f9739b12b2df80ee72048cd4cbe498f8bbe08aaaa67a5d5c8"},
{file = "brotlicffi-1.0.9.2-pp27-pypy_73-win32.whl", hash = "sha256:feb942814285bdc5e97efc77a04e48283c17dfab9ea082d79c0a7b9e53ef1eab"},
{file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a6208d82c3172eeeb3be83ed4efd5831552c7cd47576468e50fcf0fb23fcf97f"},
{file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:408c810c599786fb806556ff17e844a903884e6370ca400bcec7fa286149f39c"},
{file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a73099858ee343e8801710a08be8d194f47715ff21e98d92a19ac461058f52d1"},
{file = "brotlicffi-1.0.9.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:916b790f967a18a595e61f218c252f83718ac91f24157d622cf0fa710cd26ab7"},
{file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba4a00263af40e875ec3d6c7f623cbf8c795b55705da18c64ec36b6bf0848bc5"},
{file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:df78aa47741122b0d5463f1208b7bb18bc9706dee5152d9f56e0ead4865015cd"},
{file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:9030cd5099252d16bfa4e22659c84a89c102e94f8e81d30764788b72e2d7cfb7"},
{file = "brotlicffi-1.0.9.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:7e72978f4090a161885b114f87b784f538dcb77dafc6602592c1cf39ae8d243d"},
{file = "brotlicffi-1.0.9.2.tar.gz", hash = "sha256:0c248a68129d8fc6a217767406c731e498c3e19a7be05ea0a90c3c86637b7d96"},
]
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
cffi = [
{file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
{file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
{file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
{file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
{file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
{file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
{file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
{file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
{file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
{file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
{file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
{file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
{file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
{file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
{file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
{file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
{file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
{file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
{file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
{file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
{file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
{file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
{file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
commonmark = [
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
cssselect2 = [
{file = "cssselect2-0.6.0-py3-none-any.whl", hash = "sha256:3a83b2a68370c69c9cd3fcb88bbfaebe9d22edeef2c22d1ff3e1ed9c7fa45ed8"},
{file = "cssselect2-0.6.0.tar.gz", hash = "sha256:5b5d6dea81a5eb0c9ca39f116c8578dd413778060c94c1f51196371618909325"},
]
fonttools = [
{file = "fonttools-4.33.3-py3-none-any.whl", hash = "sha256:f829c579a8678fa939a1d9e9894d01941db869de44390adb49ce67055a06cc2a"},
{file = "fonttools-4.33.3.zip", hash = "sha256:c0fdcfa8ceebd7c1b2021240bd46ef77aa8e7408cf10434be55df52384865f8e"},
]
html5lib = [
{file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"},
{file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
oauthlib = [
{file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"},
{file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
parse = [
{file = "parse-1.19.0.tar.gz", hash = "sha256:9ff82852bcb65d139813e2a5197627a94966245c897796760a3a2a8eb66f020b"},
]
pillow = [
{file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"},
{file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"},
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"},
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"},
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"},
{file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"},
{file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"},
{file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"},
{file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"},
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"},
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"},
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"},
{file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"},
{file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"},
{file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"},
{file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"},
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"},
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"},
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"},
{file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"},
{file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"},
{file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"},
{file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"},
{file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"},
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"},
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"},
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"},
{file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"},
{file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"},
{file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"},
{file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"},
{file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"},
{file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"},
{file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"},
{file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"},
{file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"},
{file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"},
{file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pycparser = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydyf = [
{file = "pydyf-0.2.0-py3-none-any.whl", hash = "sha256:f0468bc644d3a5b0a7072d0d92bd5c024cf4beede0df56100d9919a59f15e1f0"},
{file = "pydyf-0.2.0.tar.gz", hash = "sha256:06ebc18b4de29fc1450ae49dd142ecd26bd7ba09d0b1919e365fbc3d8af8a622"},
]
pygments = [
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
]
pypandoc = [
{file = "pypandoc-1.8.1-py3-none-any.whl", hash = "sha256:3d7eda399f9169f16106362c55a8f12f30ab0575cfd2cdc6e1856b214cc4c38c"},
{file = "pypandoc-1.8.1.tar.gz", hash = "sha256:8c1b651d338e8441843b991835f59d561a8473cfe63f0126d330fdb3cb518809"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pyphen = [
{file = "pyphen-0.12.0-py3-none-any.whl", hash = "sha256:459020cd320eb200c0c5ba46b98b2278fd34c5546f520fdcd2ce5f8d733eb994"},
{file = "pyphen-0.12.0.tar.gz", hash = "sha256:b7d3dfc24b6f2178cdb2b1757ace0bd5d222de3e62c28d22ac578c5f22a13e9b"},
]
pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
requests = [
{file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"},
{file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"},
]
requests-oauthlib = [
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
]
rich = [
{file = "rich-12.4.4-py3-none-any.whl", hash = "sha256:d2bbd99c320a2532ac71ff6a3164867884357da3e3301f0240090c5d2fdac7ec"},
{file = "rich-12.4.4.tar.gz", hash = "sha256:4c586de507202505346f3e32d1363eb9ed6932f0c2f63184dea88983ff4971e2"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
tinycss2 = [
{file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"},
{file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tweepy = [
{file = "tweepy-4.10.0-py3-none-any.whl", hash = "sha256:f0abbd234a588e572f880f99a094ac321217ff3eade6c0eca118ed6db8e2cf0a"},
{file = "tweepy-4.10.0.tar.gz", hash = "sha256:7f92574920c2f233663fff154745fc2bb0d10aedc23617379a912d8e4fefa399"},
]
urllib3 = [
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
]
weasyprint = [
{file = "weasyprint-55.0-py3-none-any.whl", hash = "sha256:6a5008b3e1152498f206b2d0791b3e161f507607e31ca42b307cb31b49795462"},
{file = "weasyprint-55.0.tar.gz", hash = "sha256:ea5d5f2f159262e38b6e85939d8510e9735a47751a9647c9eaa93c22ced86230"},
]
webencodings = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
zopfli = [
{file = "zopfli-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f61ecd57bc47684c44a60e8cecb8e67f633cf238f30cc255627e172119ad72d"},
{file = "zopfli-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ca5b541544a7b959fdf5a8f614c52a31002e4be489663d835aadeef3473cb0"},
{file = "zopfli-0.2.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bbcdbfe93dad34f0e30f166092ffdf95e564e415b29732a6f6a52def7bb1c4d3"},
{file = "zopfli-0.2.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c829c577f976b05e4ec583da4f48f31448db97b9f7b65c438d45ba7893aa2a7"},
{file = "zopfli-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28a103434694ce35cbe4f2380e18077052a78f1a0de061b3c8f1bd35b54c2822"},
{file = "zopfli-0.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8a40d9d113902aea0de370dce115051cf9cae4767b50af4fdde66d931b9bcae5"},
{file = "zopfli-0.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d8457452a9151b56f17bbb9af57a4764fb41958ab84bf808e3296aefb6e61bca"},
{file = "zopfli-0.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b882f85b13c47eb19e7f07bccac7564701424ed5ec6d7ba8886ad6de04110e21"},
{file = "zopfli-0.2.1-cp310-cp310-win32.whl", hash = "sha256:e9091778e9e0dbbded72c389eace553153102acc9da560870d9d0845c8547a8d"},
{file = "zopfli-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:7e2b47662632809d7035f4fc16edbe141c4538158ad74eb3c47532b8bedb8277"},
{file = "zopfli-0.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ba4001a8c798a9bb2a59bb30284acd604e0c702477dce69b7fde35a50e55a95b"},
{file = "zopfli-0.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa38b9bb6636fd11b3348dfd6aee4839e71145c3aebc76de4ba44886ec9fd6e"},
{file = "zopfli-0.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:58b2bd497273e1344098370d959f837ec1d18bae9bfafad8f4e4a2802cbbf049"},
{file = "zopfli-0.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:51537999c2114a68b1c0fac8f7ab2a05e4251d778b568213f1666f04feb79e1c"},
{file = "zopfli-0.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:38b1928a5fc5c706ec90aa833ffb5f4512bf886fe41c1b30a95edf7fb09544d6"},
{file = "zopfli-0.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7b189d80aeedb986d226e966df9e13fe8ccc28352d9d5c6c1bd3ac208aa79769"},
{file = "zopfli-0.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f04a2cf50797dfa6fa954dd24de533f471542aa1923b89381a046a936bcbdcc5"},
{file = "zopfli-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:3663d25419476b9e999e12ab8f6f5f8d39079bf536947da2cb5f3e45491db6f4"},
{file = "zopfli-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:fe9c9276edafb8746c01be15e5d702dcdab41062e2f7fb8af1dd8fa51a18b717"},
{file = "zopfli-0.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:338c7b2bba06ff60a73f724b7e1c8a16a5aebe9155edf58cf69b4ff7cb0b46f6"},
{file = "zopfli-0.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dbf30730f169bbe77b2b6e000bf8b486559e8a36e6ab82f2471d75be4661e6bc"},
{file = "zopfli-0.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d067f39d72f364ae94a118df3b4cf2db8a9f53625f497ad0f04bf91ea8720db0"},
{file = "zopfli-0.2.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0190633ca26568f6fa810af9b7c108279d5f565a8aca1224333ff732f565086d"},
{file = "zopfli-0.2.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:faf26674e52a957b8fd76e955b6d89215265f4b8c7e13abf834c84a8e23def9d"},
{file = "zopfli-0.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:683c2d521553aa8ca4526c911d419d37223db298f76048ce920aded18ae060cb"},
{file = "zopfli-0.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0f45111b31f32aef070be180433fec5de548dc87caf92c5c304d1e3642b12815"},
{file = "zopfli-0.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b25540fee3d54e0fb627f911f7e1bed79611ea09aa048708777a34f6f1ac9b70"},
{file = "zopfli-0.2.1-cp38-cp38-win32.whl", hash = "sha256:cfffa5ad327585754f811fd49518d7170d200ba7888c49ae8629ae94c6c4a77d"},
{file = "zopfli-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:95a7ca4571797375b1fa924858cda344767621ef70a43b1b2c0b116e275f42b2"},
{file = "zopfli-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5ffcdc5ee695da73990f7806f8f1dd9a6d99d338bbe54d08fb3aaa55c9603e27"},
{file = "zopfli-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2442b99067229d786aa9f9b86976db1ba9602c1b204f361d2901d1bbcaca3441"},
{file = "zopfli-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:890a83502bb5dff27b1e2b829f8b879d9b7c1383d20a68dc13f0de71da4ff604"},
{file = "zopfli-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ee224ec851b4b53042ff6ad6e81712c8c9aa18396396acb024fe5a65c6bb8f1"},
{file = "zopfli-0.2.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a9def54c90edd112f785b07a812005ab374e7d0aaf50ecac900ca0c51adab3e7"},
{file = "zopfli-0.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e123bfb16cf86ef5abda375012a97c7d00d989b3519d785ca298326f671a81b"},
{file = "zopfli-0.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c5d722b2bfde6dbfbda548e7f6b5b50b57ee06f334b055be24d9bbcdbee60e84"},
{file = "zopfli-0.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:29357c34b8de35c05dd04d8706393bfa09b2dd8bb7a331cc5e98e2b8d39d2efd"},
{file = "zopfli-0.2.1-cp39-cp39-win32.whl", hash = "sha256:fd917247bb0489c924d74186075cf0a2d7d06b3b1413197f9f6ee8fcbd0263a0"},
{file = "zopfli-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:2cd55bf9952884f3c0a3a32f95e5ba5fb0e1f371c9b1a99ed41351c1e00f4700"},
{file = "zopfli-0.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0f052a07a6fb6b7bf87ed30a099187597eb594a8a573a4426c16a6a852a05d86"},
{file = "zopfli-0.2.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c993919cd01ea5c4b0134908bbd59321d9bdbb9085aa30c538fa1289745fe52f"},
{file = "zopfli-0.2.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:37071a3dd5ec0c7f27a1f440d6f50ed8171665e7e3b6ae9a6228e6c48d21570c"},
{file = "zopfli-0.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c9b9f73d99080ca2d79600683b8be3395d46a386b4e1d351c6ed6dc261d267e9"},
{file = "zopfli-0.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:195209711463a399f0c252b34042d8027f3b41dcb646fc112551cfaef507eab4"},
{file = "zopfli-0.2.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8bc92b4008854afa465d647be5ae51b01788ec47cfd10a362dcc865ff898c473"},
{file = "zopfli-0.2.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa18771d1d76c09d2ec2c859b6c55fdad29d2511549b4225437194aca102ad3c"},
{file = "zopfli-0.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:aa11904718fd27b2ccadf0ac88c1ca0b96ba67ad0c3c2bd584f85de060d04534"},
{file = "zopfli-0.2.1.zip", hash = "sha256:e5263d2806e2c1ccb23f52b2972a235d31d42f22f3fa3032cc9aded51e9bf2c6"},
]

25
pyproject.toml Normal file
View file

@ -0,0 +1,25 @@
[tool.poetry]
name = "wiki-postbot"
version = "0.1.0"
description = "Add posts to the wiki!"
authors = ["sneakers-the-rat <JLSaunders987@gmail.com>"]
license = "GPL-3.0"
packages = [
{ include="wiki_postbot" }
]
[tool.poetry.scripts]
wikipostbot = "wiki_postbot.main:main"
[tool.poetry.dependencies]
python = "^3.9"
tweepy = "^4.10.0"
rich = "^12.4.4"
parse = "^1.19.0"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

0
tests/__init__.py Normal file
View file

29
tests/test_commands.py Normal file
View file

@ -0,0 +1,29 @@
from wiki_postbot.bot import WikiPostBot
from wiki_postbot.actions import commands
import pytest
from tweepy import Tweet, StreamResponse
@pytest.mark.parametrize(
['test_str', 'test_res'],
[
(
'@threadodo_bot identify\nname: Myname T. Identifier\norcid: 101001010101010',
{'username': 'threadodo_bot', 'command': 'identify',
'args': {
'name': 'Myname T. Identifier',
'orcid': '101001010101010'
}}
)
] )
def test_parse_identity(test_str, test_res):
bot = WikiPostBot()
response = StreamResponse(Tweet({'text':test_str, 'id':'1095'}), {}, {}, {})
id = commands.Identify(bot)
assert id.check(response)
params = id.parse(response)
assert params.__dict__ == test_res

1
wiki_postbot/__init__.py Normal file
View file

@ -0,0 +1 @@
from wiki_postbot.bot import WikiPostBot

View file

@ -0,0 +1,2 @@
from wiki_postbot.actions.action import Action
from wiki_postbot.actions.checks import Mentioned

View file

@ -0,0 +1,61 @@
import sys
from abc import ABC, abstractmethod
import typing
from typing import Optional, Union, Tuple, List, Dict
from dataclasses import dataclass
from tweepy import Response
if typing.TYPE_CHECKING:
from wiki_postbot.bot import WikiPostBot
@dataclass
class Result:
ok:bool
"""
Whether the action completed successfully
"""
log: Optional[str] = None
"""
Message to log
"""
reply: Optional[str] = None
"""
Message to reply with
"""
class Action(ABC):
def __init__(self, bot: 'WikiPostBot'):
super(Action, self).__init__()
self.bot = bot
#@abstractmethod
def do(self, response:Response) -> Result:
"""
Encapsulate the other actions and uh do the action!
Returns:
:
"""
#@abstractmethod
def check(self, response:Response) -> bool:
"""
Check if the condition of this action is met
"""
#@abstractmethod
def get(self, response:Response) -> typing.Any:
"""
If this action sets something, get its value.
Eg. if this action sets a
Returns:
"""

View file

@ -0,0 +1,43 @@
from wiki_postbot.actions.action import Action, Result
from wiki_postbot.patterns import WIKILINK
from tweepy import Response
import re
class Check(Action):
"""
Base class for actions that check something about the tagged message
to see if we should handle it.
"""
class Mentioned(Check):
"""
Check that we have been directly mentioned in the message
"""
def do(self, response:Response) -> Result:
mentioned_users = [u['username'] == self.bot.username for u in response.data.entities.get('mentions', [])]
result = Result(ok=any(mentioned_users))
if not result.ok:
result.log = "Mentioned, but not directly mentioned"
return result
class Wikilink(Check):
"""
Check if a post contains a wikilink
"""
pattern = WIKILINK
"""
stolen from the agora bot
https://github.com/flancian/agora-bridge/blob/9cfe0a41e55bba4f628875ecf0c8fefd3ad509fd/bots/twitter/agora-bot.py#L48
"""
def do(self, response:Response) -> Result:
wikilinks = self.pattern.findall(response.data.text)
if len(wikilinks)>0:
return Result(ok=True, log=f"Found wikilinks: {wikilinks}")
else:
return Result(ok=False, log="No wikilinks found")

View file

@ -0,0 +1,172 @@
import pdb
import sys
import typing
from typing import Optional, Union, Tuple, List, Dict
from wiki_postbot.actions.action import Action, Result
from wiki_postbot.logger import init_logger
from dataclasses import dataclass
import re
from tweepy import Response, StreamResponse
import parse
@dataclass
class Command_Params:
username: str
command: str
args: Optional[dict]
class Command(Action):
"""
An action invoked by tweeting at the bot and telling it to do something
"""
name:str
command_str = r"@(?P<username>\S{1,})\s*(?P<command>\w*)"
arg_str = r"^(\w*?):\s*(.*)"
@dataclass
class Arguments:
pass
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.logger = init_logger('wiki_postbot.action.command.'+self.name, self.bot.basedir)
self.command_pattern = re.compile(self.command_str)
self.arg_pattern = re.compile(self.arg_str, re.MULTILINE)
@classmethod
def parse(cls, tweet:Union[str, Response]) -> Union[None, Command_Params]:
if isinstance(tweet, (Response, StreamResponse)):
tweet = tweet.data.text
command_pattern = re.compile(cls.command_str)
arg_pattern = re.compile(cls.arg_str, re.MULTILINE)
command_tup = command_pattern.findall(tweet)
command_tup = [c for c in command_tup if len(c) == 2 and len(c[0])>0 and len(c[1])>0]
if len(command_tup) == 0:
return None
else:
param_dict = {'username': command_tup[0][0], 'command': command_tup[0][1]}
args = arg_pattern.findall(tweet)
if args is not None:
param_dict['args'] = dict(args)
return Command_Params(**param_dict)
def check(self, response:Response) -> bool:
"""
Check if the response has a first line like::
@{bot.username}: {Command.name}
Args:
response:
Returns:
bool
"""
# like
# "{@
# split into lines
lines = str(response.data.text).split('\n')
test_str = lines[0].strip()
self.logger.debug(f"Testing string: {test_str}")
command_tup = self.command_pattern.findall(test_str)
command_tup = [c for c in command_tup if len(c) == 2 and len(c[0]) > 0 and len(c[1]) > 0]
if len(command_tup) == 0:
parse_res = None
else:
parse_res = {'username': command_tup[0][0], 'command': command_tup[0][1]}
self.logger.debug(f"parse result: {parse_res}")
if parse_res is None:
self.logger.debug('parse match was None')
return False
if parse_res['username'] == self.bot.username and \
parse_res['command'] == self.name:
return True
else:
return False
def get(self, response:Response) -> Union[Command_Params, None]:
"""Get the value of a command
Response must have the 'user_id' expansion
"""
matched = self.bot.client.search_recent_tweets(
f"from:{response.includes['users'][0]['username']} @{self.bot.username} \"{self.name}\"",
user_auth=True)
self.logger.debug("matched get request")
self.logger.debug(matched)
if matched.data is None:
return None
all_params = [self.parse(d.text) for d in matched.data]
found_params = [p for p in all_params if len(p.args)>0]
if len(found_params)>0:
return found_params[0]
else:
return all_params[0]
def arg_dict(self, tweet:Union[str, Response]) -> dict:
"""
Get arguments given to this command
"""
if isinstance(tweet, Response):
tweet = tweet.data.text
return dict(self.arg_pattern.findall(tweet))
@classmethod
def example(cls) -> str:
ex_str = f"@<bot> {cls.name}"
keys = [f"{key}:" for key in cls.Arguments.__dataclass_fields__.keys()]
ex_str = "\n".join([ex_str, *keys])
return ex_str
class Identify(Command):
name="identify"
@dataclass
class Arguments:
name: str
affiliation: Optional[str]
orcid: Optional[str]
def do(self, response:Response) -> Result:
params = self.parse(response)
if len(params.args) == 0:
id = self.get(response)
if id is None or len(id.args)==0:
reply = "No Identity Found! Set one by tweeting:\n"+self.example()
log = "Help message given"
else:
reply = "Previous Identity Found: \n" + "\n".join([f"{key}: {val}" for key, val in id.args.items()])
log = "previous identity given"
else:
reply = "Identity registered! This will be used to identify all threads from your username. If you need to update or remove your identity, delete this tweet, your information is not stored anywhere else."
log = f"Identity registered"
return Result(ok=True, reply=reply, log=log)

View file

@ -0,0 +1,41 @@
"""
Actions triggered by inline text content
"""
from typing import List, Optional
from wiki_postbot.actions import Action
class Inline(Action):
"""
An action triggered by the content of a tweet
"""
class WikiLink(Inline):
"""
Detect a wikilink and add it to the wiki!
"""
def __init__(self, **kwargs):
super(WikiLink, self).__init__(**kwargs)
self.wikilinks = None # type: Optional[List[str]]
def check(self, response: Response) -> bool:
"""
Check if the condition of this action is met
"""
wikilinks = self.pattern.findall(response.data.text)
if len(wikilinks)>0:
return Result(ok=True, log=f"Found wikilinks: {wikilinks}")
else:
return Result(ok=False, log="No wikilinks found")
def do(self, response: Response) -> Result:
"""
"""
# @abstractmethod

161
wiki_postbot/bot.py Normal file
View file

@ -0,0 +1,161 @@
from pathlib import Path
import logging
import tweepy
import typing
import pdb
from typing import List, Optional, Type
from wiki_postbot.creds import Creds, Zenodo_Creds
from wiki_postbot.thread import Thread
from wiki_postbot.logger import init_logger
from wiki_postbot.actions import checks, commands
if typing.TYPE_CHECKING:
from wiki_postbot.actions import Action
class WikiPostBot(tweepy.StreamingClient):
check_classes = [checks.Wikilink] # type: List[Type[checks.Check]]
#command_classes = [commands.Identify] # type: List[Type[commands.Command]]
command_classes = []
def __init__(
self,
creds:Path = Path('twitter_creds.json'),
wiki_creds:Path = Path('wiki_creds.json'),
username:str='wikibot3k',
following:Optional[List[str]]=None,
basedir:Path=Path().home()/"wiki_postbot",
loglevel="DEBUG",
debug:bool = False
):
self._creds = None
self._wiki_creds = None
self._client = None
self.basedir=Path(basedir)
self.creds_path = Path(creds)
self.wiki_creds_path = Path(wiki_creds)
self.username = username
self.debug = debug
self.following = following
self.logger = init_logger('wiki_postbot.bot', basedir, loglevel=loglevel)
super(WikiPostBot, self).__init__(self.creds.bearer_token)
self.add_rules(self.rule)
self.checks = [cls(self) for cls in self.check_classes]
self.commands = [cls(self) for cls in self.command_classes]
@property
def rule(self) -> tweepy.StreamRule:
"""
StreamRule for accounts we're following or if we're mentioned
"""
rule = f"@{self.username}"
if self.following is not None:
following = [f'from:{user}' for user in self.following]
rule = ' OR '.join([*following, rule])
return tweepy.StreamRule(rule)
def on_response(self, response:tweepy.Response):
"""
Check if the tweet has a wikilink in it
"""
if self.debug:
pdb.set_trace()
self.logger.info(f'Mentioned: {response.data.text}')
# Do checks to see if we should do anything!
for check in self.checks:
res = check.do(response)
if not res.ok:
if res.log:
self.logger.info(res.log)
return
# Determine what action we should do!
for command in self.commands:
if command.check(response):
self.logger.info(f"Given Command {command.name}")
res = command.do(response)
if res.log:
self.logger.debug(res.log)
if res.reply:
self.reply(response, res.reply)
return
# TODO: move this to a command class
# thread = Thread.from_tweet(self.creds, response)
# self.logger.info('thread received')
# try:
# pdf = thread.to_pdf()
# self.logger.info('pdf created')
# depo = post_pdf(pdf, thread, self.zenodo_creds)
# self.logger.info('posted pdf')
# self.logger.debug(depo)
#
# finally:
# pdf.unlink()
#
# self.reply_completed(response, depo)
def reply_completed(self, response: tweepy.Response, deposit:Deposition):
self.client.create_tweet(text=f"The preprint of your thread is ready: {deposit.doi_url} - {deposit.title}",
in_reply_to_tweet_id=response.data.id)
self.logger.info('replied')
def reply(self, response: tweepy.Response, text:str):
self.client.create_tweet(text=text,
in_reply_to_tweet_id=response.data.id)
def run(self, threaded:bool=False):
self.logger.debug('starting')
self.filter(threaded=threaded,
tweet_fields=[
"in_reply_to_user_id",
"author_id",
"created_at",
"conversation_id",
"entities",
"referenced_tweets"
],
expansions=[
'author_id'
]
)
self.logger.debug('stopped')
@property
def client(self) -> tweepy.Client:
if self._client is None:
self._client = tweepy.Client(
consumer_key=self.creds.api_key,
consumer_secret=self.creds.api_secret,
access_token=self.creds.access_token,
access_token_secret=self.creds.access_secret)
return self._client
@property
def creds(self) -> Creds:
if self._creds is None:
self._creds = Creds.from_json(self.creds_path)
return self._creds
@property
def wiki_creds(self) -> Zenodo_Creds:
if self._wiki_creds is None:
self._wiki_creds = Zenodo_Creds.from_json(self.wiki_creds_path)
return self._wiki_creds

View file

34
wiki_postbot/creds.py Normal file
View file

@ -0,0 +1,34 @@
"""
No, not the actual creds. and dont go lookin for em in the git history cuz they aint in there neither
"""
import json
from dataclasses import dataclass
from pathlib import Path
import typing
@dataclass
class Creds:
api_key: str
api_secret: str
bearer_token: str
access_token: typing.Optional[str]
access_secret: typing.Optional[str]
@classmethod
def from_json(cls, path:Path) -> 'Creds':
with open(path, 'r') as jfile:
creds = json.load(jfile)
return Creds(**creds)
@dataclass
class Zenodo_Creds:
access_token:str
@classmethod
def from_json(cls, path:Path) -> 'Zenodo_Creds':
with open(path, 'r') as jfile:
creds = json.load(jfile)
return Zenodo_Creds(**creds)

56
wiki_postbot/logger.py Normal file
View file

@ -0,0 +1,56 @@
import logging
from rich.logging import RichHandler
from pathlib import Path
import sys
import typing
from typing import Optional, Union, Tuple, List, Dict, Literal
from logging.handlers import RotatingFileHandler
def init_logger(
name:Optional[str]=None,
basedir:Optional[Path]=None,
loglevel:str='DEBUG',
loglevel_disk:Optional[str]=None
):
if name is None:
name = 'wiki_postbot'
else:
if not name.startswith('wiki_postbot'):
name = '.'.join(['wiki_postbot', name])
if loglevel_disk is None:
loglevel_disk = loglevel
logger = logging.getLogger(name)
logger.setLevel(loglevel)
if basedir is not None:
logger.addHandler(_file_handler(basedir, name, loglevel_disk))
logger.addHandler(_rich_handler())
return logger
def _file_handler(basedir:Path, name:str, loglevel:str="DEBUG") -> RotatingFileHandler:
filename = Path(basedir) / '.'.join([name, 'log'])
basedir.mkdir(parents=True, exist_ok=True)
file_handler = RotatingFileHandler(
str(filename),
mode='a',
maxBytes=2 ** 24,
backupCount=5
)
file_formatter = logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s]: %(message)s")
file_handler.setLevel(loglevel)
file_handler.setFormatter(file_formatter)
return file_handler
def _rich_handler() -> RichHandler:
rich_handler = RichHandler(rich_tracebacks=True, markup=True)
rich_formatter = logging.Formatter(
"[bold green]\[%(name)s][/bold green] %(message)s",
datefmt='[%y-%m-%dT%H:%M:%S]'
)
rich_handler.setFormatter(rich_formatter)
return rich_handler

26
wiki_postbot/main.py Normal file
View file

@ -0,0 +1,26 @@
import argparse
from wiki_postbot.bot import WikiPostBot
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run a wiki_postbot bot!")
parser.add_argument('--creds', type=Path, help="Location of twitter credentials .json file",
default=Path('twitter_creds.json'))
parser.add_argument('--wiki_creds', type=Path, help="Path to wiki credentials .json file",
default=Path('wiki_creds.json'))
parser.add_argument('-u', '--username', type=str, help="Username of our bot",
default="wikibot3k")
parser.add_argument('-b', '--basedir', help="Base directory to store files",
type=Path, default=Path().home() / "wiki_postbot")
parser.add_argument('-l', '--loglevel', type=str, help="Loglevel for wiki_postbot bot, DEBUG, INFO, WARNING, or ERROR",
default='DEBUG')
parser.add_argument('-d', '--debug', action='store_true', help="Debug responses from bot")
return parser.parse_args()
def main():
args = parse_args()
tt = WikiPostBot(**args.__dict__)
tt.run()
if __name__ == "__main__":
main()

6
wiki_postbot/patterns.py Normal file
View file

@ -0,0 +1,6 @@
"""
Regex patterns
"""
import re
WIKILINK = re.compile(r'\[\[(.*?)\]\]', re.IGNORECASE)

View file

@ -0,0 +1,372 @@
/*stolen from https://github.com/CourtBouillon/weasyprint-samples/blob/master/report/report.css*/
@page {
@top-left {
background: #fbc847;
content: counter(page);
height: 1cm;
text-align: center;
width: 1cm;
}
@top-center {
background: #fbc847;
content: '';
display: block;
height: .05cm;
opacity: .5;
width: 100%;
}
@top-right {
content: string(heading);
font-size: 9pt;
height: 1cm;
vertical-align: middle;
width: 100%;
}
}
@page :blank {
@top-left { background: none; content: '' }
@top-center { content: none }
@top-right { content: none }
}
@page no-chapter {
@top-left { background: none; content: none }
@top-center { content: none }
@top-right { content: none }
}
@page :first {
background: url(report-cover.jpg) no-repeat center;
background-size: cover;
margin: 0;
}
@page chapter {
background: #fbc847;
margin: 0;
@top-left { content: none }
@top-center { content: none }
@top-right { content: none }
}
html {
color: #393939;
font-family: Fira Sans;
font-size: 11pt;
font-weight: 300;
line-height: 1.5;
}
h1 {
color: #fbc847;
font-size: 38pt;
margin: 5cm 2cm 0 2cm;
page: no-chapter;
width: 100%;
}
h2, h3, h4 {
color: black;
font-weight: 400;
}
h2 {
font-size: 28pt;
string-set: heading content();
}
h3 {
font-weight: 300;
font-size: 15pt;
}
h4 {
font-size: 13pt;
}
#cover {
align-content: space-between;
display: flex;
flex-wrap: wrap;
height: 297mm;
}
#cover address {
background: #fbc847;
flex: 1 50%;
margin: 0 -2cm;
padding: 1cm 0;
white-space: pre-wrap;
}
#cover address:first-of-type {
padding-left: 3cm;
}
#contents {
break-before: right;
break-after: left;
page: no-chapter;
}
#contents h2 {
font-size: 20pt;
font-weight: 400;
margin-bottom: 3cm;
}
#contents h3 {
font-weight: 500;
margin: 3em 0 1em;
}
#contents h3::before {
background: #fbc847;
content: '';
display: block;
height: .08cm;
margin-bottom: .25cm;
width: 2cm;
}
#contents ul {
list-style: none;
padding-left: 0;
}
#contents ul li {
border-top: .25pt solid #c1c1c1;
margin: .25cm 0;
padding-top: .25cm;
}
#contents ul li::before {
color: #fbc847;
content: '• ';
font-size: 40pt;
line-height: 16pt;
vertical-align: bottom;
}
#contents ul li a {
color: inherit;
text-decoration-line: inherit;
}
#contents ul li a::before {
content: target-text(attr(href));
}
#contents ul li a::after {
color: #fbc847;
content: target-counter(attr(href), page);
float: right;
}
#columns section {
columns: 2;
column-gap: 1cm;
padding-top: 1cm;
}
#columns section p {
text-align: justify;
}
#columns section p:first-of-type {
font-weight: 700;
}
#skills h3 {
background: #fbc847;
margin: 0 -3cm 1cm;
padding: 1cm 1cm 1cm 3cm;
width: 21cm;
}
#skills section {
padding: .5cm 0;
}
#skills section#table-content::before {
background: url(table-content.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#heading::before {
background: url(heading.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#multi-columns::before {
background: url(multi-columns.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#internal-links::before {
background: url(internal-links.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#style::before {
background: url(style.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section h4 {
margin: 0;
}
#skills section p {
margin-top: 0;
}
#offers {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
#offers h2, #offers h3 {
width: 100%;
}
#offers section {
width: 30%;
}
#offers section h4 {
margin-bottom: 0;
}
#offers section ul {
list-style: none;
margin: 0;
padding-left: 0;
}
#offers section ul li:not(:last-of-type) {
margin: .5cm 0;
}
#offers section p {
background: #fbc847;
display: block;
font-size: 15pt;
font-weight: 700;
margin-bottom: 0;
padding: .25cm 0;
text-align: center;
}
#chapter {
align-items: center;
display: flex;
height: 297mm;
justify-content: center;
page: chapter;
}
#typography section {
display: flex;
flex-wrap: wrap;
margin: 1cm 0;
}
#typography section h4 {
border-top: 1pt solid;
flex: 1 25%;
margin: 0;
}
#typography section h4 + * {
flex: 1 75%;
margin: 0;
padding-left: .5cm;
}
#typography section p {
text-align: justify;
}
#typography section ul {
line-height: 2;
list-style: none;
}
#typography section#small-caps p {
font-variant: small-caps;
}
#typography section#ligatures dl {
display: flex;
flex-wrap: wrap;
}
#typography section#ligatures dl dt {
font-weight: 400;
width: 30%;
}
#typography section#ligatures dl dd {
flex: 1 70%;
margin: 0;
padding: 0;
}
#typography section#ligatures .none {
font-variant-ligatures: none;
}
#typography section#ligatures .common {
font-variant-ligatures: common-ligatures;
}
#typography section#ligatures .discretionary {
font-variant-ligatures: discretionary-ligatures;
}
#typography section#ligatures .contextual {
font-variant-ligatures: contextual;
}
#typography section#numbers dl {
display: flex;
flex-wrap: wrap;
}
#typography section#numbers dl dt {
font-weight: 400;
width: 30%;
}
#typography section#numbers dl dd {
flex: 1 70%;
margin: 0;
padding: 0;
}
#typography section#numbers #fractions {
font-variant-numeric: diagonal-fractions;
}
#typography section#numbers #ordinals {
font-variant-numeric: ordinal;
}
#typography section#numbers #slashed {
font-variant-numeric: slashed-zero;
}
#typography section#numbers #super {
font-variant-position: super;
}
#typography section#numbers #sub {
font-variant-position: sub;
}
#typography section#figures dl {
columns: 4;
}
#typography section#figures dl dt {
font-weight: 400;
}
#typography section#figures dl dd {
display: flex;
margin: 0;
padding: 0;
}
#typography section#figures dl dd ul {
padding: 0 1em 0 0;
}
#typography section#figures #oldstyle {
font-variant-numeric: oldstyle-nums;
}
#typography section#figures #tabular {
font-variant-numeric: tabular-nums;
}
#typography section#figures #old-tabular {
font-variant-numeric: oldstyle-nums tabular-nums;
}

View file

@ -0,0 +1,390 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<style>
/*stolen from https://github.com/CourtBouillon/weasyprint-samples/blob/master/report/report.css*/
@page {
@top-left {
background: #fbc847;
content: counter(page);
height: 1cm;
text-align: center;
width: 1cm;
}
@top-center {
background: #fbc847;
content: '';
display: block;
height: .05cm;
opacity: .5;
width: 100%;
}
@top-right {
content: string(heading);
font-size: 9pt;
height: 1cm;
vertical-align: middle;
width: 100%;
}
}
@page :blank {
@top-left { background: none; content: '' }
@top-center { content: none }
@top-right { content: none }
}
@page no-chapter {
@top-left { background: none; content: none }
@top-center { content: none }
@top-right { content: none }
}
@page chapter {
background: #fbc847;
margin: 0;
@top-left { content: none }
@top-center { content: none }
@top-right { content: none }
}
html {
color: #393939;
font-family: Fira Sans;
font-size: 11pt;
font-weight: 300;
line-height: 1.5;
}
/*h1 {*/
/* color: #fbc847;*/
/* font-size: 38pt;*/
/* margin: 5cm 2cm 0 2cm;*/
/* page: no-chapter;*/
/* width: 100%;*/
/*}*/
h1, h2, h3, h4 {
color: black;
font-weight: 400;
}
h1 {
font-size: 36pt;
}
h2 {
font-size: 28pt;
}
h3 {
font-weight: 300;
font-size: 15pt;
}
h4 {
font-size: 13pt;
}
#cover {
align-content: space-between;
display: flex;
flex-wrap: wrap;
height: 297mm;
}
#cover address {
background: #fbc847;
flex: 1 50%;
margin: 0 -2cm;
padding: 1cm 0;
white-space: pre-wrap;
}
#cover address:first-of-type {
padding-left: 3cm;
}
#contents {
break-before: right;
break-after: left;
page: no-chapter;
}
#contents h2 {
font-size: 20pt;
font-weight: 400;
margin-bottom: 3cm;
}
#contents h3 {
font-weight: 500;
margin: 3em 0 1em;
}
#contents h3::before {
background: #fbc847;
content: '';
display: block;
height: .08cm;
margin-bottom: .25cm;
width: 2cm;
}
#contents ul {
list-style: none;
padding-left: 0;
}
#contents ul li {
border-top: .25pt solid #c1c1c1;
margin: .25cm 0;
padding-top: .25cm;
}
#contents ul li::before {
color: #fbc847;
content: '• ';
font-size: 40pt;
line-height: 16pt;
vertical-align: bottom;
}
#contents ul li a {
color: inherit;
text-decoration-line: inherit;
}
#contents ul li a::before {
content: target-text(attr(href));
}
#contents ul li a::after {
color: #fbc847;
content: target-counter(attr(href), page);
float: right;
}
#columns section {
columns: 2;
column-gap: 1cm;
padding-top: 1cm;
}
#columns section p {
text-align: justify;
}
#columns section p:first-of-type {
font-weight: 700;
}
#skills h3 {
background: #fbc847;
margin: 0 -3cm 1cm;
padding: 1cm 1cm 1cm 3cm;
width: 21cm;
}
#skills section {
padding: .5cm 0;
}
#skills section#table-content::before {
background: url(table-content.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#heading::before {
background: url(heading.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#multi-columns::before {
background: url(multi-columns.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#internal-links::before {
background: url(internal-links.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section#style::before {
background: url(style.svg) no-repeat center #fbc847;
background-size: 50%;
content: '';
display: inline-block;
float: left;
height: 2cm;
margin-right: .5cm;
vertical-align: middle;
width: 2cm;
}
#skills section h4 {
margin: 0;
}
#skills section p {
margin-top: 0;
}
#offers {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
#offers h2, #offers h3 {
width: 100%;
}
#offers section {
width: 30%;
}
#offers section h4 {
margin-bottom: 0;
}
#offers section ul {
list-style: none;
margin: 0;
padding-left: 0;
}
#offers section ul li:not(:last-of-type) {
margin: .5cm 0;
}
#offers section p {
background: #fbc847;
display: block;
font-size: 15pt;
font-weight: 700;
margin-bottom: 0;
padding: .25cm 0;
text-align: center;
}
#chapter {
align-items: center;
display: flex;
height: 297mm;
justify-content: center;
page: chapter;
}
#typography section {
display: flex;
flex-wrap: wrap;
margin: 1cm 0;
}
#typography section h4 {
border-top: 1pt solid;
flex: 1 25%;
margin: 0;
}
#typography section h4 + * {
flex: 1 75%;
margin: 0;
padding-left: .5cm;
}
#typography section p {
text-align: justify;
}
#typography section ul {
line-height: 2;
list-style: none;
}
#typography section#small-caps p {
font-variant: small-caps;
}
#typography section#ligatures dl {
display: flex;
flex-wrap: wrap;
}
#typography section#ligatures dl dt {
font-weight: 400;
width: 30%;
}
#typography section#ligatures dl dd {
flex: 1 70%;
margin: 0;
padding: 0;
}
#typography section#ligatures .none {
font-variant-ligatures: none;
}
#typography section#ligatures .common {
font-variant-ligatures: common-ligatures;
}
#typography section#ligatures .discretionary {
font-variant-ligatures: discretionary-ligatures;
}
#typography section#ligatures .contextual {
font-variant-ligatures: contextual;
}
#typography section#numbers dl {
display: flex;
flex-wrap: wrap;
}
#typography section#numbers dl dt {
font-weight: 400;
width: 30%;
}
#typography section#numbers dl dd {
flex: 1 70%;
margin: 0;
padding: 0;
}
#typography section#numbers #fractions {
font-variant-numeric: diagonal-fractions;
}
#typography section#numbers #ordinals {
font-variant-numeric: ordinal;
}
#typography section#numbers #slashed {
font-variant-numeric: slashed-zero;
}
#typography section#numbers #super {
font-variant-position: super;
}
#typography section#numbers #sub {
font-variant-position: sub;
}
#typography section#figures dl {
columns: 4;
}
#typography section#figures dl dt {
font-weight: 400;
}
#typography section#figures dl dd {
display: flex;
margin: 0;
padding: 0;
}
#typography section#figures dl dd ul {
padding: 0 1em 0 0;
}
#typography section#figures #oldstyle {
font-variant-numeric: oldstyle-nums;
}
#typography section#figures #tabular {
font-variant-numeric: tabular-nums;
}
#typography section#figures #old-tabular {
font-variant-numeric: oldstyle-nums tabular-nums;
}
</style>
<meta name="description" content="Report example">
</head>
<body>
<article id="columns">
$body$
</article>
</body>
</html>

103
wiki_postbot/thread.py Normal file
View file

@ -0,0 +1,103 @@
from wiki_postbot.creds import Creds
import tweepy
import typing
import re
from pathlib import Path
import pypandoc
from dataclasses import dataclass
@dataclass
class Author:
username: str
name: str
class Thread:
def __init__(self,
tweets: typing.List[tweepy.tweet.Tweet],
responses: typing.List[tweepy.Response]
):
self.tweets = tweets
self.responses = responses
self.pdf = None
self.sort()
@property
def text(self) -> str:
"""
Text from all the tweets in a thread, double new lines between each
also replacing any @'s with markdown links to profiles i guess
"""
text = "\n\n".join([t.text for t in self.tweets])
# make @'s links
text = re.sub(r"@([\w\d_]{1,})", r"[@\1](https://twitter.com/\1)", text)
return text
@property
def author(self) -> Author:
return Author(
"@" + self.responses[0].includes['users'][0].username,
self.responses[0].includes['users'][0].name,
)
@property
def title(self) -> str:
return f"{self.author.name}'s thread - {self.tweets[0].created_at.strftime('%y-%m-%d %H:%M')}"
def sort(self):
"""
sort order of tweets
"""
self.tweets = sorted(self.tweets, key=lambda x: x.created_at)
self.responses = sorted(self.responses, key=lambda x: x.data.created_at)
def to_pdf(self, output_file:typing.Optional[Path]=None,
) -> Path:
if output_file is None:
output_file = Path('.') / f"{self.tweets[0].author_id}_{self.tweets[0].created_at.isoformat()}.pdf"
pypandoc.convert_text(
self.text, to="html", format="md",
outputfile=str(output_file),
extra_args = [
'--pdf-engine=weasyprint',
f'--data-dir={Path(__file__).parent}',
'--template=./template.html',
])
self.pdf = output_file
return output_file
@classmethod
def from_tweet(cls,
creds:Creds,
response:tweepy.Response,
limit:int=100) -> 'Thread':
"""
Starting with the mentioned tweet, get all of the tweets in a thread in order
"""
tweet = response.data
client = tweepy.Client(creds.bearer_token)
tweets = [tweet]
responses = [response]
while tweet.referenced_tweets is not None and len(tweet.referenced_tweets)>0 and len(tweets)<=limit:
replied = [t.id for t in tweet.referenced_tweets if t.type=="replied_to"]
if len(replied)==0:
# top tweet was qt
break
replied = replied[0]
response = client.get_tweet(id=replied, tweet_fields=[
"referenced_tweets",
"author_id",
"created_at"
], expansions=[
'author_id'
])
tweet = response.data
tweets.append(tweet)
responses.append(response)
return Thread(tweets, responses)