diff --git a/src/bot.py b/src/bot.py index 9d99596..c80c5a3 100644 --- a/src/bot.py +++ b/src/bot.py @@ -3,14 +3,13 @@ from src.config import settings import sqlite3 from src.wiki import Wiki import asyncio, aiohttp +from src.exceptions import * +from src.database import db_cursor logging.config.dictConfig(settings["logging"]) logger = logging.getLogger("rcgcdb.bot") logger.debug("Current settings: {settings}".format(settings=settings)) -conn = sqlite3.connect('rcgcdb.db') -c = conn.cursor() - # Log Fail states with structure wiki_id: number of fail states all_wikis = {} mw_msgs = {} # will have the type of id: tuple @@ -19,21 +18,22 @@ mw_msgs = {} # will have the type of id: tuple # Reasons for this: 1. we require amount of wikis to calculate the cooldown between requests # 2. Easier to code -for wiki in c.execute('SELECT ROWID, * FROM wikis'): +for wiki in db_cursor.execute('SELECT ROWID, * FROM wikis'): all_wikis[wiki[0]] = Wiki() # Start queueing logic async def main_loop(): - for db_wiki in c.execute('SELECT ROWID, * FROM wikis'): + for db_wiki in db_cursor.execute('SELECT ROWID, * FROM wikis'): extended = False if wiki[0] not in all_wikis: logger.debug("New wiki: {}".format(wiki[1])) all_wikis[wiki[0]] = Wiki() local_wiki = all_wikis[wiki[0]] # set a reference to a wiki object from memory - if all_wikis[wiki[0]].mw_messages is None: + if local_wiki.mw_messages is None: extended = True - wiki_response = await local_wiki.fetch_wiki(extended) try: + wiki_response = await local_wiki.fetch_wiki(extended, db_wiki[3], db_wiki[4]) await local_wiki.check_status(wiki[0], wiki_response.status, db_wiki[1]) - except: \ No newline at end of file + except (WikiServerError, WikiError): + continue # ignore this wikis if it throws errors diff --git a/src/database.py b/src/database.py index a4c4d00..14ce3f3 100644 --- a/src/database.py +++ b/src/database.py @@ -1,2 +1,4 @@ import sqlite3 +conn = sqlite3.connect('rcgcdb.db') +db_cursor = conn.cursor() diff --git a/src/discord.py b/src/discord.py index e69de29..1f733fb 100644 --- a/src/discord.py +++ b/src/discord.py @@ -0,0 +1,74 @@ +import json, random, math, logging +from collections import defaultdict + +logger = logging.getLogger("rcgcdb.discord") + +# General functions +class DiscordMessage(): + """A class defining a typical Discord JSON representation of webhook payload.""" + def __init__(self, message_type: str, event_type: str, webhook_url: str, content=None): + self.webhook_object = dict(allowed_mentions={"parse": []}) + self.webhook_url = webhook_url + + if message_type == "embed": + self.__setup_embed() + elif message_type == "compact": + self.webhook_object["content"] = content + + self.event_type = event_type + + def __setitem__(self, key, value): + """Set item is used only in embeds.""" + try: + self.embed[key] = value + except NameError: + raise TypeError("Tried to assign a value when message type is plain message!") + + def __getitem__(self, item): + return self.embed[item] + + def __repr__(self): + """Return the Discord webhook object ready to be sent""" + return json.dumps(self.webhook_object) + + def __setup_embed(self): + self.embed = defaultdict(dict) + if "embeds" not in self.webhook_object: + self.webhook_object["embeds"] = [self.embed] + else: + self.webhook_object["embeds"].append(self.embed) + self.embed["color"] = None + + def add_embed(self): + self.finish_embed() + self.__setup_embed() + + def finish_embed(self): + if self.embed["color"] is None: + self.embed["color"] = random.randrange(1, 16777215) + else: + self.embed["color"] = math.floor(self.embed["color"]) + + def set_author(self, name, url, icon_url=""): + self.embed["author"]["name"] = name + self.embed["author"]["url"] = url + self.embed["author"]["icon_url"] = icon_url + + def add_field(self, name, value, inline=False): + if "fields" not in self.embed: + self.embed["fields"] = [] + self.embed["fields"].append(dict(name=name, value=value, inline=inline)) + + def set_avatar(self, url): + self.webhook_object["avatar_url"] = url + + def set_name(self, name): + self.webhook_object["username"] = name + + +# User facing webhook functions + + +# Monitoring webhook functions +def wiki_removal(wiki_id): + pass \ No newline at end of file diff --git a/src/wiki.py b/src/wiki.py index 4d42e55..026a732 100644 --- a/src/wiki.py +++ b/src/wiki.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from src.session import session import logging, aiohttp from src.exceptions import * +from src.database import db_cursor +import src.discord logger = logging.getLogger("rcgcdb.wiki") @@ -10,8 +12,8 @@ class Wiki: mw_messages: int = None fail_times: int = 0 # corresponding to amount of times connection with wiki failed for client reasons (400-499) - async def fetch_wiki(self, extended, api_path) -> aiohttp.ClientResponse: - url_path = api_path + async def fetch_wiki(self, extended, script_path, api_path) -> aiohttp.ClientResponse: + url_path = script_path + api_path amount = 20 if extended: params = {"action": "query", "format": "json", "uselang": "content", "list": "tags|recentchanges", @@ -29,8 +31,9 @@ class Wiki: "rclimit": amount, "rctype": "edit|new|log|external", "siprop": "namespaces"} try: response = await session.get(url_path, params=params) - except: - raise NotImplemented + except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError): + logger.exception("A connection error occurred while requesting {}".format(url_path)) + raise WikiServerError return response async def check_status(self, wiki_id, status, name): @@ -41,10 +44,11 @@ class Wiki: self.fail_times += 1 logger.warning("Wiki {} responded with HTTP code {}, increased fail_times to {}, skipping...".format(name, status, self.fail_times)) if self.fail_times > 3: - await self.remove(wiki_id) + await self.remove(wiki_id, status) raise WikiError elif 499 < status < 600: logger.warning("Wiki {} responded with HTTP code {}, skipping...".format(name, status, self.fail_times)) raise WikiServerError - async def remove(self, wiki_id): \ No newline at end of file + async def remove(self, wiki_id, reason): + src.discord.wiki_removal() \ No newline at end of file