initial commit

main
Paco Hope 2022-11-25 14:03:24 -05:00
parent 2b477b2528
commit 7c7cc1611c
7 changed files with 163 additions and 36 deletions

40
.gitignore vendored
View File

@ -1,4 +1,3 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@ -21,6 +20,7 @@ parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
@ -50,7 +50,6 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
@ -73,7 +72,6 @@ instance/
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
@ -84,9 +82,7 @@ profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
@ -95,22 +91,7 @@ ipython_config.py
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
@ -122,6 +103,7 @@ celerybeat.pid
# Environments
.env
*.env
.venv
env/
venv/
@ -146,17 +128,3 @@ dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

5
example.env Normal file
View File

@ -0,0 +1,5 @@
#
# User name (email address) and password for mastodon server
MD_USER="toota-palooza@example.com"
MD_PASS="my-super-good-password"
MD_HOST="https://always.grumpy.world"

24
pyproject.toml Normal file
View File

@ -0,0 +1,24 @@
[project]
name = "toota-palooza"
version = "1.0"
authors = [{ name="Paco Hope", email="toota-palooza@filter.paco.to" } ]
description = "Fill a mastodon public timeline with public toots from bots who post gutenberg open texts."
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
]
[project.urls]
"Homepage" = "https://git.paco.to/nick/toota-palooza"
"Bug Tracker" = "https://git.paco.to/nick/toota-palooza/issues"
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project.scripts]
toota-palooza = "toota-palooza.cli:main"

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
build
Mastodon.py
python-dotenv
wheel
pip
python-daemon

6
setup.cfg Normal file
View File

@ -0,0 +1,6 @@
[metadata]
name = toota-palooza
version = 1.0
[options]
packages = toota-palooza

11
toota-palooza/__init__.py Normal file
View File

@ -0,0 +1,11 @@
from mastodon import Mastodon
'''
Mastodon.create_app(
'toota-palooza',
api_base_url = 'https://infosec.exchange',
to_file = '../.toota-palooza.env'
)
'''

107
toota-palooza/cli.py Normal file
View File

@ -0,0 +1,107 @@
'''
Command line module for calling toota-palooza to do its work
'''
__all__ = ['toota-palooza']
__author__ = 'Paco Hope <toota-palooza@filter.paco.to>'
__date__ = '25 November 2022'
__version__ = '1.0'
__copyright__ = 'Copyright © 2022 Paco Hope. See LICENSE for details.'
from mastodon import Mastodon
from dotenv import load_dotenv
import os
import time
import argparse
import sys
from pprint import pprint
# import toota-palooza
def mastodonInit():
"""Connect to the Mastodon instance based on .env files"""
server = Mastodon(
client_id = '.toota-palooza.env',
api_base_url = os.getenv('MD_HOST')
)
load_dotenv()
server.log_in(
os.getenv('MD_USER'),
os.getenv('MD_PASS'),
to_file = '.toota-palooza-usercred.env'
)
return(server)
def checkPublicTimeline( server):
"""Do one run. Connect to the database, connect to the server, get the public timeline.
Look at users and check to see if any are potential impersonators. Then exit."""
# Here's the idea: pick a chunkSize. Ask the server for that many toots off the public
# timeline. As long as the server gives us as many as we asked for, keep trying.
# as soon as we get less than we asked for, quit.
#
# XXX Not sure about rate-limiting
#
chunkSize = 20
maxPosts = 1000
timelineList = []
useridList = {}
total = 0
calls = 0
while( total < maxPosts ):
timelineList = server.timeline(timeline='public', since_id=latestId, limit=chunkSize)
calls = calls + 1
for post in timelineList:
userid = post.account.acct
username=userid.split('@')[0]
try:
domain=userid.split('@')[1]
except IndexError:
# if there is no domain, then it's a local account
domain=os.getenv('MD_HOST').split('/')[2]
useridList[userid] = (username,domain,post.account.display_name,post.account.bot,post.url)
latestId=post.id
if( len(timelineList) < 1):
# We got fewer than we asked for. Drop out of the loop.
total = total + len(timelineList)
break
else:
# record how many we did, and go again.
total = total + len(timelineList)
timelineList = []
def daemon_main():
"""Run from a command line."""
server = mastodonInit()
while(True):
# do a thing
time.sleep(600)
def once():
"""Run from a command line."""
server = mastodonInit()
return 0
def main():
parser = argparse.ArgumentParser(
description='Check for suspicious impersonators.'
)
parser.add_argument( '-d', '--debug', action='store_true',
help='Enable debugging messages.')
parser.add_argument( '-o', '--once', action='store_true',
help='Run once and exit. Default is to run as a daemon.')
args = parser.parse_args()
if( args.once ):
exit(once())
else:
daemon_main()
if __name__ == '__main__':
exit(once())