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
.directory
/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).
### 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
* Send daily overviews, that show general information about wiki activity
* Supports multiple languages (included EN, PL, BR, RU, FR, UK)

View file

@ -154,8 +154,8 @@ def set_wiki():
def set_lang():
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")
if option in ["en", "de", "ru", "pt-br", "fr", "pl"]:
"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", "uk"]:
settings["lang"] = option
return True
return False
@ -163,7 +163,7 @@ def set_lang():
def set_webhook():
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")
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)
if test_webhook.status_code != 200:
print("The webhook URL does not seem right. Reason: {}".format(test_webhook.json()["message"]))
@ -172,7 +172,7 @@ def set_webhook():
settings["webhookURL"] = option
return True
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
def set_wikiname():

View file

@ -6,7 +6,7 @@ try: # load settings
if settings["limitrefetch"] < settings["limit"] and settings["limitrefetch"] != -1:
settings["limitrefetch"] = settings["limit"]
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:
logging.critical("No config file could be found. Please make sure settings.json is in the directory.")
sys.exit(1)

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,26 +17,76 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: discussions.py:53
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{title}\""
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
msgid "Created \"{title}\""
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
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
"in {forumName}"
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
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}"
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
from collections import defaultdict
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
# 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"])
def embed_formatter(post):
def embed_formatter(post, post_type):
"""Embed formatter for Fandom discussions."""
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=settings["fandom_discussions"]["wiki_url"], 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=settings["fandom_discussions"]["wiki_url"], threadId=post["threadId"], postId=post["id"])
else:
embed["title"] = _("Created \"{title}\"").format(title=post["title"])
embed["url"] = "{wikiurl}f/p/{threadId}".format(wikiurl=settings["fandom_discussions"]["wiki_url"], 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] + ""
embed = DiscordMessage("embed", "discussion", settings["fandom_discussions"]["webhookURL"])
embed.set_author(post["createdBy"]["name"], "{wikiurl}f/u/{creatorId}".format(
wikiurl=settings["fandom_discussions"]["wiki_url"], creatorId=post["creatorId"]), icon_url=post["createdBy"]["avatarUrl"])
discussion_post_type = post["_embedded"]["thread"][0].get("containerType", "FORUM") # Can be FORUM, ARTICLE_COMMENT or WALL on UCP
if post_type == "TEXT":
if post["isReply"]:
if discussion_post_type == "FORUM":
embed.event_type = "discussion/forum/reply"
embed["title"] = _("Replied to \"{title}\"").format(title=post["_embedded"]["thread"][0]["title"])
embed["url"] = "{wikiurl}f/p/{threadId}/r/{postId}".format(
wikiurl=settings["fandom_discussions"]["wiki_url"], threadId=post["threadId"], postId=post["id"])
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/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["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': []}
formatted_embed = json.dumps(data, indent=4)
send_to_discord(formatted_embed)
embed.finish_embed()
send_to_discord(embed)
def compact_formatter(post):
def compact_formatter(post, post_type):
"""Compact formatter for Fandom discussions."""
message = None
if not post["isReply"]:
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"])
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(
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"]
)
send_to_discord(json.dumps({'content': message, 'allowed_mentions': {'parse': []}}))
discussion_post_type = post["_embedded"]["thread"][0].get("containerType",
"FORUM") # Can be FORUM, ARTICLE_COMMENT or WALL on UCP
if post_type == "TEXT":
if not post["isReply"]:
if discussion_post_type == "FORUM":
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"])
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():
messagequeue.resend_msgs()
request = safe_request(fetch_url)
if request:
try:
@ -95,11 +173,105 @@ def fetch_discussions():
if request_json:
for post in request_json:
if int(post["id"]) > storage["discussion_id"]:
formatter(post)
parse_discussion_post(post)
if int(post["id"]) > storage["discussion_id"]:
storage["discussion_id"] = int(post["id"])
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):
"""Function to assure safety of request, and do not crash the script on exceptions,"""
try:

Binary file not shown.

View file

@ -7,28 +7,56 @@ 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-07 11:53+0200\n"
"POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-07-04 01:04+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\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: de\n"
#: discussions.py:53
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{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
msgid "Created \"{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
msgid ""
"[{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}"
">) 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
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
@ -45,3 +83,23 @@ msgid ""
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) erstellte eine [Antwork](<{url}f/p/"
"{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"
"Report-Msgid-Bugs-To: \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"
"Language-Team: German\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -13,7 +12,7 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Loco-Source-Locale: de_DE\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"
#: rcgcdw.py:71
@ -241,11 +240,12 @@ msgstr ""
"Profil von {target}"
#: rcgcdw.py:358
#, fuzzy, python-brace-format
#, python-brace-format
#| msgid "[{author}]({author_url}) deleted a comment on {target} profile"
msgid "[{author}]({author_url}) purged a comment on {target} profile"
msgstr ""
"[{author}]({author_url}) löschte ein Kommentar auf dem Profil von {target}"
" dauerhaft"
#: rcgcdw.py:368
#, python-brace-format
@ -663,7 +663,7 @@ msgstr "{field} geändert zu: {desc}"
#: rcgcdw.py:775
#, python-brace-format
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
#, python-brace-format

Binary file not shown.

View file

@ -7,31 +7,84 @@ 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"
"POT-Creation-Date: 2020-06-23 14:54+0200\n"
"PO-Revision-Date: 2020-06-23 14:57+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en\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 "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 ""
"[{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}"
#: 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 ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-06 18:55+0200\n"
"PO-Revision-Date: 2020-04-22 20:59+0200\n"
"POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-07-04 01:08+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\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: fr\n"
#: discussions.py:53
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{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
msgid "Created \"{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
msgid ""
"[{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/"
"{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
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}f/p/{threadId}/r/"
@ -45,3 +86,28 @@ msgid ""
msgstr ""
"[Réponse](<{url}f/p/{threadId}/r/{postId}>) de [{author}](<{url}f/u/"
"{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 ""
"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"
"POT-Creation-Date: 2020-07-04 00:58+0200\n"
"PO-Revision-Date: 2020-07-04 01:12+0200\n"
"Last-Translator: \n"
"Language-Team: \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 "
"|| n%100>14) ? 1 : 2);\n"
#: discussions.py:53
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{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
msgid "Created \"{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
msgid ""
"[{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}"
">) 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
msgid ""
"[{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/"
"{threadId}/r/{postId}>) pod tematem [{title}](<{url}f/p/{threadId}>) w "
"{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 ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: misc.py:120
#: misc.py:143
msgid ""
"\n"
"__And more__"

119
misc.py
View file

@ -16,11 +16,11 @@
# You should have received a copy of the GNU General Public License
# 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 urllib.parse import urlparse, urlunparse
import requests
from collections import defaultdict
from configloader import settings
import gettext
@ -102,6 +102,26 @@ class MessageQueue:
def cut_messages(self, 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()
datafile = DataFile()
@ -115,6 +135,9 @@ def link_formatter(link):
"""Formats a link to not embed it"""
return "<" + re.sub(r"([)])", "\\\\\\1", link).replace(" ", "_") + ">"
def escape_formatting(data):
"""Escape Discord formatting"""
return re.sub(r"([`_*~<>{}@/|\\])", "\\\\\\1", data, 0)
class ContentParser(HTMLParser):
more = _("\n__And more__")
@ -279,14 +302,19 @@ def create_article_path(article: str) -> str:
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):
header = settings["header"]
if isinstance(data, str):
header['Content-Type'] = 'application/json'
else:
header['Content-Type'] = 'application/x-www-form-urlencoded'
header['Content-Type'] = 'application/json'
try:
result = requests.post(settings["webhookURL"], data=data,
result = requests.post(data.webhook_url, data=repr(data),
headers=header, timeout=10)
except requests.exceptions.Timeout:
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):
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:
messagequeue.add_message(data)
else:
@ -310,4 +349,68 @@ def send_to_discord(data):
messagequeue.add_message(data)
elif code < 2:
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 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
send_to_discord, DiscordMessage, send_simple
from session import session
if settings["fandom_discussions"]["enabled"]:
@ -114,16 +114,6 @@ LinkParser = LinkParser()
class MWError(Exception):
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):
try:
return profile_fields[name]
@ -235,12 +225,12 @@ def compact_formatter(action, change, parsed_comment, categories):
return
if "sitewide" not in change["logparams"]:
restriction_description = ""
if change["logparams"]["restrictions"]["pages"]:
if "pages" in change["logparams"]["restrictions"] and change["logparams"]["restrictions"]["pages"]:
restriction_description = _(" on pages: ")
for page 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)
if change["logparams"]["restrictions"]["namespaces"]:
if "namespaces" in change["logparams"]["restrictions"] and change["logparams"]["restrictions"]["namespaces"]:
namespaces = []
if restriction_description:
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)
elif action == "suppressed":
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):
data = {"embeds": []}
embed = defaultdict(dict)
colornumber = None
embed = DiscordMessage("embed", action, settings["webhookURL"])
if parsed_comment is None:
parsed_comment = _("No description provided")
if action != "suppressed":
@ -467,22 +458,21 @@ def embed_formatter(action, change, parsed_comment, categories):
amount=recent_changes.map_ips[change["user"]])
else:
author_url = create_article_path("User:{}".format(change["user"].replace(" ", "_")))
embed["author"]["name"] = change["user"]
embed["author"]["url"] = author_url
embed.set_author(change["user"], author_url)
if action in ("edit", "new"): # edit or new page
editsize = change["newlen"] - change["oldlen"]
if editsize > 0:
if editsize > 6032:
colornumber = 65280
embed["color"] = 65280
else:
colornumber = 35840 + (math.floor(editsize / 52)) * 256
embed["color"] = 35840 + (math.floor(editsize / 52)) * 256
elif editsize < 0:
if editsize < -6032:
colornumber = 16711680
embed["color"] = 16711680
else:
colornumber = 9175040 + (math.floor((editsize * -1) / 52)) * 65536
embed["color"] = 9175040 + (math.floor((editsize * -1) / 52)) * 65536
elif editsize == 0:
colornumber = 8750469
embed["color"] = 8750469
if change["title"].startswith("MediaWiki:Tag-"): # Refresh tag list when tag display name is edited
recent_changes.init_info()
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"]
)), "compare", "*")
if changed_content:
if "fields" not in embed:
embed["fields"] = []
EditDiff = ContentParser()
EditDiff.feed(changed_content)
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("****", "")
logger.debug("Changed content: {}".format(EditDiff.small_prev_ins))
if EditDiff.small_prev_del and not action == "new":
embed["fields"].append(
{"name": _("Removed"), "value": "{data}".format(data=EditDiff.small_prev_del), "inline": True})
embed.add_field(_("Removed"), "{data}".format(data=EditDiff.small_prev_del), inline=True)
if EditDiff.small_prev_ins:
embed["fields"].append(
{"name": _("Added"), "value": "{data}".format(data=EditDiff.small_prev_ins), "inline": True})
embed.add_field(_("Added"), "{data}".format(data=EditDiff.small_prev_ins), inline=True)
else:
logger.warning("Unable to download data on the edit content!")
elif action in ("upload/overwrite", "upload/upload", "upload/revert"): # sending files
@ -557,8 +543,8 @@ def embed_formatter(action, change, parsed_comment, categories):
else:
undolink = "{wiki}index.php?title={filename}&action=revert&oldimage={archiveid}".format(
wiki=WIKI_SCRIPT_PATH, filename=article_encoded, archiveid=revision["archivename"])
embed["fields"] = [{"name": _("Options"), "value": _("([preview]({link}) | [undo]({undolink}))").format(
link=image_direct_url, undolink=undolink)}]
embed.add_field(_("Options"), _("([preview]({link}) | [undo]({undolink}))").format(
link=image_direct_url, undolink=undolink))
if settings["appearance"]["embed"]["embed_images"]:
embed["image"]["url"] = image_direct_url
if action == "upload/overwrite":
@ -596,8 +582,7 @@ def embed_formatter(action, change, parsed_comment, categories):
if license is not None:
parsed_comment += _("\nLicense: {}").format(license)
if additional_info_retrieved:
embed["fields"] = [
{"name": _("Options"), "value": _("([preview]({link}))").format(link=image_direct_url)}]
embed.add_field(_("Options"), _("([preview]({link}))").format(link=image_direct_url))
if settings["appearance"]["embed"]["embed_images"]:
embed["image"]["url"] = image_direct_url
elif action == "delete/delete":
@ -640,12 +625,12 @@ def embed_formatter(action, change, parsed_comment, categories):
return
if "sitewide" not in change["logparams"]:
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: ")
for page in change["logparams"]["restrictions"]["pages"]:
restricted_pages = ["*"+i["page_title"]+"*" for i in change["logparams"]["restrictions"]["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 = []
if restriction_description:
restriction_description = restriction_description + _(" and namespaces: ")
@ -661,10 +646,7 @@ def embed_formatter(action, change, parsed_comment, categories):
if len(restriction_description) > 1020:
logger.debug(restriction_description)
restriction_description = restriction_description[:1020]+""
if "fields" not in embed:
embed["fields"] = []
embed["fields"].append(
{"name": _("Partial block details"), "value": restriction_description, "inline": True})
embed.add_field(_("Partial block details"), restriction_description, inline=True)
embed["title"] = _("Blocked {blocked_user} for {time}").format(blocked_user=user, time=block_time)
elif action == "block/reblock":
link = create_article_path(change["title"].replace(" ", "_").replace(')', '\)'))
@ -860,19 +842,10 @@ def embed_formatter(action, change, parsed_comment, categories):
embed["url"] = link
if parsed_comment is not None:
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"]:
embed["timestamp"] = change["timestamp"]
if "tags" in change and change["tags"]:
tag_displayname = []
if "fields" not in embed:
embed["fields"] = []
for tag in change["tags"]:
if tag in recent_changes.tags:
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])
else:
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))
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 ""
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})
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)
embed.add_field(_("Changed categories"), new_cat + del_cat)
embed.finish_embed()
send_to_discord(embed)
def essential_info(change, changed_categories):
@ -1015,18 +983,16 @@ def day_overview():
changed_bytes = 0
new_articles = 0
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 settings["send_empty_overview"]:
return # no changes in this day
else:
embed = defaultdict(dict)
embed["title"] = _("Daily overview")
embed["url"] = create_article_path("Special:Statistics")
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:
for item in result[0]:
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",
"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)
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:
active_users = []
for user, numberu in Counter(activity).most_common(3): # find most active users
@ -1072,7 +1031,6 @@ def day_overview():
houramount = ""
if not active_articles:
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)
fields = (
(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),
(_("Day score"), overall))
for name, value in fields:
embed["fields"].append({"name": name, "value": value, "inline": True})
data = {"embeds": [dict(embed)]}
formatted_embed = json.dumps(data, indent=4)
send_to_discord(formatted_embed)
embed.add_field(name, value, inline=True)
embed.finish_embed()
send_to_discord(embed)
else:
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)
def fetch(self, amount=settings["limit"]):
if messagequeue:
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)
messagequeue.resend_msgs()
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:
@ -1223,7 +1162,7 @@ class Recent_Changes_Class(object):
self.streak += 1
if self.streak > 8:
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"])
# 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
@ -1338,7 +1277,7 @@ class Recent_Changes_Class(object):
else:
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
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"])
self.last_downtime = time.time()
self.streak = 0
@ -1425,6 +1364,8 @@ if TESTING:
recent_changes.ids = [1]
recent_changes.fetch(amount=5)
day_overview()
import discussions
discussions.fetch_discussions()
sys.exit(0)
while 1:

View file

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