myanimebot.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  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. # yum install python3 mariadb-devel gcc python3-devel
  7. # python3.7 -m pip install --upgrade pip
  8. # pip3.7 install discord.py mysql pytz feedparser python-dateutil asyncio html2text bs4 PyNaCL aiodns cchardet configparser
  9. # pip3.7 install mysql.connector
  10. # Library import
  11. import logging
  12. import os
  13. import re
  14. import sys
  15. import discord
  16. import feedparser
  17. import pytz
  18. import aiohttp
  19. import asyncio
  20. import urllib.request
  21. import mysql.connector as mariadb
  22. import string
  23. import time
  24. import socket
  25. from configparser import ConfigParser
  26. from datetime import datetime
  27. from dateutil.parser import parse as parse_datetime
  28. from html2text import HTML2Text
  29. from aiohttp.web_exceptions import HTTPError, HTTPNotModified
  30. from bs4 import BeautifulSoup
  31. if not sys.version_info[:2] >= (3, 7):
  32. print("ERROR: Requires python 3.7 or newer.")
  33. exit(1)
  34. class ImproperlyConfigured(Exception): pass
  35. BASE_DIR = os.path.dirname(os.path.abspath(__file__))
  36. HOME_DIR = os.path.expanduser("~")
  37. DEFAULT_CONFIG_PATHS = [
  38. os.path.join("myanimebot.conf"),
  39. os.path.join(BASE_DIR, "myanimebot.conf"),
  40. os.path.join("/etc/malbot/myanimebot.conf"),
  41. os.path.join(HOME_DIR, "myanimebot.conf")
  42. ]
  43. def get_config():
  44. config = ConfigParser()
  45. config_paths = []
  46. for path in DEFAULT_CONFIG_PATHS:
  47. if os.path.isfile(path):
  48. config_paths.append(path)
  49. break
  50. else: raise ImproperlyConfigured("No configuration file found")
  51. config.read(config_paths)
  52. return config
  53. # Loading configuration
  54. try:
  55. config=get_config()
  56. except Exception as e:
  57. print ("Cannot read configuration: " + str(e))
  58. exit (1)
  59. CONFIG=config["MYANIMEBOT"]
  60. logLevel=CONFIG.get("logLevel", "INFO")
  61. dbHost=CONFIG.get("dbHost", "127.0.0.1")
  62. dbUser=CONFIG.get("dbUser", "myanimebot")
  63. dbPassword=CONFIG.get("dbPassword")
  64. dbName=CONFIG.get("dbName", "myanimebot")
  65. logPath=CONFIG.get("logPath", "myanimebot.log")
  66. timezone=pytz.timezone(CONFIG.get("timezone", "utc"))
  67. secondMax=CONFIG.getint("secondMax", 7200)
  68. token=CONFIG.get("token")
  69. # class that send logs to DB
  70. class LogDBHandler(logging.Handler):
  71. '''
  72. Customized logging handler that puts logs to the database.
  73. pymssql required
  74. '''
  75. def __init__(self, sql_conn, sql_cursor):
  76. logging.Handler.__init__(self)
  77. self.sql_cursor = sql_cursor
  78. self.sql_conn = sql_conn
  79. def emit(self, record):
  80. # Clear the log message so it can be put to db via sql (escape quotes)
  81. self.log_msg = str(record.msg.strip().replace('\'', '\'\''))
  82. # Make the SQL insert
  83. try:
  84. print (str(self.log_msg))
  85. self.sql_cursor.execute("INSERT INTO t_logs (host, level, type, log, date, source) VALUES (%s, %s, %s, %s, NOW(), %s)", (str(socket.gethostname()), str(record.levelno), str(record.levelname), self.log_msg, str(record.name)))
  86. self.sql_conn.commit()
  87. except Exception as e:
  88. print ('Error while logging into DB: ' + str(e))
  89. # Log configuration
  90. log_format='%(asctime)-13s : %(name)-15s : %(levelname)-8s : %(message)s'
  91. logging.basicConfig(handlers=[logging.FileHandler(logPath, 'a', 'utf-8')], format=log_format, level=logLevel)
  92. console = logging.StreamHandler()
  93. console.setLevel(logging.INFO)
  94. console.setFormatter(logging.Formatter(log_format))
  95. logger = logging.getLogger("myanimebot")
  96. logger.setLevel(logLevel)
  97. logging.getLogger('').addHandler(console)
  98. # Script version
  99. VERSION = "0.9.6.1"
  100. # The help message
  101. HELP = """**Here's some help for you:**
  102. ```
  103. - here :
  104. Type this command on the channel where you want to see the activity of the MAL profiles.
  105. - stop :
  106. Cancel the here command, no message will be displayed.
  107. - add :
  108. Followed by a username, add a MAL user into the database to be displayed on this server.
  109. ex: !malbot add MyUser
  110. - delete :
  111. Followed by a username, remove a user from the database.
  112. ex: !malbot delete MyUser
  113. - group :
  114. Specify a group that can use the add and delete commands.
  115. - info :
  116. Get the users already in the database for this server.
  117. - about :
  118. Get some information about this bot.
  119. ```"""
  120. logger.info("Booting MyAnimeBot " + VERSION + "...")
  121. logger.debug("DEBUG log: OK")
  122. # Initialization of the web client
  123. httpclient = aiohttp.ClientSession()
  124. feedparser.PREFERRED_XML_PARSERS.remove("drv_libxml2")
  125. # Initialization of the database
  126. try:
  127. # Main database connection
  128. conn = mariadb.connect(host=dbHost, user=dbUser, password=dbPassword, database=dbName, buffered=True)
  129. # We initialize the logs into the DB.
  130. log_conn = mariadb.connect(host=dbHost, user=dbUser, password=dbPassword, database=dbName, buffered=True)
  131. log_cursor = log_conn.cursor()
  132. logdb = LogDBHandler(log_conn, log_cursor)
  133. logging.getLogger('').addHandler(logdb)
  134. logger.info("The database logger is running.")
  135. except Exception as e:
  136. logger.critical("Can't connect to the database: " + str(e))
  137. httpclient.close()
  138. quit()
  139. # Initialization of the Discord client
  140. client = discord.Client()
  141. # Initialization of the thread handler
  142. loop = asyncio.get_event_loop()
  143. task_feed = None
  144. task_gameplayed = None
  145. task_thumbnail = None
  146. # Function used to make the embed message related to the animes status
  147. def build_embed(user, item, channel, pubDate, image):
  148. try:
  149. embed = discord.Embed(colour=0xEED000, url=item.link, description="[" + filter_name(item.title) + "](" + item.link + ")\n```" + item.description + "```", timestamp=pubDate.astimezone(pytz.timezone("utc")))
  150. embed.set_thumbnail(url=image)
  151. embed.set_author(name=user + "'s MyAnimeList", url="https://myanimelist.net/profile/" + user, icon_url="http://myanimebot.pentou.eu/rsc/mal_icon_small.jpg")
  152. embed.set_footer(text="MyAnimeBot", icon_url="https://cdn.discordapp.com/avatars/415474467033317376/02609b6e371821e42ba7448c259edf40.jpg?size=32")
  153. return embed
  154. except Exception as e:
  155. logger.error("Error when generating the message: " + str(e))
  156. return
  157. # Function used to send the embed
  158. @asyncio.coroutine
  159. def send_embed_wrapper(asyncioloop, channelid, client, embed):
  160. channel = client.get_channel(int(channelid))
  161. try:
  162. yield from channel.send(embed=embed)
  163. logger.info("Message sent in channel: " + channelid)
  164. except Exception as e:
  165. logger.debug("Impossible to send a message on '" + channelid + "': " + str(e))
  166. return
  167. def filter_name(name):
  168. name = name.replace("♥", "\♥")
  169. name = name.replace("♀", "\♀")
  170. name = name.replace("♂", "\♂")
  171. name = name.replace("♪", "\♪")
  172. name = name.replace("☆", "\☆")
  173. return name
  174. # Main function that check the RSS feeds from MyAnimeList
  175. @asyncio.coroutine
  176. def background_check_feed(asyncioloop):
  177. logger.info("Starting up background_check_feed")
  178. # We configure the http header
  179. http_headers = { "User-Agent": "MyAnimeBot Discord Bot v" + VERSION, }
  180. yield from client.wait_until_ready()
  181. logger.debug("Discord client connected, unlocking background_check_feed...")
  182. while not client.is_closed():
  183. try:
  184. db_user = conn.cursor(buffered=True)
  185. db_user.execute("SELECT mal_user, servers FROM t_users")
  186. data_user = db_user.fetchone()
  187. except Exception as e:
  188. logger.critical("Database unavailable! (" + str(e) + ")")
  189. quit()
  190. while data_user is not None:
  191. user=data_user[0]
  192. stop_boucle = 0
  193. feed_type = 1
  194. logger.debug("checking user: " + user)
  195. try:
  196. while stop_boucle == 0 :
  197. try:
  198. if feed_type == 1 :
  199. http_response = yield from httpclient.request("GET", "https://myanimelist.net/rss.php?type=rm&u=" + user, headers=http_headers)
  200. media = "manga"
  201. else :
  202. http_response = yield from httpclient.request("GET", "https://myanimelist.net/rss.php?type=rw&u=" + user, headers=http_headers)
  203. media = "anime"
  204. except Exception as e:
  205. logger.error("Error while loading RSS (" + str(feed_type) + ") of '" + user + "': " + str(e))
  206. break
  207. http_data = yield from http_response.read()
  208. feed_data = feedparser.parse(http_data)
  209. http_response.close()
  210. for item in feed_data.entries:
  211. pubDateRaw = datetime.strptime(item.published, '%a, %d %b %Y %H:%M:%S %z').astimezone(timezone)
  212. DateTimezone = pubDateRaw.strftime("%z")[:3] + ':' + pubDateRaw.strftime("%z")[3:]
  213. pubDate = pubDateRaw.strftime("%Y-%m-%d %H:%M:%S")
  214. cursor = conn.cursor(buffered=True)
  215. cursor.execute("SELECT published, title, url FROM t_feeds WHERE published=%s AND title=%s AND user=%s", [pubDate, item.title, user])
  216. data = cursor.fetchone()
  217. if data is None:
  218. var = datetime.now(timezone) - pubDateRaw
  219. logger.debug(" - " + item.title + ": " + str(var.total_seconds()))
  220. if var.total_seconds() < secondMax:
  221. logger.info(user + ": Item '" + item.title + "' not seen, processing...")
  222. if item.description.startswith('-') :
  223. if feed_type == 1 : item.description = "Re-Reading " + item.description
  224. else : item.description = "Re-Watching " + item.description
  225. cursor.execute("SELECT thumbnail FROM t_animes WHERE guid=%s LIMIT 1", [item.guid])
  226. data_img = cursor.fetchone()
  227. if data_img is None:
  228. try:
  229. image = getThumbnail(item.link)
  230. logger.info("First time seeing this " + media + ", adding thumbnail into database: " + image)
  231. except Exception as e:
  232. logger.warning("Error while getting the thumbnail: " + str(e))
  233. image = ""
  234. 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])
  235. conn.commit()
  236. else: image = data_img[0]
  237. type = item.description.partition(" - ")[0]
  238. 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))
  239. conn.commit()
  240. for server in data_user[1].split(","):
  241. db_srv = conn.cursor(buffered=True)
  242. db_srv.execute("SELECT channel FROM t_servers WHERE server = %s", [server])
  243. data_channel = db_srv.fetchone()
  244. while data_channel is not None:
  245. for channel in data_channel: yield from send_embed_wrapper(asyncioloop, channel, client, build_embed(user, item, channel, pubDateRaw, image))
  246. data_channel = db_srv.fetchone()
  247. if feed_type == 1:
  248. feed_type = 0
  249. yield from asyncio.sleep(1)
  250. else:
  251. stop_boucle = 1
  252. except Exception as e:
  253. logger.error("Error when parsing RSS for '" + user + "': " + str(e))
  254. yield from asyncio.sleep(1)
  255. data_user = db_user.fetchone()
  256. @client.event
  257. async def on_ready():
  258. logger.info("Logged in as " + client.user.name + " (" + str(client.user.id) + ")")
  259. @client.event
  260. async def on_error(event, *args, **kwargs):
  261. logger.exception("Crap! An unknown Discord error occured...")
  262. def getThumbnail(urlParam):
  263. url = "/".join((urlParam).split("/")[:5])
  264. websource = urllib.request.urlopen(url)
  265. soup = BeautifulSoup(websource.read(), "html.parser")
  266. image = re.search("(?P<url>https?://[^\s]+)", str(soup.find("img", {"itemprop": "image"}))).group("url")
  267. thumbnail = "".join(image.split('"')[:1]).replace('"','');
  268. return thumbnail
  269. def main():
  270. logger.info("Starting all tasks...")
  271. try:
  272. task_feed = loop.create_task(background_check_feed(loop))
  273. task_thumbnail = loop.create_task(update_thumbnail_catalog(loop))
  274. task_gameplayed = loop.create_task(change_gameplayed(loop))
  275. client.run(token)
  276. except:
  277. logging.info("Closing all tasks...")
  278. task_feed.cancel()
  279. task_thumbnail.cancel()
  280. task_gameplayed.cancel()
  281. loop.run_until_complete(client.close())
  282. finally:
  283. loop.close()
  284. @client.event
  285. async def on_message(message):
  286. if message.author == client.user: return
  287. words = message.content.split(" ")
  288. author = str('{0.author.mention}'.format(message))
  289. if words[0] == "!malbot":
  290. if len(words) > 1:
  291. if words[1] == "ping": await message.channel.send("pong")
  292. elif words[1] == "here":
  293. if message.author.guild_permissions.administrator:
  294. cursor = conn.cursor(buffered=True)
  295. cursor.execute("SELECT server, channel FROM t_servers WHERE server=%s", [str(message.guild.id)])
  296. data = cursor.fetchone()
  297. if data is None:
  298. cursor.execute("INSERT INTO t_servers (server, channel) VALUES (%s,%s)", [str(message.guild.id), str(message.channel.id)])
  299. conn.commit()
  300. await message.channel.send("Channel **" + str(message.channel) + "** configured for **" + str(message.guild) + "**.")
  301. else:
  302. if(data[1] == str(message.channel.id)): await message.channel.send("Channel **" + str(message.channel) + "** already in use for this server.")
  303. else:
  304. cursor.execute("UPDATE t_servers SET channel = %s WHERE server = %s", [str(message.channel.id), str(message.guild.id)])
  305. conn.commit()
  306. await message.channel.send("Channel updated to: **" + str(message.channel) + "**.")
  307. cursor.close()
  308. else: await message.channel.send("Only server's admins can use this command!")
  309. elif words[1] == "add":
  310. if len(words) > 2:
  311. if (len(words) == 3):
  312. user = words[2]
  313. if(len(user) < 15):
  314. try:
  315. urllib.request.urlopen('https://myanimelist.net/profile/' + user)
  316. cursor = conn.cursor(buffered=True)
  317. cursor.execute("SELECT servers FROM t_users WHERE LOWER(mal_user)=%s", [user.lower()])
  318. data = cursor.fetchone()
  319. if data is None:
  320. cursor.execute("INSERT INTO t_users (mal_user, servers) VALUES (%s, %s)", [user, str(message.guild.id)])
  321. conn.commit()
  322. await message.channel.send("**" + user + "** added to the database for the server **" + str(message.guild) + "**.")
  323. else:
  324. var = 0
  325. for server in data[0].split(","):
  326. if (server == str(message.guild.id)): var = 1
  327. if (var == 1):
  328. await message.channel.send("User **" + user + "** already in our database for this server!")
  329. else:
  330. cursor.execute("UPDATE t_users SET servers = %s WHERE LOWER(mal_user) = %s", [data[0] + "," + str(message.guild.id), user.lower()])
  331. conn.commit()
  332. await message.channel.send("**" + user + "** added to the database for the server **" + str(message.guild) + "**.")
  333. cursor.close()
  334. except urllib.error.HTTPError as e:
  335. if (e.code == 404): await message.channel.send("User **" + user + "** doesn't exist on MyAnimeList!")
  336. else:
  337. await message.channel.send("An error occured when we checked this username on MyAnimeList, maybe the website is down?")
  338. logger.warning("HTTP Code " + str(e.code) + " while checking to add for the new user '" + user + "'")
  339. except Exception as e:
  340. await message.channel.send("An unknown error occured while addind this user, the error has been logged.")
  341. logger.warning("Error while adding user '" + user + "' on server '" + message.guild + "': " + str(e))
  342. else: await message.channel.send("Username too long!")
  343. else: await message.channel.send("Too many arguments! You have to specify only one username.")
  344. else: await message.channel.send("You have to specify a **MyAnimeList** username!")
  345. elif words[1] == "delete":
  346. if len(words) > 2:
  347. if (len(words) == 3):
  348. user = words[2]
  349. cursor = conn.cursor(buffered=True)
  350. cursor.execute("SELECT servers FROM t_users WHERE LOWER(mal_user)=%s", [user.lower()])
  351. data = cursor.fetchone()
  352. if data is not None:
  353. srv_string = ""
  354. present = 0
  355. for server in data[0].split(","):
  356. if server != str(message.guild.id):
  357. if srv_string == "": srv_string = server
  358. else: srv_string += "," + server
  359. else: present = 1
  360. if present == 1:
  361. if srv_string == "": cursor.execute("DELETE FROM t_users WHERE LOWER(mal_user) = %s", [user.lower()])
  362. else: cursor.execute("UPDATE t_users SET servers = %s WHERE LOWER(mal_user) = %s", [srv_string, user.lower()])
  363. conn.commit()
  364. await message.channel.send("**" + user + "** deleted from the database for this server.")
  365. else: await message.channel.send("The user **" + user + "** is not in our database for this server!")
  366. else: await message.channel.send("The user **" + user + "** is not in our database for this server!")
  367. cursor.close()
  368. else: await message.channel.send("Too many arguments! You have to specify only one username.")
  369. else: await message.channel.send("You have to specify a **MyAnimeList** username!")
  370. elif words[1] == "stop":
  371. if message.author.guild_permissions.administrator:
  372. if (len(words) == 2):
  373. cursor = conn.cursor(buffered=True)
  374. cursor.execute("SELECT server FROM t_servers WHERE server=%s", [str(message.guild.id)])
  375. data = cursor.fetchone()
  376. if data is None: await client.send_message(message.channel, "The server **" + str(message.guild) + "** is not in our database.")
  377. else:
  378. cursor.execute("DELETE FROM t_servers WHERE server = %s", [message.guild.id])
  379. conn.commit()
  380. await message.channel.send("Server **" + str(message.guild) + "** deleted from our database.")
  381. cursor.close()
  382. else: await message.channel.send("Too many arguments! Only type *stop* if you want to stop this bot on **" + message.guild + "**")
  383. else: await message.channel.send("Only server's admins can use this command!")
  384. elif words[1] == "info":
  385. cursor = conn.cursor(buffered=True)
  386. cursor.execute("SELECT server FROM t_servers WHERE server=%s", [str(message.guild.id)])
  387. data = cursor.fetchone()
  388. if data is None: await message.channel.send("The server **" + str(message.guild) + "** is not in our database.")
  389. else:
  390. user = ""
  391. cursor = conn.cursor(buffered=True)
  392. cursor.execute("SELECT mal_user, servers FROM t_users")
  393. data = cursor.fetchone()
  394. cursor_channel = conn.cursor(buffered=True)
  395. cursor_channel.execute("SELECT channel FROM t_servers WHERE server=%s", [str(message.guild.id)])
  396. data_channel = cursor_channel.fetchone()
  397. if data_channel is None: await message.channel.send("No channel assigned for this bot in this server.")
  398. else:
  399. while data is not None:
  400. if (str(message.guild.id) in data[1].split(",")):
  401. if (user == ""): user = data[0]
  402. else: user += ", " + data[0]
  403. data = cursor.fetchone()
  404. if (user == ""): await message.channel.send("No user in this server.")
  405. else: await message.channel.send("Here's the user(s) in the **" + str(message.guild) + "**'s server:\n```" + user + "```\nAssigned channel: **" + str(client.get_channel(int(data_channel[0]))) + "**")
  406. cursor.close()
  407. cursor_channel.close()
  408. elif words[1] == "about": await message.channel.send(embed=discord.Embed(colour=0x777777, title="MyAnimeBot version " + 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"))
  409. elif words[1] == "help": await message.channel.send(HELP)
  410. elif words[1] == "top":
  411. if len(words) == 2:
  412. try:
  413. cursor = conn.cursor(buffered=True)
  414. cursor.execute("SELECT * FROM v_Top")
  415. data = cursor.fetchone()
  416. if data is None: await message.channel.send("It seems that there is no statistics... (what happened?!)")
  417. else:
  418. topText = "**__Here is the global statistics of this bot:__**\n\n"
  419. while data is not None:
  420. topText += " - " + str(data[0]) + ": " + str(data[1]) + "\n"
  421. data = cursor.fetchone()
  422. cursor = conn.cursor(buffered=True)
  423. cursor.execute("SELECT * FROM v_TotalFeeds")
  424. data = cursor.fetchone()
  425. topText += "\n***Total user entry***: " + str(data[0])
  426. cursor = conn.cursor(buffered=True)
  427. cursor.execute("SELECT * FROM v_TotalAnimes")
  428. data = cursor.fetchone()
  429. topText += "\n***Total unique manga/anime***: " + str(data[0])
  430. await message.channel.send(topText)
  431. cursor.close()
  432. except Exception as e:
  433. logger.warning("An error occured while displaying the global top: " + str(e))
  434. await message.channel.send("Unable to reply to your request at the moment...")
  435. elif len(words) > 2:
  436. keyword = str(' '.join(words[2:]))
  437. logger.info("Displaying the global top for the keyword: " + keyword)
  438. try:
  439. cursor = conn.cursor(buffered=True)
  440. cursor.callproc('sp_UsersPerKeyword', [str(keyword), '20'])
  441. for result in cursor.stored_results():
  442. data = result.fetchone()
  443. if data is None: await message.channel.send("It seems that there is no statistics for the keyword **" + keyword + "**.")
  444. else:
  445. topKeyText = "**__Here is the statistics for the keyword " + keyword + ":__**\n\n"
  446. while data is not None:
  447. topKeyText += " - " + str(data[0]) + ": " + str(data[1]) + "\n"
  448. data = result.fetchone()
  449. await message.channel.send(topKeyText)
  450. cursor.close()
  451. except Exception as e:
  452. logger.warning("An error occured while displaying the global top for keyword '" + keyword + "': " + str(e))
  453. await message.channel.send("Unable to reply to your request at the moment...")
  454. elif words[1] == "group":
  455. if len(words) > 2:
  456. if message.author.guild_permissions.administrator:
  457. group = words[2]
  458. await message.channel.send("admin OK")
  459. else: await message.channel.send("Only server's admins can use this command!")
  460. else:
  461. await message.channel.send("You have to specify a group!")
  462. elif client.user in message.mentions:
  463. await message.channel.send(":heart:")
  464. @asyncio.coroutine
  465. def change_gameplayed(asyncioloop):
  466. logger.info("Starting up change_gameplayed")
  467. yield from client.wait_until_ready()
  468. yield from asyncio.sleep(1)
  469. while not client.is_closed():
  470. cursor = conn.cursor(buffered=True)
  471. cursor.execute("SELECT title FROM t_animes ORDER BY RAND() LIMIT 1")
  472. data = cursor.fetchone()
  473. anime = data[0]
  474. if anime.endswith('- TV'): anime = anime[:-5]
  475. elif anime.endswith('- Movie'): anime = anime[:-8]
  476. elif anime.endswith('- Special'): anime = anime[:-10]
  477. elif anime.endswith('- OVA'): anime = anime[:-6]
  478. elif anime.endswith('- ONA'): anime = anime[:-6]
  479. elif anime.endswith('- Manga'): anime = anime[:-8]
  480. elif anime.endswith('- Manhua'): anime = anime[:-9]
  481. elif anime.endswith('- Manhwa'): anime = anime[:-9]
  482. elif anime.endswith('- Novel'): anime = anime[:-8]
  483. elif anime.endswith('- One-Shot'): anime = anime[:-11]
  484. elif anime.endswith('- Doujinshi'): anime = anime[:-12]
  485. elif anime.endswith('- Music'): anime = anime[:-8]
  486. elif anime.endswith('- OEL'): anime = anime[:-6]
  487. elif anime.endswith('- Unknown'): anime = anime[:-10]
  488. try:
  489. if data is not None: yield from client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=anime))
  490. except Exception as e:
  491. logger.warning("An error occured while changing the displayed anime title: " + str(e))
  492. cursor.close()
  493. yield from asyncio.sleep(60)
  494. @asyncio.coroutine
  495. def update_thumbnail_catalog(asyncioloop):
  496. logger.info("Starting up update_thumbnail_catalog")
  497. while not client.is_closed():
  498. yield from asyncio.sleep(43200)
  499. logger.info("Automatic check of the thumbnail database on going...")
  500. reload = 0
  501. cursor = conn.cursor(buffered=True)
  502. cursor.execute("SELECT guid, title, thumbnail FROM t_animes")
  503. data = cursor.fetchone()
  504. while data is not None:
  505. try:
  506. if (data[2] != "") : urllib.request.urlopen(data[2])
  507. else: reload = 1
  508. except urllib.error.HTTPError as e:
  509. logger.warning("HTTP Error while getting the current thumbnail of '" + str(data[1]) + "': " + str(e))
  510. reload = 1
  511. except Exception as e:
  512. logger.debug("Error while getting the current thumbnail of '" + str(data[1]) + "': " + str(e))
  513. if (reload == 1) :
  514. try:
  515. image = getThumbnail(data[0])
  516. cursor.execute("UPDATE t_animes SET thumbnail = %s WHERE guid = %s", [image, data[0]])
  517. conn.commit()
  518. logger.info("Updated thumbnail found for \"" + str(data[1]) + "\": %s", image)
  519. except Exception as e:
  520. logger.warning("Error while downloading updated thumbnail for '" + str(data[1]) + "': " + str(e))
  521. yield from asyncio.sleep(3)
  522. data = cursor.fetchone()
  523. cursor.close()
  524. logger.info("Thumbnail database checked.")
  525. # Starting main function
  526. if __name__ == "__main__":
  527. main()
  528. logger.critical("Script halted.")
  529. # We close all the ressources
  530. conn.close()
  531. log_cursor.close()
  532. log_conn.close()
  533. httpclient.close()
  534. loop.stop()
  535. loop.close()