From ed111974df284dc82986b53a295ee0192503af77 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sat, 4 Apr 2020 14:29:18 +0200 Subject: [PATCH 1/6] Initial commit, discussions work in progress, split off most important functions from rcgcdw.py since we need them for modularization --- configloader.py | 1 + discussions.py | 35 ++++++++++++++ misc.py | 118 +++++++++++++++++++++++++++++++++++------------- rcgcdw.py | 60 +++++------------------- 4 files changed, 133 insertions(+), 81 deletions(-) create mode 100644 discussions.py diff --git a/configloader.py b/configloader.py index 9386b23..51d6395 100644 --- a/configloader.py +++ b/configloader.py @@ -10,3 +10,4 @@ try: # load settings except FileNotFoundError: logging.critical("No config file could be found. Please make sure settings.json is in the directory.") sys.exit(1) + diff --git a/discussions.py b/discussions.py new file mode 100644 index 0000000..dfab679 --- /dev/null +++ b/discussions.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# Recent changes Goat compatible Discord webhook is a project for using a webhook as recent changes page from MediaWiki. +# Copyright (C) 2020 Frisk + +# This program 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. + +# This program 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 this program. If not, see . + +import logging, gettext +from configloader import settings +from misc import datafile, WIKI_SCRIPT_PATH + +# Initialize translation + +t = gettext.translation('discussions', localedir='locale', languages=[settings["lang"]]) +_ = t.gettext + +# Create a custom logger + +discussion_logger = logging.getLogger("rcgcdw.disc") + +fetch_url = "https://services.fandom.com/discussion/{wikiid}/posts?sortDirection=descending&sortKey=creation_date&limit={limit}".format(wikiid=settings["fandom_discussions"]["wiki_id"], limit=settings["fandom_discussions"]["limit"]) + +def fetch_discussions(): + pass \ No newline at end of file diff --git a/misc.py b/misc.py index 6413e2a..8bf1f9f 100644 --- a/misc.py +++ b/misc.py @@ -18,6 +18,9 @@ import json, logging, sys, re from html.parser import HTMLParser +from urllib.parse import urlparse, urlunparse +import requests + from configloader import settings import gettext @@ -30,43 +33,53 @@ _ = t.gettext misc_logger = logging.getLogger("rcgcdw.misc") -data_template = {"rcid": 99999999999, +data_template = {"rcid": 99999999999, "discussion_id": 0, "daily_overview": {"edits": None, "new_files": None, "admin_actions": None, "bytes_changed": None, "new_articles": None, "unique_editors": None, "day_score": None, "days_tracked": 0}} +WIKI_API_PATH: str = "" +WIKI_ARTICLE_PATH: str = "" +WIKI_SCRIPT_PATH: str = "" +WIKI_JUST_DOMAIN: str = "" -def generate_datafile(): - """Generate a data.json file from a template.""" - try: - with open("data.json", 'w') as data: - data.write(json.dumps(data_template, indent=4)) - except PermissionError: - misc_logger.critical("Could not create a data file (no permissions). No way to store last edit.") - sys.exit(1) +class DataFile: + """Data class which instance of is shared by multiple modules to remain consistent and do not cause too many IO operations.""" + def __init__(self): + self.data = self.load_datafile() + + @staticmethod + def generate_datafile(): + """Generate a data.json file from a template.""" + try: + with open("data.json", 'w') as data: + data.write(json.dumps(data_template, indent=4)) + except PermissionError: + misc_logger.critical("Could not create a data file (no permissions). No way to store last edit.") + sys.exit(1) + + def load_datafile(self) -> dict: + """Read a data.json file and return a dictionary with contents + :rtype: dict + """ + try: + with open("data.json") as data: + return json.loads(data.read()) + except FileNotFoundError: + self.generate_datafile() + misc_logger.info("The data file could not be found. Generating a new one...") + return data_template + + def save_datafile(self): + """Overwrites the data.json file with given dictionary""" + try: + with open("data.json", "w") as data_file: + data_file.write(json.dumps(self.data, indent=4)) + except PermissionError: + misc_logger.critical("Could not modify a data file (no permissions). No way to store last edit.") + sys.exit(1) -def load_datafile() -> dict: - """Read a data.json file and return a dictionary with contents - :rtype: dict - """ - try: - with open("data.json") as data: - return json.loads(data.read()) - except FileNotFoundError: - generate_datafile() - misc_logger.info("The data file could not be found. Generating a new one...") - return data_template - - -def save_datafile(data): - """Overwrites the data.json file with given dictionary""" - try: - with open("data.json", "w") as data_file: - data_file.write(json.dumps(data, indent=4)) - except PermissionError: - misc_logger.critical("Could not modify a data file (no permissions). No way to store last edit.") - sys.exit(1) - +datafile = DataFile() def weighted_average(value, weight, new_value): """Calculates weighted average of value number with weight weight and new_value with weight 1""" @@ -191,4 +204,45 @@ def add_to_dict(dictionary, key): dictionary[key] += 1 else: dictionary[key] = 1 - return dictionary \ No newline at end of file + return dictionary + +def prepare_paths(): + global WIKI_API_PATH + global WIKI_ARTICLE_PATH + global WIKI_SCRIPT_PATH + global WIKI_JUST_DOMAIN + """Set the URL paths for article namespace and script namespace + WIKI_API_PATH will be: WIKI_DOMAIN/api.php + WIKI_ARTICLE_PATH will be: WIKI_DOMAIN/articlepath/$1 where $1 is the replaced string + WIKI_SCRIPT_PATH will be: WIKI_DOMAIN/ + WIKI_JUST_DOMAIN will be: WIKI_DOMAIN""" + def quick_try_url(url): + """Quickly test if URL is the proper script path, + False if it appears invalid + dictionary when it appears valid""" + try: + request = requests.get(url, timeout=5) + if request.status_code == requests.codes.ok: + if request.json()["query"]["general"] is not None: + return request + return False + except (KeyError, requests.exceptions.ConnectionError): + return False + try: + parsed_url = urlparse(settings["wiki_url"]) + except KeyError: + misc_logger.critical("wiki_url is not specified in the settings. Please provide the wiki url in the settings and start the script again.") + sys.exit(1) + for url_scheme in (settings["wiki_url"], settings["wiki_url"].split("wiki")[0], urlunparse((*parsed_url[0:2], "", "", "", ""))): # check different combinations, it's supposed to be idiot-proof + tested = quick_try_url(url_scheme + "/api.php?action=query&format=json&meta=siteinfo") + if tested: + WIKI_API_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/api.php" + WIKI_SCRIPT_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/" + WIKI_ARTICLE_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["articlepath"] + WIKI_JUST_DOMAIN = urlunparse((*parsed_url[0:2], "", "", "", "")) + break + else: + misc_logger.critical("Could not verify wikis paths. Please make sure you have given the proper wiki URL in settings.json and your Internet connection is working.") + sys.exit(1) + +prepare_paths() \ No newline at end of file diff --git a/rcgcdw.py b/rcgcdw.py index 3d76e3e..0d024ca 100644 --- a/rcgcdw.py +++ b/rcgcdw.py @@ -26,9 +26,13 @@ from html.parser import HTMLParser import misc from bs4 import BeautifulSoup from collections import defaultdict, Counter -from urllib.parse import quote_plus, urlparse, urlunparse +from urllib.parse import quote_plus from configloader import settings -from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, misc_logger +from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, datafile, \ + WIKI_API_PATH, WIKI_ARTICLE_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN + +if settings["fandom_discussions"]["enabled"]: + pass if __name__ != "__main__": # return if called as a module logging.critical("The file is being executed as a module. Please execute the script using the console.") @@ -53,7 +57,7 @@ except FileNotFoundError: lang.install() ngettext = lang.ngettext -storage = misc.load_datafile() +storage = datafile.data # Remove previous data holding file if exists and limitfetch allows @@ -61,7 +65,7 @@ if settings["limitrefetch"] != -1 and os.path.exists("lastchange.txt") is True: with open("lastchange.txt", 'r') as sfile: logger.info("Converting old lastchange.txt file into new data storage data.json...") storage["rcid"] = int(sfile.read().strip()) - misc.save_datafile(storage) + datafile.save_datafile() os.remove("lastchange.txt") # A few initial vars @@ -69,10 +73,6 @@ if settings["limitrefetch"] != -1 and os.path.exists("lastchange.txt") is True: logged_in = False supported_logs = ["protect/protect", "protect/modify", "protect/unprotect", "upload/overwrite", "upload/upload", "delete/delete", "delete/delete_redir", "delete/restore", "delete/revision", "delete/event", "import/upload", "import/interwiki", "merge/merge", "move/move", "move/move_redir", "protect/move_prot", "block/block", "block/unblock", "block/reblock", "rights/rights", "rights/autopromote", "abusefilter/modify", "abusefilter/create", "interwiki/iw_add", "interwiki/iw_edit", "interwiki/iw_delete", "curseprofile/comment-created", "curseprofile/comment-edited", "curseprofile/comment-deleted", "curseprofile/comment-purged", "curseprofile/profile-edited", "curseprofile/comment-replied", "contentmodel/change", "sprite/sprite", "sprite/sheet", "sprite/slice", "managetags/create", "managetags/delete", "managetags/activate", "managetags/deactivate", "tag/update", "cargo/createtable", "cargo/deletetable", "cargo/recreatetable", "cargo/replacetable", "upload/revert"] profile_fields = {"profile-location": _("Location"), "profile-aboutme": _("About me"), "profile-link-google": _("Google link"), "profile-link-facebook":_("Facebook link"), "profile-link-twitter": _("Twitter link"), "profile-link-reddit": _("Reddit link"), "profile-link-twitch": _("Twitch link"), "profile-link-psn": _("PSN link"), "profile-link-vk": _("VK link"), "profile-link-xbl": _("XBL link"), "profile-link-steam": _("Steam link"), "profile-link-discord": _("Discord handle"), "profile-link-battlenet": _("Battle.net handle")} -WIKI_API_PATH: str = "" -WIKI_ARTICLE_PATH: str = "" -WIKI_SCRIPT_PATH: str = "" -WIKI_JUST_DOMAIN: str = "" class LinkParser(HTMLParser): @@ -112,44 +112,6 @@ LinkParser = LinkParser() class MWError(Exception): pass -def prepare_paths(): - global WIKI_API_PATH - global WIKI_ARTICLE_PATH - global WIKI_SCRIPT_PATH - global WIKI_JUST_DOMAIN - """Set the URL paths for article namespace and script namespace - WIKI_API_PATH will be: WIKI_DOMAIN/api.php - WIKI_ARTICLE_PATH will be: WIKI_DOMAIN/articlepath/$1 where $1 is the replaced string - WIKI_SCRIPT_PATH will be: WIKI_DOMAIN/ - WIKI_JUST_DOMAIN will be: WIKI_DOMAIN""" - def quick_try_url(url): - """Quickly test if URL is the proper script path, - False if it appears invalid - dictionary when it appears valid""" - try: - request = requests.get(url, timeout=5) - if request.status_code == requests.codes.ok: - if request.json()["query"]["general"] is not None: - return request - return False - except (KeyError, requests.exceptions.ConnectionError): - return False - try: - parsed_url = urlparse(settings["wiki_url"]) - except KeyError: - logger.critical("wiki_url is not specified in the settings. Please provide the wiki url in the settings and start the script again.") - sys.exit(1) - for url_scheme in (settings["wiki_url"], settings["wiki_url"].split("wiki")[0], urlunparse((*parsed_url[0:2], "", "", "", ""))): # check different combinations, it's supposed to be idiot-proof - tested = quick_try_url(url_scheme + "/api.php?action=query&format=json&meta=siteinfo") - if tested: - WIKI_API_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/api.php" - WIKI_SCRIPT_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/" - WIKI_ARTICLE_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["articlepath"] - WIKI_JUST_DOMAIN = urlunparse((*parsed_url[0:2], "", "", "", "")) - break - else: - logger.critical("Could not verify wikis paths. Please make sure you have given the proper wiki URL in settings.json and your Internet connection is working.") - sys.exit(1) def create_article_path(article: str) -> str: """Takes the string and creates an URL with it as the article name""" @@ -1069,7 +1031,7 @@ def daily_overview_sync(edits, files, admin, changed_bytes, new_articles, unique storage["daily_overview"].update({"edits": edits_avg, "new_files": files_avg, "admin_actions": admin_avg, "bytes_changed": changed_bytes_avg, "new_articles": new_articles_avg, "unique_editors": unique_contributors_avg, "day_score": day_score_avg}) storage["daily_overview"]["days_tracked"] += 1 - misc.save_datafile(storage) + datafile.save_datafile() return edits, files, admin, changed_bytes, new_articles, unique_contributors, day_score def day_overview(): @@ -1261,7 +1223,7 @@ class Recent_Changes_Class(object): if settings["limitrefetch"] != -1 and self.recent_id != self.file_id and self.recent_id != 0: # if saving to database is disabled, don't save the recent_id self.file_id = self.recent_id storage["rcid"] = self.recent_id - misc.save_datafile(storage) + datafile.save_datafile() logger.debug("Most recent rcid is: {}".format(self.recent_id)) return self.recent_id @@ -1453,7 +1415,6 @@ else: sys.exit(1) # Log in and download wiki information -prepare_paths() try: if settings["wiki_bot_login"] and settings["wiki_bot_password"]: recent_changes.log_in() @@ -1468,6 +1429,7 @@ recent_changes.fetch(amount=settings["limitrefetch"] if settings["limitrefetch"] schedule.every(settings["cooldown"]).seconds.do(recent_changes.fetch) if 1 == 2: # additional translation strings in unreachable code + # noinspection PyUnreachableCode print(_("director"), _("bot"), _("editor"), _("directors"), _("sysop"), _("bureaucrat"), _("reviewer"), _("autoreview"), _("autopatrol"), _("wiki_guardian"), ngettext("second", "seconds", 1), ngettext("minute", "minutes", 1), ngettext("hour", "hours", 1), ngettext("day", "days", 1), ngettext("week", "weeks", 1), ngettext("month", "months",1), ngettext("year", "years", 1), ngettext("millennium", "millennia", 1), ngettext("decade", "decades", 1), ngettext("century", "centuries", 1)) From 854939af6e25814fad528b1600fee2446ea570b3 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 5 Apr 2020 02:07:56 +0200 Subject: [PATCH 2/6] Further work on the discussions --- discussions.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++-- misc.py | 8 +++++- rcgcdw.py | 19 ++++++-------- session.py | 23 +++++++++++++++++ 4 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 session.py diff --git a/discussions.py b/discussions.py index dfab679..eb3209a 100644 --- a/discussions.py +++ b/discussions.py @@ -16,9 +16,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import logging, gettext +import logging, gettext, schedule, requests from configloader import settings from misc import datafile, WIKI_SCRIPT_PATH +from session import session # Initialize translation @@ -29,7 +30,70 @@ _ = t.gettext discussion_logger = logging.getLogger("rcgcdw.disc") +# Create a variable in datafile if it doesn't exist yet (in files <1.10) + +if "discussion_id" not in datafile.data: + datafile.data["discussion_id"] = 0 + datafile.save_datafile() + +storage = datafile.data + fetch_url = "https://services.fandom.com/discussion/{wikiid}/posts?sortDirection=descending&sortKey=creation_date&limit={limit}".format(wikiid=settings["fandom_discussions"]["wiki_id"], limit=settings["fandom_discussions"]["limit"]) + +def embed_formatter(post): + """Embed formatter for Fandom discussions.""" + pass + + +def compact_formatter(post): + """Compact formatter for Fandom discussions.""" + message = None + if post["isReply"]: + pass + else: + pass + + def fetch_discussions(): - pass \ No newline at end of file + request = safe_request(fetch_url) + if request: + try: + request_json = request.json()["_embedded"]["doc:posts"] + request_json.reverse() + except ValueError: + discussion_logger.warning("ValueError in fetching discussions") + return None + except KeyError: + discussion_logger.warning("Wiki returned %s" % (request_json.json())) + return None + else: + if request_json: + for post in request_json: + if post["id"] > storage["discussion_id"]: + formatter(post) + if post["id"] > storage["discussion_id"]: + storage["discussion_id"] = post["id"] + datafile.save_datafile() + +def safe_request(url): + """Function to assure safety of request, and do not crash the script on exceptions,""" + try: + request = session.get(url, timeout=10, allow_redirects=False, header={"Accept": "application/hal+json"}) + except requests.exceptions.Timeout: + discussion_logger.warning("Reached timeout error for request on link {url}".format(url=url)) + return None + except requests.exceptions.ConnectionError: + discussion_logger.warning("Reached connection error for request on link {url}".format(url=url)) + return None + except requests.exceptions.ChunkedEncodingError: + discussion_logger.warning("Detected faulty response from the web server for request on link {url}".format(url=url)) + return None + else: + if 499 < request.status_code < 600: + return None + return request + +formatter = embed_formatter if settings["fandom_discussions"]["appearance"]["mode"] == "embed" else compact_formatter + +schedule.every(settings["fandom_discussions"]["cooldown"]).seconds.do(fetch_discussions) \ No newline at end of file diff --git a/misc.py b/misc.py index 8bf1f9f..abe6c89 100644 --- a/misc.py +++ b/misc.py @@ -245,4 +245,10 @@ def prepare_paths(): misc_logger.critical("Could not verify wikis paths. Please make sure you have given the proper wiki URL in settings.json and your Internet connection is working.") sys.exit(1) -prepare_paths() \ No newline at end of file + +prepare_paths() + + +def create_article_path(article: str) -> str: + """Takes the string and creates an URL with it as the article name""" + return WIKI_ARTICLE_PATH.replace("$1", article) diff --git a/rcgcdw.py b/rcgcdw.py index 0d024ca..6f313aa 100644 --- a/rcgcdw.py +++ b/rcgcdw.py @@ -29,10 +29,11 @@ from collections import defaultdict, Counter from urllib.parse import quote_plus from configloader import settings from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, datafile, \ - WIKI_API_PATH, WIKI_ARTICLE_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN + WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN, create_article_path +from session import session if settings["fandom_discussions"]["enabled"]: - pass + import discussions if __name__ != "__main__": # return if called as a module logging.critical("The file is being executed as a module. Please execute the script using the console.") @@ -113,10 +114,6 @@ class MWError(Exception): pass -def create_article_path(article: str) -> str: - """Takes the string and creates an URL with it as the article name""" - return WIKI_ARTICLE_PATH.replace("$1", article) - def send(message, name, avatar): dictionary_creator = {"content": message} if name: @@ -188,7 +185,7 @@ def pull_comment(comment_id): def compact_formatter(action, change, parsed_comment, categories): if action != "suppressed": - author_url = link_formatter(create_article_path("User:{user}".format( user=change["user"]))) + author_url = link_formatter(create_article_path("User:{user}".format(user=change["user"]))) author = change["user"] parsed_comment = "" if parsed_comment is None else " *("+parsed_comment+")*" parsed_comment = re.sub(r"([^<]|\A)(http(s)://.*?)( |\Z)", "\\1<\\2>\\4", parsed_comment) # see #97 @@ -399,7 +396,8 @@ def compact_formatter(action, change, parsed_comment, categories): link = link_formatter(create_article_path("Special:AbuseFilter/history/{number}/diff/prev/{historyid}".format(number=change["logparams"]['newId'], historyid=change["logparams"]["historyId"]))) content = _("[{author}]({author_url}) edited abuse filter [number {number}]({filter_url})").format(author=author, author_url=author_url, number=change["logparams"]['newId'], filter_url=link) elif action == "abusefilter/create": - link = link_formatter(create_article_path("Special:AbuseFilter/{number}".format(number=change["logparams"]['newId']))) + link = link_formatter( + create_article_path("Special:AbuseFilter/{number}".format(number=change["logparams"]['newId']))) content = _("[{author}]({author_url}) created abuse filter [number {number}]({filter_url})").format(author=author, author_url=author_url, number=change["logparams"]['newId'], filter_url=link) elif action == "merge/merge": link = link_formatter(create_article_path(change["title"])) @@ -808,7 +806,7 @@ def embed_formatter(action, change, parsed_comment, categories): link = create_article_path("Special:AbuseFilter/history/{number}/diff/prev/{historyid}".format(number=change["logparams"]['newId'], historyid=change["logparams"]["historyId"])) embed["title"] = _("Edited abuse filter number {number}").format(number=change["logparams"]['newId']) elif action == "abusefilter/create": - link = create_article_path("Special:AbuseFilter/{number}".format( number=change["logparams"]['newId'])) + link = create_article_path("Special:AbuseFilter/{number}".format(number=change["logparams"]['newId'])) embed["title"] = _("Created abuse filter number {number}").format(number=change["logparams"]['newId']) elif action == "merge/merge": link = create_article_path(change["title"].replace(" ", "_")) @@ -1135,8 +1133,7 @@ class Recent_Changes_Class(object): self.unsent_messages = [] self.mw_messages = {} self.namespaces = None - self.session = requests.Session() - self.session.headers.update(settings["header"]) + self.session = session if settings["limitrefetch"] != -1: self.file_id = storage["rcid"] else: diff --git a/session.py b/session.py new file mode 100644 index 0000000..e8a5381 --- /dev/null +++ b/session.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Recent changes Goat compatible Discord webhook is a project for using a webhook as recent changes page from MediaWiki. +# Copyright (C) 2020 Frisk + +# This program 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. + +# This program 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 this program. If not, see . + +import requests +from configloader import settings + +session = requests.Session() +session.headers.update(settings["header"]) From 3e17faf68e809770ad04c9270cba19faa22a5594 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 5 Apr 2020 18:29:19 +0200 Subject: [PATCH 3/6] Split the message queue to separate class so it can be shared between modules --- discussions.py | 8 ++++++-- misc.py | 25 +++++++++++++++++++++++++ rcgcdw.py | 23 +++++++++++------------ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/discussions.py b/discussions.py index eb3209a..c07e952 100644 --- a/discussions.py +++ b/discussions.py @@ -50,9 +50,13 @@ def compact_formatter(post): """Compact formatter for Fandom discussions.""" message = None if post["isReply"]: - pass + message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) in ${forumName}".format( + author=post["createdBy"]["name"], url=WIKI_SCRIPT_PATH, creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"])) else: - pass + message = _("[${author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}".format( + author=post["createdBy"]["name"], url=WIKI_SCRIPT_PATH, creatorId=post["creatorId"], threadId=post["threadId"], postId=post["id"], title=post["_embedded"]["thread"][0]["title"], forumName=post["forumName"] + )) + {"content": message} def fetch_discussions(): diff --git a/misc.py b/misc.py index abe6c89..1410428 100644 --- a/misc.py +++ b/misc.py @@ -79,6 +79,31 @@ class DataFile: sys.exit(1) +class MessageQueue: + """Message queue class for undelivered messages""" + def __init__(self): + self._queue = [] + + def __repr__(self): + return self._queue + + def __len__(self): + return len(self._queue) + + def __iter__(self): + return self._queue + + def clear(self): + self._queue.clear() + + def add_message(self, message): + self._queue.append(message) + + def cut_messages(self, item_num): + self._queue = self._queue[item_num:] + + +messagequeue = MessageQueue() datafile = DataFile() def weighted_average(value, weight, new_value): diff --git a/rcgcdw.py b/rcgcdw.py index 6f313aa..55863e0 100644 --- a/rcgcdw.py +++ b/rcgcdw.py @@ -29,7 +29,7 @@ from collections import defaultdict, Counter from urllib.parse import quote_plus from configloader import settings from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, datafile, \ - WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN, create_article_path + WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN, create_article_path, messagequeue from session import session if settings["fandom_discussions"]["enabled"]: @@ -152,15 +152,15 @@ def send_to_discord_webhook(data): def send_to_discord(data): - if recent_changes.unsent_messages: - recent_changes.unsent_messages.append(data) + if messagequeue: + messagequeue.add_message(data) else: code = send_to_discord_webhook(data) if code == 3: - recent_changes.unsent_messages.append(data) + messagequeue.add_message(data) elif code == 2: time.sleep(5.0) - recent_changes.unsent_messages.append(data) + messagequeue.add_message(data) elif code < 2: time.sleep(2.0) pass @@ -1130,7 +1130,6 @@ class Recent_Changes_Class(object): self.tags = {} self.groups = {} self.streak = -1 - self.unsent_messages = [] self.mw_messages = {} self.namespaces = None self.session = session @@ -1193,11 +1192,11 @@ class Recent_Changes_Class(object): self.ids.pop(0) def fetch(self, amount=settings["limit"]): - if self.unsent_messages: + if messagequeue: logger.info( "{} messages waiting to be delivered to Discord due to Discord throwing errors/no connection to Discord servers.".format( - len(self.unsent_messages))) - for num, item in enumerate(self.unsent_messages): + len(messagequeue))) + for num, item in enumerate(messagequeue): logger.debug( "Trying to send a message to Discord from the queue with id of {} and content {}".format(str(num), str(item))) @@ -1208,10 +1207,10 @@ class Recent_Changes_Class(object): logger.debug("Sending message failed") break else: - self.unsent_messages = [] + messagequeue.clear() logger.debug("Queue emptied, all messages delivered") - self.unsent_messages = self.unsent_messages[num:] - logger.debug(self.unsent_messages) + messagequeue.cut_messages(num) + logger.debug(messagequeue) last_check = self.fetch_changes(amount=amount) # If the request succeeds the last_check will be the last rcid from recentchanges query if last_check is not None: From f6e7e45a38c9425c801459d4ec4abac47bc222eb Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 5 Apr 2020 23:50:36 +0200 Subject: [PATCH 4/6] Finalizing the discussions module --- discussions.py | 45 ++++++++++++++++++++++++++++++++------------- misc.py | 36 +++++++++++++++++++++++++++++++++++- rcgcdw.py | 38 +++----------------------------------- 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/discussions.py b/discussions.py index c07e952..ca1a636 100644 --- a/discussions.py +++ b/discussions.py @@ -16,9 +16,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import logging, gettext, schedule, requests +import logging, gettext, schedule, requests, json, datetime +from collections import defaultdict from configloader import settings -from misc import datafile, WIKI_SCRIPT_PATH +from misc import datafile, WIKI_SCRIPT_PATH, send_to_discord from session import session # Initialize translation @@ -43,20 +44,38 @@ fetch_url = "https://services.fandom.com/discussion/{wikiid}/posts?sortDirection def embed_formatter(post): """Embed formatter for Fandom discussions.""" - pass - + embed = defaultdict(dict) + data = {"embeds": []} + embed["author"]["name"] = post["createdBy"]["name"] + embed["author"]["icon_url"] = post["createdBy"]["avatarUrl"] + embed["author"]["url"] = "{wikiurl}f/u/{creatorId}".format(wikiurl=WIKI_SCRIPT_PATH, creatorId=post["creatorId"]) + if post["isReply"]: + embed["title"] = _("Replied to {title}").format(title=post["_embedded"]["thread"][0]["title"]) + embed["url"] = "{wikiurl}f/p/{threadId}/r/{postId}".format(wikiurl=WIKI_SCRIPT_PATH, threadId=post["threadId"], postId=post["id"]) + else: + embed["title"] = _("Created {title}").format(title=post["title"]) + embed["url"] = "{wikiurl}f/p/{threadId}".format(wikiurl=WIKI_SCRIPT_PATH, threadId=post["threadId"]) + if settings["fandom_discussions"]["appearance"]["embed"]["show_content"]: + embed["description"] = post["rawContent"] + embed["footer"]["text"] = post["forumName"] + embed["timestamp"] = datetime.datetime.fromtimestamp(post["creationDate"]["epochSecond"]).isoformat()+"Z" + data["embeds"].append(dict(embed)) + data['avatar_url'] = settings["avatars"]["embed"] + data['allowed_mentions'] = {'parse': []} + formatted_embed = json.dumps(data, indent=4) + send_to_discord(formatted_embed) def compact_formatter(post): """Compact formatter for Fandom discussions.""" message = None if post["isReply"]: - message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) in ${forumName}".format( - author=post["createdBy"]["name"], url=WIKI_SCRIPT_PATH, creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"])) + message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) in {forumName}").format( + author=post["createdBy"]["name"], url=WIKI_SCRIPT_PATH, creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"]) else: - message = _("[${author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}".format( + message = _("[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}").format( author=post["createdBy"]["name"], url=WIKI_SCRIPT_PATH, creatorId=post["creatorId"], threadId=post["threadId"], postId=post["id"], title=post["_embedded"]["thread"][0]["title"], forumName=post["forumName"] - )) - {"content": message} + ) + send_to_discord(json.dumps({'content': message, 'allowed_mentions': {'parse': []}})) def fetch_discussions(): @@ -74,16 +93,16 @@ def fetch_discussions(): else: if request_json: for post in request_json: - if post["id"] > storage["discussion_id"]: + if int(post["id"]) > storage["discussion_id"]: formatter(post) - if post["id"] > storage["discussion_id"]: - storage["discussion_id"] = post["id"] + if int(post["id"]) > storage["discussion_id"]: + storage["discussion_id"] = int(post["id"]) datafile.save_datafile() def safe_request(url): """Function to assure safety of request, and do not crash the script on exceptions,""" try: - request = session.get(url, timeout=10, allow_redirects=False, header={"Accept": "application/hal+json"}) + request = session.get(url, timeout=10, allow_redirects=False, headers={"Accept": "application/hal+json"}) except requests.exceptions.Timeout: discussion_logger.warning("Reached timeout error for request on link {url}".format(url=url)) return None diff --git a/misc.py b/misc.py index 1410428..c6aa394 100644 --- a/misc.py +++ b/misc.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import json, logging, sys, re +import json, logging, sys, re, time from html.parser import HTMLParser from urllib.parse import urlparse, urlunparse import requests @@ -277,3 +277,37 @@ prepare_paths() def create_article_path(article: str) -> str: """Takes the string and creates an URL with it as the article name""" return WIKI_ARTICLE_PATH.replace("$1", article) + + +def send_to_discord_webhook(data): + header = settings["header"] + if isinstance(data, str): + header['Content-Type'] = 'application/json' + else: + header['Content-Type'] = 'application/x-www-form-urlencoded' + try: + result = requests.post(settings["webhookURL"], data=data, + headers=header, timeout=10) + except requests.exceptions.Timeout: + misc_logger.warning("Timeouted while sending data to the webhook.") + return 3 + except requests.exceptions.ConnectionError: + misc_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): + if messagequeue: + messagequeue.add_message(data) + else: + code = send_to_discord_webhook(data) + if code == 3: + messagequeue.add_message(data) + elif code == 2: + time.sleep(5.0) + messagequeue.add_message(data) + elif code < 2: + time.sleep(2.0) + pass \ No newline at end of file diff --git a/rcgcdw.py b/rcgcdw.py index 55863e0..6222209 100644 --- a/rcgcdw.py +++ b/rcgcdw.py @@ -28,8 +28,9 @@ from bs4 import BeautifulSoup from collections import defaultdict, Counter from urllib.parse import quote_plus from configloader import settings -from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, datafile, \ - WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN, create_article_path, messagequeue +from misc import link_formatter, ContentParser, safe_read, add_to_dict, datafile, \ + WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN, create_article_path, messagequeue, send_to_discord_webhook, \ + send_to_discord from session import session if settings["fandom_discussions"]["enabled"]: @@ -132,39 +133,6 @@ def profile_field_name(name, embed): else: return _("unknown") -def send_to_discord_webhook(data): - header = settings["header"] - if isinstance(data, str): - header['Content-Type'] = 'application/json' - else: - header['Content-Type'] = 'application/x-www-form-urlencoded' - try: - result = requests.post(settings["webhookURL"], data=data, - headers=header, timeout=10) - 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): - if messagequeue: - messagequeue.add_message(data) - else: - code = send_to_discord_webhook(data) - if code == 3: - messagequeue.add_message(data) - elif code == 2: - time.sleep(5.0) - messagequeue.add_message(data) - elif code < 2: - time.sleep(2.0) - pass - def pull_comment(comment_id): try: From dd4ff911fc9c08a054a7c0b63ec24909504d7439 Mon Sep 17 00:00:00 2001 From: Frisk Date: Mon, 6 Apr 2020 00:15:31 +0200 Subject: [PATCH 5/6] Fixes to discussions feature --- discussions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discussions.py b/discussions.py index ca1a636..cf96b3e 100644 --- a/discussions.py +++ b/discussions.py @@ -56,9 +56,9 @@ def embed_formatter(post): embed["title"] = _("Created {title}").format(title=post["title"]) embed["url"] = "{wikiurl}f/p/{threadId}".format(wikiurl=WIKI_SCRIPT_PATH, threadId=post["threadId"]) if settings["fandom_discussions"]["appearance"]["embed"]["show_content"]: - embed["description"] = post["rawContent"] + embed["description"] = post["rawContent"] if len(post["rawContent"]) < 2000 else post["rawContent"][0:2000] + "…" embed["footer"]["text"] = post["forumName"] - embed["timestamp"] = datetime.datetime.fromtimestamp(post["creationDate"]["epochSecond"]).isoformat()+"Z" + embed["timestamp"] = datetime.datetime.fromtimestamp(post["creationDate"]["epochSecond"], tz=datetime.timezone.utc).isoformat() data["embeds"].append(dict(embed)) data['avatar_url'] = settings["avatars"]["embed"] data['allowed_mentions'] = {'parse': []} From 1ec24419d3d6d4f14c17b056710fc72e3e65d446 Mon Sep 17 00:00:00 2001 From: Frisk Date: Mon, 6 Apr 2020 19:24:56 +0200 Subject: [PATCH 6/6] Discussion formatting + pl translation --- discussions.pot | 42 +++ discussions.py | 8 +- locale/en/LC_MESSAGES/discussions.mo | Bin 0 -> 846 bytes locale/en/LC_MESSAGES/discussions.po | 37 +++ locale/pl/LC_MESSAGES/discussions.mo | Bin 0 -> 1060 bytes locale/pl/LC_MESSAGES/discussions.po | 49 ++++ misc.pot | 4 +- rcgcdw.pot | 394 +++++++++++++-------------- 8 files changed, 332 insertions(+), 202 deletions(-) create mode 100644 discussions.pot create mode 100644 locale/en/LC_MESSAGES/discussions.mo create mode 100644 locale/en/LC_MESSAGES/discussions.po create mode 100644 locale/pl/LC_MESSAGES/discussions.mo create mode 100644 locale/pl/LC_MESSAGES/discussions.po diff --git a/discussions.pot b/discussions.pot new file mode 100644 index 0000000..1a41ab0 --- /dev/null +++ b/discussions.pot @@ -0,0 +1,42 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-04-06 18:55+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: discussions.py:53 +#, python-brace-format +msgid "Replied to \"{title}\"" +msgstr "" + +#: discussions.py:56 +#, python-brace-format +msgid "Created \"{title}\"" +msgstr "" + +#: discussions.py:73 +#, python-brace-format +msgid "" +"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " +"in {forumName}" +msgstr "" + +#: discussions.py:76 +#, python-brace-format +msgid "" +"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" +"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" +msgstr "" diff --git a/discussions.py b/discussions.py index cf96b3e..448c5c6 100644 --- a/discussions.py +++ b/discussions.py @@ -50,10 +50,10 @@ def embed_formatter(post): embed["author"]["icon_url"] = post["createdBy"]["avatarUrl"] embed["author"]["url"] = "{wikiurl}f/u/{creatorId}".format(wikiurl=WIKI_SCRIPT_PATH, creatorId=post["creatorId"]) if post["isReply"]: - embed["title"] = _("Replied to {title}").format(title=post["_embedded"]["thread"][0]["title"]) + embed["title"] = _("Replied to \"{title}\"").format(title=post["_embedded"]["thread"][0]["title"]) embed["url"] = "{wikiurl}f/p/{threadId}/r/{postId}".format(wikiurl=WIKI_SCRIPT_PATH, threadId=post["threadId"], postId=post["id"]) else: - embed["title"] = _("Created {title}").format(title=post["title"]) + embed["title"] = _("Created \"{title}\"").format(title=post["title"]) embed["url"] = "{wikiurl}f/p/{threadId}".format(wikiurl=WIKI_SCRIPT_PATH, threadId=post["threadId"]) if settings["fandom_discussions"]["appearance"]["embed"]["show_content"]: embed["description"] = post["rawContent"] if len(post["rawContent"]) < 2000 else post["rawContent"][0:2000] + "…" @@ -65,10 +65,11 @@ def embed_formatter(post): formatted_embed = json.dumps(data, indent=4) send_to_discord(formatted_embed) + def compact_formatter(post): """Compact formatter for Fandom discussions.""" message = None - if post["isReply"]: + if not post["isReply"]: message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) in {forumName}").format( author=post["createdBy"]["name"], url=WIKI_SCRIPT_PATH, creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"]) else: @@ -117,6 +118,7 @@ def safe_request(url): return None return request + formatter = embed_formatter if settings["fandom_discussions"]["appearance"]["mode"] == "embed" else compact_formatter schedule.every(settings["fandom_discussions"]["cooldown"]).seconds.do(fetch_discussions) \ No newline at end of file diff --git a/locale/en/LC_MESSAGES/discussions.mo b/locale/en/LC_MESSAGES/discussions.mo new file mode 100644 index 0000000000000000000000000000000000000000..319abce320d31f296a889b31d0cedc869dca190a GIT binary patch literal 846 zcmdUt&uZI15XPN<7ae=fVFEoQgpFj?1V@g0Xqy;=otU76fC+SCjioK}uGk%w8sP`% z1N3on%X9QuIedmsM%=)!Z-rYGzTAvBPBb@dVZlCNQl?QI1>A z!mlV}Ytk5%hWFLn*m7|r7r1&$wuGt0B42K%*v|H?Ulqnta}1e(8fANui8bGFMq-$V z2eeKZ9f5a;g|Q^YE>(&6RHjbEhULkvxWn(NHi<8iFoG}$0}*sYa1QN@DD0eaA9y3F z(^94=Vw4NsHy)0!HtFrN!24`8p(YV83r->YL4J`-rF$^Tq;*LB`}kUPH$E%1o1+z1 zdS()((+IlHio8cLK#jJ(kq9OR6Gaf7ec%Zkhu53(=ASpoODl76ZS2BDpo?|u`e6^6 ZW&c>iQ6JhTy}#o}fZBV@RsF}$;13wz4}Smv literal 0 HcmV?d00001 diff --git a/locale/en/LC_MESSAGES/discussions.po b/locale/en/LC_MESSAGES/discussions.po new file mode 100644 index 0000000..dea73aa --- /dev/null +++ b/locale/en/LC_MESSAGES/discussions.po @@ -0,0 +1,37 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-04-05 22:10+0200\n" +"PO-Revision-Date: 2020-04-06 19:24+0200\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.3\n" +"Last-Translator: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: en\n" + +#: discussions.py:53 +#, python-brace-format +msgid "" +"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " +"in ${forumName}" +msgstr "" +"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " +"in ${forumName}" + +#: discussions.py:56 +#, python-brace-format +msgid "" +"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" +"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" +msgstr "" +"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" +"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" diff --git a/locale/pl/LC_MESSAGES/discussions.mo b/locale/pl/LC_MESSAGES/discussions.mo new file mode 100644 index 0000000000000000000000000000000000000000..d0c7c005925e2534b52541d1d84588465d4604eb GIT binary patch literal 1060 zcmb7@PiqrF6u?JY5w<7AlXw^kmV`3hKaC}9vJvbb2{bm8RMem_W+$7G-I--})|hS= ziYN8ngNS|x&))krJoF3r6?~fnl8Vsg!H+lZ&HKmf+uwJW&k)Eh-~q4$+y+9RkWauJ z;4^R!_y)WJegcJWDC}E0U#Z`gx;_#|Gqer}E^yYDDvh4eh~;R3(iC?UOgH*}X;Yb5EE7HH0^=__Nn6QxtfxuSC(oHm zxD+ln;5nqyq?H8RCr{EKAsq?s_5nF$?|BhKcHjkF>{yOPtO~KVuw8W@ZLNcC8T&NR zq@$=v!s4kQbDxSJr2!)yMx!~~#c^n~nyuYgb9=)wwx!TaK#frhO*H$U%{Zi7)NpS= zRl>A))Y&7|SzOVp&lK4eJ?V21xVZY3YvToZ$^=uBA=;ApTw`Zr(`bjOq9NInDoR`| z;<1@{n>84xxbaHD^KASWTNth%+E&AJ@Y>q=SobQ$u~fLNy6xbTld<1w*p)J**pT9s zYX&;fL#d8O=O0V7j4w`qOjqRM^gBB6V>yI4;Nj(%jM;C X;PYSCS;JVupTr{Akm%}tygb=o>k&^$ literal 0 HcmV?d00001 diff --git a/locale/pl/LC_MESSAGES/discussions.po b/locale/pl/LC_MESSAGES/discussions.po new file mode 100644 index 0000000..d220975 --- /dev/null +++ b/locale/pl/LC_MESSAGES/discussions.po @@ -0,0 +1,49 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-04-06 18:55+0200\n" +"PO-Revision-Date: 2020-04-06 18:56+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.3\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 " +"|| n%100>14) ? 1 : 2);\n" + +#: discussions.py:53 +#, python-brace-format +msgid "Replied to \"{title}\"" +msgstr "Odpowiedział(a) w „{title}”" + +#: discussions.py:56 +#, python-brace-format +msgid "Created \"{title}\"" +msgstr "Utworzył(a) „{title}”" + +#: discussions.py:73 +#, python-brace-format +msgid "" +"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " +"in {forumName}" +msgstr "" +"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [{title}](<{url}f/p/{threadId}" +">) w {forumName}" + +#: discussions.py:76 +#, python-brace-format +msgid "" +"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" +"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" +msgstr "" +"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [odpowiedź](<{url}f/p/" +"{threadId}/r/{postId}>) pod tematem [{title}](<{url}f/p/{threadId}>) w " +"{forumName}" diff --git a/misc.pot b/misc.pot index db2e0cb..ac516c8 100644 --- a/misc.pot +++ b/misc.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-17 20:53+0100\n" +"POT-Creation-Date: 2020-04-06 18:47+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: misc.py:82 +#: misc.py:120 msgid "" "\n" "__And more__" diff --git a/rcgcdw.pot b/rcgcdw.pot index a31a3f0..0fb1eea 100644 --- a/rcgcdw.pot +++ b/rcgcdw.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-17 20:53+0100\n" +"POT-Creation-Date: 2020-04-06 18:47+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,262 +18,262 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Location" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "About me" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Google link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Facebook link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Twitter link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Reddit link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Twitch link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "PSN link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "VK link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "XBL link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Steam link" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Discord handle" msgstr "" -#: rcgcdw.py:71 +#: rcgcdw.py:77 msgid "Battle.net handle" msgstr "" -#: rcgcdw.py:172 rcgcdw.py:924 +#: rcgcdw.py:132 rcgcdw.py:856 msgid "Unknown" msgstr "" -#: rcgcdw.py:174 +#: rcgcdw.py:134 msgid "unknown" msgstr "" -#: rcgcdw.py:244 +#: rcgcdw.py:172 #, python-brace-format msgid "" "[{author}]({author_url}) edited [{article}]({edit_link}){comment} ({sign}" "{edit_size})" msgstr "" -#: rcgcdw.py:246 +#: rcgcdw.py:174 #, python-brace-format msgid "" "[{author}]({author_url}) created [{article}]({edit_link}){comment} ({sign}" "{edit_size})" msgstr "" -#: rcgcdw.py:249 +#: rcgcdw.py:177 #, python-brace-format msgid "[{author}]({author_url}) uploaded [{file}]({file_link}){comment}" msgstr "" -#: rcgcdw.py:256 +#: rcgcdw.py:184 #, python-brace-format msgid "" "[{author}]({author_url}) reverted a version of [{file}]({file_link}){comment}" msgstr "" -#: rcgcdw.py:260 +#: rcgcdw.py:188 #, python-brace-format msgid "" "[{author}]({author_url}) uploaded a new version of [{file}]({file_link})" "{comment}" msgstr "" -#: rcgcdw.py:263 +#: rcgcdw.py:191 #, python-brace-format msgid "[{author}]({author_url}) deleted [{page}]({page_link}){comment}" msgstr "" -#: rcgcdw.py:267 +#: rcgcdw.py:195 #, python-brace-format msgid "" "[{author}]({author_url}) deleted redirect by overwriting [{page}]" "({page_link}){comment}" msgstr "" -#: rcgcdw.py:271 rcgcdw.py:276 +#: rcgcdw.py:199 rcgcdw.py:204 msgid "without making a redirect" msgstr "" -#: rcgcdw.py:271 rcgcdw.py:277 +#: rcgcdw.py:199 rcgcdw.py:205 msgid "with a redirect" msgstr "" -#: rcgcdw.py:272 +#: rcgcdw.py:200 #, python-brace-format msgid "" "[{author}]({author_url}) moved {redirect}*{article}* to [{target}]" "({target_url}) {made_a_redirect}{comment}" msgstr "" -#: rcgcdw.py:278 +#: rcgcdw.py:206 #, python-brace-format msgid "" "[{author}]({author_url}) moved {redirect}*{article}* over redirect to " "[{target}]({target_url}) {made_a_redirect}{comment}" msgstr "" -#: rcgcdw.py:283 +#: rcgcdw.py:211 #, python-brace-format msgid "" "[{author}]({author_url}) moved protection settings from {redirect}*{article}" "* to [{target}]({target_url}){comment}" msgstr "" -#: rcgcdw.py:294 rcgcdw.py:699 +#: rcgcdw.py:222 rcgcdw.py:631 msgid "infinity and beyond" msgstr "" -#: rcgcdw.py:311 +#: rcgcdw.py:239 msgid " on pages: " msgstr "" -#: rcgcdw.py:318 rcgcdw.py:719 +#: rcgcdw.py:246 rcgcdw.py:651 msgid " and namespaces: " msgstr "" -#: rcgcdw.py:320 +#: rcgcdw.py:248 msgid " on namespaces: " msgstr "" -#: rcgcdw.py:332 +#: rcgcdw.py:260 #, python-brace-format msgid "" "[{author}]({author_url}) blocked [{user}]({user_url}) for {time}" "{restriction_desc}{comment}" msgstr "" -#: rcgcdw.py:336 +#: rcgcdw.py:264 #, python-brace-format msgid "" "[{author}]({author_url}) changed block settings for [{blocked_user}]" "({user_url}){comment}" msgstr "" -#: rcgcdw.py:340 +#: rcgcdw.py:268 #, python-brace-format msgid "" "[{author}]({author_url}) unblocked [{blocked_user}]({user_url}){comment}" msgstr "" -#: rcgcdw.py:343 +#: rcgcdw.py:271 #, python-brace-format msgid "" "[{author}]({author_url}) left a [comment]({comment}) on {target} profile" msgstr "" -#: rcgcdw.py:343 +#: rcgcdw.py:271 msgid "their own profile" msgstr "" -#: rcgcdw.py:346 +#: rcgcdw.py:274 #, python-brace-format msgid "" "[{author}]({author_url}) replied to a [comment]({comment}) on {target} " "profile" msgstr "" -#: rcgcdw.py:349 rcgcdw.py:355 rcgcdw.py:366 rcgcdw.py:370 +#: rcgcdw.py:277 rcgcdw.py:283 rcgcdw.py:294 rcgcdw.py:298 msgid "their own" msgstr "" -#: rcgcdw.py:352 +#: rcgcdw.py:280 #, python-brace-format msgid "" "[{author}]({author_url}) edited a [comment]({comment}) on {target} profile" msgstr "" -#: rcgcdw.py:358 +#: rcgcdw.py:286 #, python-brace-format msgid "[{author}]({author_url}) purged a comment on {target} profile" msgstr "" -#: rcgcdw.py:368 +#: rcgcdw.py:296 #, python-brace-format msgid "[{author}]({author_url}) deleted a comment on {target} profile" msgstr "" -#: rcgcdw.py:374 +#: rcgcdw.py:302 #, python-brace-format msgid "[{target}]({target_url})'s" msgstr "" -#: rcgcdw.py:374 +#: rcgcdw.py:302 #, python-brace-format msgid "[their own]({target_url})" msgstr "" -#: rcgcdw.py:375 +#: rcgcdw.py:303 #, python-brace-format msgid "" "[{author}]({author_url}) edited the {field} on {target} profile. *({desc})*" msgstr "" -#: rcgcdw.py:389 rcgcdw.py:391 rcgcdw.py:800 rcgcdw.py:802 +#: rcgcdw.py:317 rcgcdw.py:319 rcgcdw.py:732 rcgcdw.py:734 msgid "none" msgstr "" -#: rcgcdw.py:397 rcgcdw.py:787 +#: rcgcdw.py:325 rcgcdw.py:719 msgid "System" msgstr "" -#: rcgcdw.py:402 +#: rcgcdw.py:330 #, python-brace-format msgid "" "[{author}]({author_url}) protected [{article}]({article_url}) with the " "following settings: {settings}{comment}" msgstr "" -#: rcgcdw.py:404 rcgcdw.py:412 rcgcdw.py:810 rcgcdw.py:816 +#: rcgcdw.py:332 rcgcdw.py:340 rcgcdw.py:742 rcgcdw.py:748 msgid " [cascading]" msgstr "" -#: rcgcdw.py:409 +#: rcgcdw.py:337 #, python-brace-format msgid "" "[{author}]({author_url}) modified protection settings of [{article}]" "({article_url}) to: {settings}{comment}" msgstr "" -#: rcgcdw.py:416 +#: rcgcdw.py:344 #, python-brace-format msgid "" "[{author}]({author_url}) removed protection from [{article}]({article_url})" "{comment}" msgstr "" -#: rcgcdw.py:420 +#: rcgcdw.py:348 #, python-brace-format msgid "" "[{author}]({author_url}) changed visibility of revision on page [{article}]" @@ -284,7 +284,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:425 +#: rcgcdw.py:353 #, python-brace-format msgid "" "[{author}]({author_url}) imported [{article}]({article_url}) with {count} " @@ -295,699 +295,699 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:430 +#: rcgcdw.py:358 #, python-brace-format msgid "[{author}]({author_url}) restored [{article}]({article_url}){comment}" msgstr "" -#: rcgcdw.py:432 +#: rcgcdw.py:360 #, python-brace-format msgid "[{author}]({author_url}) changed visibility of log events{comment}" msgstr "" -#: rcgcdw.py:434 +#: rcgcdw.py:362 #, python-brace-format msgid "[{author}]({author_url}) imported interwiki{comment}" msgstr "" -#: rcgcdw.py:437 +#: rcgcdw.py:365 #, python-brace-format msgid "" "[{author}]({author_url}) edited abuse filter [number {number}]({filter_url})" msgstr "" -#: rcgcdw.py:440 +#: rcgcdw.py:369 #, python-brace-format msgid "" "[{author}]({author_url}) created abuse filter [number {number}]({filter_url})" msgstr "" -#: rcgcdw.py:444 +#: rcgcdw.py:373 #, python-brace-format msgid "" "[{author}]({author_url}) merged revision histories of [{article}]" "({article_url}) into [{dest}]({dest_url}){comment}" msgstr "" -#: rcgcdw.py:448 +#: rcgcdw.py:377 #, python-brace-format msgid "" "[{author}]({author_url}) added an entry to the [interwiki table]" "({table_url}) pointing to {website} with {prefix} prefix" msgstr "" -#: rcgcdw.py:454 +#: rcgcdw.py:383 #, python-brace-format msgid "" "[{author}]({author_url}) edited an entry in [interwiki table]({table_url}) " "pointing to {website} with {prefix} prefix" msgstr "" -#: rcgcdw.py:460 +#: rcgcdw.py:389 #, python-brace-format msgid "" "[{author}]({author_url}) deleted an entry in [interwiki table]({table_url})" msgstr "" -#: rcgcdw.py:463 +#: rcgcdw.py:392 #, python-brace-format msgid "" "[{author}]({author_url}) changed the content model of the page [{article}]" "({article_url}) from {old} to {new}{comment}" msgstr "" -#: rcgcdw.py:467 +#: rcgcdw.py:396 #, python-brace-format msgid "" "[{author}]({author_url}) edited the sprite for [{article}]({article_url})" msgstr "" -#: rcgcdw.py:470 +#: rcgcdw.py:399 #, python-brace-format msgid "" "[{author}]({author_url}) created the sprite sheet for [{article}]" "({article_url})" msgstr "" -#: rcgcdw.py:473 +#: rcgcdw.py:402 #, python-brace-format msgid "" "[{author}]({author_url}) edited the slice for [{article}]({article_url})" msgstr "" -#: rcgcdw.py:478 +#: rcgcdw.py:407 #, python-brace-format msgid "[{author}]({author_url}) created the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:480 +#: rcgcdw.py:409 #, python-brace-format msgid "[{author}]({author_url}) deleted the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:485 +#: rcgcdw.py:414 #, python-brace-format msgid "[{author}]({author_url}) recreated the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:490 +#: rcgcdw.py:419 #, python-brace-format msgid "[{author}]({author_url}) replaced the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:493 +#: rcgcdw.py:422 #, python-brace-format msgid "[{author}]({author_url}) created a [tag]({tag_url}) \"{tag}\"" msgstr "" -#: rcgcdw.py:497 +#: rcgcdw.py:426 #, python-brace-format msgid "[{author}]({author_url}) deleted a [tag]({tag_url}) \"{tag}\"" msgstr "" -#: rcgcdw.py:501 +#: rcgcdw.py:430 #, python-brace-format msgid "[{author}]({author_url}) activated a [tag]({tag_url}) \"{tag}\"" msgstr "" -#: rcgcdw.py:504 +#: rcgcdw.py:433 #, python-brace-format msgid "[{author}]({author_url}) deactivated a [tag]({tag_url}) \"{tag}\"" msgstr "" -#: rcgcdw.py:506 +#: rcgcdw.py:435 msgid "An action has been hidden by administration." msgstr "" -#: rcgcdw.py:515 rcgcdw.py:803 +#: rcgcdw.py:444 rcgcdw.py:735 msgid "No description provided" msgstr "" -#: rcgcdw.py:563 +#: rcgcdw.py:492 msgid "(N!) " msgstr "" -#: rcgcdw.py:564 +#: rcgcdw.py:493 msgid "m" msgstr "" -#: rcgcdw.py:564 +#: rcgcdw.py:493 msgid "b" msgstr "" -#: rcgcdw.py:583 rcgcdw.py:588 +#: rcgcdw.py:512 rcgcdw.py:517 msgid "__Only whitespace__" msgstr "" -#: rcgcdw.py:594 +#: rcgcdw.py:523 msgid "Removed" msgstr "" -#: rcgcdw.py:597 +#: rcgcdw.py:526 msgid "Added" msgstr "" -#: rcgcdw.py:631 rcgcdw.py:669 +#: rcgcdw.py:560 rcgcdw.py:600 msgid "Options" msgstr "" -#: rcgcdw.py:631 +#: rcgcdw.py:560 #, python-brace-format msgid "([preview]({link}) | [undo]({undolink}))" msgstr "" -#: rcgcdw.py:634 +#: rcgcdw.py:565 #, python-brace-format msgid "Uploaded a new version of {name}" msgstr "" -#: rcgcdw.py:636 +#: rcgcdw.py:567 #, python-brace-format msgid "Reverted a version of {name}" msgstr "" -#: rcgcdw.py:638 +#: rcgcdw.py:569 #, python-brace-format msgid "Uploaded {name}" msgstr "" -#: rcgcdw.py:654 +#: rcgcdw.py:585 msgid "**No license!**" msgstr "" -#: rcgcdw.py:666 +#: rcgcdw.py:597 msgid "" "\n" "License: {}" msgstr "" -#: rcgcdw.py:669 +#: rcgcdw.py:600 #, python-brace-format msgid "([preview]({link}))" msgstr "" -#: rcgcdw.py:673 +#: rcgcdw.py:605 #, python-brace-format msgid "Deleted page {article}" msgstr "" -#: rcgcdw.py:676 +#: rcgcdw.py:608 #, python-brace-format msgid "Deleted redirect {article} by overwriting" msgstr "" -#: rcgcdw.py:680 +#: rcgcdw.py:612 msgid "No redirect has been made" msgstr "" -#: rcgcdw.py:681 +#: rcgcdw.py:613 msgid "A redirect has been made" msgstr "" -#: rcgcdw.py:682 +#: rcgcdw.py:614 #, python-brace-format msgid "Moved {redirect}{article} to {target}" msgstr "" -#: rcgcdw.py:685 +#: rcgcdw.py:617 #, python-brace-format msgid "Moved {redirect}{article} to {title} over redirect" msgstr "" -#: rcgcdw.py:689 +#: rcgcdw.py:621 #, python-brace-format msgid "Moved protection settings from {redirect}{article} to {title}" msgstr "" -#: rcgcdw.py:712 +#: rcgcdw.py:644 msgid "Blocked from editing the following pages: " msgstr "" -#: rcgcdw.py:721 +#: rcgcdw.py:653 msgid "Blocked from editing pages on following namespaces: " msgstr "" -#: rcgcdw.py:735 +#: rcgcdw.py:667 msgid "Partial block details" msgstr "" -#: rcgcdw.py:736 +#: rcgcdw.py:668 #, python-brace-format msgid "Blocked {blocked_user} for {time}" msgstr "" -#: rcgcdw.py:740 +#: rcgcdw.py:672 #, python-brace-format msgid "Changed block settings for {blocked_user}" msgstr "" -#: rcgcdw.py:744 +#: rcgcdw.py:676 #, python-brace-format msgid "Unblocked {blocked_user}" msgstr "" -#: rcgcdw.py:749 +#: rcgcdw.py:681 #, python-brace-format msgid "Left a comment on {target}'s profile" msgstr "" -#: rcgcdw.py:751 +#: rcgcdw.py:683 msgid "Left a comment on their own profile" msgstr "" -#: rcgcdw.py:756 +#: rcgcdw.py:688 #, python-brace-format msgid "Replied to a comment on {target}'s profile" msgstr "" -#: rcgcdw.py:758 +#: rcgcdw.py:690 msgid "Replied to a comment on their own profile" msgstr "" -#: rcgcdw.py:763 +#: rcgcdw.py:695 #, python-brace-format msgid "Edited a comment on {target}'s profile" msgstr "" -#: rcgcdw.py:765 +#: rcgcdw.py:697 msgid "Edited a comment on their own profile" msgstr "" -#: rcgcdw.py:768 +#: rcgcdw.py:700 #, python-brace-format msgid "Edited {target}'s profile" msgstr "" -#: rcgcdw.py:768 +#: rcgcdw.py:700 msgid "Edited their own profile" msgstr "" -#: rcgcdw.py:770 +#: rcgcdw.py:702 #, python-brace-format msgid "Cleared the {field} field" msgstr "" -#: rcgcdw.py:772 +#: rcgcdw.py:704 #, python-brace-format msgid "{field} field changed to: {desc}" msgstr "" -#: rcgcdw.py:775 +#: rcgcdw.py:707 #, python-brace-format msgid "Purged a comment on {target}'s profile" msgstr "" -#: rcgcdw.py:781 +#: rcgcdw.py:713 #, python-brace-format msgid "Deleted a comment on {target}'s profile" msgstr "" -#: rcgcdw.py:785 +#: rcgcdw.py:717 #, python-brace-format msgid "Changed group membership for {target}" msgstr "" -#: rcgcdw.py:789 +#: rcgcdw.py:721 #, python-brace-format msgid "{target} got autopromoted to a new usergroup" msgstr "" -#: rcgcdw.py:804 +#: rcgcdw.py:736 #, python-brace-format msgid "Groups changed from {old_groups} to {new_groups}{reason}" msgstr "" -#: rcgcdw.py:808 +#: rcgcdw.py:740 #, python-brace-format msgid "Protected {target}" msgstr "" -#: rcgcdw.py:814 +#: rcgcdw.py:746 #, python-brace-format msgid "Changed protection level for {article}" msgstr "" -#: rcgcdw.py:820 +#: rcgcdw.py:752 #, python-brace-format msgid "Removed protection from {article}" msgstr "" -#: rcgcdw.py:824 +#: rcgcdw.py:756 #, python-brace-format msgid "Changed visibility of revision on page {article} " msgid_plural "Changed visibility of {amount} revisions on page {article} " msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:829 +#: rcgcdw.py:761 #, python-brace-format msgid "Imported {article} with {count} revision" msgid_plural "Imported {article} with {count} revisions" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:834 +#: rcgcdw.py:766 #, python-brace-format msgid "Restored {article}" msgstr "" -#: rcgcdw.py:837 +#: rcgcdw.py:769 msgid "Changed visibility of log events" msgstr "" -#: rcgcdw.py:840 +#: rcgcdw.py:772 msgid "Imported interwiki" msgstr "" -#: rcgcdw.py:843 +#: rcgcdw.py:775 #, python-brace-format msgid "Edited abuse filter number {number}" msgstr "" -#: rcgcdw.py:846 +#: rcgcdw.py:778 #, python-brace-format msgid "Created abuse filter number {number}" msgstr "" -#: rcgcdw.py:849 +#: rcgcdw.py:781 #, python-brace-format msgid "Merged revision histories of {article} into {dest}" msgstr "" -#: rcgcdw.py:853 +#: rcgcdw.py:785 msgid "Added an entry to the interwiki table" msgstr "" -#: rcgcdw.py:854 rcgcdw.py:860 +#: rcgcdw.py:786 rcgcdw.py:792 #, python-brace-format msgid "Prefix: {prefix}, website: {website} | {desc}" msgstr "" -#: rcgcdw.py:859 +#: rcgcdw.py:791 msgid "Edited an entry in interwiki table" msgstr "" -#: rcgcdw.py:865 +#: rcgcdw.py:797 msgid "Deleted an entry in interwiki table" msgstr "" -#: rcgcdw.py:866 +#: rcgcdw.py:798 #, python-brace-format msgid "Prefix: {prefix} | {desc}" msgstr "" -#: rcgcdw.py:869 +#: rcgcdw.py:801 #, python-brace-format msgid "Changed the content model of the page {article}" msgstr "" -#: rcgcdw.py:870 +#: rcgcdw.py:802 #, python-brace-format msgid "Model changed from {old} to {new}: {reason}" msgstr "" -#: rcgcdw.py:875 +#: rcgcdw.py:807 #, python-brace-format msgid "Edited the sprite for {article}" msgstr "" -#: rcgcdw.py:878 +#: rcgcdw.py:810 #, python-brace-format msgid "Created the sprite sheet for {article}" msgstr "" -#: rcgcdw.py:881 +#: rcgcdw.py:813 #, python-brace-format msgid "Edited the slice for {article}" msgstr "" -#: rcgcdw.py:887 +#: rcgcdw.py:819 #, python-brace-format msgid "Created the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:891 +#: rcgcdw.py:823 #, python-brace-format msgid "Deleted the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:898 +#: rcgcdw.py:830 #, python-brace-format msgid "Recreated the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:905 +#: rcgcdw.py:837 #, python-brace-format msgid "Replaced the Cargo table \"{table}\"" msgstr "" -#: rcgcdw.py:909 +#: rcgcdw.py:841 #, python-brace-format msgid "Created a tag \"{tag}\"" msgstr "" -#: rcgcdw.py:913 +#: rcgcdw.py:845 #, python-brace-format msgid "Deleted a tag \"{tag}\"" msgstr "" -#: rcgcdw.py:917 +#: rcgcdw.py:849 #, python-brace-format msgid "Activated a tag \"{tag}\"" msgstr "" -#: rcgcdw.py:920 +#: rcgcdw.py:852 #, python-brace-format msgid "Deactivated a tag \"{tag}\"" msgstr "" -#: rcgcdw.py:923 +#: rcgcdw.py:855 msgid "Action has been hidden by administration." msgstr "" -#: rcgcdw.py:951 +#: rcgcdw.py:884 msgid "Tags" msgstr "" -#: rcgcdw.py:956 +#: rcgcdw.py:889 msgid "**Added**: " msgstr "" -#: rcgcdw.py:956 +#: rcgcdw.py:889 msgid " and {} more\n" msgstr "" -#: rcgcdw.py:957 +#: rcgcdw.py:890 msgid "**Removed**: " msgstr "" -#: rcgcdw.py:957 +#: rcgcdw.py:890 msgid " and {} more" msgstr "" -#: rcgcdw.py:958 +#: rcgcdw.py:891 msgid "Changed categories" msgstr "" -#: rcgcdw.py:977 +#: rcgcdw.py:911 msgid "~~hidden~~" msgstr "" -#: rcgcdw.py:983 +#: rcgcdw.py:917 msgid "hidden" msgstr "" -#: rcgcdw.py:1050 rcgcdw.py:1052 rcgcdw.py:1054 rcgcdw.py:1056 rcgcdw.py:1058 -#: rcgcdw.py:1060 rcgcdw.py:1062 +#: rcgcdw.py:984 rcgcdw.py:986 rcgcdw.py:988 rcgcdw.py:990 rcgcdw.py:992 +#: rcgcdw.py:994 rcgcdw.py:996 #, python-brace-format msgid "{value} (avg. {avg})" msgstr "" -#: rcgcdw.py:1086 rcgcdw.py:1114 +#: rcgcdw.py:1020 rcgcdw.py:1048 msgid "Daily overview" msgstr "" -#: rcgcdw.py:1088 +#: rcgcdw.py:1022 msgid "No activity" msgstr "" -#: rcgcdw.py:1123 +#: rcgcdw.py:1057 msgid " ({} action)" msgid_plural " ({} actions)" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1125 +#: rcgcdw.py:1059 msgid " ({} edit)" msgid_plural " ({} edits)" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1130 +#: rcgcdw.py:1064 msgid " UTC ({} action)" msgid_plural " UTC ({} actions)" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1132 rcgcdw.py:1133 rcgcdw.py:1137 +#: rcgcdw.py:1066 rcgcdw.py:1067 rcgcdw.py:1071 msgid "But nobody came" msgstr "" -#: rcgcdw.py:1141 +#: rcgcdw.py:1075 msgid "Most active user" msgid_plural "Most active users" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1142 +#: rcgcdw.py:1076 msgid "Most edited article" msgid_plural "Most edited articles" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1143 +#: rcgcdw.py:1077 msgid "Edits made" msgstr "" -#: rcgcdw.py:1143 +#: rcgcdw.py:1077 msgid "New files" msgstr "" -#: rcgcdw.py:1143 +#: rcgcdw.py:1077 msgid "Admin actions" msgstr "" -#: rcgcdw.py:1144 +#: rcgcdw.py:1078 msgid "Bytes changed" msgstr "" -#: rcgcdw.py:1144 +#: rcgcdw.py:1078 msgid "New articles" msgstr "" -#: rcgcdw.py:1145 +#: rcgcdw.py:1079 msgid "Unique contributors" msgstr "" -#: rcgcdw.py:1146 +#: rcgcdw.py:1080 msgid "Most active hour" msgid_plural "Most active hours" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1147 +#: rcgcdw.py:1081 msgid "Day score" msgstr "" -#: rcgcdw.py:1291 +#: rcgcdw.py:1223 #, python-brace-format msgid "Connection to {wiki} seems to be stable now." msgstr "" -#: rcgcdw.py:1292 rcgcdw.py:1407 +#: rcgcdw.py:1224 rcgcdw.py:1339 msgid "Connection status" msgstr "" -#: rcgcdw.py:1406 +#: rcgcdw.py:1338 #, python-brace-format msgid "{wiki} seems to be down or unreachable." msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "director" msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "bot" msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "editor" msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "directors" msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "sysop" msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "bureaucrat" msgstr "" -#: rcgcdw.py:1465 +#: rcgcdw.py:1397 msgid "reviewer" msgstr "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "autoreview" msgstr "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "autopatrol" msgstr "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "wiki_guardian" msgstr "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "second" msgid_plural "seconds" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "minute" msgid_plural "minutes" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "hour" msgid_plural "hours" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "day" msgid_plural "days" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "week" msgid_plural "weeks" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "month" msgid_plural "months" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "year" msgid_plural "years" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "millennium" msgid_plural "millennia" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "decade" msgid_plural "decades" msgstr[0] "" msgstr[1] "" -#: rcgcdw.py:1466 +#: rcgcdw.py:1398 msgid "century" msgid_plural "centuries" msgstr[0] ""