From 40fad78407c4f327e0a168aa0af992b51c371691 Mon Sep 17 00:00:00 2001 From: Kay Faraday Date: Mon, 6 Feb 2023 08:20:34 +0000 Subject: [PATCH] implement opt-out --- .gitignore | 2 ++ README.md | 14 +++++++++ blockbot.sql | 9 ++++++ opter_outer.example.toml | 6 ++++ opter_outer.py | 68 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 ++ 6 files changed, 102 insertions(+) create mode 100644 .gitignore create mode 100644 opter_outer.example.toml create mode 100755 opter_outer.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ed82cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.toml +!*.example.toml diff --git a/README.md b/README.md index fa0590e..c746dd9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,20 @@ This is a simple database-based block bot for Mastodon. Database admin is required to create the Python extension and function. For example: `sudo -u postgres psql mastodon < blockbot.sql`. 3. Insert a row into blockbot_config with your Mastodon hostname and a token for the account you want the blockbot to use. `INSERT INTO blockbot_config (hostname, token) VALUES ('freak.university', '...');` + +### Opter outer + +This is a python fedi bot that processes opt out/opt in requests. +By default, people are opted in, and they can run @blockbot opt out to opt out. + +1. Copy opter_outer.example.toml to opter_outer.toml +2. Edit the `db` section as needed. +3. `python -m venv .venv` +4. `. .venv/bin/activate` +5. `pip install -r requirements.txt` + +Then set your service manager (such as systemd) to run `.venv/bin/python opter_outer.py`. + ## License AGPLv3, see LICENSE.md. diff --git a/blockbot.sql b/blockbot.sql index 40db499..cbff5a4 100644 --- a/blockbot.sql +++ b/blockbot.sql @@ -5,6 +5,10 @@ CREATE TABLE blockbot_config ( token TEXT ); +CREATE TABLE blockbot_opt_out ( + account_id BIGINT +); + CREATE FUNCTION blockbot_notify( blocker TEXT, blocked TEXT, @@ -47,6 +51,11 @@ SELECT blockbot_notify( unblock, (SELECT hostname FROM blockbot_config), (SELECT token FROM blockbot_config) +) +WHERE blocker NOT IN ( + SELECT blocker + FROM blockbot_opt_out + WHERE account_id = $1 ); $$ LANGUAGE SQL; diff --git a/opter_outer.example.toml b/opter_outer.example.toml new file mode 100644 index 0000000..f644b6a --- /dev/null +++ b/opter_outer.example.toml @@ -0,0 +1,6 @@ +[db] +# Accepts all parameters accepted by https://magicstack.github.io/asyncpg/current/api/index.html#connection. +# Or leave this section blank to use the defaults. +username='mastodon' +password='...' +database='...' diff --git a/opter_outer.py b/opter_outer.py new file mode 100755 index 0000000..8dfc177 --- /dev/null +++ b/opter_outer.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +from pleroma import Pleroma +import qtoml as toml +import asyncpg +import anyio + +with open('opter_outer.toml') as f: + config = toml.load(f) + +async def main(): + db = await asyncpg.connect(**config['db']) + + hostname, access_token = await db.fetchrow('SELECT hostname, token FROM blockbot_config') + + async with ( + Pleroma( + api_base_url='https://' + hostname, + access_token=access_token, + ) as pleroma, + anyio.create_task_group() as nursery, + ): + async for notif in pleroma.stream_notifications(): + if 'opt out' in notif['status']['content']: + nursery.start_soon(opt_out, db, pleroma, notif['status']) + elif 'opt in' in notif['status']['content']: + nursery.start_soon(opt_in, db, pleroma, notif['status']) + elif 'status' in notif['status']['content']: + nursery.start_soon(status, db, pleroma, notif['status']) + elif 'help' in notif['status']['content']: + nursery.start_soon(help, db, pleroma, notif['status']) + +async def opt_out(db, pleroma, status): + await db.execute( + 'INSERT INTO blockbot_opt_out (account_id) VALUES ($1)', + int(status['account']['id']), + ) + + await pleroma.reply( + status, + 'You have successfully opted out. Your blocks will no longer be sent to the people you block.', + ) + +async def opt_in(db, pleroma, status): + await db.execute( + 'DELETE FROM blockbot_opt_out WHERE account_id = $1', + int(status['account']['id']), + ) + + await pleroma.reply( + status, + 'You have successfully opted in. Your blocks will now be sent to the people you block.' + ) + +async def status(db, pleroma, status): + if await db.fetchval( + 'SELECT 1 FROM blockbot_opt_out WHERE account_id = $1', + int(status['account']['id']), + ): + await pleroma.reply(status, 'You are opted out.') + else: + await pleroma.reply(status, 'You are opted in.') + +async def help(db, pleroma, status): + await pleroma.reply(status, 'Available commands: opt in, opt out, status, and help.') + +if __name__ == '__main__': + anyio.run(main) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5776f2e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pleroma.py +asyncpg +qtoml