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.
125 lines
3.8 KiB
125 lines
3.8 KiB
""" Runner for the Lo-Fi Beats bot at https://botsin.space/@lofibeats """ |
|
|
|
import html |
|
import logging |
|
import logging.handlers |
|
import os |
|
import pickle |
|
import random |
|
|
|
import isodate |
|
import mastodon |
|
|
|
import youtube |
|
|
|
logging.basicConfig( |
|
handlers=[logging.handlers.RotatingFileHandler('beatbot.log', maxBytes=100000, backupCount=10), |
|
logging.StreamHandler()], |
|
level=logging.INFO, |
|
format="[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s", |
|
datefmt='%Y-%m-%dT%H:%M:%S') |
|
LOGGER = logging.getLogger(__name__) |
|
|
|
|
|
def randline(fname): |
|
""" Get a random line from a file """ |
|
with open(fname, encoding='utf-8') as afile: |
|
line = next(afile) |
|
for num, aline in enumerate(afile, 2): |
|
if random.randrange(num) == 0: |
|
line = aline |
|
return line.strip() |
|
|
|
|
|
def format_details(yt_id, snippet): |
|
''' format the details of a video ''' |
|
text = f'https://youtube.com/watch?v={yt_id}' |
|
|
|
try: |
|
LOGGER.info("snippet: %s", snippet) |
|
if 'liveBroadcastContent' in snippet and snippet['liveBroadcastContent'] == 'live': |
|
duration = ' (LIVE)' |
|
else: |
|
duration = '' |
|
details = youtube.get_details(yt_id) |
|
LOGGER.info("details: %s", details) |
|
if details and 'duration' in details: |
|
delta = isodate.parse_duration(details['duration']) |
|
hours, rem = divmod(delta.seconds, 3600) |
|
minutes, seconds = divmod(rem, 60) |
|
if hours: |
|
duration = f' ({hours:d}:{minutes:02d}:{seconds:02d})' |
|
elif minutes or seconds: |
|
duration = f' ({minutes:d}:{seconds:02d})' |
|
text += f' — {html.unescape(snippet["title"])}{duration}' |
|
except RuntimeError: |
|
LOGGER.exception("Unable to get video details") |
|
|
|
return text |
|
|
|
|
|
def bot(): |
|
""" Run the bot """ |
|
genre = randline('genres.txt') |
|
noun = randline('nouns.txt') |
|
verb1 = randline('1syllableverbs.txt') |
|
verb2 = randline('2syllableverbs.txt') |
|
|
|
search_term = f'+lofi {genre} +{noun} {verb1} {verb2}' |
|
text = f"lo-fi {genre} {noun} to {verb1} and {verb2} to".replace(' ', ' ') |
|
|
|
seen_vids = set() |
|
try: |
|
with open('seen-vids.dat', 'rb') as seen: |
|
seen_vids = pickle.load(seen) |
|
except FileNotFoundError: |
|
LOGGER.info("Seen database not found") |
|
except pickle.UnpicklingError: |
|
LOGGER.exception("Seen database corrupted") |
|
|
|
videos = youtube.get_videos(search_term) |
|
LOGGER.info("%s: Found %d videos (%d new)", search_term, |
|
len(videos), len({id for id, _ in videos} - seen_vids)) |
|
|
|
yt_id = None |
|
|
|
random.shuffle(videos) |
|
|
|
# try to find a random video we haven't used before |
|
for (vid, snippet) in videos: |
|
if vid not in seen_vids: |
|
yt_id = vid |
|
LOGGER.info("Using [https://youtube.com/watch?v=%s] %s", |
|
yt_id, snippet['title']) |
|
break |
|
|
|
if not yt_id and videos: |
|
# nothing new, so just select a random one (list is already shuffled) |
|
(yt_id, snippet) = videos[0] |
|
LOGGER.info("Reusing [https://youtube.com/watch?v=%s] %s", |
|
yt_id, snippet['title']) |
|
|
|
if yt_id: |
|
text += '\n\n' + format_details(yt_id, snippet) |
|
|
|
# remember that we used it already |
|
seen_vids.add(yt_id) |
|
|
|
LOGGER.info(text) |
|
|
|
if not os.environ.get('BEATBOT_TESTING'): |
|
mdon = mastodon.Mastodon( |
|
access_token='token.secret', |
|
api_base_url='https://botsin.space') |
|
mdon.status_post(text) |
|
|
|
with open('seen-vids.dat', 'wb') as seen: |
|
pickle.dump(seen_vids, seen) |
|
|
|
|
|
if __name__ == '__main__': |
|
try: |
|
bot() |
|
except Exception: |
|
LOGGER.exception("Uncaught exception") |
|
raise
|
|
|