Issues punishments
This commit is contained in:
parent
c62d49043f
commit
a728a75245
5 changed files with 170 additions and 70 deletions
56
db.py
56
db.py
|
|
@ -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]
|
||||||
)
|
)
|
||||||
35
generate.py
35
generate.py
|
|
@ -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,8 +33,15 @@ 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:
|
||||||
|
try:
|
||||||
|
orders = yaml.safe_load(stream)
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
logger.error(exc)
|
||||||
|
return orders
|
||||||
|
|
||||||
|
def generate_order():
|
||||||
# Do we want to repeat?
|
# Do we want to repeat?
|
||||||
db = Database()
|
db = Database()
|
||||||
repeat = db.repeat_get()
|
repeat = db.repeat_get()
|
||||||
|
|
@ -44,13 +55,14 @@ def generate(filename="orders.yml"):
|
||||||
else:
|
else:
|
||||||
db.repeat_clear()
|
db.repeat_clear()
|
||||||
|
|
||||||
# Pick a new order
|
orders_config = read_config()
|
||||||
try:
|
|
||||||
orders = yaml.safe_load(stream)
|
|
||||||
except yaml.YAMLError as exc:
|
|
||||||
print(exc)
|
|
||||||
|
|
||||||
(result, repeat_p) = pick_order(orders['orders'])
|
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
|
# Log the repeat
|
||||||
if repeat_p > 0.0:
|
if repeat_p > 0.0:
|
||||||
|
|
@ -59,3 +71,10 @@ def generate(filename="orders.yml"):
|
||||||
return {
|
return {
|
||||||
"orders": result
|
"orders": result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def generate_punishment():
|
||||||
|
orders_config = read_config()
|
||||||
|
|
||||||
|
(result, repeat_p) = pick_order(orders_config['punishments'])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
|
||||||
39
main.py
39
main.py
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
15
orders.yml
15
orders.yml
|
|
@ -1,9 +1,5 @@
|
||||||
|
orders_probability: 0.7
|
||||||
orders:
|
orders:
|
||||||
- weight: 30
|
|
||||||
text: "No orders today"
|
|
||||||
- weight: 70
|
|
||||||
text: "Here are your orders:"
|
|
||||||
pick:
|
|
||||||
- weight: 20
|
- weight: 20
|
||||||
text: "MX Pants + MX Boots"
|
text: "MX Pants + MX Boots"
|
||||||
repeat: 0.7
|
repeat: 0.7
|
||||||
|
|
@ -32,3 +28,12 @@ orders:
|
||||||
text: "+ Leg Shackles"
|
text: "+ Leg Shackles"
|
||||||
- probability: 0.5
|
- probability: 0.5
|
||||||
text: "+ Collar"
|
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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue