cleantoots/cleantoots/main.py

181 lines
5.4 KiB
Python
Raw Normal View History

import configparser
import functools
import os
import pathlib
import subprocess
import webbrowser
import click
import pendulum
from mastodon import Mastodon
def config_file(filename):
return os.path.join(CONFIG_DIR, filename)
HOME = pathlib.Path.home()
CONFIG_DIR = click.get_app_dir("cleantoots")
CONFIG_FILE = config_file("config.ini")
EDITOR = os.getenv("EDITOR", "vim")
@click.group()
def cli():
"""
Provide an easy interface for deleting old toots.
Steps, in order:
1. run `setup-config`
2. run `login`
3. run `clean --delete`
"""
pass
def load_config(function):
config = configparser.ConfigParser()
config.read(CONFIG_FILE)
func = functools.partial(function, config=config)
func.__name__ = function.__name__
func.__doc__ = function.__doc__
return func
@cli.command()
def setup_config():
"""Initial setup for configuration directories and files."""
os.makedirs(CONFIG_DIR, exist_ok=True)
if os.path.isfile(CONFIG_FILE):
click.secho("{} found. Not touching anything.".format(CONFIG_FILE), fg="red")
return
config = configparser.ConfigParser()
config["DEFAULT"] = {
"boost_limit": 5,
"favorite_limit": 5,
"days_count": 30,
"timezone": "Europe/Paris",
}
config["Mastodon.social"] = {
"api_base_url": "https://mastodon.social",
"app_secret_file": "mastodon_social_app.secret",
"user_secret_file": "mastodon_social_user.secret",
"protected_toots": "1234\n5678",
}
with open(CONFIG_FILE, "w") as _file:
config.write(_file)
click.secho("{} written.".format(CONFIG_FILE), fg="green")
click.echo()
click.secho("Next steps", bold=True)
click.echo(
"You'll need to edit the config file in order to set some settings such as:"
)
click.echo("* The base URL of your Mastodon instance")
click.echo("* The toots you want to protect")
click.echo()
click.secho("We're going to open the file for you now.")
click.pause()
subprocess.run([EDITOR, CONFIG_FILE])
@cli.command()
@load_config
def config(config):
"""Display parsed config."""
for section_name in config.sections():
click.secho(section_name, bold=True)
section = config[section_name]
for key, value in section.items():
click.secho("{} = {}".format(key, value))
click.echo()
@cli.command()
@load_config
def login(config):
"""Fetch credentials for each app described in config file."""
for section in config.sections():
section = config[section]
Mastodon.create_app(
"cleantoots",
api_base_url=section.get("api_base_url"),
to_file=config_file(section.get("app_secret_file")),
)
mastodon = Mastodon(client_id=config_file(section.get("app_secret_file")))
click.echo(
"We will now open a browser for each account set in the config file."
)
click.echo(
"You'll need to authenticate and then copy the code provided in the web "
"page back into this terminal, upon prompt."
)
click.pause()
webbrowser.open(mastodon.auth_request_url())
code = click.prompt("Enter code for {}".format(section.get("api_base_url")))
mastodon.log_in(code=code, to_file=config_file(section.get("user_secret_file")))
@cli.command()
@click.option(
"--delete",
help="Delete toots that match the rules without confirmation. This is a destructive operation. "
"Without this flags, toots will only be listed.",
is_flag=True,
)
@load_config
def clean(delete, config):
"""
Delete Toots based on rules in config file.
Without the `--delete` flag, toots will only be displayed.
"""
for section in config.sections():
section = config[section]
user_secret_file = config_file(section.get("user_secret_file"))
mastodon = Mastodon(access_token=user_secret_file)
user = mastodon.me()
page = mastodon.account_statuses(user["id"])
would_delete = []
while page:
for toot in page:
if (
toot["reblogs_count"] >= section.getint("boost_limit")
or toot["favourites_count"] >= section.getint("favorite_limit")
or toot["id"]
in map(int, section.get("protected_toots", "").split())
or toot["created_at"]
>= pendulum.now(tz=section.get("timezone")).subtract(
days=section.getint("days_count")
)
):
continue
would_delete.append(toot)
page = mastodon.fetch_next(page)
if not delete:
if not would_delete:
click.secho("No toot would be deleted given the rules.", fg="blue")
return
click.secho(
"Would delete {count} toots:".format(count=len(would_delete)), fg="blue"
)
for toot in would_delete:
click.echo(toot["id"])
click.echo(toot["content"])
click.echo()
else:
click.echo("Deleting toots...")
with click.progressbar(would_delete) as bar:
for toot in bar:
mastodon.status_delete(toot)
click.secho("Deleted toot {}".format(toot["id"]), fg="green")
if __name__ == "__main__":
cli()