Merge branch 'testing' into 'master'

1.11

Closes #114, #117, #124, #123, #122, #120, #116, and #127

See merge request piotrex43/RcGcDw!67
This commit is contained in:
Frisk 2020-07-04 22:40:37 +00:00
commit 0c24cb6383
22 changed files with 1012 additions and 374 deletions

6
.gitignore vendored
View file

@ -3,3 +3,9 @@ pygettext.py
lastchange.txt lastchange.txt
.directory .directory
/debug /debug
/index.lokalize
/data.json
/main.lqa
/venv/
/lokalize-scripts/
/venvv/

View file

@ -3,7 +3,7 @@
**Screenshots** of the script in action can be found [on the wiki](https://gitlab.com/piotrex43/RcGcDw/wikis/Presentation). **Screenshots** of the script in action can be found [on the wiki](https://gitlab.com/piotrex43/RcGcDw/wikis/Presentation).
### Features ### ### Features ###
* Fetch recent changes from MediaWiki wiki and send them to Discord channel using a webhook * Fetch recent changes from MediaWiki wiki or/and Discussions from Fandom wikis and send them to Discord channel using a webhook
* Two appearance modes - embed and compact * Two appearance modes - embed and compact
* Send daily overviews, that show general information about wiki activity * Send daily overviews, that show general information about wiki activity
* Supports multiple languages (included EN, PL, BR, RU, FR, UK) * Supports multiple languages (included EN, PL, BR, RU, FR, UK)

View file

@ -154,8 +154,8 @@ def set_wiki():
def set_lang(): def set_lang():
option = default_or_custom(input( option = default_or_custom(input(
"Please provide a language code for translation of the script. Translations available: en, de, ru, pt-br, fr, pl. (default en)\n"), "en") "Please provide a language code for translation of the script. Translations available: en, de, ru, pt-br, fr, pl, uk. (default en)\n"), "en")
if option in ["en", "de", "ru", "pt-br", "fr", "pl"]: if option in ["en", "de", "ru", "pt-br", "fr", "pl", "uk"]:
settings["lang"] = option settings["lang"] = option
return True return True
return False return False
@ -163,7 +163,7 @@ def set_lang():
def set_webhook(): def set_webhook():
option = input( option = input(
"Webhook URL is required. You can get it on Discord by following instructions on this page: https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks\n") "Webhook URL is required. You can get it on Discord by following instructions on this page: https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks\n")
if option.startswith("https://discordapp.com/api/webhooks/"): if option.startswith("https://discord.com/api/webhooks/") or option.startswith("https://discordapp.com/api/webhooks/"):
test_webhook = requests.get(option) test_webhook = requests.get(option)
if test_webhook.status_code != 200: if test_webhook.status_code != 200:
print("The webhook URL does not seem right. Reason: {}".format(test_webhook.json()["message"])) print("The webhook URL does not seem right. Reason: {}".format(test_webhook.json()["message"]))
@ -172,7 +172,7 @@ def set_webhook():
settings["webhookURL"] = option settings["webhookURL"] = option
return True return True
else: else:
print("The webhook URL should start with https://discordapp.com/api/webhooks/, are you sure it's the right URL?") print("The webhook URL should start with https://discord.com/api/webhooks/, are you sure it's the right URL?")
return False return False
def set_wikiname(): def set_wikiname():

View file

@ -6,7 +6,7 @@ try: # load settings
if settings["limitrefetch"] < settings["limit"] and settings["limitrefetch"] != -1: if settings["limitrefetch"] < settings["limit"] and settings["limitrefetch"] != -1:
settings["limitrefetch"] = settings["limit"] settings["limitrefetch"] = settings["limit"]
if "user-agent" in settings["header"]: if "user-agent" in settings["header"]:
settings["header"]["user-agent"] = settings["header"]["user-agent"].format(version="1.10") # set the version in the useragent settings["header"]["user-agent"] = settings["header"]["user-agent"].format(version="1.11") # set the version in the useragent
except FileNotFoundError: except FileNotFoundError:
logging.critical("No config file could be found. Please make sure settings.json is in the directory.") logging.critical("No config file could be found. Please make sure settings.json is in the directory.")
sys.exit(1) sys.exit(1)

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 18:55+0200\n" "POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,26 +17,76 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: discussions.py:53 #: discussions.py:56
#, python-brace-format #, python-brace-format
msgid "Replied to \"{title}\"" msgid "Replied to \"{title}\""
msgstr "" msgstr ""
#: discussions.py:56 #: discussions.py:63 discussions.py:79 discussions.py:127 discussions.py:143
msgid "unknown"
msgstr ""
#: discussions.py:68
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr ""
#: discussions.py:72
#, python-brace-format #, python-brace-format
msgid "Created \"{title}\"" msgid "Created \"{title}\""
msgstr "" msgstr ""
#: discussions.py:73 #: discussions.py:86
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr ""
#: discussions.py:99
#, python-brace-format
msgid "Created a poll titled \"{title}\""
msgstr ""
#: discussions.py:104
msgid "Option {}"
msgstr ""
#: discussions.py:105
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr ""
#: discussions.py:121
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
"in {forumName}" "in {forumName}"
msgstr "" msgstr ""
#: discussions.py:76 #: discussions.py:130
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) on {user}'s Message Wall"
msgstr ""
#: discussions.py:136
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" "[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" "{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}"
msgstr "" msgstr ""
#: discussions.py:147
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) replied to [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) on {user}'s Message "
"Wall"
msgstr ""
#: discussions.py:153
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""

View file

@ -19,7 +19,8 @@
import logging, gettext, schedule, requests, json, datetime import logging, gettext, schedule, requests, json, datetime
from collections import defaultdict from collections import defaultdict
from configloader import settings from configloader import settings
from misc import datafile, send_to_discord from urllib.parse import quote_plus
from misc import datafile, send_to_discord, DiscordMessage, WIKI_SCRIPT_PATH, escape_formatting, messagequeue
from session import session from session import session
# Initialize translation # Initialize translation
@ -42,44 +43,121 @@ 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"]) 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): def embed_formatter(post, post_type):
"""Embed formatter for Fandom discussions.""" """Embed formatter for Fandom discussions."""
embed = defaultdict(dict) embed = DiscordMessage("embed", "discussion", settings["fandom_discussions"]["webhookURL"])
data = {"embeds": []} embed.set_author(post["createdBy"]["name"], "{wikiurl}f/u/{creatorId}".format(
embed["author"]["name"] = post["createdBy"]["name"] wikiurl=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"]), icon_url=post["createdBy"]["avatarUrl"])
embed["author"]["icon_url"] = post["createdBy"]["avatarUrl"] discussion_post_type = post["_embedded"]["thread"][0].get("containerType", "FORUM") # Can be FORUM, ARTICLE_COMMENT or WALL on UCP
embed["author"]["url"] = "{wikiurl}f/u/{creatorId}".format(wikiurl=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"]) if post_type == "TEXT":
if post["isReply"]: if post["isReply"]:
embed["title"] = _("Replied to \"{title}\"").format(title=post["_embedded"]["thread"][0]["title"]) if discussion_post_type == "FORUM":
embed["url"] = "{wikiurl}f/p/{threadId}/r/{postId}".format(wikiurl=settings["fandom_discussions"]["wiki_url"], threadId=post["threadId"], postId=post["id"]) embed.event_type = "discussion/forum/reply"
else: embed["title"] = _("Replied to \"{title}\"").format(title=post["_embedded"]["thread"][0]["title"])
embed["title"] = _("Created \"{title}\"").format(title=post["title"]) embed["url"] = "{wikiurl}f/p/{threadId}/r/{postId}".format(
embed["url"] = "{wikiurl}f/p/{threadId}".format(wikiurl=settings["fandom_discussions"]["wiki_url"], threadId=post["threadId"]) wikiurl=settings["fandom_discussions"]["wiki_url"], threadId=post["threadId"], postId=post["id"])
if settings["fandom_discussions"]["appearance"]["embed"]["show_content"]: elif discussion_post_type == "ARTICLE_COMMENT":
embed["description"] = post["rawContent"] if len(post["rawContent"]) < 2000 else post["rawContent"][0:2000] + "" discussion_logger.warning("Article comments are not yet implemented. For reasons see https://gitlab.com/piotrex43/RcGcDw/-/issues/126#note_366480037")
return
elif discussion_post_type == "WALL":
user_wall = _("unknown") # Fail safe
embed.event_type = "discussion/wall/reply"
if post["forumName"].endswith(' Message Wall'):
user_wall = post["forumName"][:-13]
embed["url"] = "{wikiurl}wiki/Message_Wall:{user_wall}?threadId={threadid}#{replyId}".format(wikiurl=settings["fandom_discussions"]["wiki_url"], user_wall=quote_plus(user_wall.replace(" ", "_")), threadid=post["threadId"], replyId=post["id"])
embed["title"] = _("Replied to \"{title}\" on {user}'s Message Wall").format(title=post["_embedded"]["thread"][0]["title"], user=user_wall)
else:
if discussion_post_type == "FORUM":
embed.event_type = "discussion/forum/post"
embed["title"] = _("Created \"{title}\"").format(title=post["title"])
embed["url"] = "{wikiurl}f/p/{threadId}".format(wikiurl=settings["fandom_discussions"]["wiki_url"],
threadId=post["threadId"])
elif discussion_post_type == "ARTICLE_COMMENT":
discussion_logger.warning("Article comments are not yet implemented. For reasons see https://gitlab.com/piotrex43/RcGcDw/-/issues/126#note_366480037")
return
elif discussion_post_type == "WALL":
user_wall = _("unknown") # Fail safe
embed.event_type = "discussion/wall/post"
if post["forumName"].endswith(' Message Wall'):
user_wall = post["forumName"][:-13]
embed["url"] = "{wikiurl}wiki/Message_Wall:{user_wall}?threadId={threadid}".format(
wikiurl=settings["fandom_discussions"]["wiki_url"], user_wall=quote_plus(user_wall.replace(" ", "_")),
threadid=post["threadId"])
embed["title"] = _("Created \"{title}\" on {user}'s Message Wall").format(title=post["_embedded"]["thread"][0]["title"], user=user_wall)
if settings["fandom_discussions"]["appearance"]["embed"]["show_content"]:
if post.get("jsonModel") is not None:
npost = DiscussionsFromHellParser(post)
embed["description"] = npost.parse()
if npost.image_last:
embed["image"]["url"] = npost.image_last
embed["description"] = embed["description"].replace(npost.image_last, "")
else: # Fallback when model is not available
embed["description"] = post.get("rawContent", "")
elif post_type == "POLL":
embed.event_type = "discussion/forum/poll"
poll = post["poll"]
embed["title"] = _("Created a poll titled \"{title}\"").format(title=poll["question"])
image_type = False
if poll["answers"][0]["image"] is not None:
image_type = True
for num, option in enumerate(poll["answers"]):
embed.add_field(option["text"] if image_type is True else _("Option {}").format(num+1),
option["text"] if image_type is False else _("__[View image]({image_url})__").format(image_url=option["image"]["url"]),
inline=True)
embed["footer"]["text"] = post["forumName"] embed["footer"]["text"] = post["forumName"]
embed["timestamp"] = datetime.datetime.fromtimestamp(post["creationDate"]["epochSecond"], tz=datetime.timezone.utc).isoformat() embed["timestamp"] = datetime.datetime.fromtimestamp(post["creationDate"]["epochSecond"], tz=datetime.timezone.utc).isoformat()
data["embeds"].append(dict(embed)) embed.finish_embed()
data['avatar_url'] = settings["avatars"]["embed"] send_to_discord(embed)
data['allowed_mentions'] = {'parse': []}
formatted_embed = json.dumps(data, indent=4)
send_to_discord(formatted_embed)
def compact_formatter(post): def compact_formatter(post, post_type):
"""Compact formatter for Fandom discussions.""" """Compact formatter for Fandom discussions."""
message = None message = None
if not post["isReply"]: discussion_post_type = post["_embedded"]["thread"][0].get("containerType",
message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) in {forumName}").format( "FORUM") # Can be FORUM, ARTICLE_COMMENT or WALL on UCP
author=post["createdBy"]["name"], url=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"]) if post_type == "TEXT":
else: if not post["isReply"]:
message = _("[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}").format( if discussion_post_type == "FORUM":
author=post["createdBy"]["name"], url=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"], threadId=post["threadId"], postId=post["id"], title=post["_embedded"]["thread"][0]["title"], forumName=post["forumName"] message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) in {forumName}").format(
) author=post["createdBy"]["name"], url=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"])
send_to_discord(json.dumps({'content': message, 'allowed_mentions': {'parse': []}})) elif discussion_post_type == "ARTICLE_COMMENT":
discussion_logger.warning("Article comments are not yet implemented. For reasons see https://gitlab.com/piotrex43/RcGcDw/-/issues/126#note_366480037")
return
elif discussion_post_type == "WALL":
user_wall = _("unknown") # Fail safe
if post["forumName"].endswith(' Message Wall'):
user_wall = post["forumName"][:-13]
message = _("[{author}](<{url}f/u/{creatorId}>) created [{title}](<{wikiurl}wiki/Message_Wall:{user_wall}?threadId={threadid}>) on {user}'s Message Wall").format(
author=post["createdBy"]["name"], url=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"], title=post["_embedded"]["thread"][0]["title"], user=user_wall,
wikiurl=settings["fandom_discussions"]["wiki_url"], user_wall=quote_plus(user_wall.replace(" ", "_")), threadid=post["threadId"]
)
else:
if discussion_post_type == "FORUM":
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=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"], threadId=post["threadId"], postId=post["id"], title=post["_embedded"]["thread"][0]["title"], forumName=post["forumName"]
)
elif discussion_post_type == "ARTICLE_COMMENT":
discussion_logger.warning("Article comments are not yet implemented. For reasons see https://gitlab.com/piotrex43/RcGcDw/-/issues/126#note_366480037")
return
elif discussion_post_type == "WALL":
user_wall = _("unknown") # Fail safe
if post["forumName"].endswith(' Message Wall'):
user_wall = post["forumName"][:-13]
message = _(
"[{author}](<{url}f/u/{creatorId}>) replied to [{title}](<{wikiurl}wiki/Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) on {user}'s Message Wall").format(
author=post["createdBy"]["name"], url=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"], title=post["_embedded"]["thread"][0]["title"], user=user_wall,
wikiurl=settings["fandom_discussions"]["wiki_url"], user_wall=quote_plus(user_wall.replace(" ", "_")), threadid=post["threadId"], replyId=post["id"])
elif post_type == "POLL":
message = _(
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/{threadId}>) in {forumName}").format(
author=post["createdBy"]["name"], url=settings["fandom_discussions"]["wiki_url"],
creatorId=post["creatorId"], title=post["title"], threadId=post["threadId"], forumName=post["forumName"])
send_to_discord(DiscordMessage("compact", "discussion", settings["fandom_discussions"]["webhookURL"], content=message))
def fetch_discussions(): def fetch_discussions():
messagequeue.resend_msgs()
request = safe_request(fetch_url) request = safe_request(fetch_url)
if request: if request:
try: try:
@ -95,11 +173,105 @@ def fetch_discussions():
if request_json: if request_json:
for post in request_json: for post in request_json:
if int(post["id"]) > storage["discussion_id"]: if int(post["id"]) > storage["discussion_id"]:
formatter(post) parse_discussion_post(post)
if int(post["id"]) > storage["discussion_id"]: if int(post["id"]) > storage["discussion_id"]:
storage["discussion_id"] = int(post["id"]) storage["discussion_id"] = int(post["id"])
datafile.save_datafile() datafile.save_datafile()
def parse_discussion_post(post):
"""Initial post recognition & handling"""
post_type = post.get("funnel", "TEXT")
if post_type == "TEXT":
formatter(post, post_type)
elif post_type == "POLL":
formatter(post, post_type)
else:
discussion_logger.warning("The type of {} is an unknown discussion post type. Please post an issue on the project page to have it added https://gitlab.com/piotrex43/RcGcDw/-/issues.")
class DiscussionsFromHellParser:
"""This class converts fairly convoluted Fandom jsonModal of a discussion post into Markdown formatted usable thing. Takes string, returns string.
Kudos to MarkusRost for allowing me to implement this formatter based on his code in Wiki-Bot."""
def __init__(self, post):
self.post = post
self.jsonModal = json.loads(post.get("jsonModel", "{}"))
self.markdown_text = ""
self.item_num = 1
self.image_last = None
def parse(self) -> str:
"""Main parsing logic"""
self.parse_content(self.jsonModal["content"])
if len(self.markdown_text) > 2000:
self.markdown_text = self.markdown_text[0:2000] + ""
return self.markdown_text
def parse_content(self, content, ctype=None):
self.image_last = None
for item in content:
if ctype == "bulletList":
self.markdown_text += "\t"
if ctype == "orderedList":
self.markdown_text += "\t{num}. ".format(num=self.item_num)
self.item_num += 1
if item["type"] == "text":
if "marks" in item:
prefix, suffix = self.convert_marks(item["marks"])
self.markdown_text = "{old}{pre}{text}{suf}".format(old=self.markdown_text, pre=prefix, text=escape_formatting(item["text"]), suf=suffix)
else:
if ctype == "code_block":
self.markdown_text += item["text"] # ignore formatting on preformatted text which cannot have additional formatting anyways
else:
self.markdown_text += escape_formatting(item["text"])
elif item["type"] == "paragraph":
if "content" in item:
self.parse_content(item["content"], item["type"])
self.markdown_text += "\n"
elif item["type"] == "openGraph":
if not item["attrs"]["wasAddedWithInlineLink"]:
self.markdown_text = "{old}{link}\n".format(old=self.markdown_text, link=item["attrs"]["url"])
elif item["type"] == "image":
try:
discussion_logger.debug(item["attrs"]["id"])
if item["attrs"]["id"] is not None:
self.markdown_text = "{old}{img_url}\n".format(old=self.markdown_text, img_url=self.post["_embedded"]["contentImages"][int(item["attrs"]["id"])]["url"])
self.image_last = self.post["_embedded"]["contentImages"][int(item["attrs"]["id"])]["url"]
except (IndexError, ValueError):
discussion_logger.warning("Image {} not found.".format(item["attrs"]["id"]))
discussion_logger.debug(self.markdown_text)
elif item["type"] == "code_block":
self.markdown_text += "```\n"
if "content" in item:
self.parse_content(item["content"], item["type"])
self.markdown_text += "\n```\n"
elif item["type"] == "bulletList":
if "content" in item:
self.parse_content(item["content"], item["type"])
elif item["type"] == "orderedList":
self.item_num = 1
if "content" in item:
self.parse_content(item["content"], item["type"])
elif item["type"] == "listItem":
self.parse_content(item["content"], item["type"])
def convert_marks(self, marks):
prefix = ""
suffix = ""
for mark in marks:
if mark["type"] == "mention":
prefix += "["
suffix = "]({wiki}f/u/{userid}){suffix}".format(wiki=settings["fandom_discussions"]["wiki_url"], userid=mark["attrs"]["userId"], suffix=suffix)
elif mark["type"] == "strong":
prefix += "**"
suffix = "**{suffix}".format(suffix=suffix)
elif mark["type"] == "link":
prefix += "["
suffix = "]({link}){suffix}".format(link=mark["attrs"]["href"], suffix=suffix)
elif mark["type"] == "em":
prefix += "_"
suffix = "_" + suffix
return prefix, suffix
def safe_request(url): def safe_request(url):
"""Function to assure safety of request, and do not crash the script on exceptions,""" """Function to assure safety of request, and do not crash the script on exceptions,"""
try: try:

Binary file not shown.

View file

@ -7,28 +7,56 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 18:55+0200\n" "POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-04-07 11:53+0200\n" "PO-Revision-Date: 2020-07-04 01:04+0200\n"
"Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: de\n"
#: discussions.py:53 #: discussions.py:56
#, python-brace-format #, python-brace-format
msgid "Replied to \"{title}\"" msgid "Replied to \"{title}\""
msgstr "Antwortete auf „{title}“" msgstr "Antwortete auf „{title}“"
#: discussions.py:56 #: discussions.py:63 discussions.py:79 discussions.py:127 discussions.py:143
msgid "unknown"
msgstr "Unbekannt"
#: discussions.py:68
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr "Antwortete auf „{title}“ auf der Nachrichtenseite von {user}"
#: discussions.py:72
#, python-brace-format #, python-brace-format
msgid "Created \"{title}\"" msgid "Created \"{title}\""
msgstr "Erstellte „{title}“" msgstr "Erstellte „{title}“"
#: discussions.py:73 #: discussions.py:86
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr "Erstellte „{title}“ auf der Nachrichtenseite von {user}"
#: discussions.py:99
#, python-brace-format
msgid "Created a poll titled \"{title}\""
msgstr "Erstellte eine Umfrage „{title}“"
#: discussions.py:104
msgid "Option {}"
msgstr "Option {}"
#: discussions.py:105
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr "__[Bild öffnen]({image_url})__"
#: discussions.py:121
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
@ -37,7 +65,17 @@ msgstr ""
"[{author}](<{url}f/u/{creatorId}>) erstellte [{title}](<{url}f/p/{threadId}" "[{author}](<{url}f/u/{creatorId}>) erstellte [{title}](<{url}f/p/{threadId}"
">) in {forumName}" ">) in {forumName}"
#: discussions.py:76 #: discussions.py:130
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) on {user}'s Message Wall"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) erstellte [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) auf der Nachrichtenseite von "
"{user}"
#: discussions.py:136
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" "[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
@ -45,3 +83,23 @@ msgid ""
msgstr "" msgstr ""
"[{author}](<{url}f/u/{creatorId}>) erstellte eine [Antwork](<{url}f/p/" "[{author}](<{url}f/u/{creatorId}>) erstellte eine [Antwork](<{url}f/p/"
"{threadId}/r/{postId}>) zu [{title}](<{url}f/p/{threadId}>) in {forumName}" "{threadId}/r/{postId}>) zu [{title}](<{url}f/p/{threadId}>) in {forumName}"
#: discussions.py:147
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) replied to [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) on {user}'s Message "
"Wall"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) antwortete auf [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) auf der "
"Nachrichtenseite von {user}"
#: discussions.py:153
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) erstellte eine Umfrage [{title}](<{url}f/"
"p/{threadId}>) in {forumName}"

View file

@ -3,9 +3,8 @@ msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-17 20:53+0100\n" "POT-Creation-Date: 2020-03-17 20:53+0100\n"
"PO-Revision-Date: 2020-03-17 21:22+0100\n" "PO-Revision-Date: 2020-04-23 23:25+0200\n"
"Last-Translator: Frisk <piotrex43@protonmail.ch>\n" "Last-Translator: Frisk <piotrex43@protonmail.ch>\n"
"Language-Team: German\n"
"Language: de\n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -13,7 +12,7 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Loco-Source-Locale: de_DE\n" "X-Loco-Source-Locale: de_DE\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Lokalize 19.12.3\n"
"X-Loco-Parser: loco_parse_po\n" "X-Loco-Parser: loco_parse_po\n"
#: rcgcdw.py:71 #: rcgcdw.py:71
@ -241,11 +240,12 @@ msgstr ""
"Profil von {target}" "Profil von {target}"
#: rcgcdw.py:358 #: rcgcdw.py:358
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "[{author}]({author_url}) deleted a comment on {target} profile" #| msgid "[{author}]({author_url}) deleted a comment on {target} profile"
msgid "[{author}]({author_url}) purged a comment on {target} profile" msgid "[{author}]({author_url}) purged a comment on {target} profile"
msgstr "" msgstr ""
"[{author}]({author_url}) löschte ein Kommentar auf dem Profil von {target}" "[{author}]({author_url}) löschte ein Kommentar auf dem Profil von {target}"
" dauerhaft"
#: rcgcdw.py:368 #: rcgcdw.py:368
#, python-brace-format #, python-brace-format
@ -663,7 +663,7 @@ msgstr "{field} geändert zu: {desc}"
#: rcgcdw.py:775 #: rcgcdw.py:775
#, python-brace-format #, python-brace-format
msgid "Purged a comment on {target}'s profile" msgid "Purged a comment on {target}'s profile"
msgstr "Löschte ein Kommentar auf dem Profil von {target}" msgstr "Löschte ein Kommentar auf dem Profil von {target} dauerhaft"
#: rcgcdw.py:781 #: rcgcdw.py:781
#, python-brace-format #, python-brace-format

Binary file not shown.

View file

@ -7,31 +7,84 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-05 22:10+0200\n" "POT-Creation-Date: 2020-06-23 14:54+0200\n"
"PO-Revision-Date: 2020-04-06 19:24+0200\n" "PO-Revision-Date: 2020-06-23 14:57+0200\n"
"Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\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 #: discussions.py:56
#, python-brace-format #, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Replied to \"{title}\""
#: discussions.py:63 discussions.py:79
msgid "unknown"
msgstr "unknown"
#: discussions.py:68
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr "Replied to \"{title}\" on {user}'s Message Wall"
#: discussions.py:72
#, python-brace-format
msgid "Created \"{title}\""
msgstr "Created \"{title}\""
#: discussions.py:86
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr "Created \"{title}\" on {user}'s Message Wall"
#: discussions.py:99
#, python-brace-format
msgid "Created a poll titled \"{title}\""
msgstr "Created a poll titled \"{title}\""
#: discussions.py:104
msgid "Option {}"
msgstr "Option {}"
#: discussions.py:105
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr "__[View image]({image_url})__"
#: discussions.py:118
#, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in ${forumName}"
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:121
#, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" "[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" "{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}"
msgstr "" msgstr ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" "[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}" "{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}"
#: discussions.py:126
#, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in ${forumName}"
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"

Binary file not shown.

View file

@ -7,28 +7,57 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 18:55+0200\n" "POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-04-22 20:59+0200\n" "PO-Revision-Date: 2020-07-04 01:08+0200\n"
"Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n" "X-Generator: Poedit 2.3\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language: fr\n"
#: discussions.py:53 #: discussions.py:56
#, python-brace-format #, python-brace-format
msgid "Replied to \"{title}\"" msgid "Replied to \"{title}\""
msgstr "A répondu à « {title} »" msgstr "A répondu à « {title} »"
#: discussions.py:56 #: discussions.py:63 discussions.py:79 discussions.py:127 discussions.py:143
msgid "unknown"
msgstr "inconnu"
#: discussions.py:68
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr "Réponse à « {title} » sur le mur de {user}"
#: discussions.py:72
#, python-brace-format #, python-brace-format
msgid "Created \"{title}\"" msgid "Created \"{title}\""
msgstr "Création de {title}" msgstr "Création de {title}"
#: discussions.py:73 #: discussions.py:86
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr "Création de « {title} » sur le mur de {user}"
#: discussions.py:99
#, fuzzy, python-brace-format
#| msgid "Created \"{title}\""
msgid "Created a poll titled \"{title}\""
msgstr "Création de {title}"
#: discussions.py:104
msgid "Option {}"
msgstr ""
#: discussions.py:105
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr ""
#: discussions.py:121
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
@ -37,7 +66,19 @@ msgstr ""
"Création de [{title}](<{url}f/p/{threadId}>) par [{author}](<{url}f/u/" "Création de [{title}](<{url}f/p/{threadId}>) par [{author}](<{url}f/u/"
"{creatorId}>) dans {forumName}" "{creatorId}>) dans {forumName}"
#: discussions.py:76 #: discussions.py:130
#, fuzzy, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in {forumName}"
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) on {user}'s Message Wall"
msgstr ""
"Création de [{title}](<{url}f/p/{threadId}>) par [{author}](<{url}f/u/"
"{creatorId}>) dans {forumName}"
#: discussions.py:136
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" "[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
@ -45,3 +86,28 @@ msgid ""
msgstr "" msgstr ""
"[Réponse](<{url}f/p/{threadId}/r/{postId}>) de [{author}](<{url}f/u/" "[Réponse](<{url}f/p/{threadId}/r/{postId}>) de [{author}](<{url}f/u/"
"{creatorId}>) à [{title}](<{url}f/p/{threadId}>) dans {forumName}" "{creatorId}>) à [{title}](<{url}f/p/{threadId}>) dans {forumName}"
#: discussions.py:147
#, fuzzy, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in {forumName}"
msgid ""
"[{author}](<{url}f/u/{creatorId}>) replied to [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) on {user}'s Message "
"Wall"
msgstr ""
"Création de [{title}](<{url}f/p/{threadId}>) par [{author}](<{url}f/u/"
"{creatorId}>) dans {forumName}"
#: discussions.py:153
#, fuzzy, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in {forumName}"
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""
"Création de [{title}](<{url}f/p/{threadId}>) par [{author}](<{url}f/u/"
"{creatorId}>) dans {forumName}"

Binary file not shown.

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 18:55+0200\n" "POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-04-06 18:56+0200\n" "PO-Revision-Date: 2020-07-04 01:12+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: pl\n" "Language: pl\n"
@ -19,17 +19,48 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 " "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 "
"|| n%100>14) ? 1 : 2);\n" "|| n%100>14) ? 1 : 2);\n"
#: discussions.py:53 #: discussions.py:56
#, python-brace-format #, python-brace-format
msgid "Replied to \"{title}\"" msgid "Replied to \"{title}\""
msgstr "Odpowiedział(a) w „{title}”" msgstr "Odpowiedział(a) w „{title}”"
#: discussions.py:56 #: discussions.py:63 discussions.py:79 discussions.py:127 discussions.py:143
msgid "unknown"
msgstr "nieznany"
#: discussions.py:68
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr ""
"Odpowiedział(a) na „{title}” z tablicy wiadomości użytkownika/użytkowniczki "
"{user}"
#: discussions.py:72
#, python-brace-format #, python-brace-format
msgid "Created \"{title}\"" msgid "Created \"{title}\""
msgstr "Utworzył(a) „{title}”" msgstr "Utworzył(a) „{title}”"
#: discussions.py:73 #: discussions.py:86
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr ""
"Utworzył(a) „{title}” na tablicy wiadomości użytkownika/użytkowniczki {user}"
#: discussions.py:99
#, python-brace-format
msgid "Created a poll titled \"{title}\""
msgstr "Utworzył(a) ankietę zatytułowaną „{title}”"
#: discussions.py:104
msgid "Option {}"
msgstr "Opcja {}"
#: discussions.py:105
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr "__[Zobacz zdjęcie]({image_url})__"
#: discussions.py:121
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) " "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
@ -38,7 +69,17 @@ msgstr ""
"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [{title}](<{url}f/p/{threadId}" "[{author}](<{url}f/u/{creatorId}>) utworzył(a) [{title}](<{url}f/p/{threadId}"
">) w {forumName}" ">) w {forumName}"
#: discussions.py:76 #: discussions.py:130
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) on {user}'s Message Wall"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) na tablicy wiadomości "
"użytkownika/użytkowniczki {user}"
#: discussions.py:136
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/" "[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
@ -47,3 +88,23 @@ msgstr ""
"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [odpowiedź](<{url}f/p/" "[{author}](<{url}f/u/{creatorId}>) utworzył(a) [odpowiedź](<{url}f/p/"
"{threadId}/r/{postId}>) pod tematem [{title}](<{url}f/p/{threadId}>) w " "{threadId}/r/{postId}>) pod tematem [{title}](<{url}f/p/{threadId}>) w "
"{forumName}" "{forumName}"
#: discussions.py:147
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) replied to [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) on {user}'s Message "
"Wall"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) odpowiedział(a) na[{title}](<{wikiurl}"
"wiki/Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) na tablicy "
"wiadomości użytkownika/użytkowniczki {user}"
#: discussions.py:153
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) utworzył(a) ankietę [{title}](<{url}f/p/"
"{threadId}>) w {forumName}"

Binary file not shown.

View file

@ -0,0 +1,106 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Frisk <piotrex43@protonmail.ch>, 2020.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-07-04 01:09+0200\n"
"Last-Translator: Frisk <piotrex43@protonmail.ch>\n"
"Language-Team: \n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.3\n"
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Respondido o \"{title}\""
#: discussions.py:63 discussions.py:79 discussions.py:127 discussions.py:143
msgid "unknown"
msgstr "desconhecido"
#: discussions.py:68
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr "Respondeu a \"{title}\" no mural de mensagem de {user}"
#: discussions.py:72
#, python-brace-format
msgid "Created \"{title}\""
msgstr "Criado \"{title}\""
#: discussions.py:86
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr "Criado \"{title}\" no mural de mensagem de {user}"
#: discussions.py:99
#, python-brace-format
msgid "Created a poll titled \"{title}\""
msgstr ""
#: discussions.py:104
msgid "Option {}"
msgstr ""
#: discussions.py:105
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr ""
#: discussions.py:121
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
"in {forumName}"
msgstr ""
"Criado [{title}](<{url}f/p/{threadId}>) por [{author}](<{url}f/u/{creatorId}"
">) no {forumName}"
#: discussions.py:130
#, fuzzy, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in {forumName}"
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}>) on {user}'s Message Wall"
msgstr ""
"Criado [{title}](<{url}f/p/{threadId}>) por [{author}](<{url}f/u/{creatorId}"
">) no {forumName}"
#: discussions.py:136
#, 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 ""
"[Responder](<{url}f/p/{threadId}/r/{postId}>) por [{author}](<{url}f/u/"
"{creatorId}>) do [{title}](<{url}f/p/{threadId}>) do {forumName}"
#: discussions.py:147
#, fuzzy, python-brace-format
#| msgid ""
#| "[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}"
#| ">) in {forumName}"
msgid ""
"[{author}](<{url}f/u/{creatorId}>) replied to [{title}](<{wikiurl}wiki/"
"Message_Wall:{user_wall}?threadId={threadid}#{replyId}>) on {user}'s Message "
"Wall"
msgstr ""
"Criado [{title}](<{url}f/p/{threadId}>) por [{author}](<{url}f/u/{creatorId}"
">) no {forumName}"
#: discussions.py:153
#, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 18:47+0200\n" "POT-Creation-Date: 2020-07-04 00:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: misc.py:120 #: misc.py:143
msgid "" msgid ""
"\n" "\n"
"__And more__" "__And more__"

117
misc.py
View file

@ -16,11 +16,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import json, logging, sys, re, time import json, logging, sys, re, time, random, math
from html.parser import HTMLParser from html.parser import HTMLParser
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
import requests import requests
from collections import defaultdict
from configloader import settings from configloader import settings
import gettext import gettext
@ -102,6 +102,26 @@ class MessageQueue:
def cut_messages(self, item_num): def cut_messages(self, item_num):
self._queue = self._queue[item_num:] self._queue = self._queue[item_num:]
def resend_msgs(self):
if self._queue:
misc_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):
misc_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) < 2:
misc_logger.debug("Sending message succeeded")
time.sleep(2.5)
else:
misc_logger.debug("Sending message failed")
break
else:
self.clear()
misc_logger.debug("Queue emptied, all messages delivered")
self.cut_messages(num)
misc_logger.debug(self._queue)
messagequeue = MessageQueue() messagequeue = MessageQueue()
datafile = DataFile() datafile = DataFile()
@ -115,6 +135,9 @@ def link_formatter(link):
"""Formats a link to not embed it""" """Formats a link to not embed it"""
return "<" + re.sub(r"([)])", "\\\\\\1", link).replace(" ", "_") + ">" return "<" + re.sub(r"([)])", "\\\\\\1", link).replace(" ", "_") + ">"
def escape_formatting(data):
"""Escape Discord formatting"""
return re.sub(r"([`_*~<>{}@/|\\])", "\\\\\\1", data, 0)
class ContentParser(HTMLParser): class ContentParser(HTMLParser):
more = _("\n__And more__") more = _("\n__And more__")
@ -279,14 +302,19 @@ def create_article_path(article: str) -> str:
return WIKI_ARTICLE_PATH.replace("$1", article) return WIKI_ARTICLE_PATH.replace("$1", article)
def send_simple(msgtype, message, name, avatar):
discord_msg = DiscordMessage("compact", msgtype, settings["webhookURL"], content=message)
discord_msg.set_avatar(avatar)
discord_msg.set_name(name)
messagequeue.resend_msgs()
send_to_discord(discord_msg)
def send_to_discord_webhook(data): def send_to_discord_webhook(data):
header = settings["header"] header = settings["header"]
if isinstance(data, str): header['Content-Type'] = 'application/json'
header['Content-Type'] = 'application/json'
else:
header['Content-Type'] = 'application/x-www-form-urlencoded'
try: try:
result = requests.post(settings["webhookURL"], data=data, result = requests.post(data.webhook_url, data=repr(data),
headers=header, timeout=10) headers=header, timeout=10)
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
misc_logger.warning("Timeouted while sending data to the webhook.") misc_logger.warning("Timeouted while sending data to the webhook.")
@ -299,6 +327,17 @@ def send_to_discord_webhook(data):
def send_to_discord(data): def send_to_discord(data):
for regex in settings["disallow_regexes"]:
if data.webhook_object.get("content", None):
if re.search(re.compile(regex), data.webhook_object["content"]):
misc_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):
misc_logger.info("Message \"{}\" has been rejected due to matching filter ({}).".format(
to_check, regex))
return # discard the message without anything
if messagequeue: if messagequeue:
messagequeue.add_message(data) messagequeue.add_message(data)
else: else:
@ -311,3 +350,67 @@ def send_to_discord(data):
elif code < 2: elif code < 2:
time.sleep(2.0) time.sleep(2.0)
pass pass
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":
self.webhook_object["content"] = content
self.event_type = event_type
def __setitem__(self, key, value):
"""Set item is used only in embeds."""
try:
self.embed[key] = value
except NameError:
raise TypeError("Tried to assign a value when message type is plain message!")
def __getitem__(self, item):
return self.embed[item]
def __repr__(self):
"""Return the Discord webhook object ready to be sent"""
return json.dumps(self.webhook_object)
def __setup_embed(self):
self.embed = defaultdict(dict)
if "embeds" not in self.webhook_object:
self.webhook_object["embeds"] = [self.embed]
else:
self.webhook_object["embeds"].append(self.embed)
self.embed["color"] = None
def add_embed(self):
self.finish_embed()
self.__setup_embed()
def finish_embed(self):
if self.embed["color"] is None:
if settings["appearance"]["embed"].get(self.event_type, {"color": None})["color"] is None:
self.embed["color"] = random.randrange(1, 16777215)
else:
self.embed["color"] = settings["appearance"]["embed"][self.event_type]["color"]
else:
self.embed["color"] = math.floor(self.embed["color"])
def set_author(self, name, url, icon_url=""):
self.embed["author"]["name"] = name
self.embed["author"]["url"] = url
self.embed["author"]["icon_url"] = icon_url
def add_field(self, name, value, inline=False):
if "fields" not in self.embed:
self.embed["fields"] = []
self.embed["fields"].append(dict(name=name, value=value, inline=inline))
def set_avatar(self, url):
self.webhook_object["avatar_url"] = url
def set_name(self, name):
self.webhook_object["username"] = name

File diff suppressed because it is too large Load diff

137
rcgcdw.py
View file

@ -30,7 +30,7 @@ from urllib.parse import quote_plus
from configloader import settings from configloader import settings
from misc import link_formatter, ContentParser, safe_read, add_to_dict, datafile, \ 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, \ WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN, create_article_path, messagequeue, send_to_discord_webhook, \
send_to_discord send_to_discord, DiscordMessage, send_simple
from session import session from session import session
if settings["fandom_discussions"]["enabled"]: if settings["fandom_discussions"]["enabled"]:
@ -114,16 +114,6 @@ LinkParser = LinkParser()
class MWError(Exception): class MWError(Exception):
pass pass
def send(message, name, avatar):
dictionary_creator = {"content": message}
if name:
dictionary_creator["username"] = name
if avatar:
dictionary_creator["avatar_url"] = avatar
send_to_discord(dictionary_creator)
def profile_field_name(name, embed): def profile_field_name(name, embed):
try: try:
return profile_fields[name] return profile_fields[name]
@ -235,12 +225,12 @@ def compact_formatter(action, change, parsed_comment, categories):
return return
if "sitewide" not in change["logparams"]: if "sitewide" not in change["logparams"]:
restriction_description = "" restriction_description = ""
if change["logparams"]["restrictions"]["pages"]: if "pages" in change["logparams"]["restrictions"] and change["logparams"]["restrictions"]["pages"]:
restriction_description = _(" on pages: ") restriction_description = _(" on pages: ")
for page in change["logparams"]["restrictions"]["pages"]: for page in change["logparams"]["restrictions"]["pages"]:
restricted_pages = ["*{page}*".format(page=i["page_title"]) for i in change["logparams"]["restrictions"]["pages"]] restricted_pages = ["*{page}*".format(page=i["page_title"]) for i in change["logparams"]["restrictions"]["pages"]]
restriction_description = restriction_description + ", ".join(restricted_pages) restriction_description = restriction_description + ", ".join(restricted_pages)
if change["logparams"]["restrictions"]["namespaces"]: if "namespaces" in change["logparams"]["restrictions"] and change["logparams"]["restrictions"]["namespaces"]:
namespaces = [] namespaces = []
if restriction_description: if restriction_description:
restriction_description = restriction_description + _(" and namespaces: ") restriction_description = restriction_description + _(" and namespaces: ")
@ -433,13 +423,14 @@ def compact_formatter(action, change, parsed_comment, categories):
content = _("[{author}]({author_url}) deactivated a [tag]({tag_url}) \"{tag}\"").format(author=author, author_url=author_url, tag=change["logparams"]["tag"], tag_url=link) content = _("[{author}]({author_url}) deactivated a [tag]({tag_url}) \"{tag}\"").format(author=author, author_url=author_url, tag=change["logparams"]["tag"], tag_url=link)
elif action == "suppressed": elif action == "suppressed":
content = _("An action has been hidden by administration.") content = _("An action has been hidden by administration.")
send_to_discord(json.dumps({'content': content, 'allowed_mentions': {'parse': []}})) else:
logger.warning("No entry for {event} with params: {params}".format(event=action, params=change))
return
send_to_discord(DiscordMessage("compact", action, settings["webhookURL"], content=content))
def embed_formatter(action, change, parsed_comment, categories): def embed_formatter(action, change, parsed_comment, categories):
data = {"embeds": []} embed = DiscordMessage("embed", action, settings["webhookURL"])
embed = defaultdict(dict)
colornumber = None
if parsed_comment is None: if parsed_comment is None:
parsed_comment = _("No description provided") parsed_comment = _("No description provided")
if action != "suppressed": if action != "suppressed":
@ -467,22 +458,21 @@ def embed_formatter(action, change, parsed_comment, categories):
amount=recent_changes.map_ips[change["user"]]) amount=recent_changes.map_ips[change["user"]])
else: else:
author_url = create_article_path("User:{}".format(change["user"].replace(" ", "_"))) author_url = create_article_path("User:{}".format(change["user"].replace(" ", "_")))
embed["author"]["name"] = change["user"] embed.set_author(change["user"], author_url)
embed["author"]["url"] = author_url
if action in ("edit", "new"): # edit or new page if action in ("edit", "new"): # edit or new page
editsize = change["newlen"] - change["oldlen"] editsize = change["newlen"] - change["oldlen"]
if editsize > 0: if editsize > 0:
if editsize > 6032: if editsize > 6032:
colornumber = 65280 embed["color"] = 65280
else: else:
colornumber = 35840 + (math.floor(editsize / 52)) * 256 embed["color"] = 35840 + (math.floor(editsize / 52)) * 256
elif editsize < 0: elif editsize < 0:
if editsize < -6032: if editsize < -6032:
colornumber = 16711680 embed["color"] = 16711680
else: else:
colornumber = 9175040 + (math.floor((editsize * -1) / 52)) * 65536 embed["color"] = 9175040 + (math.floor((editsize * -1) / 52)) * 65536
elif editsize == 0: elif editsize == 0:
colornumber = 8750469 embed["color"] = 8750469
if change["title"].startswith("MediaWiki:Tag-"): # Refresh tag list when tag display name is edited if change["title"].startswith("MediaWiki:Tag-"): # Refresh tag list when tag display name is edited
recent_changes.init_info() recent_changes.init_info()
link = "{wiki}index.php?title={article}&curid={pageid}&diff={diff}&oldid={oldrev}".format( link = "{wiki}index.php?title={article}&curid={pageid}&diff={diff}&oldid={oldrev}".format(
@ -503,8 +493,6 @@ def embed_formatter(action, change, parsed_comment, categories):
wiki=WIKI_API_PATH, diff=change["revid"],oldrev=change["old_revid"] wiki=WIKI_API_PATH, diff=change["revid"],oldrev=change["old_revid"]
)), "compare", "*") )), "compare", "*")
if changed_content: if changed_content:
if "fields" not in embed:
embed["fields"] = []
EditDiff = ContentParser() EditDiff = ContentParser()
EditDiff.feed(changed_content) EditDiff.feed(changed_content)
if EditDiff.small_prev_del: if EditDiff.small_prev_del:
@ -519,11 +507,9 @@ def embed_formatter(action, change, parsed_comment, categories):
EditDiff.small_prev_ins = EditDiff.small_prev_ins.replace("****", "") EditDiff.small_prev_ins = EditDiff.small_prev_ins.replace("****", "")
logger.debug("Changed content: {}".format(EditDiff.small_prev_ins)) logger.debug("Changed content: {}".format(EditDiff.small_prev_ins))
if EditDiff.small_prev_del and not action == "new": if EditDiff.small_prev_del and not action == "new":
embed["fields"].append( embed.add_field(_("Removed"), "{data}".format(data=EditDiff.small_prev_del), inline=True)
{"name": _("Removed"), "value": "{data}".format(data=EditDiff.small_prev_del), "inline": True})
if EditDiff.small_prev_ins: if EditDiff.small_prev_ins:
embed["fields"].append( embed.add_field(_("Added"), "{data}".format(data=EditDiff.small_prev_ins), inline=True)
{"name": _("Added"), "value": "{data}".format(data=EditDiff.small_prev_ins), "inline": True})
else: else:
logger.warning("Unable to download data on the edit content!") logger.warning("Unable to download data on the edit content!")
elif action in ("upload/overwrite", "upload/upload", "upload/revert"): # sending files elif action in ("upload/overwrite", "upload/upload", "upload/revert"): # sending files
@ -557,8 +543,8 @@ def embed_formatter(action, change, parsed_comment, categories):
else: else:
undolink = "{wiki}index.php?title={filename}&action=revert&oldimage={archiveid}".format( undolink = "{wiki}index.php?title={filename}&action=revert&oldimage={archiveid}".format(
wiki=WIKI_SCRIPT_PATH, filename=article_encoded, archiveid=revision["archivename"]) wiki=WIKI_SCRIPT_PATH, filename=article_encoded, archiveid=revision["archivename"])
embed["fields"] = [{"name": _("Options"), "value": _("([preview]({link}) | [undo]({undolink}))").format( embed.add_field(_("Options"), _("([preview]({link}) | [undo]({undolink}))").format(
link=image_direct_url, undolink=undolink)}] link=image_direct_url, undolink=undolink))
if settings["appearance"]["embed"]["embed_images"]: if settings["appearance"]["embed"]["embed_images"]:
embed["image"]["url"] = image_direct_url embed["image"]["url"] = image_direct_url
if action == "upload/overwrite": if action == "upload/overwrite":
@ -596,8 +582,7 @@ def embed_formatter(action, change, parsed_comment, categories):
if license is not None: if license is not None:
parsed_comment += _("\nLicense: {}").format(license) parsed_comment += _("\nLicense: {}").format(license)
if additional_info_retrieved: if additional_info_retrieved:
embed["fields"] = [ embed.add_field(_("Options"), _("([preview]({link}))").format(link=image_direct_url))
{"name": _("Options"), "value": _("([preview]({link}))").format(link=image_direct_url)}]
if settings["appearance"]["embed"]["embed_images"]: if settings["appearance"]["embed"]["embed_images"]:
embed["image"]["url"] = image_direct_url embed["image"]["url"] = image_direct_url
elif action == "delete/delete": elif action == "delete/delete":
@ -640,12 +625,12 @@ def embed_formatter(action, change, parsed_comment, categories):
return return
if "sitewide" not in change["logparams"]: if "sitewide" not in change["logparams"]:
restriction_description = "" restriction_description = ""
if change["logparams"]["restrictions"]["pages"]: if "pages" in change["logparams"]["restrictions"] and change["logparams"]["restrictions"]["pages"]:
restriction_description = _("Blocked from editing the following pages: ") restriction_description = _("Blocked from editing the following pages: ")
for page in change["logparams"]["restrictions"]["pages"]: for page in change["logparams"]["restrictions"]["pages"]:
restricted_pages = ["*"+i["page_title"]+"*" for i in change["logparams"]["restrictions"]["pages"]] restricted_pages = ["*"+i["page_title"]+"*" for i in change["logparams"]["restrictions"]["pages"]]
restriction_description = restriction_description + ", ".join(restricted_pages) restriction_description = restriction_description + ", ".join(restricted_pages)
if change["logparams"]["restrictions"]["namespaces"]: if "namespaces" in change["logparams"]["restrictions"] and change["logparams"]["restrictions"]["namespaces"]:
namespaces = [] namespaces = []
if restriction_description: if restriction_description:
restriction_description = restriction_description + _(" and namespaces: ") restriction_description = restriction_description + _(" and namespaces: ")
@ -661,10 +646,7 @@ def embed_formatter(action, change, parsed_comment, categories):
if len(restriction_description) > 1020: if len(restriction_description) > 1020:
logger.debug(restriction_description) logger.debug(restriction_description)
restriction_description = restriction_description[:1020]+"" restriction_description = restriction_description[:1020]+""
if "fields" not in embed: embed.add_field(_("Partial block details"), restriction_description, inline=True)
embed["fields"] = []
embed["fields"].append(
{"name": _("Partial block details"), "value": restriction_description, "inline": True})
embed["title"] = _("Blocked {blocked_user} for {time}").format(blocked_user=user, time=block_time) embed["title"] = _("Blocked {blocked_user} for {time}").format(blocked_user=user, time=block_time)
elif action == "block/reblock": elif action == "block/reblock":
link = create_article_path(change["title"].replace(" ", "_").replace(')', '\)')) link = create_article_path(change["title"].replace(" ", "_").replace(')', '\)'))
@ -860,19 +842,10 @@ def embed_formatter(action, change, parsed_comment, categories):
embed["url"] = link embed["url"] = link
if parsed_comment is not None: if parsed_comment is not None:
embed["description"] = parsed_comment embed["description"] = parsed_comment
if colornumber is None:
if settings["appearance"]["embed"][action]["color"] is None:
embed["color"] = random.randrange(1, 16777215)
else:
embed["color"] = settings["appearance"]["embed"][action]["color"]
else:
embed["color"] = math.floor(colornumber)
if settings["appearance"]["embed"]["show_footer"]: if settings["appearance"]["embed"]["show_footer"]:
embed["timestamp"] = change["timestamp"] embed["timestamp"] = change["timestamp"]
if "tags" in change and change["tags"]: if "tags" in change and change["tags"]:
tag_displayname = [] tag_displayname = []
if "fields" not in embed:
embed["fields"] = []
for tag in change["tags"]: for tag in change["tags"]:
if tag in recent_changes.tags: if tag in recent_changes.tags:
if recent_changes.tags[tag] is None: if recent_changes.tags[tag] is None:
@ -881,19 +854,14 @@ def embed_formatter(action, change, parsed_comment, categories):
tag_displayname.append(recent_changes.tags[tag]) tag_displayname.append(recent_changes.tags[tag])
else: else:
tag_displayname.append(tag) tag_displayname.append(tag)
embed["fields"].append({"name": _("Tags"), "value": ", ".join(tag_displayname)}) embed.add_field(_("Tags"), ", ".join(tag_displayname))
logger.debug("Current params in edit action: {}".format(change)) logger.debug("Current params in edit action: {}".format(change))
if categories is not None and not (len(categories["new"]) == 0 and len(categories["removed"]) == 0): if categories is not None and not (len(categories["new"]) == 0 and len(categories["removed"]) == 0):
if "fields" not in embed:
embed["fields"] = []
new_cat = (_("**Added**: ") + ", ".join(list(categories["new"])[0:16]) + ("\n" if len(categories["new"])<=15 else _(" and {} more\n").format(len(categories["new"])-15))) if categories["new"] else "" new_cat = (_("**Added**: ") + ", ".join(list(categories["new"])[0:16]) + ("\n" if len(categories["new"])<=15 else _(" and {} more\n").format(len(categories["new"])-15))) if categories["new"] else ""
del_cat = (_("**Removed**: ") + ", ".join(list(categories["removed"])[0:16]) + ("" if len(categories["removed"])<=15 else _(" and {} more").format(len(categories["removed"])-15))) if categories["removed"] else "" del_cat = (_("**Removed**: ") + ", ".join(list(categories["removed"])[0:16]) + ("" if len(categories["removed"])<=15 else _(" and {} more").format(len(categories["removed"])-15))) if categories["removed"] else ""
embed["fields"].append({"name": _("Changed categories"), "value": new_cat + del_cat}) embed.add_field(_("Changed categories"), new_cat + del_cat)
data["embeds"].append(dict(embed)) embed.finish_embed()
data['avatar_url'] = settings["avatars"]["embed"] send_to_discord(embed)
data['allowed_mentions'] = {'parse': []}
formatted_embed = json.dumps(data, indent=4)
send_to_discord(formatted_embed)
def essential_info(change, changed_categories): def essential_info(change, changed_categories):
@ -1015,18 +983,16 @@ def day_overview():
changed_bytes = 0 changed_bytes = 0
new_articles = 0 new_articles = 0
active_articles = [] active_articles = []
embed = DiscordMessage("embed", "daily_overview", settings["webhookURL"])
embed["title"] = _("Daily overview")
embed["url"] = create_article_path("Special:Statistics")
embed.set_author(settings["wikiname"], create_article_path(""),
icon_url=settings["appearance"]["embed"]["daily_overview"]["icon"])
if not result[0]: if not result[0]:
if not settings["send_empty_overview"]: if not settings["send_empty_overview"]:
return # no changes in this day return # no changes in this day
else: else:
embed = defaultdict(dict)
embed["title"] = _("Daily overview")
embed["url"] = create_article_path("Special:Statistics")
embed["description"] = _("No activity") embed["description"] = _("No activity")
embed["color"] = settings["appearance"]["embed"]["daily_overview"]["color"]
embed["author"]["icon_url"] = settings["appearance"]["embed"]["daily_overview"]["icon"]
embed["author"]["name"] = settings["wikiname"]
embed["author"]["url"] = create_article_path("")
else: else:
for item in result[0]: for item in result[0]:
if "actionhidden" in item or "suppressed" in item or "userhidden" in item: if "actionhidden" in item or "suppressed" in item or "userhidden" in item:
@ -1047,13 +1013,6 @@ def day_overview():
admin = admin + 1 if item["logtype"] in ["delete", "merge", "block", "protect", "import", "rights", admin = admin + 1 if item["logtype"] in ["delete", "merge", "block", "protect", "import", "rights",
"abusefilter", "interwiki", "managetags"] else admin "abusefilter", "interwiki", "managetags"] else admin
overall = round(new_articles + edits * 0.1 + files * 0.3 + admin * 0.1 + math.fabs(changed_bytes * 0.001), 2) overall = round(new_articles + edits * 0.1 + files * 0.3 + admin * 0.1 + math.fabs(changed_bytes * 0.001), 2)
embed = defaultdict(dict)
embed["title"] = _("Daily overview")
embed["url"] = create_article_path("Special:Statistics")
embed["color"] = settings["appearance"]["embed"]["daily_overview"]["color"]
embed["author"]["icon_url"] = settings["appearance"]["embed"]["daily_overview"]["icon"]
embed["author"]["name"] = settings["wikiname"]
embed["author"]["url"] = create_article_path("")
if activity: if activity:
active_users = [] active_users = []
for user, numberu in Counter(activity).most_common(3): # find most active users for user, numberu in Counter(activity).most_common(3): # find most active users
@ -1072,7 +1031,6 @@ def day_overview():
houramount = "" houramount = ""
if not active_articles: if not active_articles:
active_articles = [_("But nobody came")] active_articles = [_("But nobody came")]
embed["fields"] = []
edits, files, admin, changed_bytes, new_articles, unique_contributors, overall = daily_overview_sync(edits, files, admin, changed_bytes, new_articles, len(activity), overall) edits, files, admin, changed_bytes, new_articles, unique_contributors, overall = daily_overview_sync(edits, files, admin, changed_bytes, new_articles, len(activity), overall)
fields = ( fields = (
(ngettext("Most active user", "Most active users", len(active_users)), ', '.join(active_users)), (ngettext("Most active user", "Most active users", len(active_users)), ', '.join(active_users)),
@ -1083,10 +1041,9 @@ def day_overview():
(ngettext("Most active hour", "Most active hours", len(active_hours)), ', '.join(active_hours) + houramount), (ngettext("Most active hour", "Most active hours", len(active_hours)), ', '.join(active_hours) + houramount),
(_("Day score"), overall)) (_("Day score"), overall))
for name, value in fields: for name, value in fields:
embed["fields"].append({"name": name, "value": value, "inline": True}) embed.add_field(name, value, inline=True)
data = {"embeds": [dict(embed)]} embed.finish_embed()
formatted_embed = json.dumps(data, indent=4) send_to_discord(embed)
send_to_discord(formatted_embed)
else: else:
logger.debug("function requesting changes for day overview returned with error code") logger.debug("function requesting changes for day overview returned with error code")
@ -1163,25 +1120,7 @@ class Recent_Changes_Class(object):
self.ids.pop(0) self.ids.pop(0)
def fetch(self, amount=settings["limit"]): def fetch(self, amount=settings["limit"]):
if messagequeue: messagequeue.resend_msgs()
logger.info(
"{} messages waiting to be delivered to Discord due to Discord throwing errors/no connection to Discord servers.".format(
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)))
if send_to_discord_webhook(item) < 2:
logger.debug("Sending message succeeded")
time.sleep(2.5)
else:
logger.debug("Sending message failed")
break
else:
messagequeue.clear()
logger.debug("Queue emptied, all messages delivered")
messagequeue.cut_messages(num)
logger.debug(messagequeue)
last_check = self.fetch_changes(amount=amount) last_check = self.fetch_changes(amount=amount)
# If the request succeeds the last_check will be the last rcid from recentchanges query # If the request succeeds the last_check will be the last rcid from recentchanges query
if last_check is not None: if last_check is not None:
@ -1223,7 +1162,7 @@ class Recent_Changes_Class(object):
self.streak += 1 self.streak += 1
if self.streak > 8: if self.streak > 8:
self.streak = -1 self.streak = -1
send(_("Connection to {wiki} seems to be stable now.").format(wiki=settings["wikiname"]), send_simple("down_detector", _("Connection to {wiki} seems to be stable now.").format(wiki=settings["wikiname"]),
_("Connection status"), settings["avatars"]["connection_restored"]) _("Connection status"), settings["avatars"]["connection_restored"])
# In the first for loop we analize the categorize events and figure if we will need more changes to fetch # In the first for loop we analize the categorize events and figure if we will need more changes to fetch
# in order to cover all of the edits # in order to cover all of the edits
@ -1338,7 +1277,7 @@ class Recent_Changes_Class(object):
else: else:
if ( if (
time.time() - self.last_downtime) > 1800 and self.check_connection(): # check if last downtime happened within 30 minutes, if yes, don't send a message time.time() - self.last_downtime) > 1800 and self.check_connection(): # check if last downtime happened within 30 minutes, if yes, don't send a message
send(_("{wiki} seems to be down or unreachable.").format(wiki=settings["wikiname"]), send_simple("down_detector", _("{wiki} seems to be down or unreachable.").format(wiki=settings["wikiname"]),
_("Connection status"), settings["avatars"]["connection_failed"]) _("Connection status"), settings["avatars"]["connection_failed"])
self.last_downtime = time.time() self.last_downtime = time.time()
self.streak = 0 self.streak = 0
@ -1425,6 +1364,8 @@ if TESTING:
recent_changes.ids = [1] recent_changes.ids = [1]
recent_changes.fetch(amount=5) recent_changes.fetch(amount=5)
day_overview() day_overview()
import discussions
discussions.fetch_discussions()
sys.exit(0) sys.exit(0)
while 1: while 1:

View file

@ -14,7 +14,8 @@
"connection_failed": "https://i.imgur.com/2jWQEt1.png", "connection_failed": "https://i.imgur.com/2jWQEt1.png",
"connection_restored": "", "connection_restored": "",
"no_event": "", "no_event": "",
"embed": "" "embed": "",
"compact": ""
}, },
"ignored": ["external"], "ignored": ["external"],
"show_updown_messages": true, "show_updown_messages": true,
@ -24,6 +25,7 @@
"license_detection": true, "license_detection": true,
"license_regex_detect": "\\{\\{(license|lizenz|licence|copyright)", "license_regex_detect": "\\{\\{(license|lizenz|licence|copyright)",
"license_regex": "\\{\\{(license|lizenz|licence|copyright)(\\ |\\|)(?P<license>.*?)\\}\\}", "license_regex": "\\{\\{(license|lizenz|licence|copyright)(\\ |\\|)(?P<license>.*?)\\}\\}",
"disallow_regexes": [],
"wiki_bot_login": "", "wiki_bot_login": "",
"wiki_bot_password": "", "wiki_bot_password": "",
"show_added_categories": true, "show_added_categories": true,
@ -263,6 +265,26 @@
"suppressed":{ "suppressed":{
"icon": "https://i.imgur.com/1gps6EZ.png", "icon": "https://i.imgur.com/1gps6EZ.png",
"color": 8092539 "color": 8092539
},
"discussion/forum/post": {
"icon": "",
"color":null
},
"discussion/forum/reply": {
"icon": "",
"color":null
},
"discussion/forum/poll": {
"icon": "",
"color":null
},
"discussion/wall/post": {
"icon": "",
"color":null
},
"discussion/wall/reply": {
"icon": "",
"color":null
} }
} }
}, },