您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

6 个月前
7 个月前
7 个月前
1年前
7 个月前
6 个月前
7 个月前
7 个月前
7 个月前
1年前
1年前
7 个月前
6 个月前
6 个月前
6 个月前
1年前
1年前
1年前
6 个月前
6 个月前
7 个月前
6 个月前
6 个月前
6 个月前
6 个月前
7 个月前
7 个月前
1年前
6 个月前
1年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """ Runner for the Lo-Fi Beats bot at https://botsin.space/@lofibeats """
  2. import html
  3. import logging
  4. import logging.handlers
  5. import os
  6. import pickle
  7. import random
  8. import isodate
  9. import mastodon
  10. import youtube
  11. logging.basicConfig(
  12. handlers=[logging.handlers.RotatingFileHandler('beatbot.log', maxBytes=100000, backupCount=10),
  13. logging.StreamHandler()],
  14. level=logging.INFO,
  15. format="[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s",
  16. datefmt='%Y-%m-%dT%H:%M:%S')
  17. LOGGER = logging.getLogger(__name__)
  18. def randline(fname):
  19. """ Get a random line from a file """
  20. with open(fname) as afile:
  21. line = next(afile)
  22. for num, aline in enumerate(afile, 2):
  23. if random.randrange(num) == 0:
  24. line = aline
  25. return line.strip()
  26. def format_details(yt_id, snippet):
  27. ''' format the details of a video '''
  28. text = f'https://youtube.com/watch?v={yt_id}'
  29. try:
  30. LOGGER.info("snippet: %s", snippet)
  31. if 'liveBroadcastContent' in snippet and snippet['liveBroadcastContent'] == 'live':
  32. duration = ' (LIVE)'
  33. else:
  34. duration = ''
  35. details = youtube.get_details(yt_id)
  36. LOGGER.info("details: %s", details)
  37. if details and 'duration' in details:
  38. delta = isodate.parse_duration(details['duration'])
  39. hours, rem = divmod(delta.seconds, 3600)
  40. minutes, seconds = divmod(rem, 60)
  41. if hours:
  42. duration = f' ({hours:d}:{minutes:02d}:{seconds:02d})'
  43. elif minutes or seconds:
  44. duration = f' ({minutes:d}:{seconds:02d})'
  45. text += f' — {html.unescape(snippet["title"])}{duration}'
  46. except RuntimeError:
  47. LOGGER.exception("Unable to get video details")
  48. return text
  49. def bot():
  50. """ Run the bot """
  51. genre = randline('genres.txt')
  52. verb1 = randline('1syllableverbs.txt')
  53. verb2 = randline('2syllableverbs.txt')
  54. search_term = f'+lofi {genre} +beats {verb1} {verb2}'
  55. text = f"lo-fi {genre} beats to {verb1} and {verb2} to".replace(' ', ' ')
  56. seen_vids = set()
  57. try:
  58. with open('seen-vids.dat', 'rb') as seen:
  59. seen_vids = pickle.load(seen)
  60. except FileNotFoundError:
  61. LOGGER.info("Seen database not found")
  62. except pickle.UnpicklingError:
  63. LOGGER.exception("Seen database corrupted")
  64. videos = youtube.get_videos(search_term)
  65. LOGGER.info("%s: Found %d videos (%d new)", search_term,
  66. len(videos), len({id for id, _ in videos} - seen_vids))
  67. yt_id = None
  68. random.shuffle(videos)
  69. # try to find a random video we haven't used before
  70. for (vid, snippet) in videos:
  71. if vid not in seen_vids:
  72. yt_id = vid
  73. LOGGER.info("Using [https://youtube.com/watch?v=%s] %s",
  74. yt_id, snippet['title'])
  75. break
  76. if not yt_id and videos:
  77. # nothing new, so just select a random one (list is already shuffled)
  78. (yt_id, snippet) = videos[0]
  79. LOGGER.info("Reusing [https://youtube.com/watch?v=%s] %s",
  80. yt_id, snippet['title'])
  81. if yt_id:
  82. text += '\n\n' + format_details(yt_id, snippet)
  83. # remember that we used it already
  84. seen_vids.add(yt_id)
  85. LOGGER.info(text)
  86. if not os.environ.get('BEATBOT_TESTING'):
  87. mdon = mastodon.Mastodon(
  88. access_token='token.secret',
  89. api_base_url='https://botsin.space')
  90. mdon.status_post(text)
  91. with open('seen-vids.dat', 'wb') as seen:
  92. pickle.dump(seen_vids, seen)
  93. if __name__ == '__main__':
  94. try:
  95. bot()
  96. except Exception:
  97. LOGGER.exception("Uncaught exception")
  98. raise