diff --git a/scripts/trigger.psql b/scripts/trigger.psql
new file mode 100644
index 0000000..72dd3c3
--- /dev/null
+++ b/scripts/trigger.psql
@@ -0,0 +1,15 @@
+create or replace function public.webhook_notify() returns trigger as
+$BODY$
+begin
+ IF (TG_OP = 'DELETE') THEN
+ perform pg_notify('webhookupdates', concat('REMOVE ', old.wiki));
+ ELSIF (TG_OP = 'INSERT') then
+ perform pg_notify('webhookupdates', concat('ADD ', new.wiki));
+ end if;
+ return new;
+end;
+$BODY$
+ language plpgsql;
+
+CREATE TRIGGER RCGCDB_WEBHOOK_UPDATE BEFORE INSERT OR DELETE ON rcgcdw
+ FOR EACH ROW EXECUTE FUNCTION webhook_notify();
\ No newline at end of file
diff --git a/src/bot.py b/src/bot.py
index ac9a522..d67e975 100644
--- a/src/bot.py
+++ b/src/bot.py
@@ -18,7 +18,7 @@ from src.misc import get_paths, get_domain
from src.msgqueue import messagequeue, send_to_discord
from src.queue_handler import DBHandler
from src.wiki import Wiki, process_cats, process_mwmsgs, essential_info, essential_feeds
-from src.discord import DiscordMessage, generic_msg_sender_exception_logger, stack_message_list
+from src.discord.discord import DiscordMessage, generic_msg_sender_exception_logger, stack_message_list
from src.wiki_ratelimiter import RateLimiter
from src.irc_feed import AioIRCCat
from src.domain_manager import domains
@@ -233,6 +233,7 @@ async def main_loop():
logger.debug("Connection type: {}".format(db.connection_pool))
await populate_wikis()
# START LISTENER CONNECTION
+ # We are here
domains.run_all_domains()
try:
signals = (signal.SIGHUP, signal.SIGTERM, signal.SIGINT)
diff --git a/src/discord/__init__.py b/src/discord/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/discord.py b/src/discord/discord.py
similarity index 98%
rename from src/discord.py
rename to src/discord/discord.py
index db7ed3f..df86a9f 100644
--- a/src/discord.py
+++ b/src/discord/discord.py
@@ -40,7 +40,7 @@ async def wiki_removal(wiki_url, status):
async def webhook_removal_monitor(webhook_url: str, reason: int):
await send_to_discord_webhook_monitoring(DiscordMessage("compact", "webhook/remove", None, content="The webhook {} has been removed due to {}.".format("https://discord.com/api/webhooks/" + webhook_url, reason), wiki=None))
-
+#What about Discord message that would hold all embeds in a list and only combine them when sending the webhook? Saving stacked message would be easier then
class DiscordMessage:
"""A class defining a typical Discord JSON representation of webhook payload."""
def __init__(self, message_type: str, event_type: str, webhook_url: list, wiki, content=None):
diff --git a/src/discord/message.py b/src/discord/message.py
new file mode 100644
index 0000000..8f4fb16
--- /dev/null
+++ b/src/discord/message.py
@@ -0,0 +1,116 @@
+# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw).
+
+# RcGcDw is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# RcGcDw is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with RcGcDw. If not, see .
+
+import json
+import math
+import random
+from collections import defaultdict
+
+from src.configloader import settings
+
+
+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": []}, avatar_url=settings["avatars"].get(message_type, ""))
+ self.webhook_url = webhook_url
+
+ if message_type == "embed":
+ self.__setup_embed()
+ elif message_type == "compact":
+ if settings["event_appearance"].get(event_type, {"emoji": None})["emoji"]:
+ content = settings["event_appearance"][event_type]["emoji"] + " " + content
+ self.webhook_object["content"] = content
+
+ self.message_type = message_type
+ 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.message_type != "embed":
+ return
+ if self.embed["color"] is None:
+ if settings["event_appearance"].get(self.event_type, {"color": None})["color"] is None:
+ self.embed["color"] = random.randrange(1, 16777215)
+ else:
+ self.embed["color"] = settings["event_appearance"][self.event_type]["color"]
+ else:
+ self.embed["color"] = math.floor(self.embed["color"])
+ if not self.embed["author"].get("icon_url", None) and settings["event_appearance"].get(self.event_type, {"icon": None})["icon"]:
+ self.embed["author"]["icon_url"] = settings["event_appearance"][self.event_type]["icon"]
+ if len(self.embed["title"]) > 254:
+ self.embed["title"] = self.embed["title"][0:253] + "…"
+
+ 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
+
+ def set_link(self, link):
+ self.embed["link"] = link
+
+
+class DiscordMessageRaw(DiscordMessage):
+ def __init__(self, content: dict, webhook_url: str):
+ self.webhook_object = content
+ self.webhook_url = webhook_url
+
+class DiscordMessageMetadata:
+ def __init__(self, method, log_id = None, page_id = None, rev_id = None, webhook_url = None, new_data = None):
+ self.method = method
+ self.page_id = page_id
+ self.log_id = log_id
+ self.rev_id = rev_id
+ self.webhook_url = webhook_url
+ self.new_data = new_data
+
+ def dump_ids(self) -> (int, int, int):
+ return self.page_id, self.rev_id, self.log_id
diff --git a/src/discord/queue.py b/src/discord/queue.py
new file mode 100644
index 0000000..771507c
--- /dev/null
+++ b/src/discord/queue.py
@@ -0,0 +1,190 @@
+# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw).
+
+# RcGcDw is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# RcGcDw is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with RcGcDw. If not, see .
+
+import re
+import sys
+import time
+import logging
+from typing import Optional, Union, Tuple
+
+import requests
+
+from src.configloader import settings
+from src.discord.message import DiscordMessage, DiscordMessageMetadata, DiscordMessageRaw
+
+AUTO_SUPPRESSION_ENABLED = settings.get("auto_suppression", {"enabled": False}).get("enabled")
+if AUTO_SUPPRESSION_ENABLED:
+ from src.fileio.database import add_entry as add_message_redaction_entry
+
+rate_limit = 0
+
+logger = logging.getLogger("rcgcdw.discord.queue")
+
+class MessageQueue:
+ """Message queue class for undelivered messages"""
+ def __init__(self):
+ self._queue: list[Tuple[Union[DiscordMessage, DiscordMessageRaw], DiscordMessageMetadata]] = []
+
+ def __repr__(self):
+ return self._queue
+
+ def __len__(self):
+ return len(self._queue)
+
+ def __iter__(self):
+ return iter(self._queue)
+
+ def clear(self):
+ self._queue.clear()
+
+ def add_message(self, message: Tuple[Union[DiscordMessage, DiscordMessageRaw], DiscordMessageMetadata]):
+ self._queue.append(message)
+
+ def cut_messages(self, item_num: int):
+ self._queue = self._queue[item_num:]
+
+ @staticmethod
+ def compare_message_to_dict(metadata: DiscordMessageMetadata, to_match: dict):
+ """Compare DiscordMessageMetadata fields and match them against dictionary"""
+ for name, val in to_match.items():
+ if getattr(metadata, name, None) != val:
+ return False
+ return True
+
+ def delete_all_with_matching_metadata(self, **properties):
+ """Deletes all of the messages that have matching metadata properties (useful for message redaction)"""
+ for index, item in reversed(list(enumerate(self._queue))):
+ if self.compare_message_to_dict(item[1], properties):
+ self._queue.pop(index)
+
+ def resend_msgs(self):
+ if self._queue:
+ logger.info(
+ "{} messages waiting to be delivered to Discord due to Discord throwing errors/no connection to Discord servers.".format(
+ len(self._queue)))
+ for num, item in enumerate(self._queue):
+ logger.debug(
+ "Trying to send a message to Discord from the queue with id of {} and content {}".format(str(num),
+ str(item)))
+ if send_to_discord_webhook(item[0], metadata=item[1]) < 2:
+ logger.debug("Sending message succeeded")
+ else:
+ logger.debug("Sending message failed")
+ break
+ else:
+ self.clear()
+ logger.debug("Queue emptied, all messages delivered")
+ self.cut_messages(num)
+ logger.debug(self._queue)
+
+
+messagequeue = MessageQueue()
+
+
+def handle_discord_http(code, formatted_embed, result):
+ if 300 > code > 199: # message went through
+ return 0
+ elif code == 400: # HTTP BAD REQUEST result.status_code, data, result, header
+ logger.error(
+ "Following message has been rejected by Discord, please submit a bug on our bugtracker adding it:")
+ logger.error(formatted_embed)
+ logger.error(result.text)
+ return 1
+ elif code == 401 or code == 404: # HTTP UNAUTHORIZED AND NOT FOUND
+ if result.request.method == "POST": # Ignore not found for DELETE and PATCH requests since the message could already be removed by admin
+ logger.error("Webhook URL is invalid or no longer in use, please replace it with proper one.")
+ sys.exit(1)
+ else:
+ return 0
+ elif code == 429:
+ logger.error("We are sending too many requests to the Discord, slowing down...")
+ return 2
+ elif 499 < code < 600:
+ logger.error(
+ "Discord have trouble processing the event, and because the HTTP code returned is {} it means we blame them.".format(
+ code))
+ return 3
+ else:
+ logger.error("There was an unexpected HTTP code returned from Discord: {}".format(code))
+ return 1
+
+
+def update_ratelimit(request):
+ """Updates rate limit time"""
+ global rate_limit
+ rate_limit = 0 if int(request.headers.get('x-ratelimit-remaining', "-1")) > 0 else int(request.headers.get(
+ 'x-ratelimit-reset-after', 0))
+ rate_limit += settings.get("discord_message_cooldown", 0)
+
+
+def send_to_discord_webhook(data: Optional[DiscordMessage], metadata: DiscordMessageMetadata):
+ global rate_limit
+ header = settings["header"]
+ header['Content-Type'] = 'application/json'
+ standard_args = dict(headers=header)
+ if metadata.method == "POST":
+ req = requests.Request("POST", data.webhook_url+"?wait=" + ("true" if AUTO_SUPPRESSION_ENABLED else "false"), data=repr(data), **standard_args)
+ elif metadata.method == "DELETE":
+ req = requests.Request("DELETE", metadata.webhook_url, **standard_args)
+ elif metadata.method == "PATCH":
+ req = requests.Request("PATCH", data.webhook_url, data=repr(data), **standard_args)
+ try:
+ time.sleep(rate_limit)
+ rate_limit = 0
+ req = req.prepare()
+ result = requests.Session().send(req, timeout=10)
+ update_ratelimit(result)
+ if AUTO_SUPPRESSION_ENABLED and metadata.method == "POST":
+ if 199 < result.status_code < 300: # check if positive error log
+ try:
+ add_message_redaction_entry(*metadata.dump_ids(), repr(data), result.json().get("id"))
+ except ValueError:
+ logger.error("Couldn't get json of result of sending Discord message.")
+ else:
+ pass
+ except requests.exceptions.Timeout:
+ logger.warning("Timeouted while sending data to the webhook.")
+ return 3
+ except requests.exceptions.ConnectionError:
+ logger.warning("Connection error while sending the data to a webhook")
+ return 3
+ else:
+ return handle_discord_http(result.status_code, data, result)
+
+
+def send_to_discord(data: Optional[DiscordMessage], meta: DiscordMessageMetadata):
+ if data is not None:
+ for regex in settings["disallow_regexes"]:
+ if data.webhook_object.get("content", None):
+ if re.search(re.compile(regex), data.webhook_object["content"]):
+ logger.info("Message {} has been rejected due to matching filter ({}).".format(data.webhook_object["content"], regex))
+ return # discard the message without anything
+ else:
+ for to_check in [data.webhook_object.get("description", ""), data.webhook_object.get("title", ""), *[x["value"] for x in data["fields"]], data.webhook_object.get("author", {"name": ""}).get("name", "")]:
+ if re.search(re.compile(regex), to_check):
+ logger.info("Message \"{}\" has been rejected due to matching filter ({}).".format(
+ to_check, regex))
+ return # discard the message without anything
+ if messagequeue:
+ messagequeue.add_message((data, meta))
+ else:
+ code = send_to_discord_webhook(data, metadata=meta)
+ if code == 3:
+ messagequeue.add_message((data, meta))
+ elif code == 2:
+ time.sleep(5.0)
+ messagequeue.add_message((data, meta))
+ elif code is None or code < 2:
+ pass
\ No newline at end of file
diff --git a/src/discord/redaction.py b/src/discord/redaction.py
new file mode 100644
index 0000000..8c70ae9
--- /dev/null
+++ b/src/discord/redaction.py
@@ -0,0 +1,114 @@
+# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw).
+
+# RcGcDw is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# RcGcDw is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with RcGcDw. If not, see .
+
+import logging
+import json
+from typing import List, Union
+
+from src.configloader import settings
+from src.discord.message import DiscordMessageMetadata, DiscordMessageRaw
+from src.discord.queue import send_to_discord, messagequeue
+from src.fileio.database import db_cursor, db_connection
+from src.i18n import redaction as redaction_translation
+
+logger = logging.getLogger("rcgcdw.discord.redaction") # TODO Figure out why does this logger do not work
+_ = redaction_translation.gettext
+#ngettext = redaction_translation.ngettext
+
+
+def delete_messages(matching_data: dict):
+ """Delete messages that match given data"""
+ sql_conditions = ""
+ for key, value in matching_data.items():
+ sql_conditions += "{} = ? AND".format(key)
+ else:
+ sql_conditions = sql_conditions[0:-4] # remove last AND statement
+ to_delete = db_cursor.execute("SELECT msg_id FROM event WHERE {CON}".format(CON=sql_conditions), list(matching_data.values()))
+ if len(messagequeue) > 0:
+ messagequeue.delete_all_with_matching_metadata(**matching_data)
+ msg_to_remove = []
+ logger.debug("Deleting messages for data: {}".format(matching_data))
+ for message in to_delete:
+ webhook_url = "{main_webhook}/messages/{message_id}".format(main_webhook=settings["webhookURL"], message_id=message[0])
+ msg_to_remove.append(message[0])
+ logger.debug("Removing following message: {}".format(message[0]))
+ send_to_discord(None, DiscordMessageMetadata("DELETE", webhook_url=webhook_url))
+ for msg in msg_to_remove:
+ db_cursor.execute("DELETE FROM messages WHERE message_id = ?", (msg,))
+ db_connection.commit()
+
+
+def redact_messages(ids, entry_type: int, to_censor: dict): # : Union[List[Union[str, int]], set[Union[int, str]]]
+ """Redact past Discord messages
+
+ ids: list of ints
+ entry_type: int - 0 for revdel, 1 for logdel
+ to_censor: dict - logparams of message parts to censor"""
+ for event_id in ids:
+ if entry_type == 0:
+ message = db_cursor.execute("SELECT content, message_id FROM messages INNER JOIN event ON event.msg_id = messages.message_id WHERE event.revid = ?;", (event_id, ))
+ else:
+ message = db_cursor.execute(
+ "SELECT content, message_id FROM messages INNER JOIN event ON event.msg_id = messages.message_id WHERE event.logid = ?;",
+ (event_id,))
+ if settings["appearance"]["mode"] == "embed":
+ if message is not None:
+ row = message.fetchone()
+ try:
+ message = json.loads(row[0])
+ new_embed = message["embeds"][0]
+ except ValueError:
+ logger.error("Couldn't loads JSON for message data. What happened? Data: {}".format(row[0]))
+ return
+ except TypeError:
+ logger.error("Couldn't find entry in the database for RevDel to censor information. This is probably because the script has been recently restarted or cache cleared.")
+ return
+ if "user" in to_censor and "url" in new_embed["author"]:
+ new_embed["author"]["name"] = _("hidden")
+ new_embed["author"].pop("url")
+ if "action" in to_censor and "url" in new_embed:
+ new_embed["title"] = _("~~hidden~~")
+ new_embed.pop("url")
+ if "content" in to_censor and "fields" in new_embed:
+ new_embed.pop("fields")
+ if "comment" in to_censor:
+ new_embed["description"] = _("~~hidden~~")
+ message["embeds"][0] = new_embed
+ db_cursor.execute("UPDATE messages SET content = ? WHERE message_id = ?;", (json.dumps(message), row[1],))
+ db_connection.commit()
+ logger.debug(message)
+ send_to_discord(DiscordMessageRaw(message, settings["webhookURL"]+"/messages/"+str(row[1])), DiscordMessageMetadata("PATCH"))
+ else:
+ logger.debug("Could not find message in the database.")
+
+
+def find_middle_next(ids: List[str], pageid: int) -> set:
+ """To address #235 RcGcDw should now remove diffs in next revs relative to redacted revs to protect information in revs that revert revdeleted information.
+
+ :arg ids - list
+ :arg pageid - int
+
+ :return list"""
+ ids = [int(x) for x in ids]
+ result = set()
+ ids.sort() # Just to be sure, sort the list to make sure it's always sorted
+ messages = db_cursor.execute("SELECT revid FROM event WHERE pageid = ? AND revid >= ? ORDER BY revid", (pageid, ids[0],))
+ all_in_page = [x[0] for x in messages.fetchall()]
+ for id in ids:
+ try:
+ result.add(all_in_page[all_in_page.index(id)+1])
+ except (KeyError, ValueError):
+ logger.debug(f"Value {id} not in {all_in_page} or no value after that.")
+ return result - set(ids)
diff --git a/src/domain.py b/src/domain.py
index 3fd7a5e..9575b89 100644
--- a/src/domain.py
+++ b/src/domain.py
@@ -17,7 +17,7 @@ class Domain:
def __init__(self, name: str):
self.name = name # This should be always in format of topname.extension for example fandom.com
self.task: Optional[asyncio.Task] = None
- self.wikis: OrderedDict[str, src.wiki.Wiki] = OrderedDict()
+ self.wikis: OrderedDict[str, src.wiki.Wiki] = OrderedDict() # TODO Check if we can replace with https://docs.python.org/3/library/asyncio-queue.html
self.rate_limiter: src.wiki_ratelimiter = src.wiki_ratelimiter.RateLimiter()
self.irc: Optional[src.irc_feed.AioIRCCat] = None
@@ -56,7 +56,7 @@ class Domain:
:parameter first (optional) - bool indicating if wikis should be added as first or last in the ordered dict"""
wiki.set_domain(self)
if wiki.script_url in self.wikis:
- raise WikiExists("Wiki {} exists in domain {}".format(wiki.script_url, self.name))
+ self.wikis[wiki.script_url].update_targets()
self.wikis[wiki.script_url] = wiki
if first:
self.wikis.move_to_end(wiki.script_url, last=False)
@@ -88,7 +88,7 @@ class Domain:
async def regular_scheduler(self):
while True:
await asyncio.sleep(self.calculate_sleep_time(len(self))) # To make sure that we don't spam domains with one wiki every second we calculate a sane timeout for domains with few wikis
- await self.run_wiki_scan(self.wikis.pop())
+ await self.run_wiki_scan(next(iter(self.wikis.values())))
@cache
def calculate_sleep_time(self, queue_length: int):
diff --git a/src/domain_manager.py b/src/domain_manager.py
index 5048e5d..3bd8112 100644
--- a/src/domain_manager.py
+++ b/src/domain_manager.py
@@ -1,9 +1,10 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from urllib.parse import urlparse, urlunparse
-
+import logging
import asyncpg
+from exceptions import NoDomain
from src.config import settings
from src.domain import Domain
from src.irc_feed import AioIRCCat
@@ -12,6 +13,7 @@ from src.irc_feed import AioIRCCat
if TYPE_CHECKING:
from src.wiki import Wiki
+logger = logging.getLogger("rcgcdb.domain_manager")
class DomainManager:
def __init__(self):
diff --git a/src/exceptions.py b/src/exceptions.py
index 69d83b1..70b2611 100644
--- a/src/exceptions.py
+++ b/src/exceptions.py
@@ -54,3 +54,10 @@ class MediaWikiError(Exception):
self.message = f"MediaWiki returned the following errors: {errors}!"
super().__init__(self.message)
+class NoDomain(Exception):
+ """When given domain does not exist"""
+ pass
+
+class WikiExists(Exception):
+ """When given wiki already exists"""
+ pass
\ No newline at end of file
diff --git a/src/wiki.py b/src/wiki.py
index 4b6faa4..b3ed5aa 100644
--- a/src/wiki.py
+++ b/src/wiki.py
@@ -21,7 +21,6 @@ from src.misc import parse_link
from src.i18n import langs
from src.wiki_ratelimiter import RateLimiter
from statistics import Statistics, Log, LogType
-import src.discord
import asyncio
from src.config import settings
# noinspection PyPackageRequirements
@@ -266,21 +265,22 @@ def prepare_settings(display_mode: int) -> dict:
return template
-async def rc_processor(wiki: Wiki, change: dict, changed_categories: dict, display_options: namedtuple("Settings", ["lang", "display"]), webhooks: list) -> tuple[src.discord.DiscordMessage, src.discord.DiscordMessageMetadata]:
+async def rc_processor(wiki: Wiki, change: dict, changed_categories: dict, display_options: namedtuple("Settings", ["lang", "display"]), webhooks: list) -> tuple[
+ discord.discord.DiscordMessage, discord.discord.DiscordMessageMetadata]:
from src.misc import LinkParser
LinkParser = LinkParser()
- metadata = src.discord.DiscordMessageMetadata("POST", rev_id=change.get("revid", None), log_id=change.get("logid", None),
- page_id=change.get("pageid", None))
+ metadata = discord.discord.DiscordMessageMetadata("POST", rev_id=change.get("revid", None), log_id=change.get("logid", None),
+ page_id=change.get("pageid", None))
context = Context("embed" if display_options.display > 0 else "compact", "recentchanges", webhook, wiki.client, langs[display_options.lang]["rc_formatters"], prepare_settings(display_options.display))
if ("actionhidden" in change or "suppressed" in change) and "suppressed" not in settings["ignored"]: # if event is hidden using suppression
context.event = "suppressed"
try:
- discord_message: Optional[src.discord.DiscordMessage] = default_message("suppressed", display_options.display, formatter_hooks)(context, change)
+ discord_message: Optional[discord.discord.DiscordMessage] = default_message("suppressed", display_options.display, formatter_hooks)(context, change)
except NoFormatter:
return
except:
if settings.get("error_tolerance", 1) > 0:
- discord_message: Optional[src.discord.DiscordMessage] = None # It's handled by send_to_discord, we still want other code to run
+ discord_message: Optional[discord.discord.DiscordMessage] = None # It's handled by send_to_discord, we still want other code to run
else:
raise
else:
@@ -312,12 +312,12 @@ async def rc_processor(wiki: Wiki, change: dict, changed_categories: dict, displ
return
context.event = identification_string
try:
- discord_message: Optional[src.discord.DiscordMessage] = default_message(identification_string, formatter_hooks)(context,
- change)
+ discord_message: Optional[discord.discord.DiscordMessage] = default_message(identification_string, formatter_hooks)(context,
+ change)
except:
if settings.get("error_tolerance", 1) > 0:
discord_message: Optional[
- src.discord.DiscordMessage] = None # It's handled by send_to_discord, we still want other code to run
+ discord.discord.DiscordMessage] = None # It's handled by send_to_discord, we still want other code to run
else:
raise
if identification_string in ("delete/delete", "delete/delete_redir"): # TODO Move it into a hook?
@@ -429,8 +429,8 @@ class Wiki_old:
@staticmethod
async def remove(wiki_url, reason):
logger.info("Removing a wiki {}".format(wiki_url))
- await src.discord.wiki_removal(wiki_url, reason)
- await src.discord.wiki_removal_monitor(wiki_url, reason)
+ await discord.discord.wiki_removal(wiki_url, reason)
+ await discord.discord.wiki_removal_monitor(wiki_url, reason)
async with db.pool().acquire() as connection:
result = await connection.execute('DELETE FROM rcgcdw WHERE wiki = $1', wiki_url)
logger.warning('{} rows affected by DELETE FROM rcgcdw WHERE wiki = "{}"'.format(result, wiki_url))
@@ -512,7 +512,7 @@ async def process_mwmsgs(wiki_response: dict, local_wiki: Wiki, mw_msgs: dict):
# db_wiki: webhook, wiki, lang, display, rcid, postid
async def essential_info(change: dict, changed_categories, local_wiki: Wiki, target: tuple, paths: tuple, request: dict,
- rate_limiter: RateLimiter) -> src.discord.DiscordMessage:
+ rate_limiter: RateLimiter) -> discord.discord.DiscordMessage:
"""Prepares essential information for both embed and compact message format."""
_ = langs[target[0][0]]["wiki"].gettext
changed_categories = changed_categories.get(change["revid"], None)
@@ -546,7 +546,7 @@ async def essential_info(change: dict, changed_categories, local_wiki: Wiki, tar
return await appearance_mode(identification_string, change, parsed_comment, changed_categories, local_wiki, target, paths, rate_limiter, additional_data=additional_data)
-async def essential_feeds(change: dict, comment_pages: dict, db_wiki, target: tuple) -> src.discord.DiscordMessage:
+async def essential_feeds(change: dict, comment_pages: dict, db_wiki, target: tuple) -> discord.discord.DiscordMessage:
"""Prepares essential information for both embed and compact message format."""
appearance_mode = feeds_embed_formatter if target[0][1] > 0 else feeds_compact_formatter
identification_string = change["_embedded"]["thread"][0]["containerType"]