myanimebot.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. #!/usr/bin/env python3
  2. # Copyright Penta (c) 2018/2020 - Under BSD License - Based on feed2discord.py by Eric Eisenhart
  3. # Compatible for Python 3.7.X
  4. #
  5. # Dependencies (for CentOS 7):
  6. # curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
  7. # yum install gcc MariaDB-client MariaDB-common MariaDB-shared MariaDB-devel
  8. # python3.7 -m pip install --upgrade pip
  9. # pip3.7 install discord.py mariadb pytz feedparser python-dateutil asyncio html2text bs4 PyNaCL aiodns cchardet configparser
  10. # TODO Remove all of that
  11. import asyncio
  12. # Library import
  13. import logging
  14. import sys
  15. import urllib.request
  16. from configparser import ConfigParser
  17. from datetime import datetime
  18. from typing import List, Tuple
  19. import aiohttp
  20. import discord
  21. import feedparser
  22. from aiohttp.web_exceptions import HTTPError, HTTPNotModified
  23. from dateutil.parser import parse as parse_datetime
  24. from html2text import HTML2Text
  25. # Our modules
  26. import myanimebot.anilist as anilist
  27. import myanimebot.globals as globals
  28. import myanimebot.utils as utils
  29. import myanimebot.myanimelist as myanimelist
  30. from myanimebot.discord import send_embed_wrapper, build_embed
  31. if not sys.version_info[:2] >= (3, 7):
  32. print("ERROR: Requires python 3.7 or newer.")
  33. exit(1)
  34. # Main function that check the RSS feeds from MyAnimeList
  35. async def background_check_feed(asyncioloop):
  36. globals.logger.info("Starting up background_check_feed")
  37. # We configure the http header
  38. http_headers = { "User-Agent": "MyAnimeBot Discord Bot v" + globals.VERSION, }
  39. await globals.client.wait_until_ready()
  40. globals.logger.debug("Discord client connected, unlocking background_check_feed...")
  41. while not globals.client.is_closed():
  42. try:
  43. db_user = globals.conn.cursor(buffered=True)
  44. db_user.execute("SELECT mal_user, servers FROM t_users")
  45. data_user = db_user.fetchone()
  46. except Exception as e:
  47. globals.logger.critical("Database unavailable! (" + str(e) + ")")
  48. quit()
  49. while data_user is not None:
  50. user=data_user[0]
  51. stop_boucle = 0
  52. feed_type = 1
  53. globals.logger.debug("checking user: " + user)
  54. try:
  55. while stop_boucle == 0 :
  56. try:
  57. async with aiohttp.ClientSession() as httpclient:
  58. if feed_type == 1 :
  59. http_response = await httpclient.request("GET", "https://myanimelist.net/rss.php?type=rm&u=" + user, headers=http_headers)
  60. media = "manga"
  61. else :
  62. http_response = await httpclient.request("GET", "https://myanimelist.net/rss.php?type=rw&u=" + user, headers=http_headers)
  63. media = "anime"
  64. except Exception as e:
  65. globals.logger.error("Error while loading RSS (" + str(feed_type) + ") of '" + user + "': " + str(e))
  66. break
  67. http_data = await http_response.read()
  68. feed_data = feedparser.parse(http_data)
  69. for item in feed_data.entries:
  70. pubDateRaw = datetime.strptime(item.published, '%a, %d %b %Y %H:%M:%S %z').astimezone(globals.timezone)
  71. DateTimezone = pubDateRaw.strftime("%z")[:3] + ':' + pubDateRaw.strftime("%z")[3:]
  72. pubDate = pubDateRaw.strftime("%Y-%m-%d %H:%M:%S")
  73. cursor = globals.conn.cursor(buffered=True)
  74. cursor.execute("SELECT published, title, url FROM t_feeds WHERE published=%s AND title=%s AND user=%s", [pubDate, item.title, user])
  75. data = cursor.fetchone()
  76. if data is None:
  77. var = datetime.now(globals.timezone) - pubDateRaw
  78. globals.logger.debug(" - " + item.title + ": " + str(var.total_seconds()))
  79. if var.total_seconds() < globals.secondMax:
  80. globals.logger.info(user + ": Item '" + item.title + "' not seen, processing...")
  81. if item.description.startswith('-') :
  82. if feed_type == 1 : item.description = "Re-Reading " + item.description
  83. else : item.description = "Re-Watching " + item.description
  84. cursor.execute("SELECT thumbnail FROM t_animes WHERE guid=%s LIMIT 1", [item.guid])
  85. data_img = cursor.fetchone()
  86. if data_img is None:
  87. try:
  88. image = myanimelist.get_thumbnail(item.link)
  89. globals.logger.info("First time seeing this " + media + ", adding thumbnail into database: " + image)
  90. except Exception as e:
  91. globals.logger.warning("Error while getting the thumbnail: " + str(e))
  92. image = ""
  93. cursor.execute("INSERT INTO t_animes (guid, title, thumbnail, found, discoverer, media) VALUES (%s, %s, %s, NOW(), %s, %s)", [item.guid, item.title, image, user, media])
  94. globals.conn.commit()
  95. else: image = data_img[0]
  96. type = item.description.partition(" - ")[0]
  97. cursor.execute("INSERT INTO t_feeds (published, title, url, user, found, type) VALUES (%s, %s, %s, %s, NOW(), %s)", (pubDate, item.title, item.guid, user, type))
  98. globals.conn.commit()
  99. for server in data_user[1].split(","):
  100. db_srv = globals.conn.cursor(buffered=True)
  101. db_srv.execute("SELECT channel FROM t_servers WHERE server = %s", [server])
  102. data_channel = db_srv.fetchone()
  103. while data_channel is not None:
  104. for channel in data_channel: await send_embed_wrapper(asyncioloop, channel, globals.client, build_embed(user, item.title, item.link, item.description, pubDateRaw, image, utils.Service.MAL))
  105. data_channel = db_srv.fetchone()
  106. if feed_type == 1:
  107. feed_type = 0
  108. await asyncio.sleep(1)
  109. else:
  110. stop_boucle = 1
  111. except Exception as e:
  112. globals.logger.error("Error when parsing RSS for '" + user + "': " + str(e))
  113. await asyncio.sleep(1)
  114. data_user = db_user.fetchone()
  115. async def fetch_activities_anilist():
  116. print("Fetching activities")
  117. feed = {'__typename': 'ListActivity', 'id': 150515141, 'type': 'ANIME_LIST', 'status': 'rewatched episode', 'progress': '10 - 12', 'isLocked': False, 'createdAt': 1608738377, 'user': {'id': 102213, 'name': 'lululekiddo'}, 'media': {'id': 5081, 'siteUrl': 'https://anilist.co/anime/5081', 'title': {'romaji': 'Bakemonogatari', 'english': 'Bakemonogatari'}}}
  118. await anilist.check_new_activities()
  119. @globals.client.event
  120. async def on_ready():
  121. globals.logger.info("Logged in as " + globals.client.user.name + " (" + str(globals.client.user.id) + ")")
  122. globals.logger.info("Starting all tasks...")
  123. globals.task_feed = globals.client.loop.create_task(background_check_feed(globals.client.loop))
  124. globals.task_feed_anilist = globals.client.loop.create_task(anilist.background_check_feed(globals.client.loop))
  125. globals.task_thumbnail = globals.client.loop.create_task(update_thumbnail_catalog(globals.client.loop))
  126. globals.task_gameplayed = globals.client.loop.create_task(change_gameplayed(globals.client.loop))
  127. @globals.client.event
  128. async def on_error(event, *args, **kwargs):
  129. globals.logger.exception("Crap! An unknown Discord error occured...")
  130. def build_info_cmd_message(users, server, channels, filters : List[utils.Service]) -> str:
  131. ''' Build the corresponding message for the info command '''
  132. registered_channel = globals.client.get_channel(int(channels[0]["channel"]))
  133. # Store users
  134. mal_users = []
  135. anilist_users = []
  136. for user in users:
  137. # If user is part of the server, add it to the message
  138. if str(server.id) in user['servers'].split(','):
  139. try:
  140. user_service = utils.Service.from_str(user["service"])
  141. if user_service == utils.Service.MAL:
  142. mal_users.append(user[globals.DB_USER_NAME])
  143. elif user_service == utils.Service.ANILIST:
  144. anilist_users.append(user[globals.DB_USER_NAME])
  145. except NotImplementedError:
  146. pass # Nothing to do here
  147. if not mal_users and not anilist_users:
  148. return "No users registered on this server. Try to add one."
  149. else:
  150. message = 'Registered user(s) on **{}**\n\n'.format(server)
  151. if mal_users: # If not empty
  152. # Don't print if there is filters and MAL is not in them
  153. if not filters or (filters and utils.Service.MAL in filters):
  154. message += '**MyAnimeList** users:\n'
  155. message += '```{}```\n'.format(', '.join(mal_users))
  156. if anilist_users: # If not empty
  157. # Don't print if there is filters and MAL is not in them
  158. if not filters or (filters and utils.Service.ANILIST in filters):
  159. message += '**AniList** users:\n'
  160. message += '```{}```\n'.format(', '.join(anilist_users))
  161. message += 'Assigned channel : **{}**'.format(registered_channel)
  162. return message
  163. def get_service_filters_list(filters : str) -> List[utils.Service]:
  164. ''' Creates and returns a service filter list from a comma-separated string '''
  165. filters_list = []
  166. for filter in filters.split(','):
  167. try:
  168. filters_list.append(utils.Service.from_str(filter))
  169. except NotImplementedError:
  170. pass # Ignore incorrect filter
  171. return filters_list
  172. async def info_cmd(message, words):
  173. ''' Processes the command "info" and sends a message '''
  174. # Get filters if available
  175. filters = []
  176. if (len(words) >= 3): # If filters are specified
  177. filters = get_service_filters_list(words[2])
  178. server = message.guild
  179. if utils.is_server_in_db(server.id) == False:
  180. await message.channel.send("The server **{}** is not in our database.".format(server))
  181. else:
  182. users = utils.get_users()
  183. channels = utils.get_channels(server.id)
  184. if channels is None:
  185. await message.channel.send("No channel assigned for this bot on this server.")
  186. else:
  187. await message.channel.send(build_info_cmd_message(users, server, channels, filters))
  188. def check_user_name_validity(user_name: str, service : utils.Service) -> Tuple[bool, str]:
  189. """ Check if user_name exists on a specific service.
  190. Returns:
  191. - bool: True if user_name exists
  192. - str: Error string if the user does not exist
  193. """
  194. if service == utils.Service.MAL:
  195. try:
  196. # Ping user profile to check validity
  197. urllib.request.urlopen('{}{}'.format(globals.MAL_PROFILE_URL, user_name))
  198. except urllib.error.HTTPError as e:
  199. if (e.code == 404): # URL profile not found
  200. return False, "User **{}** doesn't exist on MyAnimeList!".format(user_name)
  201. else:
  202. globals.logger.warning("HTTP Code {} while trying to add user '{}' and checking its validity.".format(e.code, user_name))
  203. return False, "An error occured when we checked this username on MyAnimeList, maybe the website is down?"
  204. elif service == utils.Service.ANILIST:
  205. is_user_valid = anilist.check_username_validity(user_name)
  206. if is_user_valid == False:
  207. globals.logger.warning("No results returned while trying to add user '{}' and checking its validity.".format(user_name))
  208. return False, "User **{}** doesn't exist on AniList!".format(user_name)
  209. return True, None
  210. async def add_user_cmd(words, message):
  211. ''' Processes the command "add" and add a user to fetch the data for '''
  212. # Check if command is valid
  213. if len(words) != 4:
  214. if (len(words) < 4):
  215. return await message.channel.send("Usage: {} add **{}**/**{}** **username**".format(globals.prefix, globals.SERVICE_MAL, globals.SERVICE_ANILIST))
  216. return await message.channel.send("Too many arguments! You have to specify only one username.")
  217. try:
  218. service = utils.Service.from_str(words[2])
  219. except NotImplementedError:
  220. return await message.channel.send('Incorrect service. Use **"{}"** or **"{}"** for example'.format(globals.SERVICE_MAL, globals.SERVICE_ANILIST))
  221. user = words[3]
  222. server_id = str(message.guild.id)
  223. if(len(user) > 14):
  224. return await message.channel.send("Username too long!")
  225. try:
  226. # Check user validity
  227. is_valid, error_string = check_user_name_validity(user, service)
  228. if is_valid == False:
  229. return await message.channel.send(error_string)
  230. # Get user's servers
  231. user_servers = utils.get_user_servers(user, service)
  232. # User not present in database
  233. if user_servers is None:
  234. print('Inserting USER WITH {} {} {}'.format(user, service, server_id))
  235. utils.insert_user_into_db(user, service, server_id)
  236. return await message.channel.send("**{}** added to the database for the server **{}**.".format(user, str(message.guild)))
  237. else: # User present in database
  238. is_server_present = server_id in user_servers.split(',')
  239. if is_server_present == True: # The user already has registered this server
  240. return await message.channel.send("User **{}** is already registered in our database for this server!".format(user))
  241. else:
  242. new_servers = '{},{}'.format(user_servers, server_id)
  243. print('UPDATING USER WITH {} {} {}'.format(user, service, new_servers))
  244. utils.update_user_servers_db(user, service, new_servers)
  245. return await message.channel.send("**{}** added to the database for the server **{}**.".format(user, str(message.guild)))
  246. except Exception as e:
  247. globals.logger.warning("Error while adding user '{}' on server '{}': {}".format(user, message.guild, str(e)))
  248. return await message.channel.send("An unknown error occured while addind this user, the error has been logged.")
  249. async def delete_user_cmd(words, message):
  250. ''' Processes the command "delete" and remove a registered user '''
  251. # Check if command is valid
  252. if len(words) != 4:
  253. if (len(words) < 4):
  254. return await message.channel.send("Usage: {} delete **{}**/**{}** **username**".format(globals.prefix, globals.SERVICE_MAL, globals.SERVICE_ANILIST))
  255. return await message.channel.send("Too many arguments! You have to specify only one username.")
  256. try:
  257. service = utils.Service.from_str(words[2])
  258. except NotImplementedError:
  259. return await message.channel.send('Incorrect service. Use **"{}"** or **"{}"** for example'.format(globals.SERVICE_MAL, globals.SERVICE_ANILIST))
  260. user = words[3]
  261. server_id = str(message.guild.id)
  262. user_servers = utils.get_user_servers(user, service)
  263. # If user is not present in the database
  264. if user_servers is None:
  265. return await message.channel.send("The user **" + user + "** is not in our database for this server!")
  266. # Else if present, update the servers for this user
  267. srv_string = utils.remove_server_from_servers(server_id, user_servers)
  268. if srv_string is None: # Server not present in the user's servers
  269. return await message.channel.send("The user **" + user + "** is not in our database for this server!")
  270. if srv_string == "":
  271. utils.delete_user_from_db(user, service)
  272. else:
  273. utils.update_user_servers_db(user, service, srv_string)
  274. return await message.channel.send("**" + user + "** deleted from the database for this server.")
  275. @globals.client.event
  276. async def on_message(message):
  277. if message.author == globals.client.user: return
  278. words = message.content.split(" ")
  279. author = str('{0.author.mention}'.format(message))
  280. # A user is trying to get help
  281. if words[0] == globals.prefix:
  282. if len(words) > 1:
  283. if words[1] == "ping":
  284. await message.channel.send("pong")
  285. elif words[1] == "here":
  286. if message.author.guild_permissions.administrator:
  287. cursor = globals.conn.cursor(buffered=True)
  288. cursor.execute("SELECT server, channel FROM t_servers WHERE server=%s", [str(message.guild.id)])
  289. data = cursor.fetchone()
  290. if data is None:
  291. cursor.execute("INSERT INTO t_servers (server, channel) VALUES (%s,%s)", [str(message.guild.id), str(message.channel.id)])
  292. globals.conn.commit()
  293. await message.channel.send("Channel **" + str(message.channel) + "** configured for **" + str(message.guild) + "**.")
  294. else:
  295. if(data[1] == str(message.channel.id)): await message.channel.send("Channel **" + str(message.channel) + "** already in use for this server.")
  296. else:
  297. cursor.execute("UPDATE t_servers SET channel = %s WHERE server = %s", [str(message.channel.id), str(message.guild.id)])
  298. globals.conn.commit()
  299. await message.channel.send("Channel updated to: **" + str(message.channel) + "**.")
  300. cursor.close()
  301. else: await message.channel.send("Only server's admins can use this command!")
  302. elif words[1] == "add":
  303. await add_user_cmd(words, message)
  304. elif words[1] == "delete":
  305. await delete_user_cmd(words, message)
  306. elif words[1] == "stop":
  307. if message.author.guild_permissions.administrator:
  308. if (len(words) == 2):
  309. cursor = globals.conn.cursor(buffered=True)
  310. cursor.execute("SELECT server FROM t_servers WHERE server=%s", [str(message.guild.id)])
  311. data = cursor.fetchone()
  312. if data is None: await globals.client.send_message(message.channel, "The server **" + str(message.guild) + "** is not in our database.")
  313. else:
  314. cursor.execute("DELETE FROM t_servers WHERE server = %s", [message.guild.id])
  315. globals.conn.commit()
  316. await message.channel.send("Server **" + str(message.guild) + "** deleted from our database.")
  317. cursor.close()
  318. else: await message.channel.send("Too many arguments! Only type *stop* if you want to stop this bot on **" + message.guild + "**")
  319. else: await message.channel.send("Only server's admins can use this command!")
  320. elif words[1] == "info":
  321. await info_cmd(message, words)
  322. elif words[1] == "about": await message.channel.send(embed=discord.Embed(colour=0x777777, title="MyAnimeBot version " + globals.VERSION + " by Penta", description="This bot check the MyAnimeList's RSS for each user specified, and send a message if there is something new.\nMore help with the **!malbot help** command.\n\nAdd me on steam: http://steamcommunity.com/id/Penta_Pingouin").set_thumbnail(url="https://cdn.discordapp.com/avatars/415474467033317376/2d847944aab2104923c18863a41647da.jpg?size=64"))
  323. elif words[1] == "help": await message.channel.send(globals.HELP)
  324. elif words[1] == "top":
  325. if len(words) == 2:
  326. try:
  327. cursor = globals.conn.cursor(buffered=True)
  328. cursor.execute("SELECT * FROM v_Top")
  329. data = cursor.fetchone()
  330. if data is None: await message.channel.send("It seems that there is no statistics... (what happened?!)")
  331. else:
  332. topText = "**__Here is the global statistics of this bot:__**\n\n"
  333. while data is not None:
  334. topText += " - " + str(data[0]) + ": " + str(data[1]) + "\n"
  335. data = cursor.fetchone()
  336. cursor = globals.conn.cursor(buffered=True)
  337. cursor.execute("SELECT * FROM v_TotalFeeds")
  338. data = cursor.fetchone()
  339. topText += "\n***Total user entry***: " + str(data[0])
  340. cursor = globals.conn.cursor(buffered=True)
  341. cursor.execute("SELECT * FROM v_TotalAnimes")
  342. data = cursor.fetchone()
  343. topText += "\n***Total unique manga/anime***: " + str(data[0])
  344. await message.channel.send(topText)
  345. cursor.close()
  346. except Exception as e:
  347. globals.logger.warning("An error occured while displaying the global top: " + str(e))
  348. await message.channel.send("Unable to reply to your request at the moment...")
  349. elif len(words) > 2:
  350. keyword = str(' '.join(words[2:]))
  351. globals.logger.info("Displaying the global top for the keyword: " + keyword)
  352. try:
  353. cursor = globals.conn.cursor(buffered=True)
  354. cursor.callproc('sp_UsersPerKeyword', [str(keyword), '20'])
  355. for result in cursor.stored_results():
  356. data = result.fetchone()
  357. if data is None: await message.channel.send("It seems that there is no statistics for the keyword **" + keyword + "**.")
  358. else:
  359. topKeyText = "**__Here is the statistics for the keyword " + keyword + ":__**\n\n"
  360. while data is not None:
  361. topKeyText += " - " + str(data[0]) + ": " + str(data[1]) + "\n"
  362. data = result.fetchone()
  363. await message.channel.send(topKeyText)
  364. cursor.close()
  365. except Exception as e:
  366. globals.logger.warning("An error occured while displaying the global top for keyword '" + keyword + "': " + str(e))
  367. await message.channel.send("Unable to reply to your request at the moment...")
  368. elif words[1] == "group":
  369. if len(words) > 2:
  370. if message.author.guild_permissions.administrator:
  371. group = words[2]
  372. await message.channel.send("admin OK")
  373. else: await message.channel.send("Only server's admins can use this command!")
  374. else:
  375. await message.channel.send("You have to specify a group!")
  376. elif words[1] == "fetch-debug":
  377. await fetch_activities_anilist()
  378. # If mentioned
  379. elif globals.client.user in message.mentions:
  380. await message.channel.send(":heart:")
  381. # Get a random anime name and change the bot's activity
  382. async def change_gameplayed(asyncioloop):
  383. globals.logger.info("Starting up change_gameplayed")
  384. await globals.client.wait_until_ready()
  385. await asyncio.sleep(1)
  386. while not globals.client.is_closed():
  387. # Get a random anime name from the users' list
  388. cursor = globals.conn.cursor(buffered=True)
  389. cursor.execute("SELECT title FROM t_animes ORDER BY RAND() LIMIT 1")
  390. data = cursor.fetchone()
  391. anime = utils.truncate_end_show(data[0])
  392. # Try to change the bot's activity
  393. try:
  394. if data is not None: await globals.client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=anime))
  395. except Exception as e:
  396. globals.logger.warning("An error occured while changing the displayed anime title: " + str(e))
  397. cursor.close()
  398. # Do it every minute
  399. await asyncio.sleep(60)
  400. async def update_thumbnail_catalog(asyncioloop):
  401. globals.logger.info("Starting up update_thumbnail_catalog")
  402. while not globals.client.is_closed():
  403. await asyncio.sleep(43200)
  404. globals.logger.info("Automatic check of the thumbnail database on going...")
  405. reload = 0
  406. cursor = globals.conn.cursor(buffered=True)
  407. cursor.execute("SELECT guid, title, thumbnail FROM t_animes")
  408. data = cursor.fetchone()
  409. while data is not None:
  410. try:
  411. if (data[2] != "") : urllib.request.urlopen(data[2])
  412. else: reload = 1
  413. except urllib.error.HTTPError as e:
  414. globals.logger.warning("HTTP Error while getting the current thumbnail of '" + str(data[1]) + "': " + str(e))
  415. reload = 1
  416. except Exception as e:
  417. globals.logger.debug("Error while getting the current thumbnail of '" + str(data[1]) + "': " + str(e))
  418. if (reload == 1) :
  419. try:
  420. image = myanimelist.get_thumbnail(data[0])
  421. cursor.execute("UPDATE t_animes SET thumbnail = %s WHERE guid = %s", [image, data[0]])
  422. globals.conn.commit()
  423. globals.logger.info("Updated thumbnail found for \"" + str(data[1]) + "\": %s", image)
  424. except Exception as e:
  425. globals.logger.warning("Error while downloading updated thumbnail for '" + str(data[1]) + "': " + str(e))
  426. await asyncio.sleep(3)
  427. data = cursor.fetchone()
  428. cursor.close()
  429. globals.logger.info("Thumbnail database checked.")
  430. # Starting main function
  431. if __name__ == "__main__":
  432. try:
  433. globals.client.run(globals.token)
  434. except:
  435. logging.info("Closing all tasks...")
  436. globals.task_feed.cancel()
  437. globals.task_feed_anilist.cancel()
  438. globals.task_thumbnail.cancel()
  439. globals.task_gameplayed.cancel()
  440. globals.logger.critical("Script halted.")
  441. # We close all the ressources
  442. globals.conn.close()
  443. globals.log_cursor.close()
  444. globals.log_conn.close()