Portmanteau bot for Mastodon
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
6.5 KiB

3 years ago
import os
import sys
import time
import toml
import random
import re
import sched
3 years ago
import math
import string
from datetime import datetime, timedelta, timezone
3 years ago
from mastodon import Mastodon, MastodonNotFoundError
from fedbot.bot import Bot, BotClient
POST_INTERVAL = timedelta(seconds = 15)
3 years ago
TEST = "test" in sys.argv[1:]
PORT_PCT = 67
3 years ago
def next_dt():
dt = datetime.now(timezone.utc)
3 years ago
dt -= timedelta(hours = 0,
minutes = (dt.minute % 15) - 15,
3 years ago
seconds = dt.second,
microseconds = dt.microsecond)
return dt
config_path = os.path.join(os.path.dirname(sys.argv[0]), "config.toml")
loaded_config = {
"name": "portmanteaubot",
**toml.load(config_path)}
SUFFIXES = [
3 years ago
'ly$',
'ing$',
'[bdklmptw]?est$',
'[^ious]s$',
'ted$',
'[ei]ty$']
3 years ago
def is_suffixed(word):
3 years ago
return any(re.fullmatch(suf, word) for suf in SUFFIXES)
3 years ago
3 years ago
def overlap_words(left_word, right_word):
if left_word == right_word:
return None
3 years ago
3 years ago
offset = 2
attempts = []
while offset + 2 <= len(left_word):
if right_word.lower().startswith(left_word.lower()[offset : offset + 2]):
attempts.append(left_word[:offset] + right_word)
#break
offset += 1
offset = len(right_word) - 2
while offset >= 0:
if left_word.lower().endswith(right_word.lower()[offset : offset + 2]):
attempts.append(left_word + right_word[offset + 2:])
#break
offset -= 1
attempts = sorted(attempts, key = lambda w: len(w), reverse = True)
if len(attempts) == 0:
return None
return pick_one_word(attempts)
def word_weight(index, length, power = 2):
a = pow((index + 1) / length, 2)
return int(100000 * a)
def pick_one_word(words, power = 2, max_len = 12):
words = list(filter(lambda w: len(w) <= max_len, words))
if len(words) == 0:
return None
weights = [word_weight(i, len(words), power = power) for i in range(0, len(words))]
return random.choices(words, weights = weights)[0]
3 years ago
class WordMaker:
def __init__(self):
print("Loading dictionaries")
3 years ago
illegal = set(ch for ch in (string.ascii_uppercase + string.punctuation + string.digits + string.whitespace))
3 years ago
with open ("mhyph.txt", "r", encoding = "mac-roman") as f:
3 years ago
lines = [l.strip() for l in f.readlines()]
lines = filter(lambda w: len(w) > 0 and not any(ch in illegal for ch in w), lines)
words = [l.replace("", "") for l in lines]
self.all_words = words
words = list(set(sorted(words, key = lambda w: len(w), reverse = True)))
self.first_words = list(filter(lambda w: not is_suffixed(w), words))
3 years ago
self.next_words = words
3 years ago
with open("porthyph.txt", "r") as f:
lines = [line.strip() for line in f.readlines()]
3 years ago
words = list(filter(lambda l: len(l) > 0, lines))
self.all_words = list(set(sorted([w.lower() for w in [*self.all_words, *words]], key = lambda w: len(w), reverse = True)))
self.port_words = list(set(sorted(words, key = lambda w: len(w), reverse = True)))
def extend_word2(self, prev_word):
port_dict = random.randint(0, 100) < PORT_PCT
if port_dict:
next_dict = self.port_words
3 years ago
else:
3 years ago
next_dict = self.next_words
3 years ago
3 years ago
new_words = [overlap_words(prev_word, w) for w in next_dict if overlap_words(prev_word, w)]
while len(new_words) > 0:
new_word = pick_one_word(new_words, power = 2 if port_dict else 4)
if not new_word:
return None
new_words.remove(new_word)
if new_word.lower() not in self.all_words:
return new_word
3 years ago
return None
def get_portmanteau(self):
target_times = 1
3 years ago
port_dict = random.randint(0, 100) < PORT_PCT
if port_dict:
words = self.port_words
3 years ago
else:
words = self.first_words
while True:
while True:
3 years ago
word = pick_one_word(words, power = 2 if port_dict else 4)
3 years ago
times = target_times
while times > 0:
3 years ago
ext_word = self.extend_word2(word)
if ext_word is None:
3 years ago
break
3 years ago
word = ext_word
times -= 1
if times == 0:
3 years ago
break
3 years ago
if len(word) < 15:
3 years ago
break
3 years ago
word = word.lower()
print(word)
3 years ago
3 years ago
return word
3 years ago
def get_portmanteaus(self, count = 10):
return [self.get_portmanteau() for x in range(0, count)]
class PortBotClient(BotClient):
def __init__(self, bot, config):
config = {
"app_name": "PortmanteuBot",
"rate_limit": 3,
"retry_rate": 60,
"poll_interval": 15,
**config}
super().__init__(bot, config)
self.my_id = None
def on_start(self):
self.log("Starting")
self.my_id = self.api.me()["id"]
pass
def on_poll(self):
pass
def on_status(self, status):
if status["account"]["id"] != self.my_id:
return
3 years ago
#if status["created_at"] < datetime.now(timezone.utc) - timedelta(hours = 24) and status["reblogs_count"] == 0 and status["favourites_count"] == 0:
# try:
# print("Deleting", status["created_at"], status["content"])
# self.api.status_delete(status["id"])
# time.sleep(2)
# except MastodonNotFoundError:
# pass
3 years ago
def post():
for client_name, client in bot.clients.items():
3 years ago
words = wm.get_portmanteaus(3)
print()
3 years ago
if random.randint(0, 100) <= 100:
visibility = "public"
else:
visibility = "unlisted"
3 years ago
dt = next_dt()
if not TEST:
client.api.status_post("\n".join(words), visibility = visibility)
3 years ago
print("Scheduling at", dt)
if TEST:
scheduler.enter(1, 1, post)
else:
scheduler.enterabs(dt.timestamp(), 1, post)
3 years ago
wm = WordMaker()
scheduler = sched.scheduler(timefunc = time.time, delayfunc = time.sleep)
bot = Bot(PortBotClient, loaded_config)
del loaded_config
bot.start()
print("Running")
dt = next_dt()
if TEST:
scheduler.enter(1, 1, post)
else:
3 years ago
print("Scheduling at", dt)
scheduler.enterabs(dt.timestamp(), 1, post)
3 years ago
scheduler.run()