Merge branch 'wiki-rate-limiting'

# Conflicts:
#	src/formatters/discussions.py
#	src/i18n.py
This commit is contained in:
Frisk 2020-08-10 18:57:32 +02:00
commit 2cea8a8724
No known key found for this signature in database
GPG key ID: 213F7C15068AF8AC
60 changed files with 2500 additions and 2982 deletions

View file

@ -3,7 +3,6 @@ RcGcDb is a backend for handling webhooks to which recent changes of MediaWiki w
### Dependencies ###
* **Python 3.6>**
* requests 2.18.4>
* beautifulsoup 4.6.0>
* aiohttp 3.6.2>
* lxml 4.2.1>

Binary file not shown.

View file

@ -0,0 +1,35 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the RcGcDd package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: RcGcDd\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 16:46+0200\n"
"PO-Revision-Date: 2020-08-10 01:50+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.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/discord.py:25
msgid "wiki deletion"
msgstr ""
#: src/discord.py:25 src/discord.py:26
msgid "wiki becoming inaccessible"
msgstr ""
#: src/discord.py:27
msgid "unknown error"
msgstr ""
#: src/discord.py:28
msgid "The webhook for {} has been removed due to {}."
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,171 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 16:45+0200\n"
"PO-Revision-Date: 2020-08-10 16:41+0000\n"
"Last-Translator: MarkusRost <sukramxro@gmail.com>\n"
"Language-Team: German <https://weblate.frisk.space/projects/rcgcdw/"
"discussion_formatters-1/de/>\n"
"Language: de\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: Weblate 4.1.1\n"
#: src/formatters/discussions.py:21
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
"in {forumName}"
msgstr ""
"[{author}]({author_url}) erstellte [{title}](<{url}f/p/{threadId}>) in "
"{forumName}"
#: src/formatters/discussions.py:23
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""
"[{author}]({author_url}) erstellte eine Umfrage [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
#: src/formatters/discussions.py:28
#, fuzzy, 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}]({author_url}) erstellte eine [Antwork](<{url}f/p/{threadId}/r/"
"{postId}>) zu [{title}](<{url}f/p/{threadId}>) in {forumName}"
#: src/formatters/discussions.py:30 src/formatters/discussions.py:38
#: src/formatters/discussions.py:96 src/formatters/discussions.py:108
msgid "unknown"
msgstr "Unbekannt"
#: src/formatters/discussions.py:34
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}>) on [{user}'s Message Wall](<{url}wiki/"
"Message_Wall:{user_wall}>)"
msgstr ""
"[{author}]({author_url}) erstellte [{title}](<{wikiurl}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}>) auf der [Nachrichtenseite von {user}]"
"(<{url}wiki/Message_Wall:{user_wall}>)"
#: src/formatters/discussions.py:36
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}#{replyId}>) to [{title}](<{url}wiki/"
"Message_Wall:{user_wall}?threadId={threadId}) on [{user}'s Message Wall]"
"(<{url}wiki/Message_Wall:{user_wall}>)"
msgstr ""
"[{author}]({author_url}) erstellte eine [Antwort](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}#{replyId}>) auf [{title}](<{url}wiki/"
"Message_Wall:{user_wall}?threadId={threadId}>) auf der [Nachrichtenseite von "
"{user}](<{url}wiki/Message_Wall:{user_wall}>)"
#: src/formatters/discussions.py:40
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [comment](<{url}wiki/{article}?"
"threadId={threadId}>) on [{article}](<{url}wiki/{article}>)"
msgstr ""
"[{author}]({author_url}) erstellte ein [Kommentar](<{url}wiki/{article}?"
"commentId={commentId}>) zu [{article}](<{url}wiki/{article}>)"
#: src/formatters/discussions.py:42
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}wiki/{article}?"
"threadId={threadId}) to a [comment](<{url}wiki/{article}?threadId={threadId}"
"#{replyId}>) on [{article}](<{url}wiki/{article}>)"
msgstr ""
"[{author}]({author_url}) erstellte eine [Antwort](<{url}wiki/{article}?"
"threadId={threadId}>) auf ein [Kommentar](<{url}wiki/{article}?"
"commentId={commentId}&replyId={replyId}>) zu [{article}](<{url}wiki/{article}"
">)"
#: src/formatters/discussions.py:48
#, python-brace-format
msgid ""
"Unknown event `{event}` by [{author}]({author_url}), report it on the "
"[support server](<{support}>)."
msgstr ""
"Unbekanntes Event `{event}` von [{author}]({author_url}), melde es auf dem "
"[Support-Server](<{support}>)."
#: src/formatters/discussions.py:74
#, python-brace-format
msgid "Created \"{title}\""
msgstr "Erstellte „{title}“"
#: src/formatters/discussions.py:79
#, python-brace-format
msgid "Created a poll \"{title}\""
msgstr "Erstellte eine Umfrage „{title}“"
#: src/formatters/discussions.py:84
msgid "Option {}"
msgstr "Option {}"
#: src/formatters/discussions.py:85
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr "__[Bild öffnen]({image_url})__"
#: src/formatters/discussions.py:93
#, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Antwortete auf „{title}“"
#: src/formatters/discussions.py:102
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr "Erstellte „{title}“ auf der Nachrichtenseite von {user}"
#: src/formatters/discussions.py:106
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr "Antwortete auf „{title}“ auf der Nachrichtenseite von {user}"
#: src/formatters/discussions.py:112
#, python-brace-format
msgid "Commented on {article}"
msgstr "Kommentierte zu „{article}“"
#: src/formatters/discussions.py:116
#, python-brace-format
msgid "Replied to a comment on {article}"
msgstr "Antwortete auf ein Kommentar zu „{article}“"
#: src/formatters/discussions.py:120
#, python-brace-format
msgid "Unknown event `{event}`"
msgstr "Unbekanntes Event `{event}`"
#: src/formatters/discussions.py:125 src/formatters/discussions.py:127
msgid "Report this on the support server"
msgstr "Melde es auf dem Support-Server"
#, python-brace-format
#~ msgid ""
#~ "[{author}]({author_url}) created a quiz [{title}](<{url}f/p/{threadId}>) "
#~ "in {forumName}"
#~ msgstr ""
#~ "[{author}]({author_url}) erstellte ein Quiz [{title}](<{url}f/p/{threadId}"
#~ ">) in {forumName}"
#, python-brace-format
#~ msgid "Created a quiz \"{title}\""
#~ msgstr "Erstellte ein Quiz „{title}“"

Binary file not shown.

View file

@ -1,105 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
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: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"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Antwortete auf „{title}“"
#: 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: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}>) "
"in {forumName}"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) erstellte [{title}](<{url}f/p/{threadId}"
">) in {forumName}"
#: 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/"
"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}"
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}"

Binary file not shown.

View file

@ -1,24 +1,83 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-20 17:18+0200\n"
"PO-Revision-Date: 2019-05-20 17:25+0200\n"
"Language-Team: \n"
"POT-Creation-Date: 2020-08-10 00:59+0200\n"
"PO-Revision-Date: 2020-08-03 13:44+0000\n"
"Last-Translator: MarkusRost <sukramxro@gmail.com>\n"
"Language-Team: German <https://weblate.frisk.space/projects/rcgcdw/main/de/"
">\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.2.1\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.1.1\n"
"X-Loco-Source-Locale: de_DE\n"
"Generated-By: pygettext.py 1.5\n"
"X-Loco-Parser: loco_parse_po\n"
#: misc.py:76
#: src/misc.py:96
msgid "Location"
msgstr "Wohnort"
#: src/misc.py:96
msgid "About me"
msgstr "„Über mich“-Abschnitt"
#: src/misc.py:97
msgid "Google link"
msgstr "Google-Link"
#: src/misc.py:97
msgid "Facebook link"
msgstr "Facebook-Link"
#: src/misc.py:98
msgid "Twitter link"
msgstr "Twitter-Link"
#: src/misc.py:98
msgid "Reddit link"
msgstr "Reddit-Link"
#: src/misc.py:99
msgid "Twitch link"
msgstr "Twitch-Link"
#: src/misc.py:99
msgid "PSN link"
msgstr "PSN-Link"
#: src/misc.py:100
msgid "VK link"
msgstr "VK-Link"
#: src/misc.py:100
msgid "XBL link"
msgstr "Xbox-Live-Link"
#: src/misc.py:101
msgid "Steam link"
msgstr "Steam-Link"
#: src/misc.py:101
msgid "Discord handle"
msgstr "Discord-Link"
#: src/misc.py:102
msgid "Battle.net handle"
msgstr "Battle.net-Link"
#: src/misc.py:108
msgid "Unknown"
msgstr "Unbekannt"
#: src/misc.py:110
msgid "unknown"
msgstr "unbekannt"
#: src/misc.py:121
msgid ""
"\n"
"__And more__"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,37 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 01:01+0200\n"
"PO-Revision-Date: 2020-08-03 13:44+0000\n"
"Last-Translator: MarkusRost <sukramxro@gmail.com>\n"
"Language-Team: German <https://weblate.frisk.space/projects/rcgcdw/main/de/"
">\n"
"Language: de\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: Weblate 4.1.1\n"
"X-Loco-Source-Locale: de_DE\n"
"Generated-By: pygettext.py 1.5\n"
"X-Loco-Parser: loco_parse_po\n"
#: src/wiki.py:207
msgid "~~hidden~~"
msgstr "~~versteckt~~"
#: src/wiki.py:212
msgid "hidden"
msgstr "versteckt"
#, python-brace-format
#~ msgid "Connection to {wiki} seems to be stable now."
#~ msgstr "{wiki} scheint wieder erreichbar zu sein."
#~ msgid "Connection status"
#~ msgstr "Verbindungsstatus"
#, python-brace-format
#~ msgid "{wiki} seems to be down or unreachable."
#~ msgstr "Das {wiki} scheint unerreichbar zu sein."

Binary file not shown.

View file

@ -1,90 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: 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

@ -1,27 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-20 17:18+0200\n"
"PO-Revision-Date: 2019-05-20 17:32+0200\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: en\n"
#: misc.py:76
msgid ""
"\n"
"__And more__"
msgstr ""
"\n"
"__And more__"

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,36 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the RcGcDd package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: RcGcDd\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 16:46+0200\n"
"PO-Revision-Date: 2020-08-10 01:55+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3.1\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 "
"|| n%100>14) ? 1 : 2);\n"
#: src/discord.py:25
msgid "wiki deletion"
msgstr ""
#: src/discord.py:25 src/discord.py:26
msgid "wiki becoming inaccessible"
msgstr ""
#: src/discord.py:27
msgid "unknown error"
msgstr ""
#: src/discord.py:28
msgid "The webhook for {} has been removed due to {}."
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,175 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 16:45+0200\n"
"PO-Revision-Date: 2020-08-10 16:41+0000\n"
"Last-Translator: Frisk The Evil Goat Overlord <piotrex43@protonmail.ch>\n"
"Language-Team: Polish <https://weblate.frisk.space/projects/rcgcdw/"
"discussion_formatters-1/pl/>\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.1.1\n"
#: src/formatters/discussions.py:21
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}f/p/{threadId}>) "
"in {forumName}"
msgstr ""
"[{author}]({author_url}) utworzył(a) [{title}](<{url}f/p/{threadId}>) w "
"{forumName}"
#: src/formatters/discussions.py:23
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a poll [{title}](<{url}f/p/"
"{threadId}>) in {forumName}"
msgstr ""
"[{author}]({author_url}) utworzył(a) ankietę [{title}](<{url}f/p/{threadId}"
">) w {forumName}"
#: src/formatters/discussions.py:28
#, fuzzy, 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}]({author_url}) utworzył(a) [odpowiedź](<{url}f/p/{threadId}/r/"
"{postId}>) pod tematem [{title}](<{url}f/p/{threadId}>) w {forumName}"
#: src/formatters/discussions.py:30 src/formatters/discussions.py:38
#: src/formatters/discussions.py:96 src/formatters/discussions.py:108
msgid "unknown"
msgstr "nieznany"
#: src/formatters/discussions.py:34
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}>) on [{user}'s Message Wall](<{url}wiki/"
"Message_Wall:{user_wall}>)"
msgstr ""
"[{author}]({author_url}) utworzył(a) [{title}](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}>) na [tablicy wiadomości użytkownika/"
"użytkowniczki {user}](<{url}wiki/Message_Wall:{user_wall}>)"
#: src/formatters/discussions.py:36
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}#{replyId}>) to [{title}](<{url}wiki/"
"Message_Wall:{user_wall}?threadId={threadId}) on [{user}'s Message Wall]"
"(<{url}wiki/Message_Wall:{user_wall}>)"
msgstr ""
"[{author}]({author_url}) stworzył(-a) [odpowiedź](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}#{replyId}>) do [{title}](<{url}wiki/"
"Message_Wall:{user_wall}?threadId={threadId}>) na [tablicy wiadomości {user}]"
"(<{url}wiki/Message_Wall:{user_wall}>)"
#: src/formatters/discussions.py:40
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [comment](<{url}wiki/{article}?"
"threadId={threadId}>) on [{article}](<{url}wiki/{article}>)"
msgstr ""
"[{author}]({author_url}) utworzył(-a) [komentarz](<{url}wiki/{article}?"
"commentId={commentId}>) w [{article}](<{url}wiki/{article}>)"
#: src/formatters/discussions.py:42
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}wiki/{article}?"
"threadId={threadId}) to a [comment](<{url}wiki/{article}?threadId={threadId}"
"#{replyId}>) on [{article}](<{url}wiki/{article}>)"
msgstr ""
"[{author}]({author_url}) utworzył(-a) [odpowiedź](<{url}wiki/{article}?"
"threadId={threadId}) na [komentarz](<{url}wiki/{article}?"
"commentId={commentId}&replyId={replyId}>) w artykule [{article}](<{url}wiki/"
"{article}>)"
#: src/formatters/discussions.py:48
#, python-brace-format
msgid ""
"Unknown event `{event}` by [{author}]({author_url}), report it on the "
"[support server](<{support}>)."
msgstr ""
"Nieznane wydarzenie `{event}` wykonane przez [{author}]({author_url}), zgłoś "
"je na [serwerze wsparcia](<{support}>)."
#: src/formatters/discussions.py:74
#, python-brace-format
msgid "Created \"{title}\""
msgstr "Utworzył(a) „{title}”"
#: src/formatters/discussions.py:79
#, python-brace-format
msgid "Created a poll \"{title}\""
msgstr "Utworzył(a) ankietę zatytułowaną „{title}”"
#: src/formatters/discussions.py:84
msgid "Option {}"
msgstr "Opcja {}"
#: src/formatters/discussions.py:85
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr "__[Zobacz zdjęcie]({image_url})__"
#: src/formatters/discussions.py:93
#, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Odpowiedział(a) w „{title}”"
#: src/formatters/discussions.py:102
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr ""
"Utworzył(a) „{title}” na tablicy wiadomości użytkownika/użytkowniczki {user}"
#: src/formatters/discussions.py:106
#, 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}"
#: src/formatters/discussions.py:112
#, python-brace-format
msgid "Commented on {article}"
msgstr "Skomentował(a) „{article}”"
#: src/formatters/discussions.py:116
#, python-brace-format
msgid "Replied to a comment on {article}"
msgstr "Odpowiedział(a) na komentarz w „{article}”"
#: src/formatters/discussions.py:120
#, python-brace-format
msgid "Unknown event `{event}`"
msgstr "Nieznane wydarzenie `{event}`"
#: src/formatters/discussions.py:125 src/formatters/discussions.py:127
msgid "Report this on the support server"
msgstr "Zgłoś to na serwerze wsparcia"
#, python-brace-format
#~ msgid ""
#~ "[{author}]({author_url}) created a quiz [{title}](<{url}f/p/{threadId}>) "
#~ "in {forumName}"
#~ msgstr ""
#~ "[{author}]({author_url}) utworzył(a) quiz [{title}](<{url}f/p/{threadId}"
#~ ">) w {forumName}"
#, python-brace-format
#~ msgid "Created a quiz \"{title}\""
#~ msgstr "Utworzył(a) quiz „{title}”"

Binary file not shown.

View file

@ -1,110 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
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:12+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 "
"|| n%100>14) ? 1 : 2);\n"
#: discussions.py:56
#, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Odpowiedział(a) w „{title}”"
#: 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: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}>) "
"in {forumName}"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [{title}](<{url}f/p/{threadId}"
">) w {forumName}"
#: 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/"
"{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}"
msgstr ""
"[{author}](<{url}f/u/{creatorId}>) utworzył(a) [odpowiedź](<{url}f/p/"
"{threadId}/r/{postId}>) pod tematem [{title}](<{url}f/p/{threadId}>) w "
"{forumName}"
#: 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

@ -1,24 +1,85 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Project-Id-Version: RcGcDw\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-20 17:18+0200\n"
"PO-Revision-Date: 2019-05-20 17:23+0200\n"
"POT-Creation-Date: 2020-08-10 00:59+0200\n"
"PO-Revision-Date: 2020-03-17 20:57+0100\n"
"Last-Translator: Frisk <piotrex43@protonmail.ch>\n"
"Language-Team: \n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: pl\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
#: misc.py:76
#: src/misc.py:96
msgid "Location"
msgstr "Lokacja"
#: src/misc.py:96
msgid "About me"
msgstr "O mnie"
#: src/misc.py:97
msgid "Google link"
msgstr "link Google"
#: src/misc.py:97
msgid "Facebook link"
msgstr "link Facebook"
#: src/misc.py:98
msgid "Twitter link"
msgstr "link Twitter"
#: src/misc.py:98
msgid "Reddit link"
msgstr "link Reddit"
#: src/misc.py:99
msgid "Twitch link"
msgstr "link Twitch"
#: src/misc.py:99
msgid "PSN link"
msgstr "link PSN"
#: src/misc.py:100
msgid "VK link"
msgstr "link VK"
#: src/misc.py:100
msgid "XBL link"
msgstr "link XBL"
#: src/misc.py:101
msgid "Steam link"
msgstr "link Steam"
#: src/misc.py:101
msgid "Discord handle"
msgstr "konto Discord"
#: src/misc.py:102
msgid "Battle.net handle"
msgstr "konto Battle.net"
#: src/misc.py:108
msgid "Unknown"
msgstr "Nieznana"
#: src/misc.py:110
msgid "unknown"
msgstr "nieznana sekcja"
#: src/misc.py:121
msgid ""
"\n"
"__And more__"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,39 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: RcGcDw\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 01:01+0200\n"
"PO-Revision-Date: 2020-03-17 20:57+0100\n"
"Last-Translator: Frisk <piotrex43@protonmail.ch>\n"
"Language-Team: \n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
#: src/wiki.py:207
msgid "~~hidden~~"
msgstr "~~ukryte~~"
#: src/wiki.py:212
msgid "hidden"
msgstr "ukryte"
#, python-brace-format
#~ msgid "Connection to {wiki} seems to be stable now."
#~ msgstr "Połączenie z {wiki} wygląda na stabilne."
#~ msgid "Connection status"
#~ msgstr "Problem z połączeniem"
#, python-brace-format
#~ msgid "{wiki} seems to be down or unreachable."
#~ msgstr "{wiki} nie działa lub jest nieosiągalna."

Binary file not shown.

View file

@ -0,0 +1,35 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the RcGcDd package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: RcGcDd\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 16:46+0200\n"
"PO-Revision-Date: 2020-08-10 01:53+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3.1\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/discord.py:25
msgid "wiki deletion"
msgstr ""
#: src/discord.py:25 src/discord.py:26
msgid "wiki becoming inaccessible"
msgstr ""
#: src/discord.py:27
msgid "unknown error"
msgstr ""
#: src/discord.py:28
msgid "The webhook for {} has been removed due to {}."
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,168 @@
# 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-08-10 16:45+0200\n"
"PO-Revision-Date: 2020-08-10 14:11+0000\n"
"Last-Translator: Eduaddad <duduaddad@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://weblate.frisk.space/projects/"
"rcgcdw/discussion_formatters-1/pt_BR/>\n"
"Language: pt-br\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: Weblate 4.1.1\n"
#: src/formatters/discussions.py:21
#, fuzzy, 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}"
#: src/formatters/discussions.py:23
#, fuzzy, 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}>)criou uma enquete [{title}](<{url}f/p/"
"{threadId}>) no {forumName}"
#: src/formatters/discussions.py:28
#, fuzzy, 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}]({author_url}) criou uma [resposta](<{url}f/p/{threadId}/r/{postId}"
">) para [{title}](<{url}f/p/{threadId}>) em {forumName}"
#: src/formatters/discussions.py:30 src/formatters/discussions.py:38
#: src/formatters/discussions.py:96 src/formatters/discussions.py:108
msgid "unknown"
msgstr "desconhecido"
#: src/formatters/discussions.py:34
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created [{title}](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}>) on [{user}'s Message Wall](<{url}wiki/"
"Message_Wall:{user_wall}>)"
msgstr ""
"[{author}]({author_url}) criou [{title}](<{url}wiki/Message_Wall:{user_wall}?"
"threadId={threadId}>) no mural de mensagens de [{user}(<{url}wiki/"
"Message_Wall:{user_wall}>)"
#: src/formatters/discussions.py:36
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}#{replyId}>) to [{title}](<{url}wiki/"
"Message_Wall:{user_wall}?threadId={threadId}) on [{user}'s Message Wall]"
"(<{url}wiki/Message_Wall:{user_wall}>)"
msgstr ""
"[{author}]({author_url}) criou uma [resposta](<{url}wiki/Message_Wall:"
"{user_wall}?threadId={threadId}#{replyId}>) para [{title}](<{url}wiki/"
"Message_Wall:{user_wall}?threadId={threadId}>) no mural de mensagens de "
"[{user}](<{url}wiki/Message_Wall:{user_wall}>)"
#: src/formatters/discussions.py:40
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [comment](<{url}wiki/{article}?"
"threadId={threadId}>) on [{article}](<{url}wiki/{article}>)"
msgstr ""
"[{author}]({author_url})criou um [comentário](<{url}wiki/{article}?"
"commentId={commentId}>) no [{article}](<{url}wiki/{article}>)"
#: src/formatters/discussions.py:42
#, fuzzy, python-brace-format
msgid ""
"[{author}](<{url}f/u/{creatorId}>) created a [reply](<{url}wiki/{article}?"
"threadId={threadId}) to a [comment](<{url}wiki/{article}?threadId={threadId}"
"#{replyId}>) on [{article}](<{url}wiki/{article}>)"
msgstr ""
"[{author}]({author_url}) criou uma [resposta](<{url}wiki/{article}?"
"threadId={threadId}) para um [comentário](<{url}wiki/{article}?"
"commentId={commentId}&replyId={replyId}>) no [{article}](<{url}wiki/{article}"
">)"
#: src/formatters/discussions.py:48
#, python-brace-format
msgid ""
"Unknown event `{event}` by [{author}]({author_url}), report it on the "
"[support server](<{support}>)."
msgstr ""
#: src/formatters/discussions.py:74
#, python-brace-format
msgid "Created \"{title}\""
msgstr "Criado \"{title}\""
#: src/formatters/discussions.py:79
#, python-brace-format
msgid "Created a poll \"{title}\""
msgstr "Criou uma enquete \"{title}\""
#: src/formatters/discussions.py:84
msgid "Option {}"
msgstr "Option {}"
#: src/formatters/discussions.py:85
#, python-brace-format
msgid "__[View image]({image_url})__"
msgstr "__[Ver imagem]({image_url})__"
#: src/formatters/discussions.py:93
#, python-brace-format
msgid "Replied to \"{title}\""
msgstr "Respondido o \"{title}\""
#: src/formatters/discussions.py:102
#, python-brace-format
msgid "Created \"{title}\" on {user}'s Message Wall"
msgstr "Criado \"{title}\" no mural de mensagem de {user}"
#: src/formatters/discussions.py:106
#, python-brace-format
msgid "Replied to \"{title}\" on {user}'s Message Wall"
msgstr "Respondeu a \"{title}\" no mural de mensagem de {user}"
#: src/formatters/discussions.py:112
#, python-brace-format
msgid "Commented on {article}"
msgstr "Comentou em {article}"
#: src/formatters/discussions.py:116
#, python-brace-format
msgid "Replied to a comment on {article}"
msgstr "Respondeu a um comentário em {article}"
#: src/formatters/discussions.py:120
#, python-brace-format
msgid "Unknown event `{event}`"
msgstr ""
#: src/formatters/discussions.py:125 src/formatters/discussions.py:127
msgid "Report this on the support server"
msgstr ""
#, fuzzy, python-brace-format
#~ msgid ""
#~ "[{author}]({author_url}) created a quiz [{title}](<{url}f/p/{threadId}>) "
#~ "in {forumName}"
#~ msgstr ""
#~ "[{author}](<{url}f/u/{creatorId}>)criou uma enquete [{title}](<{url}f/p/"
#~ "{threadId}>) no {forumName}"
#, python-brace-format
#~ msgid "Created a quiz \"{title}\""
#~ msgstr "Criou uma enquete \"{title}\""

View file

@ -1,106 +0,0 @@
# 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 ""

Binary file not shown.

View file

@ -7,18 +7,79 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-20 17:18+0200\n"
"PO-Revision-Date: 2019-05-21 01:22+0200\n"
"Language-Team: \n"
"POT-Creation-Date: 2020-08-10 00:59+0200\n"
"PO-Revision-Date: 2020-08-04 09:51+0000\n"
"Last-Translator: Eduaddad <duduaddad@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://weblate.frisk.space/projects/"
"rcgcdw/main/pt_BR/>\n"
"Language: pt-br\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language: pt\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.1.1\n"
#: misc.py:76
#: src/misc.py:96
msgid "Location"
msgstr "Localização"
#: src/misc.py:96
msgid "About me"
msgstr "Sobre mim"
#: src/misc.py:97
msgid "Google link"
msgstr "Link do Google"
#: src/misc.py:97
msgid "Facebook link"
msgstr "Facebook link"
#: src/misc.py:98
msgid "Twitter link"
msgstr "Link do Twitter"
#: src/misc.py:98
msgid "Reddit link"
msgstr "Link do Reddit"
#: src/misc.py:99
msgid "Twitch link"
msgstr "Link do Twitch"
#: src/misc.py:99
msgid "PSN link"
msgstr "Link do PSN"
#: src/misc.py:100
msgid "VK link"
msgstr "Link do VK"
#: src/misc.py:100
msgid "XBL link"
msgstr "Link do XBL"
#: src/misc.py:101
msgid "Steam link"
msgstr "Link do Steam"
#: src/misc.py:101
msgid "Discord handle"
msgstr "Link do Discord"
#: src/misc.py:102
msgid "Battle.net handle"
msgstr "Link do Battle.net"
#: src/misc.py:108
msgid "Unknown"
msgstr "Desconhecido"
#: src/misc.py:110
msgid "unknown"
msgstr "desconhecido"
#: src/misc.py:121
msgid ""
"\n"
"__And more__"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,39 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-10 01:01+0200\n"
"PO-Revision-Date: 2020-08-04 09:51+0000\n"
"Last-Translator: Eduaddad <duduaddad@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://weblate.frisk.space/projects/"
"rcgcdw/main/pt_BR/>\n"
"Language: pt-br\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: Weblate 4.1.1\n"
#: src/wiki.py:207
msgid "~~hidden~~"
msgstr "~~ocultado~~"
#: src/wiki.py:212
msgid "hidden"
msgstr "oculto"
#, python-brace-format
#~ msgid "Connection to {wiki} seems to be stable now."
#~ msgstr "A conexão com {wiki} parece estar estável agora."
#~ msgid "Connection status"
#~ msgstr "Status da conexão"
#, python-brace-format
#~ msgid "{wiki} seems to be down or unreachable."
#~ msgstr "{wiki} parece estar inativo ou inacessível."

View file

@ -1,4 +1,3 @@
beautifulsoup4 >= 4.6.0; python_version >= '3.6'
requests >= 2.18.4
aiohttp >= 3.6.2
lxml >= 4.2.1

View file

@ -0,0 +1,18 @@
cd ..
xgettext -L Python --package-name=RcGcDb -o locale/templates/discussion_formatters.pot src/formatters/discussions.py
xgettext -L Python --package-name=RcGcDb -o locale/templates/rc_formatters.pot src/formatters/rc.py
xgettext -L Python --package-name=RcGcDb -o locale/templates/wiki.pot src/wiki.py
xgettext -L Python --package-name=RcGcDb -o locale/templates/discord.pot src/discord.py
xgettext -L Python --package-name=RcGcDb -o locale/templates/misc.pot src/misc.py
declare -a StringArray=("discussion_formatters" "rc_formatters" "discord" "wiki" "misc")
for language in de pl pt-br
do
for file in ${StringArray[@]}; do
msgmerge -U locale/$language/LC_MESSAGES/$file.po locale/templates/$file.pot
done
msgmerge -o locale/$language/LC_MESSAGES/discussion_formatters.po ~/PycharmProjects/RcGcDw/locale/$language/LC_MESSAGES/discussion_formatters.po locale/$language/LC_MESSAGES/discussion_formatters.po
msgmerge -o locale/$language/LC_MESSAGES/rc_formatters.po ~/PycharmProjects/RcGcDw/locale/$language/LC_MESSAGES/rc_formatters.po locale/$language/LC_MESSAGES/rc_formatters.po
msgmerge -o locale/$language/LC_MESSAGES/wiki.po ~/PycharmProjects/RcGcDw/locale/$language/LC_MESSAGES/rc.po locale/$language/LC_MESSAGES/wiki.po
msgmerge -o locale/$language/LC_MESSAGES/misc.po ~/PycharmProjects/RcGcDw/locale/$language/LC_MESSAGES/misc.po locale/$language/LC_MESSAGES/misc.po
done

View file

@ -2,21 +2,21 @@ import aiohttp
import asyncio
import logging.config
import signal
import sys
import traceback
from collections import defaultdict
import requests
from collections import defaultdict, namedtuple
from typing import Generator
from contextlib import asynccontextmanager
from src.argparser import command_line_args
from src.config import settings
from src.database import db_cursor
from src.exceptions import *
from src.misc import get_paths
from src.misc import get_paths, get_domain
from src.msgqueue import messagequeue
from src.queue_handler import DBHandler
from src.wiki import Wiki, process_cats, process_mwmsgs, essential_info, essential_feeds
from src.discord import DiscordMessage, formatter_exception_logger, msg_sender_exception_logger
from src.discord import DiscordMessage, generic_msg_sender_exception_logger
from src.wiki_ratelimiter import RateLimiter
logging.config.dictConfig(settings["logging"])
logger = logging.getLogger("rcgcdb.bot")
@ -34,20 +34,135 @@ mw_msgs: dict = {} # will have the type of id: tuple
# Reasons for this: 1. we require amount of wikis to calculate the cooldown between requests
# 2. Easier to code
for wiki in db_cursor.execute('SELECT DISTINCT wiki FROM rcgcdw'):
all_wikis[wiki] = Wiki()
for db_wiki in db_cursor.execute('SELECT wiki, rcid FROM rcgcdw GROUP BY wiki ORDER BY ROWID'):
all_wikis[db_wiki["wiki"]] = Wiki() # populate all_wikis
all_wikis[db_wiki["wiki"]].rc_active = db_wiki["rcid"]
queue_limit = settings.get("queue_limit", 30)
QueuedWiki = namedtuple("QueuedWiki", ['url', 'amount'])
class LimitedList(list):
def __init__(self, *args):
list.__init__(self, *args)
def append(self, object) -> None:
if len(self) < queue_limit:
self.insert(len(self), object)
return
raise ListFull
class RcQueue:
def __init__(self):
self.domain_list = {}
self.to_remove = []
async def start_group(self, group, initial_wikis):
"""Starts a task for given domain group"""
if group not in self.domain_list:
self.domain_list[group] = {"task": asyncio.create_task(scan_group(group), name=group), "last_rowid": 0, "query": LimitedList(initial_wikis), "rate_limiter": RateLimiter()}
else:
raise KeyError
async def remove_wiki_from_group(self, wiki):
"""Removes a wiki from query of given domain group"""
logger.debug(f"Removing {wiki} from group queue.")
group = get_domain(wiki)
self[group]["query"] = [x for x in self[group]["query"] if x.url == wiki]
if not self[group]["query"]: # if there is no wiki left in the queue, get rid of the task
logger.debug(f"{group} no longer has any wikis queued!")
all_wikis[wiki].rc_active = -1
self[group]["task"].cancel()
del self.domain_list[group]
@asynccontextmanager
async def retrieve_next_queued(self, group) -> Generator[QueuedWiki, None, None]:
"""Retrives next wiki in the queue for given domain"""
try:
yield self.domain_list[group]["query"][0]
except asyncio.CancelledError:
raise
except:
if command_line_args.debug:
logger.exception("RC Group exception")
shutdown(asyncio.get_event_loop())
else:
logger.exception("Group task returned error")
await generic_msg_sender_exception_logger(traceback.format_exc(), "Group task error logger", Group=group)
else:
self.domain_list[group]["query"].pop(0)
@staticmethod
def filter_rc_active(wiki_obj):
return wiki_obj[1].rc_active > -1
async def update_queues(self):
"""Makes a round on rcgcdw DB and looks for updates to the queues in self.domain_list"""
try:
fetch_all = db_cursor.execute(
'SELECT ROWID, webhook, wiki, lang, display, wikiid, rcid FROM rcgcdw WHERE rcid != -1 GROUP BY wiki ORDER BY ROWID')
self.to_remove = [x[0] for x in filter(self.filter_rc_active, all_wikis.items())] # first populate this list and remove wikis that are still in the db, clean up the rest
full = []
for db_wiki in fetch_all.fetchall():
domain = get_domain(db_wiki["wiki"])
try:
if db_wiki["wiki"] not in all_wikis:
raise AssertionError
self.to_remove.remove(db_wiki["wiki"])
except AssertionError:
all_wikis[db_wiki["wiki"]] = Wiki()
all_wikis[db_wiki["wiki"]].rc_active = db_wiki["rcid"]
except ValueError:
pass
try:
current_domain: dict = self[domain]
if not db_wiki["ROWID"] < current_domain["last_rowid"]:
current_domain["query"].append(QueuedWiki(db_wiki["wiki"], 20))
except KeyError:
await self.start_group(domain, db_wiki)
logger.info("A new domain group has been added since last time, adding it to the domain_list and starting a task...")
except ListFull:
full.append(domain)
current_domain["last_rowid"] = db_wiki["ROWID"]
continue
for wiki in self.to_remove:
await self.remove_wiki_from_group(wiki)
for group, data in self.domain_list.items():
if group not in full:
self[group]["last_rowid"] = 0 # iter reached the end without being stuck on full list
logger.debug("Current domain_list structure: {}".format(self.domain_list))
except:
if command_line_args.debug:
logger.exception("Queue error!")
shutdown(asyncio.get_event_loop())
else:
logger.exception("Exception on queue updater")
await generic_msg_sender_exception_logger(traceback.format_exc(), "Queue updator")
def __getitem__(self, item):
"""Returns the query of given domain group"""
return self.domain_list[item]
def __setitem__(self, key, value):
self.domain_list[key] = value
rcqueue = RcQueue()
# Start queueing logic
def calculate_delay() -> float:
def calculate_delay_for_group(group_length: int) -> float:
"""Calculate the delay between fetching each wiki to avoid rate limits"""
min_delay = 60 / settings["max_requests_per_minute"]
if (len(all_wikis) * min_delay) < settings["minimal_cooldown_per_wiki_in_sec"]:
return settings["minimal_cooldown_per_wiki_in_sec"] / len(all_wikis)
if group_length == 0:
group_length = 1
if (group_length * min_delay) < settings["minimal_cooldown_per_wiki_in_sec"]:
return settings["minimal_cooldown_per_wiki_in_sec"] / group_length
else:
return min_delay
return 0.0
def generate_targets(wiki_url: str) -> defaultdict:
@ -62,129 +177,110 @@ def generate_targets(wiki_url: str) -> defaultdict:
return combinations
async def generate_domain_groups():
"""Generate a list of wikis per domain (fandom.com, wikipedia.org etc.)"""
domain_wikis = defaultdict(list)
fetch_all = db_cursor.execute('SELECT ROWID, webhook, wiki, lang, display, wikiid, rcid FROM rcgcdw WHERE rcid != -1 GROUP BY wiki ORDER BY ROWID ASC')
for db_wiki in fetch_all.fetchall():
domain_wikis[get_domain(db_wiki["wiki"])].append(QueuedWiki(db_wiki["wiki"], 20))
for group, db_wikis in domain_wikis.items():
yield group, db_wikis
async def scan_group(group: str):
rate_limiter = rcqueue[group]["rate_limiter"]
while True:
try:
async with rcqueue.retrieve_next_queued(group) as queued_wiki: # acquire next wiki in queue
logger.debug("Wiki {}".format(queued_wiki.url))
local_wiki = all_wikis[queued_wiki.url] # set a reference to a wiki object from memory
extended = False
if local_wiki.mw_messages is None:
extended = True
async with aiohttp.ClientSession(headers=settings["header"],
timeout=aiohttp.ClientTimeout(3.0)) as session:
try:
wiki_response = await local_wiki.fetch_wiki(extended, queued_wiki.url, session, rate_limiter, amount=queued_wiki.amount)
await local_wiki.check_status(queued_wiki.url, wiki_response.status)
except (WikiServerError, WikiError):
logger.error("Exeption when fetching the wiki")
continue # ignore this wiki if it throws errors
try:
recent_changes_resp = await wiki_response.json()
if "error" in recent_changes_resp or "errors" in recent_changes_resp:
error = recent_changes_resp.get("error", recent_changes_resp["errors"])
if error["code"] == "readapidenied":
await local_wiki.fail_add(queued_wiki.url, 410)
continue
raise WikiError
recent_changes = recent_changes_resp['query']['recentchanges']
recent_changes.reverse()
except aiohttp.ContentTypeError:
logger.exception("Wiki seems to be resulting in non-json content.")
await local_wiki.fail_add(queued_wiki.url, 410)
continue
except:
logger.exception("On loading json of response.")
continue
if extended:
await process_mwmsgs(recent_changes_resp, local_wiki, mw_msgs)
if local_wiki.rc_active == 0: # new wiki, just get the last rc to not spam the channel
if len(recent_changes) > 0:
local_wiki.rc_active = recent_changes[-1]["rcid"]
DBHandler.add(queued_wiki.url, recent_changes[-1]["rcid"])
else:
local_wiki.rc_active = 0
DBHandler.add(queued_wiki.url, 0)
DBHandler.update_db()
continue
categorize_events = {}
targets = generate_targets(queued_wiki.url)
paths = get_paths(queued_wiki.url, recent_changes_resp)
new_events = 0
for change in recent_changes:
if change["rcid"] > local_wiki.rc_active and queued_wiki.amount != 450:
new_events += 1
if new_events == 20:
# call the function again with max limit for more results, ignore the ones in this request
logger.debug("There were too many new events, queuing wiki with 450 limit.")
rcqueue[group]["query"].insert(1, QueuedWiki(queued_wiki.url, 450))
break
await process_cats(change, local_wiki, mw_msgs, categorize_events)
else: # If we broke from previous loop (too many changes) don't execute sending messages here
for change in recent_changes: # Yeah, second loop since the categories require to be all loaded up
if change["rcid"] > local_wiki.rc_active:
for target in targets.items():
try:
await essential_info(change, categorize_events, local_wiki, target, paths,
recent_changes_resp, rate_limiter)
except asyncio.CancelledError:
raise
except:
if command_line_args.debug:
logger.exception("Exception on RC formatter")
raise
else:
logger.exception("Exception on RC formatter")
await generic_msg_sender_exception_logger(traceback.format_exc(), "Exception in RC formatter", Wiki=queued_wiki.url, Change=str(change)[0:1000])
if recent_changes:
local_wiki.rc_active = change["rcid"]
DBHandler.add(queued_wiki.url, change["rcid"])
delay_between_wikis = calculate_delay_for_group(len(rcqueue[group]["query"])) # TODO Find a way to not execute it every wiki
await asyncio.sleep(delay_between_wikis)
DBHandler.update_db()
except asyncio.CancelledError:
return
async def wiki_scanner():
"""Wiki scanner is spawned as a task which purpose is to continuously run over wikis in the DB, fetching recent changes
to add messages based on the changes to message queue later handled by message_sender coroutine."""
try:
async for group, db_wikis in generate_domain_groups(): # First scan
await rcqueue.start_group(group, db_wikis)
while True:
calc_delay = calculate_delay()
fetch_all = db_cursor.execute(
'SELECT webhook, wiki, lang, display, wikiid, rcid, postid FROM rcgcdw GROUP BY wiki')
for db_wiki in fetch_all.fetchall():
logger.debug("Wiki {}".format(db_wiki["wiki"]))
if db_wiki["wiki"] not in all_wikis:
logger.info("Registering new wiki locally: {}".format(db_wiki["wiki"]))
all_wikis[db_wiki["wiki"]] = Wiki()
local_wiki = all_wikis[db_wiki["wiki"]] # set a reference to a wiki object from memory
if db_wiki["rcid"] != -1:
extended = False
if local_wiki.mw_messages is None:
extended = True
async with aiohttp.ClientSession(headers=settings["header"],
timeout=aiohttp.ClientTimeout(3.0)) as session:
try:
wiki_response = await local_wiki.fetch_wiki(extended, db_wiki["wiki"], session)
await local_wiki.check_status(db_wiki["wiki"], wiki_response.status)
except (WikiServerError, WikiError):
logger.error("Exeption when fetching the wiki")
continue # ignore this wiki if it throws errors
try:
recent_changes_resp = await wiki_response.json()
if "error" in recent_changes_resp or "errors" in recent_changes_resp:
error = recent_changes_resp.get("error", recent_changes_resp["errors"])
if error["code"] == "readapidenied":
await local_wiki.fail_add(db_wiki["wiki"], 410)
continue
raise WikiError
recent_changes = recent_changes_resp['query']['recentchanges']
recent_changes.reverse()
except aiohttp.ContentTypeError:
logger.exception("Wiki seems to be resulting in non-json content.")
await local_wiki.fail_add(db_wiki["wiki"], 410)
continue
except:
logger.exception("On loading json of response.")
continue
if extended:
await process_mwmsgs(recent_changes_resp, local_wiki, mw_msgs)
if db_wiki["rcid"] is None: # new wiki, just get the last rc to not spam the channel
if len(recent_changes) > 0:
DBHandler.add(db_wiki["wiki"], recent_changes[-1]["rcid"])
else:
DBHandler.add(db_wiki["wiki"], 0)
DBHandler.update_db()
continue
categorize_events = {}
targets = generate_targets(db_wiki["wiki"])
paths = get_paths(db_wiki["wiki"], recent_changes_resp)
for change in recent_changes:
await process_cats(change, local_wiki, mw_msgs, categorize_events)
for change in recent_changes: # Yeah, second loop since the categories require to be all loaded up
if change["rcid"] > db_wiki["rcid"]:
for target in targets.items():
try:
await essential_info(change, categorize_events, local_wiki, db_wiki,
target, paths, recent_changes_resp)
except:
if command_line_args.debug:
raise # reraise the issue
else:
logger.exception("Exception on RC formatter")
await formatter_exception_logger(db_wiki["wiki"], change, traceback.format_exc())
if recent_changes:
DBHandler.add(db_wiki["wiki"], change["rcid"])
await asyncio.sleep(delay=2.0) # temporary measure until rate limiting is not implemented
if db_wiki["wikiid"] is not None:
header = settings["header"]
header["Accept"] = "application/hal+json"
async with aiohttp.ClientSession(headers=header,
timeout=aiohttp.ClientTimeout(3.0)) as session:
try:
feeds_response = await local_wiki.fetch_feeds(db_wiki["wikiid"], session)
except (WikiServerError, WikiError):
logger.error("Exeption when fetching the wiki")
continue # ignore this wiki if it throws errors
try:
discussion_feed_resp = await feeds_response.json(encoding="UTF-8")
if "title" in discussion_feed_resp:
error = discussion_feed_resp["error"]
if error == "site doesn't exists":
db_cursor.execute("UPDATE rcgcdw SET wikiid = ? WHERE wiki = ?",
(None, db_wiki["wiki"],))
DBHandler.update_db()
continue
raise WikiError
discussion_feed = discussion_feed_resp["_embedded"]["doc:posts"]
discussion_feed.reverse()
except aiohttp.ContentTypeError:
logger.exception("Wiki seems to be resulting in non-json content.")
continue
except:
logger.exception("On loading json of response.")
continue
if db_wiki["postid"] is None: # new wiki, just get the last post to not spam the channel
if len(discussion_feed) > 0:
DBHandler.add(db_wiki["wiki"], discussion_feed[-1]["id"], True)
else:
DBHandler.add(db_wiki["wiki"], "0", True)
DBHandler.update_db()
continue
targets = generate_targets(db_wiki["wiki"])
for post in discussion_feed:
if post["id"] > db_wiki["postid"]:
for target in targets.items():
try:
await essential_feeds(post, db_wiki, target)
except:
if command_line_args.debug:
raise # reraise the issue
else:
logger.exception("Exception on Feeds formatter")
await formatter_exception_logger(db_wiki["wiki"], post, traceback.format_exc())
if discussion_feed:
DBHandler.add(db_wiki["wiki"], post["id"], True)
await asyncio.sleep(delay=calc_delay)
DBHandler.update_db()
await asyncio.sleep(20.0)
await rcqueue.update_queues()
except asyncio.CancelledError:
raise
@ -194,35 +290,111 @@ async def message_sender():
try:
while True:
await messagequeue.resend_msgs()
except asyncio.CancelledError:
pass
except:
if command_line_args.debug:
logger.exception("Exception on DC message sender")
raise # reraise the issue
shutdown(loop=asyncio.get_event_loop())
else:
logger.exception("Exception on DC message sender")
await msg_sender_exception_logger(traceback.format_exc())
await generic_msg_sender_exception_logger(traceback.format_exc(), "Message sender exception")
async def discussion_handler():
try:
while True:
fetch_all = db_cursor.execute(
'SELECT wiki, wikiid, postid FROM rcgcdw WHERE wikiid IS NOT NULL')
for db_wiki in fetch_all.fetchall():
header = settings["header"]
header["Accept"] = "application/hal+json"
async with aiohttp.ClientSession(headers=header,
timeout=aiohttp.ClientTimeout(3.0)) as session:
try:
local_wiki = all_wikis[db_wiki["wiki"]] # set a reference to a wiki object from memory
except KeyError:
local_wiki = all_wikis[db_wiki["wiki"]] = Wiki()
try:
feeds_response = await local_wiki.fetch_feeds(db_wiki["wikiid"], session)
except (WikiServerError, WikiError):
logger.error("Exeption when fetching the wiki")
continue # ignore this wiki if it throws errors
try:
discussion_feed_resp = await feeds_response.json(encoding="UTF-8")
if "title" in discussion_feed_resp:
error = discussion_feed_resp["error"]
if error == "site doesn't exists":
db_cursor.execute("UPDATE rcgcdw SET wikiid = ? WHERE wiki = ?",
(None, db_wiki["wiki"],))
DBHandler.update_db()
continue
raise WikiError
discussion_feed = discussion_feed_resp["_embedded"]["doc:posts"]
discussion_feed.reverse()
except aiohttp.ContentTypeError:
logger.exception("Wiki seems to be resulting in non-json content.")
continue
except:
logger.exception("On loading json of response.")
continue
if db_wiki["postid"] is None: # new wiki, just get the last post to not spam the channel
if len(discussion_feed) > 0:
DBHandler.add(db_wiki["wikiid"], discussion_feed[-1]["id"], True)
else:
DBHandler.add(db_wiki["wikiid"], "0", True)
DBHandler.update_db()
continue
targets = generate_targets(db_wiki["wiki"])
for post in discussion_feed:
if post["id"] > db_wiki["postid"]:
for target in targets.items():
try:
await essential_feeds(post, db_wiki, target)
except asyncio.CancelledError:
raise
except:
if command_line_args.debug:
logger.exception("Exception on Feeds formatter")
shutdown(loop=asyncio.get_event_loop())
else:
logger.exception("Exception on Feeds formatter")
await generic_msg_sender_exception_logger(traceback.format_exc(), "Exception in feed formatter", Post=str(post)[0:1000], Wiki=db_wiki["wiki"])
if discussion_feed:
DBHandler.add(db_wiki["wikiid"], post["id"], True)
await asyncio.sleep(delay=2.0) # hardcoded really doesn't need much more
DBHandler.update_db()
except asyncio.CancelledError:
pass
except:
if command_line_args.debug:
raise # reraise the issue
else:
logger.exception("Exception on Feeds formatter")
await generic_msg_sender_exception_logger(traceback.format_exc(), "Discussion handler task exception", Wiki=db_wiki["wiki"])
def shutdown(loop, signal=None):
DBHandler.update_db()
if len(messagequeue) > 0:
logger.warning("Some messages are still queued!")
loop.stop()
logger.info("Script has shut down due to signal {}.".format(signal))
for task in asyncio.all_tasks(loop):
logger.debug("Killing task {}".format(task.get_name()))
task.cancel()
sys.exit(0)
loop.run_until_complete(asyncio.gather(*asyncio.all_tasks(loop)))
loop.stop()
logger.info("Script has shut down due to signal {}.".format(signal))
# sys.exit(0)
def global_exception_handler(loop, context):
"""Global exception handler for asyncio, lets us know when something crashes"""
msg = context.get("exception", context["message"])
logger.error("Global exception handler: {}".format(msg))
if command_line_args.debug is False:
requests.post("https://discord.com/api/webhooks/"+settings["monitoring_webhook"], data=repr(DiscordMessage("compact", "monitoring", [settings["monitoring_webhook"]], wiki=None, content="[RcGcDb] Global exception handler: {}".format(msg))), headers={'Content-Type': 'application/json'})
else:
shutdown(loop)
# def global_exception_handler(loop, context):
# """Global exception handler for asyncio, lets us know when something crashes"""
# msg = context.get("exception", context["message"])
# logger.error("Global exception handler: {}".format(msg))
# if command_line_args.debug is False:
# requests.post("https://discord.com/api/webhooks/"+settings["monitoring_webhook"], data=repr(DiscordMessage("compact", "monitoring", [settings["monitoring_webhook"]], wiki=None, content="[RcGcDb] Global exception handler: {}".format(msg))), headers={'Content-Type': 'application/json'})
# else:
# shutdown(loop)
async def main_loop():
@ -235,14 +407,15 @@ async def main_loop():
except AttributeError:
logger.info("Running on Windows, some things may not work as they should.")
signals = (signal.SIGBREAK, signal.SIGTERM, signal.SIGINT)
loop.set_exception_handler(global_exception_handler)
# loop.set_exception_handler(global_exception_handler)
try:
task1 = asyncio.create_task(wiki_scanner())
task2 = asyncio.create_task(message_sender())
task1 = asyncio.create_task(wiki_scanner(), name="Wiki RC scanner")
task2 = asyncio.create_task(message_sender(), name="Discord message sender")
task3 = asyncio.create_task(discussion_handler(), name="Discussion handler")
await task1
await task2
await task3
except KeyboardInterrupt:
shutdown(loop)
asyncio.run(main_loop(), debug=command_line_args.debug)
asyncio.run(main_loop(), debug=False)

View file

@ -21,9 +21,7 @@ default_header["X-RateLimit-Precision"] = "millisecond"
# User facing webhook functions
async def wiki_removal(wiki_url, status):
for observer in db_cursor.execute('SELECT webhook, lang FROM rcgcdw WHERE wiki = ?', (wiki_url,)):
def _(string: str) -> str:
"""Our own translation string to make it compatible with async"""
return langs[observer["lang"]].gettext(string)
_ = langs[observer["lang"]]["discord"].gettext
reasons = {410: _("wiki deletion"), 404: _("wiki deletion"), 401: _("wiki becoming inaccessible"),
402: _("wiki becoming inaccessible"), 403: _("wiki becoming inaccessible")}
reason = reasons.get(status, _("unknown error"))
@ -110,23 +108,13 @@ async def wiki_removal_monitor(wiki_url, status):
await send_to_discord_webhook_monitoring(DiscordMessage("compact", "webhook/remove", content="Removing {} because {}.".format(wiki_url, status), webhook_url=[None], wiki=None))
async def formatter_exception_logger(wiki_url, change, exception):
"""Creates a Discord message reporting a crash in RC formatter area"""
async def generic_msg_sender_exception_logger(exception: str, title: str, **kwargs):
"""Creates a Discord message reporting a crash"""
message = DiscordMessage("embed", "bot/exception", [None], wiki=None)
message["description"] = exception
message["title"] = "RC Exception Report"
change = str(change)[0:1000]
message.add_field("Wiki URL", wiki_url)
message.add_field("Change", change)
message.finish_embed()
await send_to_discord_webhook_monitoring(message)
async def msg_sender_exception_logger(exception):
"""Creates a Discord message reporting a crash in RC formatter area"""
message = DiscordMessage("embed", "bot/exception", [None], wiki=None)
message["description"] = exception
message["title"] = "MSGSENDER Exception Report"
message["title"] = title
for key, value in kwargs:
message.add_field(key, value)
message.finish_embed()
await send_to_discord_webhook_monitoring(message)

View file

@ -15,3 +15,9 @@ class WikiUnauthorizedError(Exception):
class OtherWikiError(Exception):
pass
class QueueEmpty(Exception):
pass
class ListFull(Exception):
pass

View file

@ -6,12 +6,14 @@ from src.config import settings
from src.misc import link_formatter, create_article_path, escape_formatting
from src.discord import DiscordMessage
from src.msgqueue import send_to_discord
from src.i18n import langs
logger = logging.getLogger("rcgcdw.discussion_formatters")
async def feeds_compact_formatter(post_type, post, message_target, wiki, _):
async def feeds_compact_formatter(post_type, post, message_target, wiki):
"""Compact formatter for Fandom discussions."""
_ = langs[message_target[0][0]]["discussion_formatters"].gettext
message = None
if post_type == "FORUM":
author = post["createdBy"]["name"]
@ -25,14 +27,14 @@ async def feeds_compact_formatter(post_type, post, message_target, wiki, _):
if post_type == "FORUM":
if not post["isReply"]:
thread_funnel = post.get("funnel")
msg_text = "[{author}]({author_url}) created [{title}](<{url}f/p/{threadId}>) in {forumName}"
msg_text = _("[{author}]({author_url}) created [{title}](<{url}f/p/{threadId}>) in {forumName}")
if thread_funnel == "POLL":
msg_text = "[{author}]({author_url}) created a poll [{title}](<{url}f/p/{threadId}>) in {forumName}"
msg_text = _("[{author}]({author_url}) created a poll [{title}](<{url}f/p/{threadId}>) in {forumName}")
elif thread_funnel == "QUIZ":
msg_text = "[{author}]({author_url}) created a quiz [{title}](<{url}f/p/{threadId}>) in {forumName}"
msg_text = _("[{author}]({author_url}) created a quiz [{title}](<{url}f/p/{threadId}>) in {forumName}")
elif thread_funnel != "TEXT":
logger.warning("No entry for {event} with params: {params}".format(event=thread_funnel, params=post))
message = _(msg_text).format(author=author, author_url=author_url, title=post["title"], url=wiki, threadId=post["threadId"], forumName=post["forumName"])
message = msg_text.format(author=author, author_url=author_url, title=post["title"], url=wiki, threadId=post["threadId"], forumName=post["forumName"])
else:
message = _("[{author}]({author_url}) created a [reply](<{url}f/p/{threadId}/r/{postId}>) to [{title}](<{url}f/p/{threadId}>) in {forumName}").format(author=author, author_url=author_url, url=wiki, threadId=post["threadId"], postId=post["id"], title=post["_embedded"]["thread"][0]["title"], forumName=post["forumName"])
elif post_type == "WALL":
@ -58,8 +60,9 @@ async def feeds_compact_formatter(post_type, post, message_target, wiki, _):
await send_to_discord(DiscordMessage("compact", "discussion", message_target[1], content=message, wiki=wiki))
async def feeds_embed_formatter(post_type, post, message_target, wiki, _):
async def feeds_embed_formatter(post_type, post, message_target, wiki):
"""Embed formatter for Fandom discussions."""
_ = langs[message_target[0][0]]["discussion_formatters"].gettext
embed = DiscordMessage("embed", "discussion", message_target[1], wiki=wiki)
if post_type == "FORUM":
embed.set_author(post["createdBy"]["name"], "{url}f/u/{creatorId}".format(url=wiki, creatorId=post["creatorId"]), icon_url=post["createdBy"]["avatarUrl"])

View file

@ -9,6 +9,7 @@ from src.config import settings
from src.misc import link_formatter, create_article_path, parse_link, profile_field_name, ContentParser
from src.discord import DiscordMessage
from src.msgqueue import send_to_discord
from src.i18n import langs
from bs4 import BeautifulSoup
@ -18,9 +19,11 @@ if 1 == 2: # additional translation strings in unreachable code
print(_("director"), _("bot"), _("editor"), _("directors"), _("sysop"), _("bureaucrat"), _("reviewer"),
_("autoreview"), _("autopatrol"), _("wiki_guardian"), ngettext("second", "seconds", 1), ngettext("minute", "minutes", 1), ngettext("hour", "hours", 1), ngettext("day", "days", 1), ngettext("week", "weeks", 1), ngettext("month", "months",1), ngettext("year", "years", 1), ngettext("millennium", "millennia", 1), ngettext("decade", "decades", 1), ngettext("century", "centuries", 1))
async def compact_formatter(action, change, parsed_comment, categories, recent_changes, message_target, _, ngettext, paths,
async def compact_formatter(action, change, parsed_comment, categories, recent_changes, message_target, paths, rate_limiter,
additional_data=None):
"""Recent Changes compact formatter, part of RcGcDw"""
_ = langs[message_target[0][0]]["rc_formatters"].gettext
ngettext = langs[message_target[0][0]]["rc_formatters"].ngettext
if additional_data is None:
additional_data = {"namespaces": {}, "tags": {}}
WIKI_API_PATH = paths[0]
@ -185,7 +188,7 @@ async def compact_formatter(action, change, parsed_comment, categories, recent_c
content = _("[{author}]({author_url}) edited the {field} on {target} profile. *({desc})*").format(author=author,
author_url=author_url,
target=target,
field=profile_field_name(change["logparams"]['4:section'], False, _),
field=profile_field_name(change["logparams"]['4:section'], False, message_target[0][0]),
desc=BeautifulSoup(change["parsedcomment"], "lxml").get_text())
elif action in ("rights/rights", "rights/autopromote"):
link = link_formatter(create_article_path("User:{user}".format(user=change["title"].split(":")[1]), WIKI_ARTICLE_PATH))
@ -330,8 +333,10 @@ async def compact_formatter(action, change, parsed_comment, categories, recent_c
await send_to_discord(DiscordMessage("compact", action, message_target[1], content=content, wiki=WIKI_SCRIPT_PATH))
async def embed_formatter(action, change, parsed_comment, categories, recent_changes, message_target, _, ngettext, paths, additional_data=None):
async def embed_formatter(action, change, parsed_comment, categories, recent_changes, message_target, paths, rate_limiter, additional_data=None):
"""Recent Changes embed formatter, part of RcGcDw"""
_ = langs[message_target[0][0]]["rc_formatters"].gettext
ngettext = langs[message_target[0][0]]["rc_formatters"].ngettext
if additional_data is None:
additional_data = {"namespaces": {}, "tags": {}}
WIKI_API_PATH = paths[0]
@ -374,14 +379,14 @@ async def embed_formatter(action, change, parsed_comment, categories, recent_cha
changed_content = await recent_changes.safe_request(
"{wiki}?action=compare&format=json&fromtext=&torev={diff}&topst=1&prop=diff".format(
wiki=WIKI_API_PATH, diff=change["revid"]
), "compare", "*")
), rate_limiter, "compare", "*")
else:
changed_content = await recent_changes.safe_request(
"{wiki}?action=compare&format=json&fromrev={oldrev}&torev={diff}&topst=1&prop=diff".format(
wiki=WIKI_API_PATH, diff=change["revid"],oldrev=change["old_revid"]
), "compare", "*")
), rate_limiter, "compare", "*")
if changed_content:
EditDiff = ContentParser(_)
EditDiff = ContentParser(message_target[0][0])
EditDiff.feed(changed_content)
if EditDiff.small_prev_del:
if EditDiff.small_prev_del.replace("~~", "").isspace():
@ -404,7 +409,7 @@ async def embed_formatter(action, change, parsed_comment, categories, recent_cha
license = None
urls = await recent_changes.safe_request(
"{wiki}?action=query&format=json&prop=imageinfo&list=&meta=&titles={filename}&iiprop=timestamp%7Curl%7Carchivename&iilimit=5".format(
wiki=WIKI_API_PATH, filename=change["title"]), "query", "pages")
wiki=WIKI_API_PATH, filename=change["title"]), rate_limiter, "query", "pages")
link = create_article_path(change["title"], WIKI_ARTICLE_PATH)
additional_info_retrieved = False
if urls is not None:
@ -520,21 +525,21 @@ async def embed_formatter(action, change, parsed_comment, categories, recent_cha
embed["title"] = _("Unblocked {blocked_user}").format(blocked_user=user)
elif action == "curseprofile/comment-created":
if settings["appearance"]["embed"]["show_edit_changes"]:
parsed_comment = await recent_changes.pull_comment(change["logparams"]["4:comment_id"], WIKI_API_PATH)
parsed_comment = await recent_changes.pull_comment(change["logparams"]["4:comment_id"], WIKI_API_PATH, rate_limiter)
link = create_article_path("Special:CommentPermalink/{commentid}".format(commentid=change["logparams"]["4:comment_id"]), WIKI_ARTICLE_PATH)
embed["title"] = _("Left a comment on {target}'s profile").format(target=change["title"].split(':')[1]) if change["title"].split(':')[1] != \
change["user"] else _(
"Left a comment on their own profile")
elif action == "curseprofile/comment-replied":
if settings["appearance"]["embed"]["show_edit_changes"]:
parsed_comment = await recent_changes.pull_comment(change["logparams"]["4:comment_id"], WIKI_API_PATH)
parsed_comment = await recent_changes.pull_comment(change["logparams"]["4:comment_id"], WIKI_API_PATH, rate_limiter)
link = create_article_path("Special:CommentPermalink/{commentid}".format(commentid=change["logparams"]["4:comment_id"]), WIKI_ARTICLE_PATH)
embed["title"] = _("Replied to a comment on {target}'s profile").format(target=change["title"].split(':')[1]) if change["title"].split(':')[1] != \
change["user"] else _(
"Replied to a comment on their own profile")
elif action == "curseprofile/comment-edited":
if settings["appearance"]["embed"]["show_edit_changes"]:
parsed_comment = await recent_changes.pull_comment(change["logparams"]["4:comment_id"], WIKI_API_PATH)
parsed_comment = await recent_changes.pull_comment(change["logparams"]["4:comment_id"], WIKI_API_PATH, rate_limiter)
link = create_article_path("Special:CommentPermalink/{commentid}".format(commentid=change["logparams"]["4:comment_id"]), WIKI_ARTICLE_PATH)
embed["title"] = _("Edited a comment on {target}'s profile").format(target=change["title"].split(':')[1]) if change["title"].split(':')[1] != \
change["user"] else _(
@ -543,9 +548,9 @@ async def embed_formatter(action, change, parsed_comment, categories, recent_cha
link = create_article_path("UserProfile:{target}".format(target=change["title"].split(':')[1]), WIKI_ARTICLE_PATH)
embed["title"] = _("Edited {target}'s profile").format(target=change["title"].split(':')[1]) if change["user"] != change["title"].split(':')[1] else _("Edited their own profile")
if not change["parsedcomment"]: # If the field is empty
parsed_comment = _("Cleared the {field} field").format(field=profile_field_name(change["logparams"]['4:section'], True, _))
parsed_comment = _("Cleared the {field} field").format(field=profile_field_name(change["logparams"]['4:section'], True, message_target[0][0]))
else:
parsed_comment = _("{field} field changed to: {desc}").format(field=profile_field_name(change["logparams"]['4:section'], True, _), desc=BeautifulSoup(change["parsedcomment"], "lxml").get_text())
parsed_comment = _("{field} field changed to: {desc}").format(field=profile_field_name(change["logparams"]['4:section'], True, message_target[0][0]), desc=BeautifulSoup(change["parsedcomment"], "lxml").get_text())
elif action == "curseprofile/comment-purged":
link = create_article_path("Special:CommentPermalink/{commentid}".format(commentid=change["logparams"]["4:comment_id"]), WIKI_ARTICLE_PATH)
embed["title"] = _("Purged a comment on {target}'s profile").format(target=change["title"].split(':')[1])

View file

@ -1,19 +1,17 @@
import sys, logging, gettext
from collections import defaultdict
logger = logging.getLogger("rcgcdb.i18n")
supported_languages = ('de', 'pl', 'pt-br')
translated_files = ('wiki', 'misc', 'discord', 'rc_formatters', 'discussion_formatters')
try:
en = gettext.translation('rcgcdw', localedir='locale', languages=["en"])
de = gettext.translation('rcgcdw', localedir='locale', languages=["de"])
pl = gettext.translation('rcgcdw', localedir='locale', languages=["pl"])
ptbr = gettext.translation('rcgcdw', localedir='locale', languages=["pt-br"])
#ru = gettext.translation('rcgcdw', localedir='locale', languages=["ru"])
#uk = gettext.translation('rcgcdw', localedir='locale', languages=["uk"])
#fr = gettext.translation('rcgcdw', localedir='locale', languages=["fr"])
langs = {"en": en, "de": de, "pl": pl, "pt-br": ptbr}
#langs = {"en": en, "de": de, "pl": pl, "pt-br": ptbr, "ru": ru, "uk": uk, "fr": fr}
langs = defaultdict(dict)
for lang in supported_languages:
for file in translated_files:
langs[lang][file] = gettext.translation(file, localedir='locale', languages=[lang])
for file in translated_files:
langs["en"][file] = gettext.NullTranslations()
except FileNotFoundError:
logger.critical("No language files have been found. Make sure locale folder is located in the directory.")
sys.exit(1)
#ngettext = en.ngettext

View file

@ -3,6 +3,7 @@ import base64, re
import logging
from urllib.parse import urlparse, urlunparse
from src.i18n import langs
logger = logging.getLogger("rcgcdw.misc")
@ -17,6 +18,12 @@ def get_paths(wiki: str, request) -> tuple:
return WIKI_API_PATH, WIKI_SCRIPT_PATH, WIKI_ARTICLE_PATH, WIKI_JUST_DOMAIN
def get_domain(url: str) -> str:
"""Get domain of given URL"""
parsed_url = urlparse(url)
return ".".join(urlunparse((*parsed_url[0:2], "", "", "", "")).split(".")[-2:]) # something like gamepedia.com, fandom.com
class LinkParser(HTMLParser):
new_string = ""
@ -84,7 +91,8 @@ def create_article_path(article: str, WIKI_ARTICLE_PATH: str) -> str:
return WIKI_ARTICLE_PATH.replace("$1", article)
def profile_field_name(name, embed, _):
def profile_field_name(name, embed, lang):
_ = langs[lang]["misc"].gettext
profile_fields = {"profile-location": _("Location"), "profile-aboutme": _("About me"),
"profile-link-google": _("Google link"), "profile-link-facebook": _("Facebook link"),
"profile-link-twitter": _("Twitter link"), "profile-link-reddit": _("Reddit link"),
@ -108,9 +116,9 @@ class ContentParser(HTMLParser):
small_prev_del = ""
added = False
def __init__(self, _):
def __init__(self, lang):
super().__init__()
self.more = _("\n__And more__")
self.more = langs[lang]["misc"].gettext("\n__And more__")
self.ins_length = len(self.more)
self.del_length = len(self.more)

View file

@ -16,8 +16,11 @@ class UpdateDB:
def update_db(self):
for update in self.updated:
update_type = "postid" if update[2] is not None else "rcid"
db_cursor.execute("UPDATE rcgcdw SET {} = ? WHERE wiki = ?".format(update_type), (update[1],update[0],))
if update[2] is None:
sql = "UPDATE rcgcdw SET rcid = ? WHERE wiki = ? AND rcid != -1"
else:
sql = "UPDATE rcgcdw SET postid = ? WHERE wikiid = ?"
db_cursor.execute(sql, (update[1], update[0],))
db_connection.commit()
self.clear_list()

View file

@ -1,23 +0,0 @@
import aiohttp
import logging
from src.config import settings
logger = logging.getLogger("rcgcdb.request_tracking")
class WikiRequestTracking:
def __init__(self):
self.current_timeout = 0
async def add_timeout(self, time: float):
self.current_timeout += time
def is_fandom(self, url):
if any(x in url for x in ("fandom.com", "gamepedia.com", "wikia.org")):
return True
return False
async def on_request_start(session, trace_config_ctx, params):
if
trace_config = aiohttp.TraceConfig()
trace_config.on_request_start.append(on_request_start)

View file

@ -7,6 +7,7 @@ from src.formatters.rc import embed_formatter, compact_formatter
from src.formatters.discussions import feeds_embed_formatter, feeds_compact_formatter
from src.misc import parse_link
from src.i18n import langs
from src.wiki_ratelimiter import RateLimiter
import src.discord
import asyncio
from src.config import settings
@ -23,12 +24,12 @@ class Wiki:
mw_messages: int = None
fail_times: int = 0 # corresponding to amount of times connection with wiki failed for client reasons (400-499)
session: aiohttp.ClientSession = None
rc_active: int = 0
@staticmethod
async def fetch_wiki(extended, script_path, session: aiohttp.ClientSession) -> aiohttp.ClientResponse:
async def fetch_wiki(extended, script_path, session: aiohttp.ClientSession, ratelimiter: RateLimiter, amount=20) -> aiohttp.ClientResponse:
await ratelimiter.timeout_wait()
url_path = script_path + "api.php"
amount = 20
if extended:
params = {"action": "query", "format": "json", "uselang": "content", "list": "tags|recentchanges",
"meta": "allmessages|siteinfo",
@ -45,6 +46,7 @@ class Wiki:
"rclimit": amount, "rctype": "edit|new|log|categorize", "siprop": "namespaces|general"}
try:
response = await session.get(url_path, params=params)
ratelimiter.timeout_add(1.0)
except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError, asyncio.TimeoutError):
logger.exception("A connection error occurred while requesting {}".format(url_path))
raise WikiServerError
@ -63,10 +65,12 @@ class Wiki:
return response
@staticmethod
async def safe_request(url, *keys):
async def safe_request(url, ratelimiter, *keys):
await ratelimiter.timeout_wait()
try:
async with aiohttp.ClientSession(headers=settings["header"], timeout=aiohttp.ClientTimeout(3.0)) as session:
request = await session.get(url, allow_redirects=False)
ratelimiter.timeout_add(1.0)
request.raise_for_status()
json_request = await request.json(encoding="UTF-8")
except (aiohttp.ClientConnectionError, aiohttp.ServerTimeoutError, asyncio.TimeoutError):
@ -108,11 +112,11 @@ class Wiki:
logger.warning('{} rows affected by DELETE FROM rcgcdw WHERE wiki = "{}"'.format(db_cursor.rowcount, wiki_url))
db_connection.commit()
async def pull_comment(self, comment_id, WIKI_API_PATH):
async def pull_comment(self, comment_id, WIKI_API_PATH, rate_limiter):
try:
comment = await self.safe_request(
"{wiki}?action=comment&do=getRaw&comment_id={comment}&format=json".format(wiki=WIKI_API_PATH,
comment=comment_id), "text")
comment=comment_id), rate_limiter, "text")
logger.debug("Got the following comment from the API: {}".format(comment))
if comment is None:
raise TypeError
@ -186,20 +190,16 @@ async def process_mwmsgs(wiki_response: dict, local_wiki: Wiki, mw_msgs: dict):
local_wiki.mw_messages = key
# db_wiki: webhook, wiki, lang, display, wikiid, rcid, postid
async def essential_info(change: dict, changed_categories, local_wiki: Wiki, db_wiki: tuple, target: tuple, paths: tuple, request: dict):
async def essential_info(change: dict, changed_categories, local_wiki: Wiki, target: tuple, paths: tuple, request: dict,
rate_limiter: RateLimiter):
"""Prepares essential information for both embed and compact message format."""
def _(string: str) -> str:
"""Our own translation string to make it compatible with async"""
return lang.gettext(string)
lang = langs[target[0][0]]
ngettext = lang.ngettext
_ = langs[target[0][0]]["wiki"].gettext
# recent_changes = RecentChangesClass() # TODO Look into replacing RecentChangesClass with local_wiki
changed_categories = changed_categories.get(change["revid"], None)
logger.debug("List of categories in essential_info: {}".format(changed_categories))
appearance_mode = embed_formatter if target[0][1] > 0 else compact_formatter
if "actionhidden" in change or "suppressed" in change: # if event is hidden using suppression
await appearance_mode("suppressed", change, "", changed_categories, local_wiki, target, _, ngettext, paths)
await appearance_mode("suppressed", change, "", changed_categories, local_wiki, target, paths, rate_limiter)
return
if "commenthidden" not in change:
parsed_comment = parse_link(paths[3], change["parsedcomment"])
@ -223,16 +223,11 @@ async def essential_info(change: dict, changed_categories, local_wiki: Wiki, db_
additional_data["tags"][tag["name"]] = (BeautifulSoup(tag["displayname"], "lxml")).get_text()
except KeyError:
additional_data["tags"][tag["name"]] = None # Tags with no displ
await appearance_mode(identification_string, change, parsed_comment, changed_categories, local_wiki, target, _, ngettext, paths, additional_data=additional_data)
await appearance_mode(identification_string, change, parsed_comment, changed_categories, local_wiki, target, paths, rate_limiter, additional_data=additional_data)
async def essential_feeds(change: dict, db_wiki: tuple, target: tuple):
"""Prepares essential information for both embed and compact message format."""
def _(string: str) -> str:
"""Our own translation string to make it compatible with async"""
return lang.gettext(string)
lang = langs[target[0][0]]
appearance_mode = feeds_embed_formatter if target[0][1] > 0 else feeds_compact_formatter
identification_string = change["_embedded"]["thread"][0]["containerType"]
await appearance_mode(identification_string, change, target, db_wiki["wiki"], _)
await appearance_mode(identification_string, change, target, db_wiki["wiki"])

19
src/wiki_ratelimiter.py Normal file
View file

@ -0,0 +1,19 @@
import logging, time, asyncio
logger = logging.getLogger("rcgcdw.ratelimiter")
class RateLimiter:
def __init__(self):
self.timeout_until = 0
def timeout_add(self, timeout: float):
"""This function sets a new timeout"""
self.timeout_until = time.time() + timeout
logger.debug("Added {} timeout".format(timeout))
async def timeout_wait(self):
"""This awaitable calculates the time to wait according to timeout_until, does not wait if it's past the timeout to not skip a cycle"""
calculated_timeout = self.timeout_until - time.time()
logger.debug("Waiting {}".format(calculated_timeout))
if calculated_timeout > 0:
await asyncio.sleep(calculated_timeout)