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 sqlite3
import datetime
SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3')
@ -16,17 +17,30 @@ TABLE_ORDER_STATUS = '''
CREATE TABLE IF NOT EXISTS order_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mastodon_id TEXT NOT NULL,
created_at DATETIME NOT NULL,
created_at TIMESTAMP 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:
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.table_init(TABLE_REPEATS)
self.table_init(TABLE_PUNISHMENT_STATUS)
self.table_init(TABLE_ORDER_STATUS)
def table_init(self, table_sql):
@ -38,6 +52,7 @@ class Database:
c = self.conn.cursor()
c.execute(sql, args)
self.conn.commit()
return c.lastrowid
def repeat_get(self):
c = self.conn.cursor()
@ -64,7 +79,11 @@ class Database:
(mastodon_id, created_at, text)
VALUES (?, ?, ?);
''',
[mastodon_id, created_at, text]
[
mastodon_id,
created_at,
text
]
)
def order_status_outstanding(self):
@ -72,7 +91,7 @@ class Database:
sql = '''
SELECT id, mastodon_id, created_at, confirmed_at
FROM order_status
WHERE confirmed_at IS NULL
WHERE confirmed_at IS NULL AND punishment_id IS NULL
'''
c.execute(sql)
return c.fetchall()
@ -84,5 +103,30 @@ class Database:
SET confirmed_at=?
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
from db import Database
logger = logging.getLogger(__name__)
def pick_order(orders):
picked = random.choices(orders,
weights=[o['weight'] for o in orders],
@ -29,33 +33,48 @@ def pick_order(orders):
return (result, repeat)
def generate(filename="orders.yml"):
def read_config(filename="orders.yml"):
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:
orders = yaml.safe_load(stream)
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
if repeat_p > 0.0:
db.repeat_put(repeat_p, json.dumps(result))
orders_config = read_config()
return {
"orders": result
}
if orders_config['orders_probability'] > random.random():
# 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 logging
import argparse
import asyncio
import aiohttp
import datetime
from generate import generate
from generate import generate_order, generate_punishment
from mastodon import Mastodon
from db import Database
logger = logging.getLogger(__name__)
ORDER_TIMEOUT = datetime.timedelta(
hours=os.getenv('ORDER_TIMEOUT', default=3)
)
MASTODON_USERNAME = os.getenv('MASTODON_USERNAME')
def make_session():
return aiohttp.ClientSession()
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:
post += f"These are the same orders from the last {orders_info['count']} days\n\n"
post += "\n".join(orders_info['orders'])
@ -45,7 +56,7 @@ async def check():
for d in context['descendants']:
if (
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
):
confirmed_at = d['created_at']
@ -56,6 +67,26 @@ async def check():
if confirmed_at is None:
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__':
logging.basicConfig(
format="%(asctime)s %(module)s [%(levelname)-4.4s] %(message)s",

View file

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

View file

@ -1,34 +1,39 @@
orders_probability: 0.7
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
text: "No orders today"
- weight: 70
text: "Here are your orders:"
pick:
- 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
text: "Army Uniform"
repeat: 0.7
add:
- probability: 0.9
text: "+ Leg Shackles"
- probability: 0.5
text: "+ Collar"
text: "Army Uniform"
repeat: 0.7
add:
- probability: 0.9
text: "+ Leg Shackles"
- probability: 0.5
text: "+ Collar"
punishments:
- weight: 20
text: "Kneel facing a wall for 30 minutes"
- weight: 5
text: "Kneel facing a wall for 60 minutes"
- weight: 40
text: "2 hours of ball stretcher time"
- weight: 5
text: "4 hours of ball stretcher time"