Issues punishments

This commit is contained in:
Johnny Gear 2025-11-13 22:03:20 -06:00
parent c62d49043f
commit a728a75245
5 changed files with 170 additions and 70 deletions

56
db.py
View file

@ -1,5 +1,6 @@
import os import os
import sqlite3 import sqlite3
import datetime
SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3') SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3')
@ -16,17 +17,30 @@ TABLE_ORDER_STATUS = '''
CREATE TABLE IF NOT EXISTS order_status ( CREATE TABLE IF NOT EXISTS order_status (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
mastodon_id TEXT NOT NULL, mastodon_id TEXT NOT NULL,
created_at DATETIME NOT NULL, created_at TIMESTAMP NOT NULL,
text TEXT NOT NULL, text TEXT NOT NULL,
confirmed_at DATETIME confirmed_at TIMESTAMP,
punishment_id INTEGER,
FOREIGN KEY(punishment_id) REFERENCES punishment_status(id)
);
'''
TABLE_PUNISHMENT_STATUS = '''
CREATE TABLE IF NOT EXISTS punishment_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mastodon_id TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
text TEXT NOT NULL,
confirmed_at TIMESTAMP
); );
''' '''
class Database: class Database:
def __init__(self): def __init__(self):
self.conn = sqlite3.connect(SQLITE_DB, detect_types=sqlite3.PARSE_DECLTYPES) self.conn = sqlite3.connect(SQLITE_DB)
self.conn.row_factory = sqlite3.Row self.conn.row_factory = sqlite3.Row
self.table_init(TABLE_REPEATS) self.table_init(TABLE_REPEATS)
self.table_init(TABLE_PUNISHMENT_STATUS)
self.table_init(TABLE_ORDER_STATUS) self.table_init(TABLE_ORDER_STATUS)
def table_init(self, table_sql): def table_init(self, table_sql):
@ -38,6 +52,7 @@ class Database:
c = self.conn.cursor() c = self.conn.cursor()
c.execute(sql, args) c.execute(sql, args)
self.conn.commit() self.conn.commit()
return c.lastrowid
def repeat_get(self): def repeat_get(self):
c = self.conn.cursor() c = self.conn.cursor()
@ -64,7 +79,11 @@ class Database:
(mastodon_id, created_at, text) (mastodon_id, created_at, text)
VALUES (?, ?, ?); VALUES (?, ?, ?);
''', ''',
[mastodon_id, created_at, text] [
mastodon_id,
created_at,
text
]
) )
def order_status_outstanding(self): def order_status_outstanding(self):
@ -72,7 +91,7 @@ class Database:
sql = ''' sql = '''
SELECT id, mastodon_id, created_at, confirmed_at SELECT id, mastodon_id, created_at, confirmed_at
FROM order_status FROM order_status
WHERE confirmed_at IS NULL WHERE confirmed_at IS NULL AND punishment_id IS NULL
''' '''
c.execute(sql) c.execute(sql)
return c.fetchall() return c.fetchall()
@ -84,5 +103,30 @@ class Database:
SET confirmed_at=? SET confirmed_at=?
WHERE id=?; WHERE id=?;
''', ''',
[confirmed_at, id] [
confirmed_at,
id
]
)
def punishment_status_put(self, order_status_id, mastodon_id, created_at, text):
punishment_status_id = self.update(
'''
INSERT INTO punishment_status
(mastodon_id, created_at, text)
VALUES (?, ?, ?);
''',
[
mastodon_id,
created_at,
text
]
)
self.update(
'''
UPDATE order_status
SET punishment_id=?
WHERE id=?
''',
[punishment_status_id, order_status_id]
) )

View file

@ -1,7 +1,11 @@
import yaml, json import yaml
import json
import logging
import random import random
from db import Database from db import Database
logger = logging.getLogger(__name__)
def pick_order(orders): def pick_order(orders):
picked = random.choices(orders, picked = random.choices(orders,
weights=[o['weight'] for o in orders], weights=[o['weight'] for o in orders],
@ -29,33 +33,48 @@ def pick_order(orders):
return (result, repeat) return (result, repeat)
def generate(filename="orders.yml"): def read_config(filename="orders.yml"):
with open(filename) as stream: with open(filename) as stream:
# Do we want to repeat?
db = Database()
repeat = db.repeat_get()
if repeat is not None:
if repeat['probability'] > random.random():
db.repeat_increment()
return {
"orders": json.loads(repeat['orders']),
"count": repeat['count']
}
else:
db.repeat_clear()
# Pick a new order
try: try:
orders = yaml.safe_load(stream) orders = yaml.safe_load(stream)
except yaml.YAMLError as exc: except yaml.YAMLError as exc:
print(exc) logger.error(exc)
return orders
(result, repeat_p) = pick_order(orders['orders']) def generate_order():
# Do we want to repeat?
db = Database()
repeat = db.repeat_get()
if repeat is not None:
if repeat['probability'] > random.random():
db.repeat_increment()
return {
"orders": json.loads(repeat['orders']),
"count": repeat['count']
}
else:
db.repeat_clear()
# Log the repeat orders_config = read_config()
if repeat_p > 0.0:
db.repeat_put(repeat_p, json.dumps(result))
return { if orders_config['orders_probability'] > random.random():
"orders": result # No orders today
} return { }
# Pick new orders
(result, repeat_p) = pick_order(orders_config['orders'])
# Log the repeat
if repeat_p > 0.0:
db.repeat_put(repeat_p, json.dumps(result))
return {
"orders": result
}
def generate_punishment():
orders_config = read_config()
(result, repeat_p) = pick_order(orders_config['punishments'])
return result

39
main.py
View file

@ -1,22 +1,33 @@
import os
import sys import sys
import logging import logging
import argparse import argparse
import asyncio import asyncio
import aiohttp import aiohttp
import datetime
from generate import generate from generate import generate_order, generate_punishment
from mastodon import Mastodon from mastodon import Mastodon
from db import Database from db import Database
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ORDER_TIMEOUT = datetime.timedelta(
hours=os.getenv('ORDER_TIMEOUT', default=3)
)
MASTODON_USERNAME = os.getenv('MASTODON_USERNAME')
def make_session(): def make_session():
return aiohttp.ClientSession() return aiohttp.ClientSession()
async def immediate(): async def immediate():
orders_info = generate() orders_info = generate_order()
post = "Here are today's orders for @johnnygear - \n\n" if 'orders' not in orders_info:
logger.info("No orders for today")
return
post = "Here are today's orders for @%s - \n\n" % MASTODON_USERNAME
if "count" in orders_info and orders_info['count'] > 1: if "count" in orders_info and orders_info['count'] > 1:
post += f"These are the same orders from the last {orders_info['count']} days\n\n" post += f"These are the same orders from the last {orders_info['count']} days\n\n"
post += "\n".join(orders_info['orders']) post += "\n".join(orders_info['orders'])
@ -45,7 +56,7 @@ async def check():
for d in context['descendants']: for d in context['descendants']:
if ( if (
d['in_reply_to_id'] == outstanding_order['mastodon_id'] and d['in_reply_to_id'] == outstanding_order['mastodon_id'] and
d['account']['username'] == 'johnnygear' and d['account']['username'] == MASTODON_USERNAME and
len(d['media_attachments']) > 0 len(d['media_attachments']) > 0
): ):
confirmed_at = d['created_at'] confirmed_at = d['created_at']
@ -56,6 +67,26 @@ async def check():
if confirmed_at is None: if confirmed_at is None:
logger.info('Order %s remains unconfirmed' % (outstanding_order['created_at'])) logger.info('Order %s remains unconfirmed' % (outstanding_order['created_at']))
oo_created_at = datetime.datetime.fromisoformat(outstanding_order['created_at'])
if(oo_created_at + ORDER_TIMEOUT < datetime.datetime.now(datetime.UTC)):
logger.info('Time to issue a punishment for %s' % outstanding_order['created_at'])
punishment = generate_punishment()
post = "@%s has failed to post proof of compliance. Punishment is as follows -\n\n" % MASTODON_USERNAME
post += "\n".join(punishment)
punishment_status = await m.statusPost(
post,
in_reply_to_id=outstanding_order['mastodon_id']
)
db.punishment_status_put(
outstanding_order['id'],
punishment_status['id'],
punishment_status['created_at'],
post
)
if __name__=='__main__': if __name__=='__main__':
logging.basicConfig( logging.basicConfig(
format="%(asctime)s %(module)s [%(levelname)-4.4s] %(message)s", format="%(asctime)s %(module)s [%(levelname)-4.4s] %(message)s",

View file

@ -49,13 +49,14 @@ class Mastodon:
return await response.json() return await response.json()
async def statusPost(self, status): async def statusPost(self, status, in_reply_to_id=None):
return await self.post( return await self.post(
self.instance, self.instance,
API_STATUSES, API_STATUSES,
data={ data={
'status': status, 'status': status,
'visibility': 'direct' 'visibility': 'direct',
'in_reply_to_id': in_reply_to_id
}) })
async def statusContext(self, id): async def statusContext(self, id):

View file

@ -1,34 +1,39 @@
orders_probability: 0.7
orders: orders:
- weight: 20
text: "MX Pants + MX Boots"
repeat: 0.7
add:
- probability: 0.5
text: "+ Collar"
- weight: 20
text: "Race Pants + MX Boots"
repeat: 0.7
add:
- probability: 0.5
text: "+ Collar"
- weight: 40
text: "Marine Uniform"
repeat: 0.7
add:
- probability: 0.9
text: "+ Leg shackles"
- probability: 0.5
text: "+ Collar"
- weight: 30 - weight: 30
text: "No orders today" text: "Army Uniform"
- weight: 70 repeat: 0.7
text: "Here are your orders:" add:
pick: - probability: 0.9
- weight: 20 text: "+ Leg Shackles"
text: "MX Pants + MX Boots" - probability: 0.5
repeat: 0.7 text: "+ Collar"
add: punishments:
- probability: 0.5 - weight: 20
text: "+ Collar" text: "Kneel facing a wall for 30 minutes"
- weight: 20 - weight: 5
text: "Race Pants + MX Boots" text: "Kneel facing a wall for 60 minutes"
repeat: 0.7 - weight: 40
add: text: "2 hours of ball stretcher time"
- probability: 0.5 - weight: 5
text: "+ Collar" text: "4 hours of ball stretcher time"
- weight: 40
text: "Marine Uniform"
repeat: 0.7
add:
- probability: 0.9
text: "+ Leg shackles"
- probability: 0.5
text: "+ Collar"
- weight: 30
text: "Army Uniform"
repeat: 0.7
add:
- probability: 0.9
text: "+ Leg Shackles"
- probability: 0.5
text: "+ Collar"