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.
201 lines
5.9 KiB
201 lines
5.9 KiB
import os
|
|
import sys
|
|
import time
|
|
import toml
|
|
import random
|
|
import re
|
|
from datetime import datetime, timedelta, timezone
|
|
import sched
|
|
|
|
from mastodon import Mastodon, MastodonNotFoundError
|
|
from fedbot.bot import Bot, BotClient
|
|
|
|
POST_INTERVAL = timedelta(seconds = 15)
|
|
|
|
def next_dt():
|
|
dt = datetime.now(timezone.utc)
|
|
dt -= timedelta(hours = -1,
|
|
minutes = dt.minute,
|
|
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 = [
|
|
['ly'],
|
|
['ing'],
|
|
['[bdklmptw]?est$'],
|
|
['[^ious]s$'],
|
|
['ted'],
|
|
['[ei]$', 'ty']]
|
|
|
|
def is_suffixed(word):
|
|
for suffix in SUFFIXES:
|
|
if len(suffix) > len(word):
|
|
continue
|
|
|
|
syllables = list(zip(suffix, word[-len(suffix):]))
|
|
|
|
if all(re.fullmatch(suf, syl) for suf, syl in syllables):
|
|
#print(word, "matched", suffix)
|
|
return True
|
|
|
|
return False
|
|
|
|
class WordMaker:
|
|
def __init__(self):
|
|
print("Loading dictionaries")
|
|
with open ("mhyph.txt", "r", encoding = "mac-roman") as f:
|
|
lines = [line.strip() for line in f.readlines()]
|
|
lines = filter(lambda word: len(word) > 0 and not re.search(r'[- A-Z]', word), lines)
|
|
words = [line.split("•") for line in lines]
|
|
words = sorted(words, key = lambda w: len(w), reverse = True)
|
|
self.words = words
|
|
self.first_words = list(filter(lambda word: not is_suffixed(word), words))
|
|
self.plain_words = ["".join(word) for word in words]
|
|
|
|
with open("porthyph.txt", "r") as f:
|
|
lines = [line.strip() for line in f.readlines()]
|
|
words = [line.split("=") for line in lines]
|
|
words = sorted(words, key = lambda w: len(w), reverse = True)
|
|
self.alt_words = words
|
|
self.plain_words.append(["".join(word) for word in words])
|
|
|
|
def get_one_word(self, words):
|
|
weights = [int(100.0 * (x + 1.0) / len(words)) for x in range(0, len(words))]
|
|
return random.choices(words, weights = weights)[0]
|
|
|
|
def get_second_word(self, first_word):
|
|
first_word = list(first_word)
|
|
first_end = first_word[-1].lower()
|
|
|
|
if random.randint(0, 100) < 50:
|
|
second_dict = self.alt_words
|
|
else:
|
|
second_dict = self.words
|
|
|
|
if random.randint(0, 100) < 50:
|
|
second_iter = filter(lambda w: w[0].lower().startswith(first_end) or first_end.startswith(w[0].lower()), second_dict)
|
|
else:
|
|
second_iter = filter(lambda w: w[0].lower().startswith(first_end), second_dict)
|
|
second_words = list(second_iter)
|
|
|
|
#if len(second_words) < 8:
|
|
# return None
|
|
|
|
while len(second_words) > 0:
|
|
second_word_orig = self.get_one_word(second_words)
|
|
second_word = [s.lower() for s in second_word_orig]
|
|
second_words.remove(second_word_orig)
|
|
word = [*first_word[:-1], *second_word]
|
|
if not "".join(word) in self.plain_words:
|
|
return word
|
|
|
|
return None
|
|
|
|
def get_portmanteau(self):
|
|
target_times = 1
|
|
if random.randint(0, 100) > 50:
|
|
words = self.alt_words
|
|
else:
|
|
words = self.first_words
|
|
|
|
while True:
|
|
while True:
|
|
word = self.get_one_word(words)
|
|
|
|
end = word[-1]
|
|
|
|
times = target_times
|
|
while times > 0:
|
|
next_word = self.get_second_word(word)
|
|
if next_word is None:
|
|
break
|
|
else:
|
|
word = next_word
|
|
times -= 1
|
|
|
|
if times > 0:
|
|
continue
|
|
else:
|
|
break
|
|
|
|
word_str = "".join(word)
|
|
|
|
if len(word_str) < 15:
|
|
break
|
|
|
|
print(word_str)
|
|
|
|
return word_str
|
|
|
|
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
|
|
|
|
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
|
|
pass
|
|
|
|
def post():
|
|
for client_name, client in bot.clients.items():
|
|
words = wm.get_portmanteaus(1)
|
|
if random.randint(0, 100) <= 100:
|
|
visibility = "public"
|
|
else:
|
|
visibility = "unlisted"
|
|
|
|
client.api.status_post("\n".join(words), visibility = visibility)
|
|
|
|
dt = next_dt()
|
|
print("Scheduling at", dt)
|
|
scheduler.enterabs(dt.timestamp(), 1, post)
|
|
|
|
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()
|
|
print("Scheduling at", dt)
|
|
scheduler.enterabs(dt.timestamp(), 1, post)
|
|
scheduler.run()
|
|
|