reformat w/ black

This commit is contained in:
Tomáš Mládek 2021-09-14 14:08:01 +02:00
parent 0394c10616
commit 604a1378b7
3 changed files with 581 additions and 166 deletions

View file

@ -64,40 +64,66 @@ class DelojzaDB:
return [res[0] for res in results.fetchall()]
def get_chat(self, id):
return self.db.execute("SELECT id, protected FROM chats WHERE id == ?", (id,)).fetchone()
return self.db.execute(
"SELECT id, protected FROM chats WHERE id == ?", (id,)
).fetchone()
def set_chat_protected(self, id, protected):
chat_in_db = self.get_chat(id)
if chat_in_db:
self.db.execute("UPDATE chats SET protected = ? WHERE id = ?", (protected, id))
self.db.execute(
"UPDATE chats SET protected = ? WHERE id = ?", (protected, id)
)
else:
self.db.execute("INSERT INTO chats (id, protected) VALUES (?, ?)", (id, protected))
self.db.execute(
"INSERT INTO chats (id, protected) VALUES (?, ?)", (id, protected)
)
self.db.commit()
def get_tag(self, tag):
return self.db.execute("SELECT id, tag, protected FROM tags WHERE tag == ?", (tag,)).fetchone()
return self.db.execute(
"SELECT id, tag, protected FROM tags WHERE tag == ?", (tag,)
).fetchone()
def set_tag_protected(self, tag, protected):
tag_in_db = self.get_tag(tag)
if tag_in_db:
self.db.execute("UPDATE tags SET protected = ? WHERE tag = ?", (protected, tag))
self.db.execute(
"UPDATE tags SET protected = ? WHERE tag = ?", (protected, tag)
)
else:
self.db.execute("INSERT INTO tags (tag, protected) VALUES (?, ?)", (tag, protected))
self.db.execute(
"INSERT INTO tags (tag, protected) VALUES (?, ?)", (tag, protected)
)
self.db.commit()
class DelojzaBot:
def __init__(self, tg_api_key, out_dir, redirects=None, tmp_dir=None, db_path=None, protected_password=None,
acoustid_key=None, tumblr_name=None, tumblr_keys=None, markov=None):
def __init__(
self,
tg_api_key,
out_dir,
redirects=None,
tmp_dir=None,
db_path=None,
protected_password=None,
acoustid_key=None,
tumblr_name=None,
tumblr_keys=None,
markov=None,
):
self._setup_logging(os.path.dirname(os.path.realpath(__file__)))
self.db = DelojzaDB(db_path or os.path.join(os.path.dirname(os.path.realpath(__file__)), "delojza.db"))
self.db = DelojzaDB(
db_path
or os.path.join(os.path.dirname(os.path.realpath(__file__)), "delojza.db")
)
self.out_dir = os.path.abspath(out_dir)
self.out_dir = self.out_dir[:-1] if self.out_dir[-1] == "/" else self.out_dir
self.logger.debug('OUT_DIR: ' + out_dir)
self.logger.debug("OUT_DIR: " + out_dir)
self.tmp_dir = tmp_dir if tmp_dir else tempfile.gettempdir()
self.logger.debug('TMP_DIR: ' + tmp_dir)
self.logger.debug("TMP_DIR: " + tmp_dir)
self.markov = markov
self.redirects = {}
@ -146,7 +172,9 @@ class DelojzaBot:
dfh = logging.FileHandler(log_path + "/delojza.log")
dfh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s [%(levelname)s] %(message)s')
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s"
)
ch.setFormatter(formatter)
dfh.setFormatter(formatter)
@ -156,15 +184,16 @@ class DelojzaBot:
def _log_msg(self, update):
from_user = update.message.from_user
self.logger.debug(f"Received from {from_user.username or (from_user.first_name + from_user.last_name)}"
f" ({update.message.chat.id}): " + (update.message.text or "<NONE>"))
self.logger.debug(
f"Received from {from_user.username or (from_user.first_name + from_user.last_name)}"
f" ({update.message.chat.id}): " + (update.message.text or "<NONE>")
)
@staticmethod
def ytdl_can(url):
ies = youtube_dl.extractor.gen_extractors()
for ie in ies:
if ie.suitable(url) and ie.IE_NAME != 'generic' \
and '/channel/' not in url:
if ie.suitable(url) and ie.IE_NAME != "generic" and "/channel/" not in url:
# Site has dedicated extractor
return True
return False
@ -174,14 +203,21 @@ class DelojzaBot:
def sanitize(filepath):
if filepath is None:
return None
filepath = unicodedata.normalize('NFKD', filepath).encode('ascii', 'ignore').decode('ascii')
return re.sub(r'[^\w.()\[\]{}#-]', '_', filepath)
filepath = (
unicodedata.normalize("NFKD", filepath)
.encode("ascii", "ignore")
.decode("ascii")
)
return re.sub(r"[^\w.()\[\]{}#-]", "_", filepath)
@staticmethod
def _get_tags(filepath):
try:
audio = EasyID3(filepath)
return audio["artist"][0] if audio["artist"] else None, audio["title"][0] if audio["title"] else None
return (
audio["artist"][0] if audio["artist"] else None,
audio["title"][0] if audio["title"] else None,
)
except mutagen.id3.ID3NoHeaderError:
return None, None
@ -213,12 +249,16 @@ class DelojzaBot:
if self.acoustid_key:
try:
self.logger.debug("Requesting AcoustID for {}".format(filepath))
results = sorted(acoustid.match(self.acoustid_key, filepath), key=itemgetter(0), reverse=True)
results = sorted(
acoustid.match(self.acoustid_key, filepath),
key=itemgetter(0),
reverse=True,
)
if len(results) > 0:
score, rid, aid_title, aid_artist = results[0]
if score > .4:
if score > 0.4:
title = aid_title
artist = re.sub(r' *; +', ' & ', aid_artist)
artist = re.sub(r" *; +", " & ", aid_artist)
best_acoustid_score = score
source = "AcoustID ({}%)".format(round(score * 100))
except acoustid.NoBackendError:
@ -226,30 +266,32 @@ class DelojzaBot:
except acoustid.FingerprintGenerationError:
self.logger.warning("fingerprint could not be calculated")
except acoustid.WebServiceError as exc:
self.logger.warning("web service request failed: {}".format(exc.message))
self.logger.warning(
"web service request failed: {}".format(exc.message)
)
if best_acoustid_score < .8:
if 'track' in info:
title = info['track']
if 'artist' in info:
artist = info['artist']
if best_acoustid_score < 0.8:
if "track" in info:
title = info["track"]
if "artist" in info:
artist = info["artist"]
if 'track' in info or 'artist' in info:
if "track" in info or "artist" in info:
source = "supplied metadata"
if title is None and artist is None and '-' in info.get("title", ""):
split = info['title'].split("-")
if title is None and artist is None and "-" in info.get("title", ""):
split = info["title"].split("-")
artist = split[0]
title = split[1]
source = "fallback (artist - title)"
if title is None and 'title' in info:
title = info['title']
if title is None and "title" in info:
title = info["title"]
source = "full title fallback"
if 'soundcloud' in info.get("extractor", "") and artist is None:
artist = info['uploader']
source = "soundcloud \"fallback\""
if "soundcloud" in info.get("extractor", "") and artist is None:
artist = info["uploader"]
source = 'soundcloud "fallback"'
artist = artist.strip() if artist else None
title = title.strip() if title else None
@ -258,14 +300,18 @@ class DelojzaBot:
message.reply_text("Tried tagging, found nothing :(")
return
message.reply_text("Tagging as \"{}\" by \"{}\"\nvia {}".format(title, artist, source))
self.logger.info("Tagging {} w/ {} - {} [{}]...".format(filepath, title, artist, source))
message.reply_text(
'Tagging as "{}" by "{}"\nvia {}'.format(title, artist, source)
)
self.logger.info(
"Tagging {} w/ {} - {} [{}]...".format(filepath, title, artist, source)
)
self._tag_file(filepath, artist, title)
@staticmethod
def _get_percent_filled(directory):
output = subprocess.check_output(["df", directory])
percents_re = re.search(r"[0-9]+%", output.decode('utf-8'))
percents_re = re.search(r"[0-9]+%", output.decode("utf-8"))
if not percents_re:
raise RuntimeError
return int(percents_re.group(0)[:-1])
@ -273,18 +319,22 @@ class DelojzaBot:
# noinspection PyUnusedLocal
def download_ytdl(self, urls, out_path, date, message, audio=False, filetitle=None):
ytdl = {
'noplaylist': True,
'restrictfilenames': True,
'outtmpl': os.path.join(self.tmp_dir, '{}__%(title)s__%(id)s.%(ext)s'.format(datestr(date)))
"noplaylist": True,
"restrictfilenames": True,
"outtmpl": os.path.join(
self.tmp_dir, "{}__%(title)s__%(id)s.%(ext)s".format(datestr(date))
),
}
if audio:
ytdl['format'] = 'bestaudio/best'
ytdl['postprocessors'] = [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '256'
}]
ytdl['postprocessor_args'] = ['-ar', '44100']
ytdl["format"] = "bestaudio/best"
ytdl["postprocessors"] = [
{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "256",
}
]
ytdl["postprocessor_args"] = ["-ar", "44100"]
filenames = []
with youtube_dl.YoutubeDL(ytdl) as ytdl:
attempts = 0
@ -294,7 +344,7 @@ class DelojzaBot:
break
except DownloadError as exc:
attempts += 1
if '403' in str(exc) and attempts < 5:
if "403" in str(exc) and attempts < 5:
self.logger.warning("Received a 403!")
sleep(1.357)
if self.markov:
@ -303,7 +353,7 @@ class DelojzaBot:
raise exc
for info in [ytdl.extract_info(url, download=False) for url in urls]:
filename = ytdl.prepare_filename(info)
globbeds = glob(os.path.splitext(filename)[0] + '.*')
globbeds = glob(os.path.splitext(filename)[0] + ".*")
for globbed in globbeds:
if globbed.endswith("mp3"):
self._autotag_file(globbed, message, info=info)
@ -315,24 +365,34 @@ class DelojzaBot:
def download_raw(self, urls, out_path, date, message, audio=False, filetitle=None):
filenames = []
for url in urls:
local_filename = os.path.join(out_path, "{}__{}".format(datestr(date),
self.sanitize(filetitle or url.split('/')[-1])))
local_filename = os.path.join(
out_path,
"{}__{}".format(
datestr(date), self.sanitize(filetitle or url.split("/")[-1])
),
)
final_filename = local_filename
is_mp3 = local_filename.endswith("mp3")
r = requests.get(url, stream=True)
with open(local_filename, 'wb') as f:
with open(local_filename, "wb") as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
if not re.match(r'.*\..{3,5}$', os.path.split(local_filename)[-1]):
if not re.match(r".*\..{3,5}$", os.path.split(local_filename)[-1]):
kind = filetype.guess(local_filename)
if kind is None:
self.logger.error("File has no extension and could not be determined!")
self.logger.error(
"File has no extension and could not be determined!"
)
else:
self.logger.info('Moving file without extension... %s?' % kind.extension)
final_filename = shutil.move(local_filename, local_filename + '.' + kind.extension)
self.logger.info(
"Moving file without extension... %s?" % kind.extension
)
final_filename = shutil.move(
local_filename, local_filename + "." + kind.extension
)
is_mp3 = kind.extension == "mp3"
filenames.append(final_filename)
@ -340,7 +400,7 @@ class DelojzaBot:
if audio and is_mp3:
try:
id3 = mutagen.id3.ID3(final_filename)
untagged = 'TIT2' not in id3
untagged = "TIT2" not in id3
except mutagen.id3.ID3NoHeaderError:
untagged = True
if untagged:
@ -350,10 +410,18 @@ class DelojzaBot:
@staticmethod
def extract_hashtags(message):
hashtags = list(map(message.parse_entity,
list(filter(lambda e: e.type == 'hashtag', message.entities))))
hashtags += list(map(message.parse_caption_entity,
list(filter(lambda e: e.type == 'hashtag', message.caption_entities))))
hashtags = list(
map(
message.parse_entity,
list(filter(lambda e: e.type == "hashtag", message.entities)),
)
)
hashtags += list(
map(
message.parse_caption_entity,
list(filter(lambda e: e.type == "hashtag", message.caption_entities)),
)
)
if len(hashtags) > 0:
hashtags = [hashtag[1:].upper() for hashtag in hashtags]
for i, hashtag in enumerate(hashtags):
@ -370,24 +438,30 @@ class DelojzaBot:
return hashtags
def handle_text(self, message, hashtags):
if len(hashtags) == 0 or hashtags[0] not in ('TEXT', 'TXT'):
if len(hashtags) == 0 or hashtags[0] not in ("TEXT", "TXT"):
return
info_line = self.sanitize("-".join(re.sub(r'#[\w]+', '', message.text).strip().split()[:7]))
info_line = self.sanitize(
"-".join(re.sub(r"#[\w]+", "", message.text).strip().split()[:7])
)
if len(info_line) > 64:
info_line = info_line[:64]
filename = '{}__{}.txt'.format(datestr(message.date), info_line)
filename = "{}__{}.txt".format(datestr(message.date), info_line)
out_dir = self.redirects.get(hashtags[0], self.out_dir)
out_path = os.path.join(out_dir, *hashtags[1:] or ['TEXT'])
out_path = os.path.join(out_dir, *hashtags[1:] or ["TEXT"])
file_path = os.path.join(out_path, filename)
mkdir_p(out_path)
with open(file_path, 'w') as out_file:
with open(file_path, "w") as out_file:
out_file.write(message.text)
message.reply_text("Saved text to \"{}\"...".format(os.path.join(*hashtags[1:] or ['TEXT'], filename)))
message.reply_text(
'Saved text to "{}"...'.format(
os.path.join(*hashtags[1:] or ["TEXT"], filename)
)
)
# noinspection PyBroadException
def handle(self, urls, message, hashtags, download_fn, filetitle=None):
@ -405,10 +479,15 @@ class DelojzaBot:
else:
out_dir = self.out_dir
if any(hashtag in self.db.get_protected_tags() for hashtag in original_hashtags):
if any(
hashtag in self.db.get_protected_tags() for hashtag in original_hashtags
):
if message.chat.id not in self.db.get_protected_chats():
self.logger.info("Redirecting {} in chat {} due to protected hashtags: {}..."
.format(urls, message.chat.title, hashtags))
self.logger.info(
"Redirecting {} in chat {} due to protected hashtags: {}...".format(
urls, message.chat.title, hashtags
)
)
hashtags.insert(0, "PUBLIC")
for i in range(len(hashtags)):
@ -417,55 +496,87 @@ class DelojzaBot:
test_path = current_path
test_path[-1] = "_" + test_path[-1]
if os.path.isdir(os.path.join(out_dir, *test_path)):
self.logger.debug(f"Rerouting {current_path[-1]} to _{test_path[-1]}")
self.logger.debug(
f"Rerouting {current_path[-1]} to _{test_path[-1]}"
)
hashtags[i] = test_path[-1]
self.last_hashtags[message.chat.id] = None
self.logger.info("Downloading %s into '%s' (%s)" % (urls, "/".join(original_hashtags), out_dir))
self.logger.info(
"Downloading %s into '%s' (%s)"
% (urls, "/".join(original_hashtags), out_dir)
)
out_path = os.path.join(out_dir, *hashtags)
mkdir_p(out_path)
reply = 'Downloading to "{}"...'.format("/".join(original_hashtags))
audio = any([any([tag in hashtag for tag in ('AUDIO', 'RADIO')]) for hashtag in original_hashtags])
audio = any(
[
any([tag in hashtag for tag in ("AUDIO", "RADIO")])
for hashtag in original_hashtags
]
)
if audio and download_fn != self.download_raw:
reply += ' (And also guessing you want to extract the audio)'
reply += " (And also guessing you want to extract the audio)"
message.reply_text(reply)
filenames = download_fn(urls, out_path, message.date, message, audio=audio, filetitle=filetitle)
filenames = download_fn(
urls, out_path, message.date, message, audio=audio, filetitle=filetitle
)
cmd_hashtag = original_hashtags[0]
tumblr_ids = []
if cmd_hashtag in ('TUMBLR', 'TUMBLR_NOW') and self.tumblr_client:
now = cmd_hashtag == 'TUMBLR_NOW'
reply = '(btw, {})'.format("***FIRING TO TUMBLR RIGHT AWAY***" if now else "queueing to tumblr")
if cmd_hashtag in ("TUMBLR", "TUMBLR_NOW") and self.tumblr_client:
now = cmd_hashtag == "TUMBLR_NOW"
reply = "(btw, {})".format(
"***FIRING TO TUMBLR RIGHT AWAY***" if now else "queueing to tumblr"
)
message.reply_text(reply, parse_mode=telegram.ParseMode.MARKDOWN)
for filename in filenames:
if filename.endswith(".mp4"):
try:
output_filename = filename[: -len(".mp4")] + ".gif"
subprocess.check_output(['ffmpeg', '-i', filename, output_filename])
subprocess.check_output(
["ffmpeg", "-i", filename, output_filename]
)
filename = output_filename
except subprocess.CalledProcessError:
message.reply_text("Conversion to gif failed, sorry! Check log...")
message.reply_text(
"Conversion to gif failed, sorry! Check log..."
)
continue
response = self.tumblr_client.create_photo(self.tumblr_name, data=filename,
state="published" if now else "queue")
if 'id' in response:
tumblr_ids.append(response['id'])
response = self.tumblr_client.create_photo(
self.tumblr_name,
data=filename,
state="published" if now else "queue",
)
if "id" in response:
tumblr_ids.append(response["id"])
else:
self.logger.warning("Did not receive 'id' in tumblr response: \n" + pprint.pformat(response))
message.reply_text('Something weird happened with the tumblrs, check it!')
self.logger.warning(
"Did not receive 'id' in tumblr response: \n"
+ pprint.pformat(response)
)
message.reply_text(
"Something weird happened with the tumblrs, check it!"
)
self.last_downloaded[message.chat.id] = filenames, original_hashtags, tumblr_ids
self.last_downloaded[message.chat.id] = (
filenames,
original_hashtags,
tumblr_ids,
)
return True
except:
exc_type, exc_value, __ = sys.exc_info()
if "Timed out" not in str(exc_value):
message.reply_text("Something is FUCKED: [{}] {}".format(exc_type, exc_value))
message.reply_text(
"Something is FUCKED: [{}] {}".format(exc_type, exc_value)
)
return False
def handle_tg_message(self, message, bot, hashtag):
@ -488,13 +599,19 @@ class DelojzaBot:
if file is not None:
url = bot.getFile(file).file_path
return self.handle([url], message, hashtag, self.download_raw, filetitle=filetitle)
return self.handle(
[url], message, hashtag, self.download_raw, filetitle=filetitle
)
else:
return False
def handle_urls(self, message, hashtags):
urls = list(map(lambda e: message.parse_entity(e),
filter(lambda e: e.type == 'url', message.entities)))
urls = list(
map(
lambda e: message.parse_entity(e),
filter(lambda e: e.type == "url", message.entities),
)
)
ytdl_res = False
ytdl_urls = [url for url in urls if self.ytdl_can(url)]
@ -504,8 +621,11 @@ class DelojzaBot:
raw_res = False
normal_urls = [url for url in urls if not self.ytdl_can(url)]
if len(normal_urls) > 0:
file_urls = [url for url in normal_urls if
"text" not in requests.head(url).headers.get("Content-Type", "text")]
file_urls = [
url
for url in normal_urls
if "text" not in requests.head(url).headers.get("Content-Type", "text")
]
if len(file_urls) > 0:
raw_res = self.handle(file_urls, message, hashtags, self.download_raw)
@ -515,64 +635,109 @@ class DelojzaBot:
self._log_msg(update)
hashtags = self._get_hashtags(update.message)
if hashtags:
url_res = self.handle_urls(update.message, self._get_hashtags(update.message))
url_res = self.handle_urls(
update.message, self._get_hashtags(update.message)
)
if url_res:
return
msg_res = self.handle_tg_message(update.message, bot, self._get_hashtags(update.message))
msg_res = self.handle_tg_message(
update.message, bot, self._get_hashtags(update.message)
)
if msg_res:
return
hashtags = self.extract_hashtags(update.message)
if len(hashtags) > 0:
self.handle_text(update.message.reply_to_message or update.message, hashtags)
self.handle_text(
update.message.reply_to_message or update.message, hashtags
)
if update.message.reply_to_message:
self.handle_tg_message(update.message.reply_to_message, bot, hashtags)
self.handle_urls(update.message.reply_to_message, hashtags)
else:
self.last_hashtags[update.message.chat.id] = update.message.from_user, datetime.now(), hashtags
self.last_hashtags[update.message.chat.id] = (
update.message.from_user,
datetime.now(),
hashtags,
)
else:
if self.markov and update.message.text:
self.markov.add_to_corpus(update.message.text)
def _get_tag_dirs(self):
return list(filter(lambda x: x.upper() == x,
filter(lambda directory: os.path.isdir(os.path.join(self.out_dir, directory)),
os.listdir(self.out_dir)))) + list(self.redirects.keys())
return (
list(
filter(
lambda x: x.upper() == x,
filter(
lambda directory: os.path.isdir(
os.path.join(self.out_dir, directory)
),
os.listdir(self.out_dir),
),
)
)
+ list(self.redirects.keys())
)
def tg_stats(self, _, update):
self._log_msg(update)
self.db.initialize()
if update.message.chat.id not in self.db.get_protected_chats():
update.message.reply_text((self.markov.make_sentence() + "!") if self.markov and random() > .7 else "nope.")
update.message.reply_text(
(self.markov.make_sentence() + "!")
if self.markov and random() > 0.7
else "nope."
)
return
tag_dirs = self._get_tag_dirs()
reply = "Total number of tags: {}\n\n".format(len(tag_dirs))
counts = [(directory, os.listdir(os.path.join(self.out_dir, directory))) for directory in
tag_dirs] # TODO REDIRECTS
counts = [
(directory, os.listdir(os.path.join(self.out_dir, directory)))
for directory in tag_dirs
] # TODO REDIRECTS
counts.sort(key=itemgetter(0))
counts.sort(key=lambda x: len(x[1]), reverse=True)
for directory, files in counts:
if len(files) == 1:
break
abs_paths = [os.path.join(self.out_dir, directory, file) for file in files] # TODO REDIRECTS
abs_paths = [
os.path.join(self.out_dir, directory, file) for file in files
] # TODO REDIRECTS
abs_files = list(filter(os.path.isfile, abs_paths))
# mimes = [magic.from_file(path, mime=True).split("/")[0] for path in abs_files]
# mime_counts = [(mime, mimes.count(mime)) for mime in set(mimes)]
exts = [ext[1:] for ext in [os.path.splitext(path)[1] for path in abs_files] if len(ext) > 0]
exts = [
ext[1:]
for ext in [os.path.splitext(path)[1] for path in abs_files]
if len(ext) > 0
]
ext_counts = [(ext, exts.count(ext)) for ext in set(exts)]
dir_cnt = len(abs_paths) - len(abs_files)
type_counts = ext_counts + ([("directorie", dir_cnt)] if dir_cnt > 0 else [])
details = ", ".join(["{} {}s".format(cnt, mime) for mime, cnt in
sorted(type_counts, key=itemgetter(1), reverse=True)])
type_counts = ext_counts + (
[("directorie", dir_cnt)] if dir_cnt > 0 else []
)
details = ", ".join(
[
"{} {}s".format(cnt, mime)
for mime, cnt in sorted(
type_counts, key=itemgetter(1), reverse=True
)
]
)
if len(type_counts) == 1:
reply += "<b>{}:</b> {}\n".format(directory, details)
else:
reply += "<b>{}:</b> {} files ({})\n".format(directory, len(files), details)
reply += "<b>{}:</b> {} files ({})\n".format(
directory, len(files), details
)
orphans = list(filter(lambda cnt: len(cnt[1]) <= 1, counts))
if len(orphans) > 0:
reply += "\nFollowing tags are orphans: " + ", ".join(map(itemgetter(0), orphans))
reply += "\nFollowing tags are orphans: " + ", ".join(
map(itemgetter(0), orphans)
)
update.message.reply_text(reply, parse_mode=telegram.ParseMode.HTML)
def _get_orphan_tags(self):
@ -589,20 +754,30 @@ class DelojzaBot:
self._log_msg(update)
self.db.initialize()
if update.message.chat.id not in self.db.get_protected_chats():
update.message.reply_text((self.markov.make_sentence() + "!") if self.markov and random() > .7 else "nope.")
update.message.reply_text(
(self.markov.make_sentence() + "!")
if self.markov and random() > 0.7
else "nope."
)
return
orphans = self._get_orphan_tags()
if len(orphans) == 0:
update.message.reply_text("Good job, no orphan tags!")
else:
update.message.reply_text("The following tags only contain a single file:\n" +
", ".join(map(itemgetter(0), orphans)))
update.message.reply_text(
"The following tags only contain a single file:\n"
+ ", ".join(map(itemgetter(0), orphans))
)
def tg_orphan_full(self, _, update):
self._log_msg(update)
self.db.initialize()
if update.message.chat.id not in self.db.get_protected_chats():
update.message.reply_text((self.markov.make_sentence() + "!") if self.markov and random() > .7 else "nope.")
update.message.reply_text(
(self.markov.make_sentence() + "!")
if self.markov and random() > 0.7
else "nope."
)
return
orphans = self._get_orphan_tags()
if len(orphans) == 0:
@ -625,7 +800,7 @@ class DelojzaBot:
out_dir = self.redirects.get(hashtags[0], self.out_dir)
mp3s = [filename for filename in files if filename.endswith("mp3")]
if len(mp3s) > 0:
arg_raw = re.sub(r'^/[@\w]+ ?', '', update.message.text).strip()
arg_raw = re.sub(r"^/[@\w]+ ?", "", update.message.text).strip()
artist, title = None, None
reverse = len(arg_raw) == 0
@ -643,10 +818,16 @@ class DelojzaBot:
title, artist = orig_artist, orig_title
self._tag_file(mp3, artist, title)
update.message.reply_text("Tagging \"{}\" as \"{}\" by \"{}\"!"
.format(mp3[len(out_dir) + 1:], title, artist))
update.message.reply_text(
'Tagging "{}" as "{}" by "{}"!'.format(
mp3[len(out_dir) + 1 :], title, artist
)
)
else:
update.message.reply_text((self.markov.make_sentence() if self.markov and random() > .7 else "") + "???")
update.message.reply_text(
(self.markov.make_sentence() if self.markov and random() > 0.7 else "")
+ "???"
)
def tg_delete(self, _, update):
self._log_msg(update)
@ -654,19 +835,28 @@ class DelojzaBot:
files, hashtags, tumblr_ids = self.last_downloaded[update.message.chat.id]
out_dir = self.redirects.get(hashtags[0], self.out_dir)
for file in files:
update.message.reply_text("Removing \"{}\"!".format(file[len(out_dir) + 1:]))
update.message.reply_text(
'Removing "{}"!'.format(file[len(out_dir) + 1 :])
)
os.remove(file)
parent_dir = os.path.dirname(file)
while True:
if len(os.listdir(parent_dir)) == 0:
update.message.reply_text("Removing directory \"{}\" as it's empty..."
.format(parent_dir[len(out_dir) + 1:]))
update.message.reply_text(
'Removing directory "{}" as it\'s empty...'.format(
parent_dir[len(out_dir) + 1 :]
)
)
os.rmdir(parent_dir)
if parent_dir == out_dir:
break
parent_dir = os.path.dirname(parent_dir)
if len(tumblr_ids) > 0:
plural = "s (all {} of them)".format(len(tumblr_ids)) if len(tumblr_ids) > 1 else ""
plural = (
"s (all {} of them)".format(len(tumblr_ids))
if len(tumblr_ids) > 1
else ""
)
update.message.reply_text("Also deleting tumblr post{}!".format(plural))
for tumblr_id in tumblr_ids:
if self.tumblr_client:
@ -681,13 +871,16 @@ class DelojzaBot:
msg_split = update.message.text.split(" ")
if len(msg_split) != 3:
update.message.reply_text((self.markov.make_sentence() if self.markov and random() > .7 else "") + "???")
update.message.reply_text(
(self.markov.make_sentence() if self.markov and random() > 0.7 else "")
+ "???"
)
return
chat_in_db = self.db.get_chat(update.message.chat.id)
cmd = msg_split[1]
if cmd == 'tag':
if cmd == "tag":
if chat_in_db and chat_in_db[1]:
tag = msg_split[2].upper()
tag_in_db = self.db.get_tag(tag)
@ -698,10 +891,18 @@ class DelojzaBot:
end_protected = True
self.db.set_tag_protected(tag, end_protected)
update.message.reply_text(f"got it, will {'NOT ' if not end_protected else ''}protect tag {tag}!")
update.message.reply_text(
f"got it, will {'NOT ' if not end_protected else ''}protect tag {tag}!"
)
else:
update.message.reply_text((self.markov.make_sentence() if self.markov and random() > .7 else "hublubl"))
elif cmd == 'chat':
update.message.reply_text(
(
self.markov.make_sentence()
if self.markov and random() > 0.7
else "hublubl"
)
)
elif cmd == "chat":
password = msg_split[2]
if password == self.protected_password:
if chat_in_db:
@ -711,37 +912,63 @@ class DelojzaBot:
end_protected = True
self.db.set_chat_protected(update.message.chat.id, end_protected)
update.message.reply_text(f"got it, will {'NOT ' if not end_protected else ''}protect this chat!")
update.message.reply_text(
f"got it, will {'NOT ' if not end_protected else ''}protect this chat!"
)
else:
update.message.reply_text((self.markov.make_sentence() if self.markov and random() > .7 else "hublubl"))
update.message.reply_text(
(
self.markov.make_sentence()
if self.markov and random() > 0.7
else "hublubl"
)
)
else:
update.message.reply_text((self.markov.make_sentence() if self.markov and random() > .7 else "") + "???")
update.message.reply_text(
(self.markov.make_sentence() if self.markov and random() > 0.7 else "")
+ "???"
)
def tg_queue(self, _, update):
if self.tumblr_client:
blog_info = self.tumblr_client.blog_info(self.tumblr_name)
update.message.reply_text("Currently queued tumblr posts: " + str(blog_info['blog'].get('queue', "???")))
update.message.reply_text(
"Currently queued tumblr posts: "
+ str(blog_info["blog"].get("queue", "???"))
)
else:
update.message.reply_text((self.markov.make_sentence() if self.markov and random() > .7 else "") + "???")
update.message.reply_text(
(self.markov.make_sentence() if self.markov and random() > 0.7 else "")
+ "???"
)
# noinspection PyMethodMayBeStatic
def tg_version(self, _, update):
self._log_msg(update)
delojza_date = datetime.fromtimestamp(os.path.getmtime(os.path.realpath(__file__))) \
.strftime('%Y/%m/%d - %H:%M:%S')
update.message.reply_text("delojza modified date: {}\nyoutube-dl version: {}"
.format(delojza_date, YTDL_VERSION))
delojza_date = datetime.fromtimestamp(
os.path.getmtime(os.path.realpath(__file__))
).strftime("%Y/%m/%d - %H:%M:%S")
update.message.reply_text(
"delojza modified date: {}\nyoutube-dl version: {}".format(
delojza_date, YTDL_VERSION
)
)
def tg_start(self, _, update):
self._log_msg(update)
update.message.reply_text(self.markov.make_sentence() if self.markov else "HELLO")
update.message.reply_text(
self.markov.make_sentence() if self.markov else "HELLO"
)
def tg_error(self, bot, update, error):
self.logger.error(error)
if "Timed out" in str(error):
if update is not None:
default = "Mmmm, I like it..."
update.message.reply_text((self.markov.make_sentence(tries=100) if self.markov else default) or default)
update.message.reply_text(
(self.markov.make_sentence(tries=100) if self.markov else default)
or default
)
self.tg_handle(bot, update)
else:
if update is not None:
@ -755,7 +982,7 @@ class DelojzaBot:
class MarkovBlabberer:
def __init__(self, filepath):
self.logger = logging.getLogger('markov')
self.logger = logging.getLogger("markov")
self.filepath = filepath
with open(filepath) as f:
@ -770,21 +997,28 @@ class MarkovBlabberer:
text = text.lower()
new_sentence = markovify.NewlineText(text)
self.markov = markovify.combine([self.markov, new_sentence])
with open(self.filepath, 'a') as f:
f.write(text + '\n')
with open(self.filepath, "a") as f:
f.write(text + "\n")
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
if __name__ == "__main__":
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
_DIR_ = os.path.dirname(os.path.realpath(__file__))
CONFIG_PATHS = ['/etc/delojza/delojza.ini',
CONFIG_PATHS = [
"/etc/delojza/delojza.ini",
os.path.join(os.getenv("HOME") or "", ".config/delojza/delojza.ini"),
os.path.join(_DIR_, "delojza.ini")]
os.path.join(_DIR_, "delojza.ini"),
]
config = ConfigParser()
try:
CONF_FILE = next(conf_path for conf_path in CONFIG_PATHS if os.path.isfile(conf_path))
CONF_FILE = next(
conf_path for conf_path in CONFIG_PATHS if os.path.isfile(conf_path)
)
config.read(CONF_FILE)
except StopIteration:
logging.error("No config file found, stopping.")
@ -793,24 +1027,30 @@ if __name__ == '__main__':
try:
markov = MarkovBlabberer("initial.txt")
except FileNotFoundError:
logging.warning("Didn't find `initial.txt`, continuing without markov blabbering!")
logging.warning(
"Didn't find `initial.txt`, continuing without markov blabbering!"
)
markov = None
try:
redirects = config.items('redirects')
redirects = config.items("redirects")
except NoSectionError:
redirects = {}
delojza = DelojzaBot(config.get('delojza', 'tg_api_key'),
config.get('delojza', 'OUT_DIR', fallback=os.path.join(_DIR_, "out")),
tmp_dir=config.get('delojza', 'tmp_dir', fallback=tempfile.gettempdir()),
delojza = DelojzaBot(
config.get("delojza", "tg_api_key"),
config.get("delojza", "OUT_DIR", fallback=os.path.join(_DIR_, "out")),
tmp_dir=config.get("delojza", "tmp_dir", fallback=tempfile.gettempdir()),
redirects=redirects,
protected_password=config.get('delojza', 'protected_password', fallback=None),
acoustid_key=config.get('delojza', 'acoustid_api_key', fallback=None),
tumblr_name=config.get('tumblr', 'blog_name', fallback=None),
tumblr_keys=(config.get('tumblr', 'consumer_key', fallback=None),
config.get('tumblr', 'consumer_secret', fallback=None),
config.get('tumblr', 'oauth_key', fallback=None),
config.get('tumblr', 'oauth_secret', fallback=None)),
markov=markov)
protected_password=config.get("delojza", "protected_password", fallback=None),
acoustid_key=config.get("delojza", "acoustid_api_key", fallback=None),
tumblr_name=config.get("tumblr", "blog_name", fallback=None),
tumblr_keys=(
config.get("tumblr", "consumer_key", fallback=None),
config.get("tumblr", "consumer_secret", fallback=None),
config.get("tumblr", "oauth_key", fallback=None),
config.get("tumblr", "oauth_secret", fallback=None),
),
markov=markov,
)
delojza.run_idle()

176
poetry.lock generated
View file

@ -6,6 +6,33 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "black"
version = "21.9b0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
click = ">=7.1.2"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0,<1"
platformdirs = ">=2"
regex = ">=2020.1.8"
tomli = ">=0.2.6,<2.0.0"
typing-extensions = [
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
]
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
python2 = ["typed-ast (>=1.4.2)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2021.5.30"
@ -36,6 +63,25 @@ python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.1"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "cryptography"
version = "3.4.8"
@ -98,6 +144,14 @@ category = "main"
optional = false
python-versions = ">=3.5, <4"
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "oauthlib"
version = "3.1.1"
@ -111,6 +165,26 @@ rsa = ["cryptography (>=3.0.0,<4)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "pathspec"
version = "0.9.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "platformdirs"
version = "2.3.0"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
[[package]]
name = "pyacoustid"
version = "1.2.2"
@ -160,6 +234,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
future = "*"
requests-oauthlib = "*"
[[package]]
name = "regex"
version = "2021.8.28"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "requests"
version = "2.26.0"
@ -193,6 +275,22 @@ requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "tomli"
version = "1.2.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "typing-extensions"
version = "3.10.0.2"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "unidecode"
version = "1.3.1"
@ -225,12 +323,16 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "01568add6ada6b27a0123da237bc38f136148cd1dad83399c994fb9991c906e7"
content-hash = "c6e54519de2d7d86694444f2c26f29af31da90a5df5dcd06c619d8d2dc7d2fd7"
[metadata.files]
audioread = [
{file = "audioread-2.1.9.tar.gz", hash = "sha256:a3480e42056c8e80a8192a54f6729a280ef66d27782ee11cbd63e9d4d1523089"},
]
black = [
{file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"},
{file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"},
]
certifi = [
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
@ -286,6 +388,14 @@ charset-normalizer = [
{file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"},
{file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"},
]
click = [
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
cryptography = [
{file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"},
{file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"},
@ -323,10 +433,22 @@ mutagen = [
{file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"},
{file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
oauthlib = [
{file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
{file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
{file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"},
{file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"},
]
pyacoustid = [
{file = "pyacoustid-1.2.2.tar.gz", hash = "sha256:c279d9c30a7f481f1420fc37db65833b5f9816cd364dc2acaa93a11c482d4141"},
]
@ -342,6 +464,49 @@ pytumblr = [
{file = "PyTumblr-0.1.0-py2.py3-none-any.whl", hash = "sha256:a3774d3978bcff2db98f36a2e5d17bb8496ac21157b1b518089adad86d0dca72"},
{file = "PyTumblr-0.1.0.tar.gz", hash = "sha256:eaa4d98217df7ab6392fa5d8801f4a2bdcba35bf0fd49328aa3c98e3b231b6f2"},
]
regex = [
{file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"},
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"},
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"},
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"},
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"},
{file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"},
{file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"},
{file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"},
{file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"},
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"},
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"},
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"},
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"},
{file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"},
{file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"},
{file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"},
{file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"},
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"},
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"},
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"},
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"},
{file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"},
{file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"},
{file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"},
{file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"},
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"},
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"},
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"},
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"},
{file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"},
{file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"},
{file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"},
{file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"},
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"},
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"},
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"},
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"},
{file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"},
{file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"},
{file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"},
{file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"},
]
requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
@ -351,6 +516,15 @@ requests-oauthlib = [
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
]
tomli = [
{file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"},
{file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"},
]
typing-extensions = [
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
unidecode = [
{file = "Unidecode-1.3.1-py3-none-any.whl", hash = "sha256:5f58926b9125b499f8ab6816828e737578fa3e31fa24d351a3ab7f4b7c064ab0"},
{file = "Unidecode-1.3.1.tar.gz", hash = "sha256:6efac090bf8f29970afc90caf4daae87b172709b786cb1b4da2d0c0624431ecc"},

View file

@ -17,6 +17,7 @@ PyTumblr = "^0.1.0"
markovify = "^0.9.3"
[tool.poetry.dev-dependencies]
black = {version = "^21.9b0", allow-prereleases = true}
[build-system]
requires = ["poetry-core>=1.0.0"]