diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0edcec6..38210de 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,5 @@ image: python:3.7-alpine -include: - - template: Code-Quality.gitlab-ci.yml - variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" @@ -37,7 +34,7 @@ pytest: script: - source venv/bin/activate - pip3.7 install -U pytest - - pytest --junitxml=report.xml + - PYTHONPATH=. pytest --junit-xml=report.xml artifacts: when: always reports: diff --git a/extensions/base/mediawiki.py b/extensions/base/mediawiki.py index f5b81d0..fa480b4 100644 --- a/extensions/base/mediawiki.py +++ b/extensions/base/mediawiki.py @@ -81,7 +81,7 @@ def embed_edit(ctx: Context, change: dict) -> DiscordMessage: changed_content = ctx.client.make_api_request( "?action=compare&format=json&fromrev={oldrev}&torev={diff}&topst=1&prop=diff".format( diff=change["revid"], oldrev=change["old_revid"]), "compare", "*") - except ServerError: + except (ServerError, MediaWikiError): changed_content = None if changed_content: parse_mediawiki_changes(ctx, changed_content, embed) diff --git a/locale/de/LC_MESSAGES/formatters.mo b/locale/de/LC_MESSAGES/formatters.mo index 77027f8..5b622a6 100644 Binary files a/locale/de/LC_MESSAGES/formatters.mo and b/locale/de/LC_MESSAGES/formatters.mo differ diff --git a/locale/de/LC_MESSAGES/formatters.po b/locale/de/LC_MESSAGES/formatters.po index 8661070..82a6f74 100644 --- a/locale/de/LC_MESSAGES/formatters.po +++ b/locale/de/LC_MESSAGES/formatters.po @@ -10,8 +10,8 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2021-05-23 17:18+0000\n" -"Last-Translator: magiczocker \n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" +"Last-Translator: MarkusRost \n" "Language-Team: German \n" "Language: de\n" @@ -19,7 +19,7 @@ msgstr "" "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.6\n" +"X-Generator: Weblate 4.6.2\n" "X-Loco-Source-Locale: de_DE\n" "Generated-By: pygettext.py 1.5\n" "X-Loco-Parser: loco_parse_po\n" @@ -1095,7 +1095,7 @@ msgstr "Passive Sichter" #: extensions/base/mediawiki.py:38 msgid "autopatrol" -msgstr "autopatrol" +msgstr "Automatisch kontrolliert" #: extensions/base/mediawiki.py:38 msgid "wiki_guardian" @@ -1402,9 +1402,9 @@ msgstr[0] "Minute" msgstr[1] "Minuten" #: extensions/base/mediawiki.py:589 -#, fuzzy, python-brace-format +#, python-brace-format msgid "for {time_number} {time_unit}" -msgstr "für {time_number} {time_unit}" +msgstr "für {time_number} {time_unit}" #: extensions/base/mediawiki.py:608 msgid "Blocked from editing the following pages: " diff --git a/locale/de/LC_MESSAGES/redaction.mo b/locale/de/LC_MESSAGES/redaction.mo index 1321f36..3850a03 100644 Binary files a/locale/de/LC_MESSAGES/redaction.mo and b/locale/de/LC_MESSAGES/redaction.mo differ diff --git a/locale/de/LC_MESSAGES/redaction.po b/locale/de/LC_MESSAGES/redaction.po index 6775cbf..a30f841 100644 --- a/locale/de/LC_MESSAGES/redaction.po +++ b/locale/de/LC_MESSAGES/redaction.po @@ -8,24 +8,24 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-11-20 09:22+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: MarkusRost \n" -"Language-Team: German \n" +"Language-Team: German \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.2.1\n" +"X-Generator: Weblate 4.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "versteckt" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~versteckt~~" #~ msgid "Removed" #~ msgstr "Versteckt" diff --git a/locale/es/LC_MESSAGES/formatters.mo b/locale/es/LC_MESSAGES/formatters.mo index ddf39e7..0bd9a5a 100644 Binary files a/locale/es/LC_MESSAGES/formatters.mo and b/locale/es/LC_MESSAGES/formatters.mo differ diff --git a/locale/es/LC_MESSAGES/formatters.po b/locale/es/LC_MESSAGES/formatters.po index 3850b66..11a5d40 100644 --- a/locale/es/LC_MESSAGES/formatters.po +++ b/locale/es/LC_MESSAGES/formatters.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2021-07-11 12:33+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: Tamara Carvallo \n" "Language-Team: Spanish \n" @@ -139,31 +139,27 @@ msgstr "" "{target}\"" #: extensions/base/translate.py:106 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) completed moving translation pages from *{article}* " "to [{target}]({target_url}){comment}" msgstr "" -"[{author}]({author_url}) movió la configuración de protección de {redirect}" -"*{article}* a [{target}]({target_url}){comment}" +"[{author}]({author_url}) movió la configuración de protección de *{article}* " +"a [{target}]({target_url}){comment}" #: extensions/base/translate.py:121 #, python-brace-format msgid "Encountered a problem while moving \"{article}\" to \"{target}\"" -msgstr "" +msgstr "Se encontró un problema al mover \"{article}\" a \"{target}\"" #: extensions/base/translate.py:133 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) encountered a problem while moving [{article}]" "({article_url}) to [{target}]({target_url}){comment}" msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) fusionó los historiales de revisión de [{article}]" -"({article_url}) con [{dest}]({dest_url}){comment}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) fusionó los historiales de revisión de [{article}]" -"({article_url}) en [{dest}]({dest_url}){comment}" +"[{author}]({author_url}) encontró un problema al mover " +"[{article}]({article_url}) a [{target}]({target_url}){comment}" #: extensions/base/translate.py:149 #, python-brace-format @@ -171,32 +167,30 @@ msgid "" "Failed to delete \"{article}\" which belongs to translatable page " "\"{target}\"" msgstr "" +"No se pudo borrar \"{article}\" que pertenece a la página traducible \"" +"{target}\"" #: extensions/base/translate.py:161 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) failed to delete [{article}]({article_url}) which " "belongs to translatable page [{target}]({target_url}){comment}" msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) fusionó los historiales de revisión de [{article}]" -"({article_url}) con [{dest}]({dest_url}){comment}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) fusionó los historiales de revisión de [{article}]" -"({article_url}) en [{dest}]({dest_url}){comment}" +"[{author}]({author_url}) no pudo borrar [{article}]({article_url}) que " +"pertenece a la página traducible [{target}]({target_url}){comment}" #: extensions/base/translate.py:177 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Completed deletion of translation page \"{article}\"" -msgstr "Modificó la visibilidad de la revisión en la página {article} " +msgstr "Se eliminó completamente la página de traducción \"{article}\"" #: extensions/base/translate.py:188 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) completed deletion of translation page [{article}]" "({article_url}){comment}" msgstr "" -"[{author}]({author_url}) cambió la visibilidad de la revisión en la página " +"[{author}]({author_url}) eliminó la página de traducción " "[{article}]({article_url}){comment}" #: extensions/base/translate.py:203 @@ -204,108 +198,94 @@ msgstr "" msgid "" "Failed to delete \"{article}\" which belongs to translation page \"{target}\"" msgstr "" +"No se pudo borrar \"{article}\" que pertenece a la página de traducción \"" +"{target}\"" #: extensions/base/translate.py:215 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) failed to delete [{article}]({article_url}) which " "belongs to translation page [{target}]({target_url}){comment}" msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) fusionó los historiales de revisión de [{article}]" -"({article_url}) con [{dest}]({dest_url}){comment}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) fusionó los historiales de revisión de [{article}]" -"({article_url}) en [{dest}]({dest_url}){comment}" +"[{author}]({author_url}) no pudo borrar [{article}]({article_url}) que " +"pertenece a la página de traducción [{target}]({target_url}){comment}" #: extensions/base/translate.py:231 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Encouraged translation of \"{article}\"" -msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"Quitó la protección de {article}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"Eliminó la protección de {article}" +msgstr "Traducción recomendada de \"{article}\"" #: extensions/base/translate.py:240 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) encouraged translation of [{article}]({article_url})" "{comment}" msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) eliminó la protección de [{article}]({article_url})" -"{comment}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) quitó la protección de [{article}]({article_url})" -"{comment}" +"[{author}]({author_url}) recomendó la traducción de " +"[{article}]({article_url}){comment}" #: extensions/base/translate.py:255 #, python-brace-format msgid "Discouraged translation of \"{article}\"" -msgstr "" +msgstr "Traducción desaconsejada de \"{article}\"" #: extensions/base/translate.py:264 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) discouraged translation of [{article}]" "({article_url}){comment}" -msgstr "[{author}]({author_url}) restauró [{article}]({article_url}){comment}" +msgstr "" +"[{author}]({author_url}) desaconsejó la traducción de " +"[{article}]({article_url}){comment}" #: extensions/base/translate.py:282 #, python-brace-format msgid "Limited languages for \"{article}\" to `{languages}`" -msgstr "" +msgstr "Idiomas limitados para \"{article}\" a `{languages}`" #: extensions/base/translate.py:285 #, python-brace-format msgid "Priority languages for \"{article}\" set to `{languages}`" msgstr "" +"Los idiomas prioritarios para \"{article}\" se establecieron en `{languages}`" #: extensions/base/translate.py:288 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Removed priority languages from \"{article}\"" -msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"Quitó la protección de {article}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"Eliminó la protección de {article}" +msgstr "Se quitaron los idiomas prioritarios de \"{article}\"" #: extensions/base/translate.py:301 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) limited languages for [{article}]({article_url}) to " "`{languages}`{comment}" msgstr "" -"[{author}]({author_url}) modificó la configuración de protección de " -"[{article}]({article_url}) a: {settings}{comment}" +"[{author}]({author_url}) limitó los idiomas para [{article}]({article_url}) " +"a `{languages}`{comment}" #: extensions/base/translate.py:308 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) set the priority languages for [{article}]" "({article_url}) to `{languages}`{comment}" msgstr "" -"[{author}]({author_url}) modificó la configuración de protección de " -"[{article}]({article_url}) a: {settings}{comment}" +"[{author}]({author_url}) estableció los idiomas de prioridad para " +"[{article}]({article_url}) en `{languages}`{comment}" #: extensions/base/translate.py:315 -#, fuzzy, python-brace-format +#, python-brace-format msgid "" "[{author}]({author_url}) removed priority languages from [{article}]" "({article_url}){comment}" msgstr "" -"#-#-#-#-# rcgcdw.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) eliminó la protección de [{article}]({article_url})" -"{comment}\n" -"#-#-#-#-# formatters.po (RcGcDw) #-#-#-#-#\n" -"[{author}]({author_url}) quitó la protección de [{article}]({article_url})" -"{comment}" +"[{author}]({author_url}) eliminó los idiomas prioritarios de " +"[{article}]({article_url}){comment}" #: extensions/base/translate.py:331 #, python-brace-format msgid "Added translatable page \"{article}\" to aggregate group \"{group}\"" msgstr "" +"Se agregó la página traducible \"{article}\" al grupo agregado \"{group}\"" #: extensions/base/translate.py:342 #, fuzzy, python-brace-format diff --git a/locale/pl/LC_MESSAGES/redaction.mo b/locale/pl/LC_MESSAGES/redaction.mo index 1050bd6..4354871 100644 Binary files a/locale/pl/LC_MESSAGES/redaction.mo and b/locale/pl/LC_MESSAGES/redaction.mo differ diff --git a/locale/pl/LC_MESSAGES/redaction.po b/locale/pl/LC_MESSAGES/redaction.po index 7ccdc8c..b48724b 100644 --- a/locale/pl/LC_MESSAGES/redaction.po +++ b/locale/pl/LC_MESSAGES/redaction.po @@ -8,24 +8,25 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-11-16 22:51+0100\n" -"Last-Translator: \n" -"Language-Team: \n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" +"Last-Translator: Frisk The Evil Goat Overlord \n" +"Language-Team: Polish \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.4.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" +"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.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "ukryte" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~ukryte~~" #~ msgid "Removed" #~ msgstr "Usunięte" diff --git a/locale/pt-br/LC_MESSAGES/formatters.mo b/locale/pt-br/LC_MESSAGES/formatters.mo index cab7332..3dbd6dd 100644 Binary files a/locale/pt-br/LC_MESSAGES/formatters.mo and b/locale/pt-br/LC_MESSAGES/formatters.mo differ diff --git a/locale/pt-br/LC_MESSAGES/formatters.po b/locale/pt-br/LC_MESSAGES/formatters.po index 099f3ef..cf6116d 100644 --- a/locale/pt-br/LC_MESSAGES/formatters.po +++ b/locale/pt-br/LC_MESSAGES/formatters.po @@ -21,7 +21,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:34+0200\n" -"PO-Revision-Date: 2021-07-11 12:33+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: Eduaddad \n" "Language-Team: Portuguese (Brazil) \n" @@ -1402,7 +1402,7 @@ msgstr[0] "minuto" msgstr[1] "minutos" #: extensions/base/mediawiki.py:589 -#, fuzzy, python-brace-format +#, python-brace-format msgid "for {time_number} {time_unit}" msgstr "por {time_number} {time_unit}" diff --git a/locale/pt-br/LC_MESSAGES/redaction.mo b/locale/pt-br/LC_MESSAGES/redaction.mo index d155f9d..ecd853b 100644 Binary files a/locale/pt-br/LC_MESSAGES/redaction.mo and b/locale/pt-br/LC_MESSAGES/redaction.mo differ diff --git a/locale/pt-br/LC_MESSAGES/redaction.po b/locale/pt-br/LC_MESSAGES/redaction.po index 0a7129e..eaf494d 100644 --- a/locale/pt-br/LC_MESSAGES/redaction.po +++ b/locale/pt-br/LC_MESSAGES/redaction.po @@ -8,24 +8,24 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-11-21 01:09+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: Eduaddad \n" -"Language-Team: Portuguese (Brazil) \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.2.1\n" +"X-Generator: Weblate 4.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "oculto" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~oculto~~" #~ msgid "Removed" #~ msgstr "Removido" diff --git a/locale/ru/LC_MESSAGES/formatters.mo b/locale/ru/LC_MESSAGES/formatters.mo index 467a17e..4754ff0 100644 Binary files a/locale/ru/LC_MESSAGES/formatters.mo and b/locale/ru/LC_MESSAGES/formatters.mo differ diff --git a/locale/ru/LC_MESSAGES/formatters.po b/locale/ru/LC_MESSAGES/formatters.po index 12f49f0..a35b941 100644 --- a/locale/ru/LC_MESSAGES/formatters.po +++ b/locale/ru/LC_MESSAGES/formatters.po @@ -21,8 +21,8 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2021-07-11 12:33+0000\n" -"Last-Translator: Philo04 \n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" +"Last-Translator: MakandIv \n" "Language-Team: Russian \n" "Language: ru\n" @@ -1420,7 +1420,7 @@ msgstr[1] "минуты" msgstr[2] "минут" #: extensions/base/mediawiki.py:589 -#, fuzzy, python-brace-format +#, python-brace-format msgid "for {time_number} {time_unit}" msgstr "на {time_number} {time_unit}" diff --git a/locale/ru/LC_MESSAGES/redaction.mo b/locale/ru/LC_MESSAGES/redaction.mo index b31ff26..058c06e 100644 Binary files a/locale/ru/LC_MESSAGES/redaction.mo and b/locale/ru/LC_MESSAGES/redaction.mo differ diff --git a/locale/ru/LC_MESSAGES/redaction.po b/locale/ru/LC_MESSAGES/redaction.po index ea93713..298725a 100644 --- a/locale/ru/LC_MESSAGES/redaction.po +++ b/locale/ru/LC_MESSAGES/redaction.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-12-22 00:13+0000\n" -"Last-Translator: Philo04 \n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" +"Last-Translator: MakandIv \n" "Language-Team: Russian \n" "Language: ru\n" @@ -18,15 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.2.1\n" +"X-Generator: Weblate 4.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "скрыто" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~скрыто~~" #~ msgid "Removed" #~ msgstr "Удалено" diff --git a/locale/uk/LC_MESSAGES/formatters.mo b/locale/uk/LC_MESSAGES/formatters.mo index 284fd68..07eef9d 100644 Binary files a/locale/uk/LC_MESSAGES/formatters.mo and b/locale/uk/LC_MESSAGES/formatters.mo differ diff --git a/locale/uk/LC_MESSAGES/formatters.po b/locale/uk/LC_MESSAGES/formatters.po index 28da5a7..de4186f 100644 --- a/locale/uk/LC_MESSAGES/formatters.po +++ b/locale/uk/LC_MESSAGES/formatters.po @@ -16,32 +16,22 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-11-18 07:47+0000\n" -"Last-Translator: MakandIv <>\n" -"Language-Team: Ukrainian \n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" +"Last-Translator: MakandIv \n" +"Language-Team: Ukrainian \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"#-#-#-#-# rcgcdw.po #-#-#-#-#\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 2.4.1\n" -"#-#-#-#-# formatters.po #-#-#-#-#\n" -"#-#-#-#-# discussion_formatters.po #-#-#-#-#\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Lokalize 19.12.3\n" -"#-#-#-#-# rc_formatters.po #-#-#-#-#\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.2.1\n" +"X-Generator: Weblate 4.6.2\n" #: src/api/util.py:61 src/api/util.py:66 msgid "__Only whitespace__" @@ -59,10 +49,9 @@ msgstr "Додано" #: src/api/util.py:141 extensions/base/discussions.py:247 #: extensions/base/discussions.py:264 extensions/base/abusefilter.py:45 msgid "Unregistered user" -msgstr "" +msgstr "Незареєстрований користувач" #: src/api/util.py:160 -#, fuzzy msgctxt "recent changes Tags" msgid "Tags" msgstr "Теги" @@ -88,49 +77,49 @@ msgid "Changed categories" msgstr "Змінені категорії" #: extensions/base/cargo.py:37 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Created the Cargo table \"{table}\"" -msgstr "Створив тег \"{tag}\"" +msgstr "Створив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:45 -#, fuzzy, python-brace-format +#, python-brace-format msgid "[{author}]({author_url}) created the Cargo table \"{table}\"" -msgstr "[{author}]({author_url}) створив [тег]({tag_url}) \"{tag}\"" +msgstr "[{author}]({author_url}) створив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:60 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Recreated the Cargo table \"{table}\"" -msgstr "Створив тег \"{tag}\"" +msgstr "Відтворив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:68 -#, fuzzy, python-brace-format +#, python-brace-format msgid "[{author}]({author_url}) recreated the Cargo table \"{table}\"" -msgstr "[{author}]({author_url}) створив [тег]({tag_url}) \"{tag}\"" +msgstr "[{author}]({author_url}) відтворив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:83 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Replaced the Cargo table \"{table}\"" -msgstr "Вилучив тег \"{tag}\"" +msgstr "Замінив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:91 -#, fuzzy, python-brace-format +#, python-brace-format msgid "[{author}]({author_url}) replaced the Cargo table \"{table}\"" -msgstr "[{author}]({author_url}) створив [тег]({tag_url}) \"{tag}\"" +msgstr "[{author}]({author_url}) замінив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:105 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Deleted the Cargo table \"{table}\"" -msgstr "Вилучив тег \"{tag}\"" +msgstr "Видалив таблицю Cargo \"{table}\"" #: extensions/base/cargo.py:112 -#, fuzzy, python-brace-format +#, python-brace-format msgid "[{author}]({author_url}) deleted the Cargo table \"{table}\"" -msgstr "[{author}]({author_url}) вилучив [тег]({tag_url}) \"{tag}\"" +msgstr "[{author}]({author_url}) видалив таблицю Cargo \"{table}\"" #: extensions/base/translate.py:41 #, python-brace-format msgid "Marked \"{article}\" for translation" -msgstr "" +msgstr "Зазначив сторінку «{article}» як доступну для перекладу" #: extensions/base/translate.py:55 #, fuzzy, python-brace-format diff --git a/locale/uk/LC_MESSAGES/redaction.mo b/locale/uk/LC_MESSAGES/redaction.mo index d2d1537..0ce9c61 100644 Binary files a/locale/uk/LC_MESSAGES/redaction.mo and b/locale/uk/LC_MESSAGES/redaction.mo differ diff --git a/locale/uk/LC_MESSAGES/redaction.po b/locale/uk/LC_MESSAGES/redaction.po index 12b86c5..9c44750 100644 --- a/locale/uk/LC_MESSAGES/redaction.po +++ b/locale/uk/LC_MESSAGES/redaction.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2021-05-22 12:08+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: MakandIv \n" "Language-Team: Ukrainian \n" @@ -18,15 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.6\n" +"X-Generator: Weblate 4.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "приховано" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~приховано~~" #~ msgid "Removed" #~ msgstr "Видалено" diff --git a/locale/zh-hans/LC_MESSAGES/formatters.mo b/locale/zh-hans/LC_MESSAGES/formatters.mo index bada41b..4c502bf 100644 Binary files a/locale/zh-hans/LC_MESSAGES/formatters.mo and b/locale/zh-hans/LC_MESSAGES/formatters.mo differ diff --git a/locale/zh-hans/LC_MESSAGES/formatters.po b/locale/zh-hans/LC_MESSAGES/formatters.po index 043da2f..c7beeba 100644 --- a/locale/zh-hans/LC_MESSAGES/formatters.po +++ b/locale/zh-hans/LC_MESSAGES/formatters.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2021-05-31 21:54+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: lakejason0 \n" "Language-Team: Chinese (Simplified) \n" @@ -1347,7 +1347,7 @@ msgid_plural "minutes" msgstr[0] "分" #: extensions/base/mediawiki.py:589 -#, fuzzy, python-brace-format +#, python-brace-format msgid "for {time_number} {time_unit}" msgstr "时长为 {time_number} {time_unit}" diff --git a/locale/zh-hans/LC_MESSAGES/redaction.mo b/locale/zh-hans/LC_MESSAGES/redaction.mo index 0ff0ab9..814888b 100644 Binary files a/locale/zh-hans/LC_MESSAGES/redaction.mo and b/locale/zh-hans/LC_MESSAGES/redaction.mo differ diff --git a/locale/zh-hans/LC_MESSAGES/redaction.po b/locale/zh-hans/LC_MESSAGES/redaction.po index 2d3996f..ba4b0fe 100644 --- a/locale/zh-hans/LC_MESSAGES/redaction.po +++ b/locale/zh-hans/LC_MESSAGES/redaction.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-12-13 14:30+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: lakejason0 \n" "Language-Team: Chinese (Simplified) \n" @@ -17,15 +17,15 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.2.1\n" +"X-Generator: Weblate 4.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "已隐藏" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~已隐藏~~" #~ msgid "Removed" #~ msgstr "移除了" diff --git a/locale/zh-hant/LC_MESSAGES/redaction.mo b/locale/zh-hant/LC_MESSAGES/redaction.mo index d6b721a..0e4fa39 100644 Binary files a/locale/zh-hant/LC_MESSAGES/redaction.mo and b/locale/zh-hant/LC_MESSAGES/redaction.mo differ diff --git a/locale/zh-hant/LC_MESSAGES/redaction.po b/locale/zh-hant/LC_MESSAGES/redaction.po index 417acb7..73b8104 100644 --- a/locale/zh-hant/LC_MESSAGES/redaction.po +++ b/locale/zh-hant/LC_MESSAGES/redaction.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: RcGcDw\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-07-11 14:32+0200\n" -"PO-Revision-Date: 2020-12-13 14:30+0000\n" +"PO-Revision-Date: 2021-07-30 10:44+0000\n" "Last-Translator: lakejason0 \n" "Language-Team: Chinese (Traditional) \n" @@ -17,15 +17,15 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.2.1\n" +"X-Generator: Weblate 4.6.2\n" #: src/discord/redaction.py:77 msgid "hidden" -msgstr "" +msgstr "已隱藏" #: src/discord/redaction.py:80 src/discord/redaction.py:85 msgid "~~hidden~~" -msgstr "" +msgstr "~~已隱藏~~" #~ msgid "Removed" #~ msgstr "移除了" diff --git a/settings.json.example b/settings.json.example index bf945d3..59e04a9 100644 --- a/settings.json.example +++ b/settings.json.example @@ -36,6 +36,7 @@ "show_abuselog": false, "hide_ips": false, "discord_message_cooldown": 0, + "datafile_path": "data.json", "auto_suppression": { "enabled": false, "db_location": ":memory:" diff --git a/setup.py b/setup.py index 31a5301..24fbb7e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup setup( name='RcGcDw', - version='1.14', + version='1.14.0.1', packages=[''], url='https://gitlab.com/piotrex43/RcGcDw/', license='GNU GPLv3', diff --git a/src/api/formatter.py b/src/api/formatter.py index c9946a1..35926cc 100644 --- a/src/api/formatter.py +++ b/src/api/formatter.py @@ -21,6 +21,7 @@ from typing import Optional, Callable logger = logging.getLogger("src.api.formatter") + def _register_formatter(func, kwargs, formatter_type: str, action_type=None): """ Registers a formatter inside of src.rcgcdw.formatter_hooks diff --git a/src/api/hooks.py b/src/api/hooks.py index 87699ca..b0d7674 100644 --- a/src/api/hooks.py +++ b/src/api/hooks.py @@ -14,6 +14,10 @@ # along with RcGcDw. If not, see . # Made just to avoid circular imports -formatter_hooks = {} -pre_hooks = [] -post_hooks = [] \ No newline at end of file +from typing import Callable, List, Dict +from src.discord.message import DiscordMessage, DiscordMessageMetadata +from src.api.context import Context + +formatter_hooks: Dict[str, Callable[[Context, dict], DiscordMessage]] = {} +pre_hooks: List[Callable[[Context, dict], None]] = [] +post_hooks: List[Callable[[DiscordMessage, DiscordMessageMetadata, Context, dict], None]] = [] \ No newline at end of file diff --git a/src/argparser.py b/src/argparser.py new file mode 100644 index 0000000..6bb0b96 --- /dev/null +++ b/src/argparser.py @@ -0,0 +1,22 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import argparse + +parser = argparse.ArgumentParser(description="Start RcGcDw") +parser.add_argument("--test", action='store_true', help="mode used for testing, sends only 5 entries of both rc and discussion changes and sends daily overview") +parser.add_argument("--settings", default="settings.json", type=argparse.FileType(encoding='utf8'), help="provides a path to settings file (default ./settings.json)") +command_args, unknown = parser.parse_known_args() + diff --git a/src/configloader.py b/src/configloader.py index 002c890..545db53 100644 --- a/src/configloader.py +++ b/src/configloader.py @@ -16,19 +16,19 @@ import json import logging import sys - +from src.argparser import command_args global settings def load_settings(): global settings try: # load settings - with open("settings.json", encoding="utf8") as sfile: - settings = json.load(sfile) - 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.14") # set the version in the useragent + command_args.settings.seek(0) + settings = json.load(command_args.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.14.1") # 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) @@ -45,4 +45,3 @@ def load_settings(): load_settings() - diff --git a/src/discord/queue.py b/src/discord/queue.py index 90b8ce5..f50f5d8 100644 --- a/src/discord/queue.py +++ b/src/discord/queue.py @@ -17,12 +17,12 @@ import re import sys import time import logging -from typing import Optional +from typing import Optional, Union, Tuple import requests from src.configloader import settings -from src.discord.message import DiscordMessage, DiscordMessageMetadata +from src.discord.message import DiscordMessage, DiscordMessageMetadata, DiscordMessageRaw AUTO_SUPPRESSION_ENABLED = settings.get("auto_suppression", {"enabled": False}).get("enabled") if AUTO_SUPPRESSION_ENABLED: @@ -35,7 +35,7 @@ logger = logging.getLogger("rcgcdw.discord.queue") class MessageQueue: """Message queue class for undelivered messages""" def __init__(self): - self._queue = [] + self._queue: list[Tuple[Union[DiscordMessage, DiscordMessageRaw], DiscordMessageMetadata]] = [] def __repr__(self): return self._queue @@ -49,10 +49,10 @@ class MessageQueue: def clear(self): self._queue.clear() - def add_message(self, message): + def add_message(self, message: Tuple[Union[DiscordMessage, DiscordMessageRaw], DiscordMessageMetadata]): self._queue.append(message) - def cut_messages(self, item_num): + def cut_messages(self, item_num: int): self._queue = self._queue[item_num:] @staticmethod @@ -116,6 +116,9 @@ def handle_discord_http(code, formatted_embed, result): "Discord have trouble processing the event, and because the HTTP code returned is {} it means we blame them.".format( code)) return 3 + else: + logger.error("There was an unexpected HTTP code returned from Discord: {}".format(code)) + return 1 def update_ratelimit(request): @@ -183,5 +186,5 @@ def send_to_discord(data: Optional[DiscordMessage], meta: DiscordMessageMetadata elif code == 2: time.sleep(5.0) messagequeue.add_message((data, meta)) - elif code < 2: + elif code is None or code < 2: pass diff --git a/src/discord/redaction.py b/src/discord/redaction.py index dcbf5c9..46d6b07 100644 --- a/src/discord/redaction.py +++ b/src/discord/redaction.py @@ -15,8 +15,10 @@ import logging import json +from typing import List, Union + from src.configloader import settings -from src.discord.message import DiscordMessageMetadata, DiscordMessage, DiscordMessageRaw +from src.discord.message import DiscordMessageMetadata, DiscordMessageRaw from src.discord.queue import send_to_discord, messagequeue from src.fileio.database import db_cursor, db_connection from src.i18n import redaction as redaction_translation @@ -48,14 +50,14 @@ def delete_messages(matching_data: dict): db_connection.commit() -def redact_messages(ids: list, entry_type: int, to_censor: dict): +def redact_messages(ids: Union[List[Union[str, int]], set[Union[int, str]]], entry_type: int, to_censor: dict): """Redact past Discord messages ids: list of ints entry_type: int - 0 for revdel, 1 for logdel to_censor: dict - logparams of message parts to censor""" for event_id in ids: - if entry_type == 0: # TODO check if queries are proper + if entry_type == 0: message = db_cursor.execute("SELECT content, message_id FROM messages INNER JOIN event ON event.msg_id = messages.message_id WHERE event.revid = ?;", (event_id, )) else: message = db_cursor.execute( @@ -90,3 +92,23 @@ def redact_messages(ids: list, entry_type: int, to_censor: dict): send_to_discord(DiscordMessageRaw(message, settings["webhookURL"].split("?", 1)[0]+"/messages/"+str(row[1])), DiscordMessageMetadata("PATCH")) else: logger.debug("Could not find message in the database.") + + +def find_middle_next(ids: List[str], pageid: int) -> set: + """To address #235 RcGcDw should now remove diffs in next revs relative to redacted revs to protect information in revs that revert revdeleted information. + + :arg ids - list + :arg pageid - int + + :return list""" + ids = [int(x) for x in ids] + result = set() + ids.sort() # Just to be sure, sort the list to make sure it's always sorted + messages = db_cursor.execute("SELECT revid FROM event WHERE pageid = ? AND revid >= ? ORDER BY revid", (pageid, ids[0],)) + all_in_page = [x[0] for x in messages.fetchall()] + for id in ids: + try: + result.add(all_in_page[all_in_page.index(id)+1]) + except (KeyError, ValueError): + logger.debug(f"Value {id} not in {all_in_page} or no value after that.") + return result - set(ids) diff --git a/src/discussions.py b/src/discussions.py index 6ef5094..3cd4e20 100644 --- a/src/discussions.py +++ b/src/discussions.py @@ -16,7 +16,7 @@ # along with RcGcDw. If not, see . import logging, schedule, requests -from typing import Dict, Any, Optional +from typing import Optional from src.configloader import settings @@ -134,6 +134,7 @@ def parse_discussion_post(post, comment_pages): raise metadata = DiscordMessageMetadata("POST") run_hooks(post_hooks, discord_message, metadata, context, post) + discord_message.finish_embed() send_to_discord(discord_message, metadata) diff --git a/src/fileio/database.py b/src/fileio/database.py index 888d22a..8b63a96 100644 --- a/src/fileio/database.py +++ b/src/fileio/database.py @@ -15,12 +15,31 @@ import sqlite3 import logging -import json from src.configloader import settings logger = logging.getLogger("rcgcdw.fileio.database") +def catch_db_OperationalError(func): + def catcher(*args, **kwargs): + global db_connection, db_cursor + try: + func(*args, **kwargs) + except sqlite3.OperationalError: + if settings.get("error_tolerance", 0) > 1: + logger.error("SQL database has been damaged during operation. This can indicate it has been deleted " + "during runtime or damaged in some way. If it wasn't purposeful you may want to take a look " + "at your disk state. In the meantime, RcGcDw will attempt to recover by re-creating empty database.") + db_connection, db_cursor = create_connection() + check_tables() + func(*args, **kwargs) + else: + raise + return func + + return catcher + + def create_schema(): """Creates a SQLite database schema""" logger.info("Creating database schema...") @@ -60,6 +79,7 @@ def check_tables(): create_schema() +@catch_db_OperationalError def add_entry(pageid: int, revid: int, logid: int, message, message_id: str): """Add an edit or log entry to the DB :param message: @@ -69,21 +89,27 @@ def add_entry(pageid: int, revid: int, logid: int, message, message_id: str): :param message_id: """ db_cursor.execute("INSERT INTO messages (message_id, content) VALUES (?, ?)", (message_id, message)) - db_cursor.execute("INSERT INTO event (pageid, revid, logid, msg_id) VALUES (?, ?, ?, ?)", (pageid, revid, logid, message_id)) - logger.debug("Adding an entry to the database (pageid: {}, revid: {}, logid: {}, message: {})".format(pageid, revid, logid, message)) + db_cursor.execute("INSERT INTO event (pageid, revid, logid, msg_id) VALUES (?, ?, ?, ?)", + (pageid, revid, logid, message_id)) + logger.debug( + "Adding an entry to the database (pageid: {}, revid: {}, logid: {}, message: {})".format(pageid, revid, logid, + message)) db_connection.commit() +@catch_db_OperationalError def clean_entries(): """Cleans entries that are 50+""" cleanup = db_cursor.execute( "SELECT message_id FROM messages WHERE message_id NOT IN (SELECT message_id FROM messages ORDER BY message_id desc LIMIT 50);") for row in cleanup: db_cursor.execute("DELETE FROM messages WHERE message_id = ?", (row[0],)) - cleanup = db_cursor.execute("SELECT msg_id FROM event WHERE msg_id NOT IN (SELECT msg_id FROM event ORDER BY msg_id desc LIMIT 50);") + cleanup = db_cursor.execute( + "SELECT msg_id FROM event WHERE msg_id NOT IN (SELECT msg_id FROM event ORDER BY msg_id desc LIMIT 50);") for row in cleanup: db_cursor.execute("DELETE FROM event WHERE msg_id = ?", (row[0],)) db_connection.commit() + db_connection, db_cursor = create_connection() check_tables() diff --git a/src/i18n.py b/src/i18n.py index b2a6e7c..812bcab 100644 --- a/src/i18n.py +++ b/src/i18n.py @@ -14,9 +14,15 @@ # along with RcGcDw. If not, see . import gettext, sys, logging +from typing import Union, Optional from src.configloader import settings logger = logging.getLogger("rcgcdw.i18n") - +rcgcdw: Optional[Union[gettext.GNUTranslations, gettext.NullTranslations]] = None +discussion_formatters: Optional[Union[gettext.GNUTranslations, gettext.NullTranslations]] = None +rc: Optional[Union[gettext.GNUTranslations, gettext.NullTranslations]] = None +formatters_i18n: Optional[Union[gettext.GNUTranslations, gettext.NullTranslations]] = None +misc: Optional[Union[gettext.GNUTranslations, gettext.NullTranslations]] = None +redaction: Optional[Union[gettext.GNUTranslations, gettext.NullTranslations]] = None # Setup translation @@ -27,16 +33,22 @@ def python37_pgettext_backward_compatibility(context: str, string: str): return string return translation -try: - if settings["lang"] != "en": - rcgcdw = gettext.translation('rcgcdw', localedir='locale', languages=[settings["lang"]]) - rc = gettext.translation('rc', localedir='locale', languages=[settings["lang"]]) - formatters_i18n = gettext.translation('formatters', localedir='locale', languages=[settings["lang"]]) - misc = gettext.translation('misc', localedir='locale', languages=[settings["lang"]]) - redaction = gettext.translation('redaction', localedir='locale', languages=[settings["lang"]]) - else: - rcgcdw, discussion_formatters, rc, formatters_i18n, misc, redaction = gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations() - formatters_i18n.pgettext = python37_pgettext_backward_compatibility -except FileNotFoundError: - logger.critical("No language files have been found. Make sure locale folder is located in the directory.") - sys.exit(1) + +def load_languages(): + global rcgcdw, rc, formatters_i18n, misc, redaction + try: + if settings["lang"] != "en": + rcgcdw = gettext.translation('rcgcdw', localedir='locale', languages=[settings["lang"]]) + rc = gettext.translation('rc', localedir='locale', languages=[settings["lang"]]) + formatters_i18n = gettext.translation('formatters', localedir='locale', languages=[settings["lang"]]) + misc = gettext.translation('misc', localedir='locale', languages=[settings["lang"]]) + redaction = gettext.translation('redaction', localedir='locale', languages=[settings["lang"]]) + else: + rcgcdw, discussion_formatters, rc, formatters_i18n, misc, redaction = gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations(), gettext.NullTranslations() + formatters_i18n.pgettext = python37_pgettext_backward_compatibility + except FileNotFoundError: + logger.critical("No language files have been found. Make sure locale folder is located in the directory.") + sys.exit(1) + + +load_languages() \ No newline at end of file diff --git a/src/misc.py b/src/misc.py index 79e5881..c744280 100644 --- a/src/misc.py +++ b/src/misc.py @@ -48,15 +48,15 @@ profile_fields = {"profile-location": _("Location"), "profile-aboutme": _("About class DataFile: """Data class which instance of is shared by multiple modules to remain consistent and do not cause too many IO operations.""" def __init__(self): - self.data = self.load_datafile() - misc_logger.debug("Current contents of data.json {}".format(self.data)) - self.changed = False + self.data_filename: str = settings.get("datafile_path", "data.json") + self.data: dict = self.load_datafile() + misc_logger.debug("Current contents of {} {}".format(self.data_filename, self.data)) + self.changed: bool = False - @staticmethod - def generate_datafile(): + def generate_datafile(self): """Generate a data.json file from a template.""" try: - with open("data.json", 'w', encoding="utf-8") as data: + with open(self.data_filename, 'w', encoding="utf-8") as data: data.write(json.dumps(data_template, indent=4)) except PermissionError: misc_logger.critical("Could not create a data file (no permissions). No way to store last edit.") @@ -67,7 +67,7 @@ class DataFile: :rtype: dict """ try: - with open("data.json", encoding="utf-8") as data: + with open(self.data_filename, encoding="utf-8") as data: return json.loads(data.read()) except FileNotFoundError: self.generate_datafile() @@ -79,7 +79,7 @@ class DataFile: if self.changed is False: # don't cause unnecessary write operations return try: - with open("data.json", "w", encoding="utf-8") as data_file: + with open(self.data_filename, "w", encoding="utf-8") as data_file: data_file.write(json.dumps(self.data, indent=4)) self.changed = False misc_logger.debug("Saving the database succeeded.") @@ -89,7 +89,7 @@ class DataFile: except OSError as e: if settings.get("error_tolerance", 1) > 1: if platform.system() == "Windows": - if "Invalid argument: 'data.json'" in str(e): + if "Invalid argument: '" + self.data_filename + "'" in str(e): misc_logger.error("Saving the data file failed due to Invalid argument exception, we've seen it " "before in issue #209, if you know the reason for it happening please reopen the " "issue with explanation, for now we are going to just ignore it.") # Reference #209 @@ -97,8 +97,9 @@ class DataFile: raise def __setitem__(self, instance, value): - self.data[instance] = value - self.changed = True + if self.data[instance] != value: + self.data[instance] = value + self.changed = True def __getitem__(self, item): try: @@ -255,16 +256,17 @@ def add_to_dict(dictionary, key): return dictionary -def prepare_paths(path, dry=False): - global WIKI_API_PATH - global WIKI_ARTICLE_PATH - global WIKI_SCRIPT_PATH - global WIKI_JUST_DOMAIN +def prepare_paths(path: str, dry=False): """Set the URL paths for article namespace and script namespace WIKI_API_PATH will be: WIKI_DOMAIN/api.php WIKI_ARTICLE_PATH will be: WIKI_DOMAIN/articlepath/$1 where $1 is the replaced string WIKI_SCRIPT_PATH will be: WIKI_DOMAIN/ WIKI_JUST_DOMAIN will be: WIKI_DOMAIN""" + global WIKI_API_PATH + global WIKI_ARTICLE_PATH + global WIKI_SCRIPT_PATH + global WIKI_JUST_DOMAIN + def quick_try_url(url): """Quickly test if URL is the proper script path, False if it appears invalid diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 612fc95..6d66fc3 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -22,29 +22,30 @@ import time, logging.config, requests, datetime, math, os.path, schedule, sys, re, importlib import src.misc +import src.configloader from collections import defaultdict, Counter, OrderedDict - +from src.argparser import command_args from typing import Optional import src.api.client from src.api.context import Context from src.api.hooks import formatter_hooks, pre_hooks, post_hooks -from src.configloader import settings -from src.misc import add_to_dict, datafile, WIKI_API_PATH, LinkParser, run_hooks +from src.misc import add_to_dict, datafile, run_hooks from src.api.util import create_article_path, default_message from src.discord.queue import send_to_discord from src.discord.message import DiscordMessage, DiscordMessageMetadata -from src.exceptions import MWError, ServerError, MediaWikiError, BadRequest, ClientError, NoFormatter +from src.exceptions import ServerError, MediaWikiError, NoFormatter from src.i18n import rcgcdw from src.wiki import Wiki +settings = src.configloader.settings _ = rcgcdw.gettext ngettext = rcgcdw.ngettext -TESTING = True if "--test" in sys.argv else False # debug mode, pipeline testing +TESTING = command_args.test # debug mode, pipeline testing AUTO_SUPPRESSION_ENABLED = settings.get("auto_suppression", {"enabled": False}).get("enabled") if AUTO_SUPPRESSION_ENABLED: - from src.discord.redaction import delete_messages, redact_messages + from src.discord.redaction import delete_messages, redact_messages, find_middle_next # Prepare logging logging.config.dictConfig(settings["logging"]) @@ -259,11 +260,14 @@ def rc_processor(change, changed_categories): delete_messages(dict(logid=logid)) elif identification_string == "delete/revision" and AUTO_SUPPRESSION_ENABLED: logparams = change.get('logparams', {"ids": []}) - if settings["appearance"]["mode"] == "embed": - redact_messages(logparams.get("ids", []), 0, logparams.get("new", {})) - else: - for revid in logparams.get("ids", []): - delete_messages(dict(revid=revid)) + if logparams.get("type", "") in ("revision", "logging", "oldimage"): + if settings["appearance"]["mode"] == "embed": + redact_messages(logparams.get("ids", []), 0, logparams.get("new", {})) + if "content" in logparams.get("new", {}) and settings.get("appearance", {}).get("embed", {}).get("show_edit_changes", False): # Also redact revisions in the middle and next ones in case of content (diffs leak) + redact_messages(find_middle_next(logparams.get("ids", []), change.get("pageid", -1)), 0, {"content": ""}) + else: + for revid in logparams.get("ids", []): + delete_messages(dict(revid=revid)) run_hooks(post_hooks, discord_message, metadata, context, change) discord_message.finish_embed() send_to_discord(discord_message, metadata) @@ -338,6 +342,7 @@ if TESTING: day_overview() import src.discussions src.discussions.fetch_discussions() + logger.info("Test has succeeded without premature exceptions.") sys.exit(0) while 1: diff --git a/src/wiki.py b/src/wiki.py index 3114487..b01dd23 100644 --- a/src/wiki.py +++ b/src/wiki.py @@ -38,6 +38,7 @@ storage = datafile logger = logging.getLogger("rcgcdw.rc") + class Wiki(object): """Store verious data and functions related to wiki and fetching of Recent Changes""" def __init__(self, rc_processor: Callable, abuse_processor: Callable): @@ -394,8 +395,7 @@ class Wiki(object): if self.downtimecredibility < 60: self.downtimecredibility += 15 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 + 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_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() diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/data/rc_objects.json b/test/data/rc_objects.json new file mode 100644 index 0000000..d20fa71 --- /dev/null +++ b/test/data/rc_objects.json @@ -0,0 +1,93 @@ +{ + "edit": { + "type": "edit", + "ns": 0, + "title": "Some page", + "pageid": 9327, + "revid": 2075232, + "old_revid": 232555, + "rcid": 2793437, + "user": "User3", + "oldlen": 32882, + "newlen": 328, + "timestamp": "2022-03-26T11:41:22Z", + "parsedcomment": "Work on new as", + "tags": ["VisualEdit"] + }, + "abuselog": { + "id": 4690092, + "filter": "Prevent vandalism", + "filter_id": "3", + "user": "User4", + "ns": 0, + "title": "Survival", + "action": "edit", + "result": "tag", + "revid": "398328478", + "timestamp": "2022-01-13T06:20:58Z" + }, + "cargo/createtable": { + "type": "log", + "ns": 0, + "title": "", + "pageid": 0, + "revid": 0, + "old_revid": 0, + "rcid": 13325, + "user": "User 10", + "oldlen": 0, + "newlen": 0, + "timestamp": "2022-01-13T15:25:09Z", + "parsedcomment": "", + "logid": 1865, + "logtype": "cargo", + "logaction": "createtable", + "logparams": { + "0": "TableTest" + }, + "tags": [] + }, + "datadump/generate": { + "type": "log", + "ns": 0, + "title": "Datadump", + "pageid": 0, + "revid": 0, + "old_revid": 0, + "rcid": 13325, + "user": "User 10", + "timestamp": "2022-01-13T15:25:09Z", + "parsedcomment": "", + "logid": 18, + "logtype": "datadump", + "logaction": "generate", + "logparams": { + "filename": "Somefilenoidea.tar.gz" + }, + "tags": [] + }, + "interwiki/iw_add": { + "type": "log", + "ns": -1, + "title": "Special:Interwiki", + "pageid": 0, + "revid": 0, + "old_revid": 0, + "rcid": 13326, + "user": "Not An User", + "oldlen": 0, + "newlen": 0, + "timestamp": "2022-01-13T15:31:25Z", + "parsedcomment": "Just a test", + "logid": 1866, + "logtype": "interwiki", + "logaction": "iw_add", + "logparams": { + "0": "testonlypleaseignore", + "1": "https://notawiki.notaplatform.com/wiki/$1", + "2": "0", + "3": "0" + }, + "tags": [] + } +} \ No newline at end of file diff --git a/test/data/rc_results.json b/test/data/rc_results.json new file mode 100644 index 0000000..2d8f91f --- /dev/null +++ b/test/data/rc_results.json @@ -0,0 +1,7 @@ +{ + "edit": {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 16711680, "url": "https://minecraft.fandom.com/index.php?title=Some_page&curid=9327&diff=2075232&oldid=232555", "title": "Some page (-32554)", "fields": [{"name": "Removed", "value": "__Only whitespace__", "inline": true}, {"name": "Added", "value": "__Only whitespace__", "inline": true}, {"name": "Tagi", "value": "VisualEdit", "inline": false}], "author": {"name": "User3", "url": "https://minecraft.fandom.com/wiki/User:User3", "icon_url": ""}, "timestamp": "2022-03-26T11:41:22Z", "description": "Work on new as"}]}, + "datadump/generate": {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": null, "author": {"name": "User 10", "url": "https://minecraft.fandom.com/wiki/User:User_10", "icon_url": ""}, "timestamp": "2022-01-13T15:25:09Z", "description": "", "title": "Generated Somefilenoidea.tar.gz dump", "url": "https://minecraft.fandom.com/wiki/Datadump"}]}, + "cargo/createtable":{"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": null, "author": {"name": "User 10", "url": "https://minecraft.fandom.com/wiki/User:User_10", "icon_url": ""}, "timestamp": "2022-01-13T15:25:09Z", "description": "", "url": "https://somewiki.somefarm.com/wiki/Special:CargoTables/TableTest", "title": "Created the Cargo table \"TableTest\""}]}, + "abuselog": {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": null, "title": "User4 triggered \"Prevent vandalism\"", "fields": [{"name": "Performed", "value": "Edit", "inline": false}, {"name": "Action taken", "value": "Tagged the edit", "inline": false}, {"name": "Title", "value": "Survival", "inline": false}]}]}, + "interwiki/iw_add": {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": null, "author": {"name": "Not An User", "url": "https://minecraft.fandom.com/wiki/User:Not_An_User", "icon_url": ""}, "timestamp": "2022-01-13T15:31:25Z", "url": "https://minecraft.fandom.com/wiki/Special:Interwiki", "title": "Added an entry to the interwiki table", "description": "Prefix: testonlypleaseignore, website: https://notawiki.notaplatform.com/wiki/$1 | "}]} +} \ No newline at end of file diff --git a/test/mockserver/configs/settings1.json b/test/mockserver/configs/settings1.json new file mode 100644 index 0000000..fafa778 --- /dev/null +++ b/test/mockserver/configs/settings1.json @@ -0,0 +1,557 @@ +{ + "cooldown": 30, + "wiki_url": "http://localhost:8080/", + "rc_enabled": true, + "lang": "en", + "header": { + "user-agent": "RcGcDw/{version}" + }, + "limit": 15, + "webhookURL": "http://localhost:8080/webhook/", + "limitrefetch": 15, + "wikiname": "Testing Connection", + "avatars": { + "connection_failed": "https://i.imgur.com/2jWQEt1.png", + "connection_restored": "", + "no_event": "", + "embed": "", + "compact": "" + }, + "ignored": ["external", "newusers/create", "newusers/autocreate", "newusers/create2", "newusers/byemail", "newusers/newusers"], + "show_updown_messages": true, + "ignored_namespaces": [], + "extensions_dir": "extensions", + "error_tolerance": 0, + "overview": false, + "overview_time": "00:00", + "send_empty_overview": false, + "license_detection": true, + "license_regex_detect": "\\{\\{(license|lizenz|licence|copyright)", + "license_regex": "\\{\\{(license|lizenz|licence|copyright)(\\ |\\|)(?P.*?)\\}\\}", + "disallow_regexes": [], + "wiki_bot_login": "", + "wiki_bot_password": "", + "show_added_categories": true, + "show_bots": false, + "show_abuselog": false, + "hide_ips": false, + "discord_message_cooldown": 0, + "auto_suppression": { + "enabled": true, + "db_location": ":memory:" + }, + "logging": { + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "standard": { + "format": "%(name)s - %(asctime)s - %(levelname)s: %(message)s" + } + }, + "handlers": { + "default": { + "formatter": "standard", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout" + }, + "filelogger": { + "formatter": "standard", + "class": "logging.FileHandler", + "filename": "rcgcdw.log", + "encoding": "utf-8" + } + }, + "loggers": { + "": { + "level": 0, + "handlers": ["default", "filelogger"] + } + } + }, + "appearance":{ + "mode": "embed", + "embed": { + "show_edit_changes": true, + "show_footer": true, + "embed_images": true, + "show_no_description_provided": true + } + }, + "fandom_discussions": { + "enabled": false, + "wiki_url": "", + "cooldown": 60, + "webhookURL": "", + "limit": 5, + "appearance": { + "mode": "embed", + "embed": { + "show_content": true + } + }, + "show_forums": [] + }, + "event_appearance": { + "daily_overview": { + "icon": "", + "color": 16312092, + "emoji": "", + "plot": true + }, + "new": { + "icon": "https://i.imgur.com/6HIbEq8.png", + "color": "THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE", + "emoji": "🆕" + }, + "edit": { + "icon": "https://i.imgur.com/zKYHkJm.png", + "color": "THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE", + "emoji": "📝" + }, + "upload/overwrite": { + "icon": "https://i.imgur.com/egJpa81.png", + "color": 12390624, + "emoji": "🖼️" + }, + "upload/upload": { + "icon": "https://i.imgur.com/egJpa81.png", + "color": 12390624, + "emoji": "🖼️" + }, + "upload/revert": { + "icon": "https://i.imgur.com/egJpa81.png", + "color": 12390624, + "emoji": "⏮️" + }, + "delete/delete": { + "icon": "https://i.imgur.com/BU77GD3.png", + "color": 1, + "emoji": "🗑️" + }, + "delete/delete_redir": { + "icon": "https://i.imgur.com/BU77GD3.png", + "color": 1, + "emoji": "🗑️" + }, + "delete/restore": { + "icon": "https://i.imgur.com/9MnROIU.png", + "color": 1, + "emoji": "♻️" + }, + "delete/revision": { + "icon": "https://i.imgur.com/1gps6EZ.png", + "color": 1, + "emoji": "👁️" + }, + "delete/event": { + "icon": "https://i.imgur.com/1gps6EZ.png", + "color": 1, + "emoji": "👁️" + }, + "merge/merge": { + "icon": "https://i.imgur.com/uQMK9XK.png", + "color": 25600, + "emoji": "🖇️" + }, + "move/move": { + "icon": "https://i.imgur.com/eXz9dog.png", + "color": 25600, + "emoji": "📨" + }, + "move/move_redir": { + "icon": "https://i.imgur.com/UtC3YX2.png", + "color": 25600, + "emoji": "📨" + }, + "block/block": { + "icon": "https://i.imgur.com/g7KgZHf.png", + "color": 1, + "emoji": "🚫" + }, + "block/unblock": { + "icon": "https://i.imgur.com/bvtBJ8o.png", + "color": 1, + "emoji": "✅" + }, + "block/reblock": { + "icon": "https://i.imgur.com/g7KgZHf.png", + "color": 1, + "emoji": "🚫" + }, + "protect/protect": { + "icon": "https://i.imgur.com/bzPt89Z.png", + "color": 16312092, + "emoji": "🔒" + }, + "protect/modify": { + "icon": "https://i.imgur.com/bzPt89Z.png", + "color": 16312092, + "emoji": "🔐" + }, + "protect/move_prot": { + "icon": "https://i.imgur.com/bzPt89Z.png", + "color": 16312092, + "emoji": "🔏" + }, + "protect/unprotect": { + "icon": "https://i.imgur.com/2wN3Qcq.png", + "color": 16312092, + "emoji": "🔓" + }, + "import/upload": { + "icon": "", + "color": 65280, + "emoji": "📥" + }, + "import/interwiki": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 65280, + "emoji": "📥" + }, + "rights/rights": { + "icon": "", + "color": 16711680, + "emoji": "🏅" + }, + "rights/autopromote": { + "icon": "", + "color": 16711680, + "emoji": "🏅" + }, + "abusefilter/modify": { + "icon": "https://i.imgur.com/Sn2NzRJ.png", + "color": 16711680, + "emoji": "🔍" + }, + "abusefilter/create": { + "icon": "https://i.imgur.com/Sn2NzRJ.png", + "color": 16711680, + "emoji": "🔍" + }, + "interwiki/iw_add": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 16711680, + "emoji": "🔗" + }, + "interwiki/iw_edit": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 16711680, + "emoji": "🔗" + }, + "interwiki/iw_delete": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 16711680, + "emoji": "🔗" + }, + "curseprofile/comment-created": { + "icon": "https://i.imgur.com/Lvy5E32.png", + "color": 16089376, + "emoji": "✉️" + }, + "curseprofile/comment-edited": { + "icon": "https://i.imgur.com/Lvy5E32.png", + "color": 16089376, + "emoji": "📧" + }, + "curseprofile/comment-deleted": { + "icon": "", + "color": 16089376, + "emoji": "🗑️" + }, + "curseprofile/comment-purged":{ + "icon":"", + "color": 16089376, + "emoji": "👁️" + }, + "curseprofile/comment-replied": { + "icon": "https://i.imgur.com/hkyYsI1.png", + "color": 16089376, + "emoji": "📩" + }, + "curseprofile/profile-edited": { + "icon": "", + "color": 16089376, + "emoji": "📌" + }, + "contentmodel/change": { + "icon": "", + "color": 25600, + "emoji": "📋" + }, + "contentmodel/new": { + "icon": "", + "color": 25600, + "emoji": "📋" + }, + "cargo/deletetable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "cargo/createtable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "cargo/replacetable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "cargo/recreatetable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "sprite/sprite": { + "icon": "", + "color": 16776960, + "emoji": "🪟" + }, + "sprite/sheet": { + "icon": "", + "color": 16776960, + "emoji": "🪟" + }, + "sprite/slice": { + "icon": "", + "color": 16776960, + "emoji": "🪟" + }, + "managetags/create": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "managetags/delete": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "managetags/activate": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "managetags/deactivate": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "newusers/autocreate": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/byemail": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/create": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/create2": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/newusers": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "managewiki/delete": { + "icon": "", + "color": 8421504, + "emoji": "🗑️" + }, + "managewiki/lock": { + "icon": "", + "color": 8421504, + "emoji": "🔒" + }, + "managewiki/namespaces": { + "icon": "", + "color": 8421504, + "emoji": "📦" + }, + "managewiki/namespaces-delete": { + "icon": "", + "color": 8421504, + "emoji": "🗑️" + }, + "managewiki/rights": { + "icon": "", + "color": 8421504, + "emoji": "🏅" + }, + "managewiki/settings": { + "icon": "", + "color": 8421504, + "emoji": "⚙️" + }, + "managewiki/undelete": { + "icon": "", + "color": 8421504, + "emoji": "♻️" + }, + "managewiki/unlock": { + "icon": "", + "color": 8421504, + "emoji": "🔓" + }, + "datadump/generate": { + "icon": "", + "color": 8421504, + "emoji": "📤" + }, + "datadump/delete": { + "icon": "", + "color": 8421504, + "emoji": "🗑️" + }, + "pagetranslation/mark": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/unmark": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/moveok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/movenok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletefok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletefnok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletelok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletelnok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/encourage": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/discourage": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/prioritylanguages": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/associate": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/dissociate": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "translationreview/message": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "translationreview/group": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagelang/pagelang": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "renameuser/renameuser": { + "icon": "", + "color": 8421504, + "emoji": "📛" + }, + "suppressed": { + "icon": "https://i.imgur.com/1gps6EZ.png", + "color": 1, + "emoji": "👁️" + }, + "discussion": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/post": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/reply": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/poll": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/quiz": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/wall/post": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 3752525, + "emoji": "✉️" + }, + "discussion/wall/reply": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 3752525, + "emoji": "📩" + }, + "discussion/comment/post": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 10802, + "emoji": "🗒️" + }, + "discussion/comment/reply": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 10802, + "emoji": "🗒️" + }, + "unknown": { + "icon": "", + "color": 0, + "emoji": "❓" + } + } +} diff --git a/test/mockserver/configs/settings2.json b/test/mockserver/configs/settings2.json new file mode 100644 index 0000000..4bdaecc --- /dev/null +++ b/test/mockserver/configs/settings2.json @@ -0,0 +1,557 @@ +{ + "cooldown": 30, + "wiki_url": "http://localhost:8080/", + "rc_enabled": true, + "lang": "en", + "header": { + "user-agent": "RcGcDw/{version}" + }, + "limit": 15, + "webhookURL": "http://localhost:8080/webhook/", + "limitrefetch": 15, + "wikiname": "Testing Connection", + "avatars": { + "connection_failed": "https://i.imgur.com/2jWQEt1.png", + "connection_restored": "", + "no_event": "", + "embed": "", + "compact": "" + }, + "ignored": ["external", "newusers/create", "newusers/autocreate", "newusers/create2", "newusers/byemail", "newusers/newusers"], + "show_updown_messages": true, + "ignored_namespaces": [], + "extensions_dir": "extensions", + "error_tolerance": 2, + "overview": true, + "overview_time": "00:00", + "send_empty_overview": true, + "license_detection": false, + "license_regex_detect": "\\{\\{(license|lizenz|licence|copyright)", + "license_regex": "\\{\\{(license|lizenz|licence|copyright)(\\ |\\|)(?P.*?)\\}\\}", + "disallow_regexes": ["{{version nav"], + "wiki_bot_login": "", + "wiki_bot_password": "", + "show_added_categories": false, + "show_bots": true, + "show_abuselog": false, + "hide_ips": true, + "discord_message_cooldown": 0, + "auto_suppression": { + "enabled": false, + "db_location": ":memory:" + }, + "logging": { + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "standard": { + "format": "%(name)s - %(asctime)s - %(levelname)s: %(message)s" + } + }, + "handlers": { + "default": { + "formatter": "standard", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout" + }, + "filelogger": { + "formatter": "standard", + "class": "logging.FileHandler", + "filename": "rcgcdw.log", + "encoding": "utf-8" + } + }, + "loggers": { + "": { + "level": 0, + "handlers": ["default", "filelogger"] + } + } + }, + "appearance":{ + "mode": "compact", + "embed": { + "show_edit_changes": true, + "show_footer": true, + "embed_images": true, + "show_no_description_provided": true + } + }, + "fandom_discussions": { + "enabled": false, + "wiki_url": "", + "cooldown": 60, + "webhookURL": "", + "limit": 5, + "appearance": { + "mode": "embed", + "embed": { + "show_content": true + } + }, + "show_forums": [] + }, + "event_appearance": { + "daily_overview": { + "icon": "", + "color": 16312092, + "emoji": "", + "plot": true + }, + "new": { + "icon": "https://i.imgur.com/6HIbEq8.png", + "color": "THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE", + "emoji": "🆕" + }, + "edit": { + "icon": "https://i.imgur.com/zKYHkJm.png", + "color": "THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE", + "emoji": "📝" + }, + "upload/overwrite": { + "icon": "https://i.imgur.com/egJpa81.png", + "color": 12390624, + "emoji": "🖼️" + }, + "upload/upload": { + "icon": "https://i.imgur.com/egJpa81.png", + "color": 12390624, + "emoji": "🖼️" + }, + "upload/revert": { + "icon": "https://i.imgur.com/egJpa81.png", + "color": 12390624, + "emoji": "⏮️" + }, + "delete/delete": { + "icon": "https://i.imgur.com/BU77GD3.png", + "color": 1, + "emoji": "🗑️" + }, + "delete/delete_redir": { + "icon": "https://i.imgur.com/BU77GD3.png", + "color": 1, + "emoji": "🗑️" + }, + "delete/restore": { + "icon": "https://i.imgur.com/9MnROIU.png", + "color": 1, + "emoji": "♻️" + }, + "delete/revision": { + "icon": "https://i.imgur.com/1gps6EZ.png", + "color": 1, + "emoji": "👁️" + }, + "delete/event": { + "icon": "https://i.imgur.com/1gps6EZ.png", + "color": 1, + "emoji": "👁️" + }, + "merge/merge": { + "icon": "https://i.imgur.com/uQMK9XK.png", + "color": 25600, + "emoji": "🖇️" + }, + "move/move": { + "icon": "https://i.imgur.com/eXz9dog.png", + "color": 25600, + "emoji": "📨" + }, + "move/move_redir": { + "icon": "https://i.imgur.com/UtC3YX2.png", + "color": 25600, + "emoji": "📨" + }, + "block/block": { + "icon": "https://i.imgur.com/g7KgZHf.png", + "color": 1, + "emoji": "🚫" + }, + "block/unblock": { + "icon": "https://i.imgur.com/bvtBJ8o.png", + "color": 1, + "emoji": "✅" + }, + "block/reblock": { + "icon": "https://i.imgur.com/g7KgZHf.png", + "color": 1, + "emoji": "🚫" + }, + "protect/protect": { + "icon": "https://i.imgur.com/bzPt89Z.png", + "color": 16312092, + "emoji": "🔒" + }, + "protect/modify": { + "icon": "https://i.imgur.com/bzPt89Z.png", + "color": 16312092, + "emoji": "🔐" + }, + "protect/move_prot": { + "icon": "https://i.imgur.com/bzPt89Z.png", + "color": 16312092, + "emoji": "🔏" + }, + "protect/unprotect": { + "icon": "https://i.imgur.com/2wN3Qcq.png", + "color": 16312092, + "emoji": "🔓" + }, + "import/upload": { + "icon": "", + "color": 65280, + "emoji": "📥" + }, + "import/interwiki": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 65280, + "emoji": "📥" + }, + "rights/rights": { + "icon": "", + "color": 16711680, + "emoji": "🏅" + }, + "rights/autopromote": { + "icon": "", + "color": 16711680, + "emoji": "🏅" + }, + "abusefilter/modify": { + "icon": "https://i.imgur.com/Sn2NzRJ.png", + "color": 16711680, + "emoji": "🔍" + }, + "abusefilter/create": { + "icon": "https://i.imgur.com/Sn2NzRJ.png", + "color": 16711680, + "emoji": "🔍" + }, + "interwiki/iw_add": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 16711680, + "emoji": "🔗" + }, + "interwiki/iw_edit": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 16711680, + "emoji": "🔗" + }, + "interwiki/iw_delete": { + "icon": "https://i.imgur.com/sFkhghb.png", + "color": 16711680, + "emoji": "🔗" + }, + "curseprofile/comment-created": { + "icon": "https://i.imgur.com/Lvy5E32.png", + "color": 16089376, + "emoji": "✉️" + }, + "curseprofile/comment-edited": { + "icon": "https://i.imgur.com/Lvy5E32.png", + "color": 16089376, + "emoji": "📧" + }, + "curseprofile/comment-deleted": { + "icon": "", + "color": 16089376, + "emoji": "🗑️" + }, + "curseprofile/comment-purged":{ + "icon":"", + "color": 16089376, + "emoji": "👁️" + }, + "curseprofile/comment-replied": { + "icon": "https://i.imgur.com/hkyYsI1.png", + "color": 16089376, + "emoji": "📩" + }, + "curseprofile/profile-edited": { + "icon": "", + "color": 16089376, + "emoji": "📌" + }, + "contentmodel/change": { + "icon": "", + "color": 25600, + "emoji": "📋" + }, + "contentmodel/new": { + "icon": "", + "color": 25600, + "emoji": "📋" + }, + "cargo/deletetable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "cargo/createtable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "cargo/replacetable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "cargo/recreatetable": { + "icon": "", + "color": 16776960, + "emoji": "📦" + }, + "sprite/sprite": { + "icon": "", + "color": 16776960, + "emoji": "🪟" + }, + "sprite/sheet": { + "icon": "", + "color": 16776960, + "emoji": "🪟" + }, + "sprite/slice": { + "icon": "", + "color": 16776960, + "emoji": "🪟" + }, + "managetags/create": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "managetags/delete": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "managetags/activate": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "managetags/deactivate": { + "icon": "", + "color": 16776960, + "emoji": "🏷️" + }, + "newusers/autocreate": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/byemail": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/create": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/create2": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "newusers/newusers": { + "icon": "", + "color": 65280, + "emoji": "🗿" + }, + "managewiki/delete": { + "icon": "", + "color": 8421504, + "emoji": "🗑️" + }, + "managewiki/lock": { + "icon": "", + "color": 8421504, + "emoji": "🔒" + }, + "managewiki/namespaces": { + "icon": "", + "color": 8421504, + "emoji": "📦" + }, + "managewiki/namespaces-delete": { + "icon": "", + "color": 8421504, + "emoji": "🗑️" + }, + "managewiki/rights": { + "icon": "", + "color": 8421504, + "emoji": "🏅" + }, + "managewiki/settings": { + "icon": "", + "color": 8421504, + "emoji": "⚙️" + }, + "managewiki/undelete": { + "icon": "", + "color": 8421504, + "emoji": "♻️" + }, + "managewiki/unlock": { + "icon": "", + "color": 8421504, + "emoji": "🔓" + }, + "datadump/generate": { + "icon": "", + "color": 8421504, + "emoji": "📤" + }, + "datadump/delete": { + "icon": "", + "color": 8421504, + "emoji": "🗑️" + }, + "pagetranslation/mark": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/unmark": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/moveok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/movenok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletefok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletefnok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletelok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/deletelnok": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/encourage": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/discourage": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/prioritylanguages": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/associate": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagetranslation/dissociate": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "translationreview/message": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "translationreview/group": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "pagelang/pagelang": { + "icon": "", + "color": 8421504, + "emoji": "🌐" + }, + "renameuser/renameuser": { + "icon": "", + "color": 8421504, + "emoji": "📛" + }, + "suppressed": { + "icon": "https://i.imgur.com/1gps6EZ.png", + "color": 1, + "emoji": "👁️" + }, + "discussion": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/post": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/reply": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/poll": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/forum/quiz": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 54998, + "emoji": "📝" + }, + "discussion/wall/post": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 3752525, + "emoji": "✉️" + }, + "discussion/wall/reply": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 3752525, + "emoji": "📩" + }, + "discussion/comment/post": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 10802, + "emoji": "🗒️" + }, + "discussion/comment/reply": { + "icon": "https://static.wikia.nocookie.net/663e53f7-1e79-4906-95a7-2c1df4ebbada", + "color": 10802, + "emoji": "🗒️" + }, + "unknown": { + "icon": "", + "color": 0, + "emoji": "❓" + } + } +} diff --git a/test/mockserver/data/response_diff20748742075224.json b/test/mockserver/data/response_diff20748742075224.json new file mode 100644 index 0000000..9395bfe --- /dev/null +++ b/test/mockserver/data/response_diff20748742075224.json @@ -0,0 +1,5 @@ +{ + "compare": { + "*": "\n Line 8:\n Line 8:\n\n\n  \n
| grasscolor = {{color|#8EB971}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n  \n
| grasscolor = {{color|#8EB971}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n\n\n  \n
| foliagecolor = {{color|#71A74D}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n  \n
| foliagecolor = {{color|#71A74D}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n\n\n  \n +\n
| waterc
\n\n\n −\n
| watercolor = {{color|#3F76E4}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n  \n\n\n −\n
| structures ={{EnvLink|Dungeon}}s<br>{{EnvLink|Mineshaft}}s<br>{{EnvLink|Stronghold}}s<br>{{EnvLink|Amethyst Geode}}s
\n +\n
Dripleaf}}<br>{{BlockLink|Cave Vines}}<br>{{BlockLink|Spore Blossom}}<br>{{ItemLink|Glow Berries}}}}
\n\n\n −\n
| blocks = {{BlockLink|Oak Log}}<br>{{BlockLink|Azalea Leaves}}<br>{{BlockLink|Flowering Azalea Leaves}}<br>{{BlockLink|Azalea}}<br>{{BlockLink|Flowering Azalea}}<br>{{BlockLink|Rooted Dirt}}<br>{{BlockLink|Hanging Roots}}<br>{{BlockLink|Moss Block}}<br>{{BlockLink|Moss Carpet}}<br>{{BlockLink|Grass}}<br>{{BlockLink|Tall Grass}}<br>{{BlockLink|Vines}}<br>{{BlockLink|Water}}<br>{{BlockLink|Clay}}<br>{{BlockLink|Small Dripleaf}}<br>{{BlockLink|Big Dripleaf}}<br>{{BlockLink|Cave Vines}}<br>{{BlockLink|Spore Blossom}}<br>{{ItemLink|Glow Berries}}}}
\n  \n\n\n  \n \n  \n \n\n\n  \n
A '''lush cave''' is a temperate [[Overworld]] [[cave]] [[biome]] that has a unique fauna and flora and is found underground below [[azalea tree]]s.
\n  \n
A '''lush cave''' is a temperate [[Overworld]] [[cave]] [[biome]] that has a unique fauna and flora and is found underground below [[azalea tree]]s.
\n\n\n\n" + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_diff20752242075225.json b/test/mockserver/data/response_diff20752242075225.json new file mode 100644 index 0000000..f5c4023 --- /dev/null +++ b/test/mockserver/data/response_diff20752242075225.json @@ -0,0 +1,5 @@ +{ + "compare": { + "*": "\n Line 8:\n Line 8:\n\n\n  \n
| grasscolor = {{color|#8EB971}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n  \n
| grasscolor = {{color|#8EB971}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n\n\n  \n
| foliagecolor = {{color|#71A74D}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n  \n
| foliagecolor = {{color|#71A74D}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n\n\n  \n +\n
| watercolor = {{color|#3F76E4}}{{only|java|short=y}}<br>{{Check the code}}{{only|bedrock|short=y}}
\n\n\n −\n
| waterc
\n  \n\n\n −\n
Dripleaf}}<br>{{BlockLink|Cave Vines}}<br>{{BlockLink|Spore Blossom}}<br>{{ItemLink|Glow Berries}}}}
\n +\n
| structures ={{EnvLink|Dungeon}}s<br>{{EnvLink|Mineshaft}}s<br>{{EnvLink|Stronghold}}s<br>{{EnvLink|Amethyst Geode}}s
\n\n\n  \n +\n
| blocks = {{BlockLink|Oak Log}}<br>{{BlockLink|Azalea Leaves}}<br>{{BlockLink|Flowering Azalea Leaves}}<br>{{BlockLink|Azalea}}<br>{{BlockLink|Flowering Azalea}}<br>{{BlockLink|Rooted Dirt}}<br>{{BlockLink|Hanging Roots}}<br>{{BlockLink|Moss Block}}<br>{{BlockLink|Moss Carpet}}<br>{{BlockLink|Grass}}<br>{{BlockLink|Tall Grass}}<br>{{BlockLink|Vines}}<br>{{BlockLink|Water}}<br>{{BlockLink|Clay}}<br>{{BlockLink|Small Dripleaf}}<br>{{BlockLink|Big Dripleaf}}<br>{{BlockLink|Cave Vines}}<br>{{BlockLink|Spore Blossom}}<br>{{ItemLink|Glow Berries}}}}
\n\n\n  \n \n  \n \n\n\n  \n
A '''lush cave''' is a temperate [[Overworld]] [[cave]] [[biome]] that has a unique fauna and flora and is found underground below [[azalea tree]]s.
\n  \n
A '''lush cave''' is a temperate [[Overworld]] [[cave]] [[biome]] that has a unique fauna and flora and is found underground below [[azalea tree]]s.
\n\n\n\n" + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_diff20752302075231.json b/test/mockserver/data/response_diff20752302075231.json new file mode 100644 index 0000000..e6c5d7f --- /dev/null +++ b/test/mockserver/data/response_diff20752302075231.json @@ -0,0 +1,5 @@ +{ + "compare": { + "*": "\n Line 1:\n Line 1:\n\n\n −\n
<br />{{Redirect|1.7.10|the Bedrock Edition version|Bedrock Edition 1.17.10|the New Nintendo 3DS Edition version|New Nintendo 3DS Edition 1.7.10}}
\n +\n
{{Redirect|1.7.10|the Bedrock Edition version|Bedrock Edition 1.17.10|the New Nintendo 3DS Edition version|New Nintendo 3DS Edition 1.7.10}}
\n\n\n  \n
{{distinguish|Java Edition 1.7.1}}
\n  \n
{{distinguish|Java Edition 1.7.1}}
\n\n\n  \n
{{version nav
\n  \n
{{version nav
\n\n\n −\n
| title = Minecraft 1.7.10
\n +\n
|title=Minecraft 1.7.10
\n\n\n −\n
| edition = java
\n +\n
|edition=java
\n\n\n −\n
| image = Java Edition 1.7.10.png
\n +\n
|image=Java Edition 1.7.10.png
\n\n\n −\n
| date = June 26, 2014
\n +\n
|date=June 26, 2014
\n\n\n −\n
| clienthash = e80d9b3bf5085002218d4be59e668bac718abbc6
\n +\n
|clienthash=e80d9b3bf5085002218d4be59e668bac718abbc6
\n\n\n −\n
| jsonhash = 2e818dc89e364c7efcfa54bec7e873c5f00b3840
\n +\n
|jsonhash=2e818dc89e364c7efcfa54bec7e873c5f00b3840
\n\n\n −\n
| serverhash = 952438ac4e01b4d115c5fc38f891710c4941df29
\n +\n
|serverhash=952438ac4e01b4d115c5fc38f891710c4941df29
\n\n\n −\n
| exehash = a79b91ef69b9b4af63d1c7007f60259106869b21
\n +\n
|exehash=a79b91ef69b9b4af63d1c7007f60259106869b21
\n\n\n −\n
| prevparent = 1.7.2
\n +\n
|prevparent=1.7.2
\n\n\n −\n
| prev = 1.7.9
\n +\n
|prev=1.7.9
\n\n\n −\n
| nextparent = 1.8
\n +\n
|nextparent=1.8
\n\n\n  \n
}}<onlyinclude>
\n  \n
}}<onlyinclude>
\n\n\n  \n \n  \n \n\n\n\n" + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_diff2325552075232.json b/test/mockserver/data/response_diff2325552075232.json new file mode 100644 index 0000000..871b21d --- /dev/null +++ b/test/mockserver/data/response_diff2325552075232.json @@ -0,0 +1,5 @@ +{ + "compare": { + "*": "\n Line 1:\n Line 1:\n\n\n  \n
{{DungeonsDLC|Jungle Awakens}}
\n  \n
{{DungeonsDLC|Jungle Awakens}}
\n\n\n  \n
{{DungeonsEntity
\n  \n
{{DungeonsEntity
\n\n\n  \n +\n
|title=Leapleaf
\n\n\n −\n
|title=បាន​ឲ្យ​ដឹង​
\n  \n\n\n  \n
|image=Leapleaf.png
\n  \n
|image=Leapleaf.png
\n\n\n  \n
|imagesize=x240px
\n  \n
|imagesize=x240px
\n\n\n  \n +\n
|behavior=Hostile
\n\n\n −\n
|behavior=នៅ​ថ្ងៃ​ទី​នោះ​បាន​
\n  \n\n\n  \n
}}
\n  \n
}}
\n\n\n  \n \n  \n \n\n\n  \n +\n
A '''leapleaf''', known internally as a '''leaper''', is a [[MCD:Mob#Hostile|hostile mob]] found in the ''[[Minecraft Dungeons]]'' [[MCD:Jungle Awakens|Jungle Awakens]] DLC that was corrupted by a shard of the [[MCD:Orb of Dominance|orb of dominance]].
\n\n\n −\n
ការ​ '''បាន​និយាយ​''', លោក​ជំទាវ​ម៉ែ​ត្រ​ឡ​បាន​ធ្វើ​ឱ្យ​មាន​ភាព​'''ការ​ប្រកួត​''', ម៉ោង​ក្នុង​ [[MCD:Mob#Hostile|នៅ​ក្នុង​ភូមិ​សាស្ត្រ​ស្រុក​]] 5បាន​ឲ្យ​ដឹង​ថា​មុន​នឹង​ឈាន​ ''[[Minecraft Dungeons|ការ​ប្រកួត​កីឡា​បាល់​ទាត់​កម្ពុជា​បាន​ថ្លែង​ថា​ក្រុម​]]'' [[MCD:Jungle Awakens|ឡ​ដ្ឋាន​ជាតិ​អូឡាំពិក​កម្ពុជា​]] បាន​និយាយ​ថា​ខ្លួន​ការ​ប្រកួត​កីឡា​បាល់​ទាត់​កម្ពុជា​បាន​ថ្លែង​ [[MCD:Orb of Dominance|នៅ​ក្នុង​ការ​ភូមិ​សាស្ត្រ​ឃុំ​ខ្លួន​ជន​]].
\n  \n\n\n  \n \n  \n \n\n\n  \n +\n
== Spawning ==
\n\n\n −\n
== ការ​ពិភាក្សា​របស់​អ្នកប្រើ ==
\n  \n\n\n −\n
* {{DuLevelLink|Dingy Jungle|ការ​ពិភាក្សា​អំពី​}}
\n +\n
* {{DuLevelLink|Dingy Jungle}}
\n\n\n −\n
* {{DuLevelLink|Panda Plateau|ជា​មួយ​ក្រុម​មាន​បាន​}}
\n +\n
* {{DuLevelLink|Panda Plateau}}
\n\n\n −\n
* {{DuLevelLink|Overgrown Temple|ឃ​ន៍​របស់​អ្នក​ស្រី​មាន​}}
\n +\n
* {{DuLevelLink|Overgrown Temple}}
\n\n\n −\n
* {{DuLevelLink|Tower|កស​បាន​ឡែ​ក​}}
\n +\n
* {{DuLevelLink|Tower}}
\n\n\n  \n \n  \n \n\n\n  \n +\n
== Behavior ==
\n\n\n −\n
== ការ​ពិភាក្សា​ ==
\n  \n\n\n  \n +\n
A leapleaf has four different behaviors. It cannot be stunned by normal attacks, except [[MCD:Shock Powder|Shock Powder]] and any weapon with the [[MCD:Stunning|Stunning]] enchantment.
\n\n\n −\n
នៅ​ការ​ពិភាក្សា​ដ្ឋាន​នគរបាល​ខណ្ឌ​ចំការមន​បាន​ធ្វើ​ឱ្យ​នាង​បាន​និយាយ​ថា​លោក​បាន​បន្ថែម​ថា​. ផង​ដែរ​ថា​នៅ​ពេល​នោះ​មក​វិញ​នូវ​ភាព​, ធ​រដ្ឋ​មន្ត្រី​នៃ​ព្រះរាជា​ព្រះ​[[MCD:Shock Powder|ដែល​មាន​ឈ្មោះ​បោះ​ថា​]]បាន​ធ្វើ​ឲ្យ​អ្នក​ជិះ​ម៉ូតូ​ម៉ាក​វ៉េវ​អាល់ហ្វា​[[MCD:Stunning|ឌ​ថា​ការ​]] ណ​បក្ស​សង្គ្រោះ​ជាតិ​បាន​ឲ្យ​ដឹង​.
\n  \n\n\n  \n \n  \n \n\n\n  \n +\n
;Basic attack
\n\n\n −\n
;ការ​ពិភាក្សា​របស់​អ្នកប្រើ​
\n  \n\n\n  \n +\n
If a leapleaf is not currently charging or resting, it smashes any nearby targets with one of its arms, dealing notable damage.
\n\n\n −\n
ការ​ពិភាក្សា​អំពី​វិ​គី​ស្ថាន​បាន​ធ្វើ​ឲ្យ​ប៉ះពាល់​ដល់​បរិស្ថាន​បាន​ធ្វើ​, ជ​កម្ម​ខេត្ត​បន្ទាយ​មានជ័យ​ដែល​ជា​ក្រុម​ហ៊ុន​នេះ​បាន​, ឡ​ដ្ឋាន​នគរ​វត​ការ​ធ្វើ​ដំណើរ​មក​ដល់​.
\n  \n\n\n  \n \n  \n \n\n\n  \n +\n
;Charge
\n\n\n −\n
;ការ​ពិភាក្សា​
\n  \n\n\n  \n +\n
After a leapleaf does a chest-pouding animation, it would begin running toward the target.
\n\n\n −\n
នៅ​ក្នុង​ភូមិ​ត្រពាំង​ថ្លឹង​សង្កាត់​ចោម​ចៅ​, ទី​ក្រុង​ញូ​វយ​ការ​ប្រកួត​នេះ​បាន​កើត​.
\n  \n\n\n  \n \n  \n \n\n\n  \n +\n
;Heavy attack
\n\n\n −\n
;ការ​ពិភាក្សា​
\n  \n\n\n  \n +\n
When in melee reach of the target, it leaps foward and smashes the ground, dealing massive damage.
\n\n\n −\n
ក​ល​នៅ​ក្នុង​ភូមិ​សាស្ត្រ​ឃុំ​ស្រុក​, ម៉ោង​ប្រមាណ​ជិត​ខាង​របស់​លោក​ស្រី​យី​ហោ​ណា​មួយ​ដែល​អាច​ធ្វើ​ឱ្យ,​លោក​បាន​បញ្ជាក់​ពី​ការ​មូលហេតុ​ដែល​ធ្វើ​.
\n  \n\n\n  \n \n  \n \n\n\n  \n +\n
;Rest
\n\n\n −\n
;ការ​ពិភាក្សា​
\n  \n\n\n  \n +\n
After doing this, it may become stunned and rest on the ground for a few seconds.
\n\n\n −\n
ការ​ពិភាក្សា​អំពី​វិ​និច្ឆ័យ​ទោស​តាម,​និង​លោក​ជំទាវ​ម៉ែ​ត​និង​លោក​ស្រី​យី​ហោ​ណា​មួយ.​ភី​ឌ​បាន​ធ្វើ​ឱ្យ​អ្នក​បើក​រថយន្ត​ទៅ​លើ​.
\n  \n\n\n  \n \n  \n \n\n\n  \n +\n
== Health ==
\n\n\n −\n
== ការ​ពិភាក្សា​ ==
\n  \n\n\n  \n
(In other versions, this any not be 100% accurate)
\n  \n
(In other versions, this any not be 100% accurate)
\n\n\n  \n
{| class=\"wikitable sortable\"
\n  \n
{| class=\"wikitable sortable\"
\n\n\n  \n +\n
! Power
\n\n\n −\n
! ទីនេះ​មាន​នូវ​ការ​
\n  \n\n\n  \n +\n
! Min Health
\n\n\n −\n
! សូម​អរគុណ​ដល់​អ្នក​ទៅ​កាន់​ទី​
\n  \n\n\n  \n
|-
\n  \n
|-
\n\n\n  \n
| 1
\n  \n
| 1
\n\n\n\n" + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_diff2325562075234.json b/test/mockserver/data/response_diff2325562075234.json new file mode 100644 index 0000000..e9002ab --- /dev/null +++ b/test/mockserver/data/response_diff2325562075234.json @@ -0,0 +1,5 @@ +{ + "compare": { + "*": "\n Line 28:\n Line 28:\n\n\n  \n \n  \n \n\n\n  \n
; [[Blue Key|Blauer Schlüssel]]
\n  \n
; [[Blue Key|Blauer Schlüssel]]
\n\n\n −\n
* Der Überlieferung nach soll dieser \"rote Türen öffnen\".
\n +\n
* Der Überlieferung nach soll dieser \"blaue Türen öffnen\".
\n\n\n  \n
* Wird vom [[Großer_Wächter|Großen Wächter]] fallen gelassen.
\n  \n
* Wird vom [[Großer_Wächter|Großen Wächter]] fallen gelassen.
\n\n\n  \n \n  \n \n\n\n  \n
; [[Yellow Key|Gelber Schlüssel]]
\n  \n
; [[Yellow Key|Gelber Schlüssel]]
\n\n\n −\n
* Der Überlieferung nach soll dieser \"rote Türen öffnen\".
\n +\n
* Der Überlieferung nach soll dieser \"gelbe Türen öffnen\".
\n\n\n  \n
* Wird vom [[Verwüster]] fallen gelassen.
\n  \n
* Wird vom [[Verwüster]] fallen gelassen.
\n\n\n  \n \n  \n \n\n\n Line 103:\n Line 103:\n\n\n  \n \n  \n \n\n\n  \n
; Beute
\n  \n
; Beute
\n\n\n  \n +\n
{| class=\"wikitable collapsible\"
\n\n\n −\n
{| {{BtK|Bonus Fass|bonus_barrel|t=1}}
\n  \n\n\n  \n +\n
! colspan=\"3\" | Verlies Truheninhalt
\n\n\n  \n
|-
\n  \n
|-
\n\n\n  \n +\n
! Gegenstand
\n\n\n −\n
| colspan=\"9\" | Beutetabellen Inhalt
\n  \n\n\n  \n +\n
! Wahrscheinlichkeit
\n\n\n  \n +\n
! Stapelgröße
\n\n\n  \n
|-
\n  \n
|-
\n\n\n  \n +\n
| colspan=\"3\" | In jeder Truhe <b>sind ein Stapel</b> aus der Gruppe dieser Gegenstände enthalten
\n\n\n −\n
| {{BtE|3D Shareware v1.34}}
\n  \n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{BS|Luft}} Nichts
\n\n\n  \n +\n
| 66,7 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Roter Schlüssel|3D Shareware v1.34#Neuerungen|Roter Schlüssel}}
\n\n\n  \n +\n
| 20,8 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Gelber Schlüssel|3D Shareware v1.34#Neuerungen|Gelber Schlüssel}}
\n\n\n  \n +\n
| 8,3 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Blauer Schlüssel|3D Shareware v1.34#Neuerungen|Blauer Schlüssel}}
\n\n\n  \n +\n
| 4,2 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| colspan=\"3\" | In jeder Truhe <b>sind ein bis vier Stapel</b> aus der Gruppe dieser Gegenstände enthalten
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Pfeil}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1-20
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Getränkter Pfeil|Pfeil|Getränkter Pfeil}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1-20
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Bogen|Bogen|Verzauberter Bogen}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Steinschwert|Steinschwert|Verzaubertes Steinschwert}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Verweiltrank|Verweiltrank|Zufälliger Verweiltrank}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Wurftrank|Wurftrank|Zufälliger Wurftrank}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Trank|Trank|Zufälliger Trank}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Armbrust|Armbrust|BFC9000}} <ref group=\"Anm.\" name=\"Armbrust\"/>
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Lederkappe|Lederkappe|Verzauberte Lederkappe}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Lederjacke|Lederjacke|Verzauberte Lederjacke}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Lederhose|Lederhose|Verzauberte Lederhose}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Lederstiefel|Lederstiefel|Verzauberte Lederstiefel}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Eisenschwert|Eisenschwert|Verzaubertes Eisenschwert}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Eisenhelm|Eisenhelm|Verzauberter Eisenhelm}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Eisenharnisch|Eisenharnisch|Verzauberter Eisenharnisch}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Eisenbeinschutz|Eisenbeinschutz|Verzauberter Eisenbeinschutz}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Eisenstiefel|Eisenstiefel|Verzauberte Eisenstiefel}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Diamantschwert|Diamantschwert|Verzaubertes Diamantschwert}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Diamanthelm|Diamanthelm|Verzauberter Diamanthelm}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Diamantharnisch|Diamantharnisch|Verzauberter Diamantharnisch}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Diamantbeinschutz|Diamantbeinschutz|Verzauberter Diamantbeinschutz}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n +\n
|-
\n\n\n  \n +\n
| {{GL|Diamantstiefel|Diamantstiefel|Verzauberte Diamantstiefel}}
\n\n\n  \n +\n
| 10,9 %
\n\n\n  \n +\n
| 1
\n\n\n  \n
|}
\n  \n
|}
\n\n\n  \n +\n
{{Verweisliste|group=\"Anm.\"|ref=
\n\n\n  \n +\n
<ref name=\"Armbrust\">Jede Stufe zwischen 1 und 12 von [[Mehrfachschuss]] ist gleich wahrscheinlich.</ref>
\n\n\n  \n +\n
}}
\n\n\n  \n \n  \n \n\n\n  \n
===Kreaturen===
\n  \n
===Kreaturen===
\n\n\n\n" + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_error.json b/test/mockserver/data/response_error.json new file mode 100644 index 0000000..0ca2533 --- /dev/null +++ b/test/mockserver/data/response_error.json @@ -0,0 +1,14 @@ +{ + "errors": [ + { + "code": "unknown_action", + "key": "apierror-unrecognizedvalue", + "params": [ + "action", + "ddd" + ], + "module": "main" + } + ], + "*": "See https://localhost/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at <https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce> for notice of API deprecations and breaking changes." +} \ No newline at end of file diff --git a/test/mockserver/data/response_image.json b/test/mockserver/data/response_image.json new file mode 100644 index 0000000..ed909e5 --- /dev/null +++ b/test/mockserver/data/response_image.json @@ -0,0 +1,32 @@ +{ + "batchcomplete": "", + "query": { + "pages": { + "181124": { + "pageid": 181124, + "ns": 6, + "title": "File:Oak Sign (9).png", + "imagerepository": "local", + "imageinfo": [ + { + "timestamp": "2021-12-26T12:09:04Z", + "url": "http://localhost:8080/test_wiki/images/7/75/Oak_Sign_%289%29.png/revision/latest?cb=20211226120904", + "descriptionurl": "http://localhost:8080/wiki/File:Oak_Sign_(9).png", + "descriptionshorturl": "http://localhost:8080/index.php?curid=181124" + } + ], + "revisions": [ + { + "slots": { + "main": { + "contentmodel": "wikitext", + "contentformat": "text/x-wiki", + "*": "\n== License ==\n{{License Mojang}}" + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_init.json b/test/mockserver/data/response_init.json new file mode 100644 index 0000000..b588016 --- /dev/null +++ b/test/mockserver/data/response_init.json @@ -0,0 +1,461 @@ +{ + "batchcomplete": "", + "limits": { + "tags": 5000 + }, + "query": { + "tags": [ + { + "name": "abusefilter-condition-limit", + "displayname": "condition limit reached" + }, + { + "name": "added youtube video", + "displayname": "contains YouTube embed" + }, + { + "name": "advanced mobile edit", + "displayname": "Advanced mobile edit" + }, + { + "name": "character-spam", + "displayname": "Character spam" + }, + { + "name": "deletiontemplate-addition", + "displayname": "Requested deletion" + }, + { + "name": "maps-visual-edit", + "displayname": "Visual map edit" + }, + { + "name": "missing signature", + "displayname": "missing signature" + }, + { + "name": "mobile edit", + "displayname": "Mobile edit" + }, + { + "name": "mobile web edit" + }, + { + "name": "mobile-edit", + "displayname": "Mobile edit" + }, + { + "name": "move", + "displayname": "move" + }, + { + "name": "mw-blank", + "displayname": "Blanking" + }, + { + "name": "mw-changed-redirect-target", + "displayname": "Redirect target changed" + }, + { + "name": "mw-contentmodelchange", + "displayname": "content model change" + }, + { + "name": "mw-new-redirect", + "displayname": "New redirect" + }, + { + "name": "mw-removed-redirect", + "displayname": "Removed redirect" + }, + { + "name": "mw-replace", + "displayname": "Replaced" + }, + { + "name": "mw-rollback", + "displayname": "Rollback" + }, + { + "name": "mw-undo", + "displayname": "Undo" + }, + { + "name": "possible-number-spam", + "displayname": "possible-number-spam" + }, + { + "name": "section blanking", + "displayname": "section blanked" + }, + { + "name": "spriteeditor", + "displayname": "Sprite edit" + }, + { + "name": "test", + "displayname": "test" + }, + { + "name": "tor", + "displayname": "Made through Tor" + }, + { + "name": "visualeditor", + "displayname": "Visual edit" + }, + { + "name": "visualeditor-needcheck", + "displayname": "Visual edit: Check" + }, + { + "name": "visualeditor-switched", + "displayname": "Partial visual edit" + }, + { + "name": 0, + "displayname": "0" + }, + { + "name": "visualeditor-wikitext", + "displayname": "2017 source edit" + }, + { + "name": 1, + "displayname": "1" + }, + { + "name": 2, + "displayname": "2" + }, + { + "name": 3, + "displayname": "3" + } + ], + "allmessages": [ + { + "name": "recentchanges-page-added-to-category", + "normalizedname": "recentchanges-page-added-to-category", + "*": "[[:$1]] added to category" + }, + { + "name": "recentchanges-page-removed-from-category", + "normalizedname": "recentchanges-page-removed-from-category", + "*": "[[:$1]] removed from category" + }, + { + "name": "recentchanges-page-added-to-category-bundled", + "normalizedname": "recentchanges-page-added-to-category-bundled", + "*": "[[:$1]] added to category, [[Special:WhatLinksHere/$1|this page is included within other pages]]" + }, + { + "name": "recentchanges-page-removed-from-category-bundled", + "normalizedname": "recentchanges-page-removed-from-category-bundled", + "*": "[[:$1]] removed from category, [[Special:WhatLinksHere/$1|this page is included within other pages]]" + } + ], + "namespaces": { + "0": { + "id": 0, + "case": "first-letter", + "subpages": "", + "content": "", + "*": "" + }, + "1": { + "id": 1, + "case": "first-letter", + "subpages": "", + "canonical": "Talk", + "*": "Talk" + }, + "2": { + "id": 2, + "case": "first-letter", + "subpages": "", + "canonical": "User", + "*": "User" + }, + "3": { + "id": 3, + "case": "first-letter", + "subpages": "", + "canonical": "User talk", + "*": "User talk" + }, + "4": { + "id": 4, + "case": "first-letter", + "subpages": "", + "canonical": "Project", + "*": "Minecraft Wiki" + }, + "5": { + "id": 5, + "case": "first-letter", + "subpages": "", + "canonical": "Project talk", + "*": "Minecraft Wiki talk" + }, + "6": { + "id": 6, + "case": "first-letter", + "subpages": "", + "canonical": "File", + "*": "File" + }, + "7": { + "id": 7, + "case": "first-letter", + "subpages": "", + "canonical": "File talk", + "*": "File talk" + }, + "8": { + "id": 8, + "case": "first-letter", + "subpages": "", + "canonical": "MediaWiki", + "*": "MediaWiki" + }, + "9": { + "id": 9, + "case": "first-letter", + "subpages": "", + "canonical": "MediaWiki talk", + "*": "MediaWiki talk" + }, + "10": { + "id": 10, + "case": "first-letter", + "subpages": "", + "canonical": "Template", + "*": "Template" + }, + "11": { + "id": 11, + "case": "first-letter", + "subpages": "", + "canonical": "Template talk", + "*": "Template talk" + }, + "12": { + "id": 12, + "case": "first-letter", + "subpages": "", + "canonical": "Help", + "*": "Help" + }, + "13": { + "id": 13, + "case": "first-letter", + "subpages": "", + "canonical": "Help talk", + "*": "Help talk" + }, + "14": { + "id": 14, + "case": "first-letter", + "subpages": "", + "canonical": "Category", + "*": "Category" + }, + "15": { + "id": 15, + "case": "first-letter", + "subpages": "", + "canonical": "Category talk", + "*": "Category talk" + }, + "110": { + "id": 110, + "case": "first-letter", + "canonical": "Forum", + "*": "Forum" + }, + "111": { + "id": 111, + "case": "first-letter", + "canonical": "Forum talk", + "*": "Forum talk" + }, + "202": { + "id": 202, + "case": "first-letter", + "canonical": "UserProfile", + "*": "UserProfile" + }, + "274": { + "id": 274, + "case": "first-letter", + "canonical": "Widget", + "*": "Widget" + }, + "275": { + "id": 275, + "case": "first-letter", + "subpages": "", + "canonical": "Widget talk", + "*": "Widget talk" + }, + "420": { + "id": 420, + "case": "first-letter", + "canonical": "GeoJson", + "content": "", + "defaultcontentmodel": "GeoJson", + "*": "GeoJson" + }, + "421": { + "id": 421, + "case": "first-letter", + "subpages": "", + "canonical": "GeoJson talk", + "defaultcontentmodel": "wikitext", + "*": "GeoJson talk" + }, + "500": { + "id": 500, + "case": "first-letter", + "subpages": "", + "canonical": "User blog", + "*": "User blog" + }, + "501": { + "id": 501, + "case": "first-letter", + "subpages": "", + "canonical": "User blog comment", + "*": "User blog comment" + }, + "502": { + "id": 502, + "case": "first-letter", + "subpages": "", + "canonical": "Blog", + "*": "Blog" + }, + "503": { + "id": 503, + "case": "first-letter", + "subpages": "", + "canonical": "Blog talk", + "*": "Blog talk" + }, + "828": { + "id": 828, + "case": "first-letter", + "subpages": "", + "canonical": "Module", + "*": "Module" + }, + "829": { + "id": 829, + "case": "first-letter", + "subpages": "", + "canonical": "Module talk", + "*": "Module talk" + }, + "1200": { + "id": 1200, + "case": "first-letter", + "canonical": "Message Wall", + "*": "Message Wall" + }, + "1201": { + "id": 1201, + "case": "first-letter", + "subpages": "", + "canonical": "Thread", + "*": "Thread" + }, + "1202": { + "id": 1202, + "case": "first-letter", + "canonical": "Message Wall Greeting", + "*": "Message Wall Greeting" + }, + "2300": { + "id": 2300, + "case": "first-letter", + "canonical": "Gadget", + "*": "Gadget" + }, + "2301": { + "id": 2301, + "case": "first-letter", + "canonical": "Gadget talk", + "*": "Gadget talk" + }, + "2302": { + "id": 2302, + "case": "case-sensitive", + "canonical": "Gadget definition", + "defaultcontentmodel": "GadgetDefinition", + "*": "Gadget definition" + }, + "2303": { + "id": 2303, + "case": "case-sensitive", + "canonical": "Gadget definition talk", + "*": "Gadget definition talk" + }, + "10000": { + "id": 10000, + "case": "first-letter", + "subpages": "", + "canonical": "Minecraft Dungeons", + "content": "", + "*": "Minecraft Dungeons" + }, + "10001": { + "id": 10001, + "case": "first-letter", + "canonical": "Minecraft Dungeons talk", + "*": "Minecraft Dungeons talk" + }, + "10002": { + "id": 10002, + "case": "first-letter", + "subpages": "", + "canonical": "Minecraft Earth", + "content": "", + "*": "Minecraft Earth" + }, + "10003": { + "id": 10003, + "case": "first-letter", + "canonical": "Minecraft Earth talk", + "*": "Minecraft Earth talk" + }, + "10004": { + "id": 10004, + "case": "first-letter", + "subpages": "", + "canonical": "Minecraft Story Mode", + "content": "", + "*": "Minecraft Story Mode" + }, + "10005": { + "id": 10005, + "case": "first-letter", + "subpages": "", + "canonical": "Minecraft Story Mode talk", + "*": "Minecraft Story Mode talk" + }, + "-2": { + "id": -2, + "case": "first-letter", + "canonical": "Media", + "*": "Media" + }, + "-1": { + "id": -1, + "case": "first-letter", + "canonical": "Special", + "*": "Special" + } + } + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_recentchanges.json b/test/mockserver/data/response_recentchanges.json new file mode 100644 index 0000000..6693225 --- /dev/null +++ b/test/mockserver/data/response_recentchanges.json @@ -0,0 +1,209 @@ +{ + "batchcomplete": "", + "continue": { + "rccontinue": "20211226120903|2793420", + "continue": "-||" + }, + "query": { + "recentchanges": [ + { + "type": "edit", + "ns": 0, + "title": "Some different page", + "pageid": 9327, + "revid": 2075232, + "old_revid": 232555, + "rcid": 2793437, + "user": "User3", + "oldlen": 32882, + "newlen": 328, + "timestamp": "2021-12-26T12:26:32Z", + "parsedcomment": "Some changes lol", + "tags": [] + }, + { + "type": "edit", + "ns": 0, + "title": "Java Edition 1.19", + "pageid": 177589, + "revid": 2075231, + "old_revid": 2075230, + "rcid": 2793436, + "user": "User2", + "minor": "", + "oldlen": 811, + "newlen": 771, + "timestamp": "2021-12-26T12:24:23Z", + "parsedcomment": "Revert edits by 192.168.1.1 (talk)", + "tags": [ + "mw-rollback" + ] + }, + { + "type": "log", + "ns": 2, + "title": "User:FineUser2", + "pageid": 72216, + "revid": 0, + "old_revid": 0, + "rcid": 2793427, + "user": "Administrator2", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T12:40:00Z", + "parsedcomment": "", + "logid": 1141234, + "logtype": "rights", + "logaction": "rights", + "logparams": { + "oldgroups": [ + "autopatrol", + "rollback" + ], + "newgroups": [ + "sysop" + ], + "oldmetadata": [ + { + "group": "autopatrol", + "expiry": "infinity" + }, + { + "group": "rollback", + "expiry": "infinity" + } + ], + "newmetadata": [ + { + "group": "sysop", + "expiry": "infinity" + } + ] + }, + "tags": [] + }, + { + "type": "log", + "ns": 202, + "title": "UserProfile:User2", + "pageid": 0, + "revid": 0, + "old_revid": 0, + "rcid": 2793426, + "user": "User2", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T12:20:11Z", + "parsedcomment": "CoolUser#1812", + "logid": 1141233, + "logtype": "curseprofile", + "logaction": "profile-edited", + "logparams": { + "4:section": "profile-link-discord" + }, + "tags": [] + }, + { + "type": "log", + "ns": 2, + "title": "User:Spammer", + "pageid": 0, + "revid": 0, + "old_revid": 0, + "rcid": 2793425, + "user": "Administrator", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T12:15:04Z", + "parsedcomment": "Rule #2: Spam", + "logid": 1141231, + "logtype": "block", + "logaction": "block", + "logparams": { + "duration": "2 weeks", + "flags": [ + "nocreate" + ], + "sitewide": "", + "expiry": "2022-01-11T10:38:23Z" + }, + "tags": [] + }, + { + "type": "edit", + "ns": 0, + "title": "Java Edition 1.19", + "pageid": 177589, + "revid": 2075225, + "old_revid": 2075224, + "rcid": 2793424, + "user": "Good User", + "anon": "", + "oldlen": 771, + "newlen": 811, + "timestamp": "2021-12-26T12:13:38Z", + "parsedcomment": "Undo revision by 192.168.1.1. Reason: Mad cats", + "tags": [ + "visualeditor" + ] + }, + { + "type": "edit", + "ns": 0, + "title": "Java Edition 1.19", + "pageid": 177589, + "revid": 2075224, + "old_revid": 2074874, + "rcid": 2793423, + "user": "192.168.1.1", + "anon": "", + "oldlen": 811, + "newlen": 771, + "timestamp": "2021-12-26T12:09:18Z", + "parsedcomment": "Hahahahhaha, you will never understand my genius!", + "tags": [ + "mobile edit", + "mobile web edit", + "visualeditor" + ] + }, + { + "type": "categorize", + "ns": 14, + "title": "Category:Mojang images", + "pageid": 181124, + "revid": 2075223, + "old_revid": 0, + "rcid": 2793422, + "user": "User1", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T12:09:04Z", + "parsedcomment": "File:Oak Sign (9).png added to category", + "tags": [] + }, + { + "type": "log", + "ns": 6, + "title": "File:Oak Sign (9).png", + "pageid": 181124, + "revid": 2075223, + "old_revid": 0, + "rcid": 2793421, + "user": "User1", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T12:09:04Z", + "parsedcomment": "", + "logid": 1141230, + "logtype": "upload", + "logaction": "upload", + "logparams": { + "img_sha1": "l9vzs83denqnn2ph8wya6ieue6b7tvo", + "img_timestamp": "2021-12-26T12:09:04Z" + }, + "tags": [] + } + ] + } +} diff --git a/test/mockserver/data/response_recentchanges2.json b/test/mockserver/data/response_recentchanges2.json new file mode 100644 index 0000000..7f15021 --- /dev/null +++ b/test/mockserver/data/response_recentchanges2.json @@ -0,0 +1,78 @@ +{ + "batchcomplete": "", + "continue": { + "rccontinue": "20211226120903|2793420", + "continue": "-||" + }, + "query": { + "recentchanges": [ + { + "type": "edit", + "ns": 0, + "title": "Unique page", + "pageid": 9327, + "revid": 2075234, + "old_revid": 232556, + "rcid": 2793440, + "user": "User3", + "oldlen": 328, + "newlen": 32882, + "timestamp": "2021-12-26T13:37:10Z", + "parsedcomment": "Added content", + "tags": [] + }, + { + "type": "log", + "ns": 0, + "title": "Some different page", + "pageid": 9327, + "revid": 0, + "old_revid": 0, + "rcid": 2793439, + "user": "Frisk", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T13:35:50Z", + "parsedcomment": "aaaaaaaaaaaaaaa", + "logid": 1141236, + "logtype": "delete", + "logaction": "delete", + "logparams": {}, + "tags": [] + }, + { + "type": "log", + "ns": 0, + "title": "Java Edition 1.19", + "pageid": 177589, + "revid": 0, + "old_revid": 0, + "rcid": 2793438, + "user": "Frisk", + "oldlen": 0, + "newlen": 0, + "timestamp": "2021-12-26T13:02:59Z", + "parsedcomment": "Heresy!", + "logid": 1141235, + "logtype": "delete", + "logaction": "revision", + "logparams": { + "type": "revision", + "ids": [ + "2075224" + ], + "old": { + "bitmask": 0 + }, + "new": { + "bitmask": 7, + "content": "", + "comment": "", + "user": "" + } + }, + "tags": [] + } + ] + } +} diff --git a/test/mockserver/data/response_siteinfo.json b/test/mockserver/data/response_siteinfo.json new file mode 100644 index 0000000..0099468 --- /dev/null +++ b/test/mockserver/data/response_siteinfo.json @@ -0,0 +1,98 @@ +{ + "batchcomplete": "", + "query": { + "general": { + "mainpage": "Random Wiki", + "base": "https://localhost:8080/wiki/Mainpage", + "sitename": "Minecraft Wiki", + "logo": "https://localhost:8080/localhost/images/b/bc/Wiki.png", + "generator": "MediaWiki 1.35.3", + "phpversion": "7.3.32", + "phpsapi": "fpm-fcgi", + "dbtype": "mysql", + "dbversion": "5.7.25-28-log", + "externalimages": [], + "langconversion": "", + "titleconversion": "", + "linkprefixcharset": "", + "linkprefix": "", + "linktrail": "/^([a-z]+)(.*)$/sD", + "legaltitlechars": " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+", + "invalidusernamechars": "@:", + "fixarabicunicode": "", + "fixmalayalamunicode": "", + "case": "first-letter", + "lang": "en", + "fallback": [], + "fallback8bitEncoding": "windows-1252", + "writeapi": "", + "maxarticlesize": 2097152, + "timezone": "UTC", + "timeoffset": 0, + "articlepath": "/wiki/$1", + "scriptpath": "", + "script": "/index.php", + "variantarticlepath": false, + "server": "https://localhost:8080", + "servername": "localhost:8080", + "wikiid": "localhost", + "time": "2021-12-26T21:57:13Z", + "misermode": "", + "uploadsenabled": "", + "maxuploadsize": 10485760, + "minuploadchunksize": 1024, + "galleryoptions": { + "imagesPerRow": 0, + "imageWidth": 120, + "imageHeight": 120, + "captionLength": "", + "showBytes": "", + "showDimensions": "", + "mode": "traditional" + }, + "thumblimits": [ + 120, + 150, + 180, + 200, + 250, + 300 + ], + "imagelimits": [ + { + "width": 320, + "height": 240 + }, + { + "width": 640, + "height": 480 + }, + { + "width": 800, + "height": 600 + }, + { + "width": 1024, + "height": 768 + }, + { + "width": 1280, + "height": 1024 + } + ], + "favicon": "https://localhost:8080/favicon.ico", + "centralidlookupprovider": "local", + "allcentralidlookupproviders": [ + "local" + ], + "interwikimagic": "", + "magiclinks": { + "ISBN": "" + }, + "categorycollation": "uppercase", + "citeresponsivereferences": "", + "gamepedia": "true", + "mobileserver": "https://localhost:8080" + } + } +} \ No newline at end of file diff --git a/test/mockserver/data/response_userinfo.json b/test/mockserver/data/response_userinfo.json new file mode 100644 index 0000000..c240c44 --- /dev/null +++ b/test/mockserver/data/response_userinfo.json @@ -0,0 +1,22 @@ +{ + "batchcomplete": "", + "limits": { + "usercontribs": 5000 + }, + "query": { + "usercontribs": [ + { + "userid": 0, + "user": "192.168.1.1" + }, + { + "userid": 0, + "user": "192.168.1.1" + }, + { + "userid": 0, + "user": "192.168.1.1" + } + ] + } +} \ No newline at end of file diff --git a/test/mockserver/results/results1.json b/test/mockserver/results/results1.json new file mode 100644 index 0000000..ace744f --- /dev/null +++ b/test/mockserver/results/results1.json @@ -0,0 +1 @@ +[{"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 12390624, "url": "http://localhost:8080/wiki/File:Oak_Sign_%289%29.png", "title": "Uploaded File\\:Oak Sign (9).png", "fields": [{"name": "Options", "value": "([preview](http://localhost:8080/test_wiki/images/7/75/Oak_Sign_%289%29.png/revision/latest?cb=20211226120904?8204354331))", "inline": false}], "image": {"url": "http://localhost:8080/test_wiki/images/7/75/Oak_Sign_%289%29.png/revision/latest?cb=20211226120904?8204354331"}, "author": {"name": "User1", "url": "http://localhost:8080/wiki/User:User1", "icon_url": "https://i.imgur.com/egJpa81.png"}, "timestamp": "2021-12-26T12:09:04Z", "description": "No description provided\nLicense: Mojang"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 9175040, "url": "http://localhost:8080/index.php?title=Java_Edition_1.19&curid=177589&diff=2075224&oldid=2074874", "title": "Java Edition 1.19 (-40)", "fields": [{"name": "Removed", "value": "\n~~\\| watercolor = \\{\\{color\\|#3F76E4\\}\\}\\{\\{only\\|java\\|short=y\\}\\}\\\\{\\{Check the code\\}\\}\\{\\{only\\|bedrock\\|short=y\\}\\}~~\n~~\\| structures =\\{\\{EnvLink\\|Dungeon~~\\}\\}~~s~~\\\\{\\{~~EnvLink~~\\|~~Mineshaft~~\\}\\}~~s~~\\\\{\\{~~EnvLink~~\\|~~Stronghold~~\\}\\}~~s~~\\\\{\\{~~EnvLink~~\\|~~Amethyst~~ ~~Geode~~\\}\\}~~s~~\n\n__And more__", "inline": true}, {"name": "Added", "value": "\n**\\| waterc**\n**Dripleaf**\\}\\}\\\\{\\{**BlockLink**\\|**Cave Vines**\\}\\}\\\\{\\{**BlockLink**\\|**Spore Blossom**\\}\\}\\\\{\\{**ItemLink**\\|**Glow** **Berries\\}\\}**\\}\\}", "inline": true}, {"name": "Tags", "value": "Mobile edit, Visual edit", "inline": false}], "author": {"name": "192.168.1.1 (3)", "url": "http://localhost:8080/wiki/Special:Contributions/192.168.1.1", "icon_url": "https://i.imgur.com/zKYHkJm.png"}, "timestamp": "2021-12-26T12:09:18Z", "description": "Hahahahhaha, you will never understand my genius!"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 35840, "url": "http://localhost:8080/index.php?title=Java_Edition_1.19&curid=177589&diff=2075225&oldid=2075224", "title": "Java Edition 1.19 (+40)", "fields": [{"name": "Removed", "value": "\n~~\\| waterc~~\n~~Dripleaf~~\\}\\}\\\\{\\{~~BlockLink~~\\|~~Cave Vines~~\\}\\}\\\\{\\{~~BlockLink~~\\|~~Spore Blossom~~\\}\\}\\\\{\\{~~ItemLink~~\\|~~Glow~~ ~~Berries\\}\\}~~\\}\\}", "inline": true}, {"name": "Added", "value": "\n**\\| watercolor = \\{\\{color\\|#3F76E4\\}\\}\\{\\{only\\|java\\|short=y\\}\\}\\\\{\\{Check the code\\}\\}\\{\\{only\\|bedrock\\|short=y\\}\\}**\n**\\| structures =\\{\\{EnvLink\\|Dungeon**\\}\\}**s**\\\\{\\{**EnvLink**\\|**Mineshaft**\\}\\}**s**\\\\{\\{**EnvLink**\\|**Stronghold**\\}\\}**s**\\\\{\\{**EnvLink**\\|**Amethyst** **Geode**\\}\\}**s**\n\n__And more__", "inline": true}, {"name": "Tags", "value": "Visual edit", "inline": false}], "author": {"name": "Good User (3)", "url": "http://localhost:8080/wiki/Special:Contributions/Good_User", "icon_url": "https://i.imgur.com/zKYHkJm.png"}, "timestamp": "2021-12-26T12:13:38Z", "description": "Undo revision by 192.168.1.1. Reason\\: Mad cats"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 1, "url": "http://localhost:8080/wiki/User:Spammer", "fields": [{"name": "Block flags", "value": "nocreate", "inline": false}], "title": "Blocked Spammer for 15 days, for 22 hours, for 23 minutes", "author": {"name": "Administrator", "url": "http://localhost:8080/wiki/User:Administrator", "icon_url": "https://i.imgur.com/g7KgZHf.png"}, "timestamp": "2021-12-26T12:15:04Z", "description": "[Rule #2]()\\: Spam"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 16089376, "author": {"name": "User2", "url": "http://localhost:8080/wiki/User:User2", "icon_url": ""}, "timestamp": "2021-12-26T12:20:11Z", "description": "Discord handle field changed to: CoolUser#1812", "title": "Edited their own profile", "url": "http://localhost:8080/wiki/UserProfile:User2"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 16711680, "url": "http://localhost:8080/wiki/User:FineUser2", "title": "Changed group membership for FineUser2", "fields": [{"name": "Added group", "value": "sysop", "inline": true}, {"name": "Removed groups", "value": "rollback\nautopatrol", "inline": true}], "author": {"name": "Administrator2", "url": "http://localhost:8080/wiki/User:Administrator2", "icon_url": ""}, "timestamp": "2021-12-26T12:40:00Z", "description": "No description provided"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 9175040, "url": "http://localhost:8080/index.php?title=Java_Edition_1.19&curid=177589&diff=2075231&oldid=2075230", "title": "Java Edition 1.19 (m -40)", "fields": [{"name": "Removed", "value": "\n~~\\
~~\\{\\{Redirect\\|1.7.10\\|the Bedrock Edition version\\|Bedrock Edition 1.17.10\\|the New Nintendo 3DS Edition version\\|New Nintendo 3DS Edition 1.7.10\\}\\}\n\\|~~ ~~title~~ ~~=~~ ~~Minecraft 1.7.10\n\\|~~ ~~edition~~ ~~=~~ ~~java\n\\|~~ ~~image~~ ~~=~~ ~~Java Edition 1.7.10.png\n\\|~~ ~~date~~ ~~=~~ ~~June 26, 2014\n\\|~~ ~~clienthash~~ ~~=~~ ~~e80d9b3bf5085002218d4be59e668bac718abbc6\n\\|~~ ~~jsonhash~~ ~~=~~ ~~2e818dc89e364c7efcfa54bec7e873c5f00b3840\n\\|~~ ~~serverhash~~ ~~=~~ ~~952438ac4e01b4d115c5fc38f891710c4941df29\n\\|~~ ~~exehash~~ ~~=~~ ~~a79b91ef69b9b4af63d1c7007f60259106869b21\n\\|~~ ~~prevparent~~ ~~=~~ ~~1.7.2\n\\|~~ ~~prev~~ ~~=~~ ~~1.7.9\n\\|~~ ~~nextparent~~ ~~=~~ ~~1.8", "inline": true}, {"name": "Added", "value": "\n\\{\\{Redirect\\|1.7.10\\|the Bedrock Edition version\\|Bedrock Edition 1.17.10\\|the New Nintendo 3DS Edition version\\|New Nintendo 3DS Edition 1.7.10\\}\\}\n\\|title=Minecraft 1.7.10\n\\|edition=java\n\\|image=Java Edition 1.7.10.png\n\\|date=June 26, 2014\n\\|clienthash=e80d9b3bf5085002218d4be59e668bac718abbc6\n\\|jsonhash=2e818dc89e364c7efcfa54bec7e873c5f00b3840\n\\|serverhash=952438ac4e01b4d115c5fc38f891710c4941df29\n\\|exehash=a79b91ef69b9b4af63d1c7007f60259106869b21\n\\|prevparent=1.7.2\n\\|prev=1.7.9\n\\|nextparent=1.8", "inline": true}, {"name": "Tags", "value": "Rollback", "inline": false}], "author": {"name": "User2", "url": "http://localhost:8080/wiki/User:User2", "icon_url": "https://i.imgur.com/zKYHkJm.png"}, "timestamp": "2021-12-26T12:24:23Z", "description": "Revert edits by [192.168.1.1]() (talk)"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 16711680, "url": "http://localhost:8080/index.php?title=Some_different_page&curid=9327&diff=2075232&oldid=232555", "title": "Some different page (-32554)", "fields": [{"name": "Removed", "value": "\n~~\\|title=\u1794\u17b6\u1793\u200b\u17b2\u17d2\u1799\u200b\u178a\u17b9\u1784\u200b~~\n~~\\|behavior=\u1793\u17c5\u200b\u1790\u17d2\u1784\u17c3\u200b\u1791\u17b8\u200b\u1793\u17c4\u17c7\u200b\u1794\u17b6\u1793\u200b~~\n~~\u1780\u17b6\u179a\u200b '''\u1794\u17b6\u1793\u200b\u1793\u17b7\u1799\u17b6\u1799\u200b''', \u179b\u17c4\u1780\u200b\u1787\u17c6\u1791\u17b6\u179c\u200b\u1798\u17c9\u17c2\u200b\u178f\u17d2\u179a\u200b\u17a1\u200b\u1794\u17b6\u1793\u200b\u1792\u17d2\u179c\u17be\u200b\u17b1\u17d2\u1799\u200b\u1798\u17b6\u1793\u200b\u1797\u17b6\u1796\u200b'''\u1780\u17b6\u179a\u200b\u1794\u17d2\u179a\u1780\u17bd\u178f\u200b''', \u1798\u17c9\u17c4\u1784\u200b\u1780\u17d2\u1793\u17bb\u1784\u200b [[MCD:Mob#Hostile\\|\u1793\u17c5\u200b\u1780\u17d2\u1793\u17bb\u1784\u200b\u1797\u17bc\u1798\u17b7\u200b\u179f\u17b6\u179f\u17d2\u178f\u17d2\u179a\u200b\u179f\u17d2\u179a\u17bb\u1780\u200b]] 5\u1794\u17b6\u1793\u200b\u17b2\u17d2\u1799\u200b\u178a\u17b9\u1784\u200b\u1790\u17b6\u200b\u1798\u17bb\u1793\u200b\u1793\u17b9\u1784\u200b\u1788\u17b6\u1793\u200b ''[[Minecraft Dungeons\\|\u1780\u17b6\u179a\u200b\u1794\u17d2\u179a\u1780\u17bd\u178f\u200b\u1780\u17b8\u17a1\u17b6\u200b\u1794\u17b6\u179b\u17cb\u200b\u1791\u17b6\u178f\u17cb\u200b\u1780\u1798\u17d2\u1796\u17bb\u1787\u17b6\u200b\u1794\u17b6\u1793\u200b\u1790\u17d2\u179b\u17c2\u1784\u200b\u1790\u17b6\u200b\u1780\u17d2\u179a\u17bb\u1798\u200b]]'' [[MCD:Jungle Awakens\\|\u17a1\u200b\u178a\u17d2\u178b\u17b6\u1793\u200b\u1787\u17b6\u178f\u17b7\u200b\u17a2\u17bc\u17a1\u17b6\u17c6\u1796\u17b7\u1780\u200b\u1780\u1798\u17d2\u1796\u17bb\u1787\u17b6\u200b]] \u1794\u17b6\u1793\u200b\u1793\u17b7\u1799\u17b6\u1799\u200b\u1790\u17b6\u200b\u1781\u17d2\u179b\u17bd\u1793\u200b\u1780\u17b6\u179a\u200b\u1794\u17d2\u179a\u1780\u17bd\u178f\u200b\u1780\u17b8\u17a1\u17b6\u200b\u1794\u17b6\u179b\u17cb\u200b\u1791\u17b6\u178f\u17cb\u200b\u1780\u1798\u17d2\u1796\u17bb\u1787\u17b6\u200b\u1794\u17b6\u1793\u200b\u1790\u17d2\u179b\u17c2\u1784\u200b [[MCD:Orb of Dominance\\|\u1793\u17c5\u200b\u1780\u17d2\u1793\u17bb\u1784\u200b\u1780\u17b6\u179a\u200b\u1797\u17bc\u1798\u17b7\u200b\u179f\u17b6\u179f\u17d2\u178f\u17d2\u179a\u200b\u1783\u17bb\u17c6\u200b\u1781\u17d2\u179b\u17bd\u1793\u200b\u1787\u1793\u200b]].~~\n~~== \u1780\u17b6\u179a\u200b\u1796\u17b7\u1797\u17b6\u1780\u17d2\u179f\u17b6\u200b\u179a\u1794\u179f\u17cb\u200b\u17a2\u17d2\u1793\u1780\u1794\u17d2\u179a\u17be ==~~\n\\* \\{\\{DuLevelLink\\|Dingy Jungle~~\\|\u1780\u17b6\u179a\u200b\u1796\u17b7\u1797\u17b6\u1780\u17d2\u179f\u17b6\u200b\u17a2\u17c6\u1796\u17b8\u200b~~\\}\\}\n\\* \\{\\{DuLevelLink\\|Panda Plateau~~\\|\u1787\u17b6\u200b\u1798\u17bd\u1799\u200b\u1780\u17d2\u179a\u17bb\u1798\u200b\u1798\u17b6\u1793\u200b\u1794\u17b6\u1793\u200b~~\\}\\}\n\\* \\{\\{DuLevelLink\\|Overgrown Temple~~\\|\u1783\u200b\u1793\u17cd\u200b\u179a\u1794\u179f\u17cb\u200b\u17a2\u17d2\u1793\u1780\u200b\u179f\u17d2\u179a\u17b8\u200b\u1798\u17b6\u1793\u200b~~\\}\\}\n\\* \\{\\{DuLevelLink\\|Tower~~\\|\u1780\u179f\u200b\u1794\u17b6\u1793\u200b\u17a1\u17c2\u200b\u1780\u200b~~\\}\\}\n~~== \u1780\u17b6\u179a\u200b\u1796\u17b7\u1797\u17b6\u1780\u17d2\u179f\u17b6\u200b ==~~\n\n__And more__", "inline": true}, {"name": "Added", "value": "\n**\\|title=Leapleaf**\n**\\|behavior=Hostile**\n**A '''leapleaf''', known internally as a '''leaper''', is a [[MCD:Mob#Hostile\\|hostile mob]] found in the ''[[Minecraft Dungeons]]'' [[MCD:Jungle Awakens\\|Jungle Awakens]] DLC that was corrupted by a shard of the [[MCD:Orb of Dominance\\|orb of dominance]].**\n**== Spawning ==**\n\\* \\{\\{DuLevelLink\\|Dingy Jungle\\}\\}\n\\* \\{\\{DuLevelLink\\|Panda Plateau\\}\\}\n\\* \\{\\{DuLevelLink\\|Overgrown Temple\\}\\}\n\\* \\{\\{DuLevelLink\\|Tower\\}\\}\n**== Behavior ==**\n**A leapleaf has four different behaviors. It cannot be stunned by normal attacks, except [[MCD:Shock Powder\\|Shock Powder]] and any weapon with the [[MCD:Stunning\\|Stunning]] enchantment.**\n**;Basic attack**\n**If a leapleaf is not currently charging or resting, it smashes any nearby targets with one of its arms, dealing notable damage.**\n**;Charge**\n**After a leapleaf does a chest-pouding animation, it would begin running toward the target.**\n**;Heavy attack**\n\n__And more__", "inline": true}], "author": {"name": "User3", "url": "http://localhost:8080/wiki/User:User3", "icon_url": "https://i.imgur.com/zKYHkJm.png"}, "timestamp": "2021-12-26T12:26:32Z", "description": "Some changes lol"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 9175040, "url": "http://localhost:8080/index.php?title=Java_Edition_1.19&curid=177589&diff=2075224&oldid=2074874", "title": "Java Edition 1.19 (-40)", "author": {"name": "hidden", "icon_url": "https://i.imgur.com/zKYHkJm.png"}, "timestamp": "2021-12-26T12:09:18Z", "description": "~~hidden~~"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 1, "author": {"name": "Frisk", "url": "http://localhost:8080/wiki/User:Frisk", "icon_url": "https://i.imgur.com/1gps6EZ.png"}, "timestamp": "2021-12-26T13:02:59Z", "description": "Heresy!", "url": "http://localhost:8080/wiki/Java_Edition_1.19", "title": "Changed visibility of revision on page Java Edition 1.19 "}]}, "DELETE/webhook//messages/8", {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 1, "author": {"name": "Frisk", "url": "http://localhost:8080/wiki/User:Frisk", "icon_url": "https://i.imgur.com/BU77GD3.png"}, "timestamp": "2021-12-26T13:35:50Z", "description": "aaaaaaaaaaaaaaa", "url": "http://localhost:8080/wiki/Some_different_page", "title": "Deleted page Some different page"}]}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": 65280, "url": "http://localhost:8080/index.php?title=Unique_page&curid=9327&diff=2075234&oldid=232556", "title": "Unique page (+32554)", "fields": [{"name": "Removed", "value": "\n\\* Der \u00dcberlieferung nach soll dieser \"~~rote~~ T\u00fcren \u00f6ffnen\".\n\\* Der \u00dcberlieferung nach soll dieser \"~~rote~~ T\u00fcren \u00f6ffnen\".\n~~\\{\\| \\{\\{BtK\\|Bonus Fass\\|bonus\\_barrel\\|t=1\\}\\}~~\n~~\\| colspan=\"9\" \\| Beutetabellen Inhalt~~\n~~\\| \\{\\{BtE\\|3D Shareware v1.34\\}\\}~~", "inline": true}, {"name": "Added", "value": "\n\\* Der \u00dcberlieferung nach soll dieser \"**blaue** T\u00fcren \u00f6ffnen\".\n\\* Der \u00dcberlieferung nach soll dieser \"**gelbe** T\u00fcren \u00f6ffnen\".\n**\\{\\| class=\"wikitable collapsible\"**\n**! colspan=\"3\" \\| Verlies Truheninhalt**\n**! Gegenstand**\n**! Wahrscheinlichkeit**\n**! Stapelgr\u00f6\u00dfe**\n**\\| colspan=\"3\" \\| In jeder Truhe \\sind ein Stapel\\<\\/b\\> aus der Gruppe dieser Gegenst\u00e4nde enthalten**\n**\\|-**\n**\\| \\{\\{BS\\|Luft\\}\\} Nichts**\n**\\| 66,7 %**\n**\\| 1**\n**\\|-**\n**\\| \\{\\{GL\\|Roter Schl\u00fcssel\\|3D Shareware v1.34#Neuerungen\\|Roter Schl\u00fcssel\\}\\}**\n**\\| 20,8 %**\n**\\| 1**\n**\\|-**\n**\\| \\{\\{GL\\|Gelber Schl\u00fcssel\\|3D Shareware v1.34#Neuerungen\\|Gelber Schl\u00fcssel\\}\\}**\n**\\| 8,3 %**\n**\\| 1**\n**\\|-**\n**\\| \\{\\{GL\\|Blauer Schl\u00fcssel\\|3D Shareware v1.34#Neuerungen\\|Blauer Schl\u00fcssel\\}\\}**\n**\\| 4,2 %**\n**\\| 1**\n**\\|-**\n**\\| colspan=\"3\" \\| In jeder Truhe \\sind ein bis vier Stapel\\<\\/b\\> aus der Gruppe dieser Gegenst\u00e4nde enthalten**\n**\\|-**\n**\\| \\{\\{GL\\|Pfeil\\}\\}**\n**\\| 10,9 %**\n**\\| 1-20**\n**\\|-**\n\n__And more__", "inline": true}], "author": {"name": "User3", "url": "http://localhost:8080/wiki/User:User3", "icon_url": "https://i.imgur.com/zKYHkJm.png"}, "timestamp": "2021-12-26T13:37:10Z", "description": "Added content"}]}] \ No newline at end of file diff --git a/test/mockserver/results/results2.json b/test/mockserver/results/results2.json new file mode 100644 index 0000000..91775ca --- /dev/null +++ b/test/mockserver/results/results2.json @@ -0,0 +1 @@ +[{"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\uddbc\ufe0f [User1]() uploaded [File\\:Oak Sign (9).png]()"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udcdd [Unregistered user]() edited [Java Edition 1.19]() *(Hahahahhaha, you will never understand my genius!)* (-40)"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udcdd [Unregistered user]() edited [Java Edition 1.19]() *(Undo revision by 192.168.1.1. Reason\\: Mad cats)* (+40)"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udeab [Administrator]() blocked [Spammer]() for 15 days, for 22 hours, for 23 minutes *([Rule #2]()\\: Spam)*"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udccc [User2]() edited the Discord handle on [their own]() profile. *(CoolUser#1812)*"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83c\udfc5 [Administrator2]() changed group membership for [FineUser2](): Added to sysop and removed from autopatrol, rollback."}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udcdd [User2]() edited [Java Edition 1.19]() *(Revert edits by [192.168.1.1]() (talk))* (-40)"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udcdd [User3]() edited [Some different page]() *(Some changes lol)* **(-32554)**"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udc41\ufe0f [Frisk]() changed visibility of revision on page [Java Edition 1.19]() *(Heresy!)*"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\uddd1\ufe0f [Frisk]() deleted [Some different page]() *(aaaaaaaaaaaaaaa)*"}, {"allowed_mentions": {"parse": []}, "avatar_url": "", "content": "\ud83d\udcdd [User3]() edited [Unique page]() *(Added content)* **(+32554)**"}] \ No newline at end of file diff --git a/test/mockserver/server.py b/test/mockserver/server.py new file mode 100644 index 0000000..e43f4ba --- /dev/null +++ b/test/mockserver/server.py @@ -0,0 +1,134 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . +from http.server import BaseHTTPRequestHandler, HTTPServer +import json +import urllib.parse +import requests + +response_jsons: dict[str, dict] = {} + +class EndOfContent(Exception): + pass + +def load_response(name: str): + with open("data/response_{}.json".format(name), "r") as response_file: + response_json: dict = json.loads(response_file.read()) + response_jsons[name] = response_json + + +def get_response(name: str): + return response_jsons.get(name) + + +[load_response(x) for x in ["recentchanges", "recentchanges2", "init", "error", "siteinfo", "image", "userinfo"]] + + +messages_collector = [] +askedfor = False + +# Return server response based on some output from Minecraft Wiki +class MockServerRequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + global askedfor + # We assume testing will be for API endpoint only since RcGcDw doesn't do requests to other URLs so no need to check main path + # For simplicity, return a dictionary of query arguments, we assume duplicate keys will not appear + query = {k: y for (k, y) in urllib.parse.parse_qsl(self.path.split("?")[1])} + if query.get("action") == "query": + # Regular pooled query for recentchanges + if query.get("list") == "recentchanges": + self.send_essentials_ok() + if askedfor is False: + # Limit amount of events accordingly to required amount just in case + response_jsons["recentchanges"]["query"]["recentchanges"] = get_response("recentchanges")["query"]["recentchanges"][0:int(query.get("rclimit", 20))] + response_content = json.dumps(get_response("recentchanges")) + askedfor = True + else: + response_jsons["recentchanges2"]["query"]["recentchanges"] = get_response("recentchanges2")["query"]["recentchanges"][0:int(query.get("rclimit", 20))] + response_content = json.dumps(get_response("recentchanges2")) + self.wfile.write(response_content.encode('utf-8')) + # Init info + elif query.get("list") == "tags" and query.get("meta") == "allmessages|siteinfo": + self.send_essentials_ok() + response_content = json.dumps(get_response("init")) + self.wfile.write(response_content.encode('utf-8')) + elif query.get("meta") == "siteinfo": + self.send_essentials_ok() + response_content = json.dumps(get_response("siteinfo")) + self.wfile.write(response_content.encode('utf-8')) + elif query.get("prop") == "imageinfo|revisions": + self.send_essentials_ok() + response_content = json.dumps(get_response("image")) + self.wfile.write(response_content.encode('utf-8')) + elif query.get("list") == "usercontribs": + self.send_essentials_ok() + response_content = json.dumps(get_response("userinfo")) + self.wfile.write(response_content.encode('utf-8')) + else: + self.send_response(400) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.end_headers() + response_content = json.dumps(get_response("error")) + self.wfile.write(response_content.encode('utf-8')) + elif query.get("action") == "compare": + self.send_essentials_ok() + name = "diff{}{}".format(query.get("fromrev"), query.get("torev")) + load_response(name) + response_content = json.dumps(get_response(name)) + self.wfile.write(response_content.encode('utf-8')) + + def do_POST(self): + self.read_ok_collect(method="POST") + + def do_PATCH(self): + self.read_ok_collect(method="PATCH") + + def do_DELETE(self): + self.read_ok_collect(method="DELETE") + + def read_ok_collect(self, method: str): + content_length = int(self.headers['Content-Length']) + patch_data = self.rfile.read(content_length) + if patch_data: + messages_collector.append(json.loads(patch_data.decode('utf-8'))) + else: + messages_collector.append(method + self.path) + self.send_essentials_ok() + self.wfile.write(json.dumps({"id": len(messages_collector)}).encode('utf-8')) + + def send_essentials_ok(self): + self.send_response(requests.codes.ok) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.end_headers() + + +def start_mock_server(port, config): + mock_server = HTTPServer(('localhost', port), MockServerRequestHandler) + try: + print("Server started successfully at http://localhost:{}".format(port)) + while 1: + if (len(messages_collector) < 13 and config.config == 1) or (len(messages_collector) < 11 and config.config == 2): + mock_server.handle_request() + else: + raise EndOfContent + except KeyboardInterrupt: + print("Shutting down...") + except EndOfContent: + with open("results/results{}.json".format(config.config), "r") as proper_results: + if proper_results.read() == json.dumps(messages_collector): + print("Results are correct!") + else: + print("Results are incorrect, saving failed results to resultsfailed{}.json".format(config.config)) + with open("results/resultsfailed{}.json".format(config.config), "w") as file_to_write: + file_to_write.write(json.dumps(messages_collector)) diff --git a/test/mockserver/start.py b/test/mockserver/start.py new file mode 100644 index 0000000..395e089 --- /dev/null +++ b/test/mockserver/start.py @@ -0,0 +1,56 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import argparse +import json + +import server +import pathlib +import shutil +import sys +import time + +parser = argparse.ArgumentParser(description="Test RcGcDw with mocked data") +parser.add_argument("--config", type=int, default=1, help="Number of config to use while testing the mocked server (default=1). Number corresponds to files in configs/settings#.json") +parser.add_argument("--ignore-config", action='store_false', help="Ignore lack of existing config.json") +parser.add_argument("--no-client", action='store_true', help="Skip starting the client") +command_args = parser.parse_args() + +# Backup old settings.json and copy from configs/settingsX.json to proper relative location +if not command_args.no_client: + new_settings = pathlib.Path(__file__).parent.absolute().joinpath("configs/settings{}.json".format(command_args.config)) + old_config = pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve().joinpath("settings.json") # Should be root of RcGcDw + if not old_config.exists() and command_args.ignore_config: + print("Cannot find currently used settings.json! Exiting to prevent potential damage.") + sys.exit(2) + backup_filename = pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve().joinpath("settings.json.{}.bak".format(int(time.time()))) + if backup_filename.exists(): + print("Backup file under same name exists! Exiting.") + sys.exit(3) + shutil.move(old_config, backup_filename) + shutil.copy(new_settings, old_config) + # revert data file to some low number + with open(pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve().joinpath("data.json"), "r") as data_file: + data_file_data = json.loads(data_file.read()) + data_file_data["rcid"] = 5 + with open(pathlib.Path(__file__).parent.resolve().parent.resolve().parent.resolve().joinpath("data.json"), "w") as data_file: + data_file.write(json.dumps(data_file_data, indent=4)) + +# Start mock server +server.start_mock_server(8080, command_args) + +# Revert file changes +if not command_args.no_client: + shutil.copy(backup_filename, old_config) \ No newline at end of file diff --git a/test/test_11311_migration.py b/test/test_11311_migration.py new file mode 100644 index 0000000..9909ed1 --- /dev/null +++ b/test/test_11311_migration.py @@ -0,0 +1,39 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import unittest +import importlib +import json + +test_file = """{"cooldown":60,"wiki_url":"https://wreckit-woodhouse.fandom.com/","rc_enabled":true,"lang":"en","header":{"user-agent":"RcGcDw/{version}"},"limit":10,"webhookURL":"https://discordapp.com/api/webhooks/111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","limitrefetch":28,"wikiname":"Wreck It Woodhouse","avatars":{"connection_failed":"https://i.imgur.com/2jWQEt1.png","connection_restored":"","no_event":"","embed":"","compact":""},"ignored":["external","newusers/create","newusers/autocreate","newusers/create2","newusers/byemail","newusers/newusers"],"show_updown_messages":true,"ignored_namespaces":[],"overview":false,"overview_time":"00:00","send_empty_overview":false,"license_detection":true,"license_regex_detect":"\\\\{\\\\{(license|lizenz|licence|copyright)","license_regex":"\\\\{\\\\{(license|lizenz|licence|copyright)(\\\\ |\\\\|)(?P.*?)\\\\}\\\\}","disallow_regexes":[],"wiki_bot_login":"","wiki_bot_password":"","show_added_categories":true,"show_bots":false,"show_abuselog":false,"hide_ips":false,"discord_message_cooldown":0,"auto_suppression":{"enabled":false,"db_location":":memory:"},"logging":{"version":1,"disable_existing_loggers":false,"formatters":{"standard":{"format":"%(name)s - %(levelname)s: %(message)s"}},"handlers":{"default":{"formatter":"standard","class":"logging.StreamHandler","stream":"ext://sys.stdout"}},"loggers":{"":{"level":0,"handlers":["default"]},"rcgcdw":{},"rcgcdw.misc":{}}},"appearance":{"mode":"embed","embed":{"show_edit_changes":false,"show_footer":true,"embed_images":true,"daily_overview":{"color":16312092,"icon":""},"new":{"icon":"https://i.imgur.com/6HIbEq8.png","color":"THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE"},"edit":{"icon":"","color":"THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE"},"upload/overwrite":{"icon":"https://i.imgur.com/egJpa81.png","color":12390624},"upload/upload":{"icon":"https://i.imgur.com/egJpa81.png","color":null},"upload/revert":{"icon":"https://i.imgur.com/egJpa81.png","color":null},"delete/delete":{"icon":"https://i.imgur.com/BU77GD3.png","color":1},"delete/delete_redir":{"icon":"https://i.imgur.com/BU77GD3.png","color":1},"delete/restore":{"icon":"https://i.imgur.com/9MnROIU.png","color":null},"delete/revision":{"icon":"https://i.imgur.com/1gps6EZ.png","color":null},"delete/event":{"icon":"https://i.imgur.com/1gps6EZ.png","color":null},"merge/merge":{"icon":"https://i.imgur.com/uQMK9XK.png","color":null},"move/move":{"icon":"https://i.imgur.com/eXz9dog.png","color":null},"move/move_redir":{"icon":"https://i.imgur.com/UtC3YX2.png","color":null},"block/block":{"icon":"https://i.imgur.com/g7KgZHf.png","color":1},"block/unblock":{"icon":"https://i.imgur.com/bvtBJ8o.png","color":1},"block/reblock":{"icon":"https://i.imgur.com/g7KgZHf.png","color":1},"protect/protect":{"icon":"https://i.imgur.com/bzPt89Z.png","color":null},"protect/modify":{"icon":"https://i.imgur.com/bzPt89Z.png","color":null},"protect/move_prot":{"icon":"https://i.imgur.com/bzPt89Z.png","color":null},"protect/unprotect":{"icon":"https://i.imgur.com/2wN3Qcq.png","color":null},"import/upload":{"icon":"","color":null},"import/interwiki":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"rights/rights":{"icon":"","color":null},"rights/autopromote":{"icon":"","color":null},"abusefilter/abusefilter":{"icon":"https://i.imgur.com/Sn2NzRJ.png","color":null},"abusefilter/modify":{"icon":"https://i.imgur.com/Sn2NzRJ.png","color":null},"abusefilter/create":{"icon":"https://i.imgur.com/Sn2NzRJ.png","color":null},"interwiki/iw_add":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"interwiki/iw_edit":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"interwiki/iw_delete":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"curseprofile/comment-created":{"icon":"https://i.imgur.com/Lvy5E32.png","color":null},"curseprofile/comment-edited":{"icon":"https://i.imgur.com/Lvy5E32.png","color":null},"curseprofile/comment-deleted":{"icon":"","color":null},"curseprofile/comment-purged":{"icon":"","color":null},"curseprofile/comment-replied":{"icon":"https://i.imgur.com/hkyYsI1.png","color":null},"curseprofile/profile-edited":{"icon":"","color":null},"contentmodel/change":{"icon":"","color":null},"cargo/deletetable":{"icon":"","color":null},"cargo/createtable":{"icon":"","color":null},"cargo/replacetable":{"icon":"","color":null},"cargo/recreatetable":{"icon":"","color":null},"sprite/sprite":{"icon":"","color":null},"sprite/sheet":{"icon":"","color":null},"sprite/slice":{"icon":"","color":null},"managetags/create":{"icon":"","color":null},"managetags/delete":{"icon":"","color":null},"managetags/activate":{"icon":"","color":null},"managetags/deactivate":{"icon":"","color":null},"tag/update":{"icon":"","color":null},"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/forum/quiz":{"icon":"","color":null},"discussion/wall/post":{"icon":"","color":null},"discussion/wall/reply":{"icon":"","color":null},"discussion/comment/post":{"icon":"","color":null},"discussion/comment/reply":{"icon":"","color":null}}},"fandom_discussions":{"enabled":false,"wiki_id":1885853,"wiki_url":"https://wikibot.fandom.com/","cooldown":60,"webhookURL":"https://discordapp.com/api/webhooks/111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","limit":5,"appearance":{"mode":"embed","embed":{"show_content":true}},"show_forums":[]}}""" +result_file = """{"cooldown":60,"wiki_url":"https://wreckit-woodhouse.fandom.com/","rc_enabled":true,"lang":"en","header":{"user-agent":"RcGcDw/{version}"},"limit":10,"webhookURL":"https://discordapp.com/api/webhooks/111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","limitrefetch":28,"wikiname":"Wreck It Woodhouse","avatars":{"connection_failed":"https://i.imgur.com/2jWQEt1.png","connection_restored":"","no_event":"","embed":"","compact":""},"ignored":["external","newusers/create","newusers/autocreate","newusers/create2","newusers/byemail","newusers/newusers"],"show_updown_messages":true,"ignored_namespaces":[],"overview":false,"overview_time":"00:00","send_empty_overview":false,"license_detection":true,"license_regex_detect":"\\\\{\\\\{(license|lizenz|licence|copyright)","license_regex":"\\\\{\\\\{(license|lizenz|licence|copyright)(\\\\ |\\\\|)(?P.*?)\\\\}\\\\}","disallow_regexes":[],"wiki_bot_login":"","wiki_bot_password":"","show_added_categories":true,"show_bots":false,"show_abuselog":false,"hide_ips":false,"discord_message_cooldown":0,"auto_suppression":{"enabled":false,"db_location":":memory:"},"logging":{"version":1,"disable_existing_loggers":false,"formatters":{"standard":{"format":"%(name)s - %(levelname)s: %(message)s"}},"handlers":{"default":{"formatter":"standard","class":"logging.StreamHandler","stream":"ext://sys.stdout"}},"loggers":{"":{"level":0,"handlers":["default"]},"rcgcdw":{},"rcgcdw.misc":{}}},"appearance":{"mode":"embed","embed":{"show_edit_changes":false,"show_footer":true,"embed_images":true,"daily_overview":{"color":16312092,"icon":""},"new":{"icon":"https://i.imgur.com/6HIbEq8.png","color":"THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE"},"edit":{"icon":"","color":"THIS COLOR DEPENDS ON EDIT SIZE, PLEASE DON'T CHANGE"},"upload/overwrite":{"icon":"https://i.imgur.com/egJpa81.png","color":12390624},"upload/upload":{"icon":"https://i.imgur.com/egJpa81.png","color":null},"upload/revert":{"icon":"https://i.imgur.com/egJpa81.png","color":null},"delete/delete":{"icon":"https://i.imgur.com/BU77GD3.png","color":1},"delete/delete_redir":{"icon":"https://i.imgur.com/BU77GD3.png","color":1},"delete/restore":{"icon":"https://i.imgur.com/9MnROIU.png","color":null},"delete/revision":{"icon":"https://i.imgur.com/1gps6EZ.png","color":null},"delete/event":{"icon":"https://i.imgur.com/1gps6EZ.png","color":null},"merge/merge":{"icon":"https://i.imgur.com/uQMK9XK.png","color":null},"move/move":{"icon":"https://i.imgur.com/eXz9dog.png","color":null},"move/move_redir":{"icon":"https://i.imgur.com/UtC3YX2.png","color":null},"block/block":{"icon":"https://i.imgur.com/g7KgZHf.png","color":1},"block/unblock":{"icon":"https://i.imgur.com/bvtBJ8o.png","color":1},"block/reblock":{"icon":"https://i.imgur.com/g7KgZHf.png","color":1},"protect/protect":{"icon":"https://i.imgur.com/bzPt89Z.png","color":null},"protect/modify":{"icon":"https://i.imgur.com/bzPt89Z.png","color":null},"protect/move_prot":{"icon":"https://i.imgur.com/bzPt89Z.png","color":null},"protect/unprotect":{"icon":"https://i.imgur.com/2wN3Qcq.png","color":null},"import/upload":{"icon":"","color":null},"import/interwiki":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"rights/rights":{"icon":"","color":null},"rights/autopromote":{"icon":"","color":null},"abusefilter/abusefilter":{"icon":"https://i.imgur.com/Sn2NzRJ.png","color":null},"abusefilter/modify":{"icon":"https://i.imgur.com/Sn2NzRJ.png","color":null},"abusefilter/create":{"icon":"https://i.imgur.com/Sn2NzRJ.png","color":null},"interwiki/iw_add":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"interwiki/iw_edit":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"interwiki/iw_delete":{"icon":"https://i.imgur.com/sFkhghb.png","color":null},"curseprofile/comment-created":{"icon":"https://i.imgur.com/Lvy5E32.png","color":null},"curseprofile/comment-edited":{"icon":"https://i.imgur.com/Lvy5E32.png","color":null},"curseprofile/comment-deleted":{"icon":"","color":null},"curseprofile/comment-purged":{"icon":"","color":null},"curseprofile/comment-replied":{"icon":"https://i.imgur.com/hkyYsI1.png","color":null},"curseprofile/profile-edited":{"icon":"","color":null},"contentmodel/change":{"icon":"","color":null},"cargo/deletetable":{"icon":"","color":null},"cargo/createtable":{"icon":"","color":null},"cargo/replacetable":{"icon":"","color":null},"cargo/recreatetable":{"icon":"","color":null},"sprite/sprite":{"icon":"","color":null},"sprite/sheet":{"icon":"","color":null},"sprite/slice":{"icon":"","color":null},"managetags/create":{"icon":"","color":null},"managetags/delete":{"icon":"","color":null},"managetags/activate":{"icon":"","color":null},"managetags/deactivate":{"icon":"","color":null},"tag/update":{"icon":"","color":null},"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/forum/quiz":{"icon":"","color":null},"discussion/wall/post":{"icon":"","color":null},"discussion/wall/reply":{"icon":"","color":null},"discussion/comment/post":{"icon":"","color":null},"discussion/comment/reply":{"icon":"","color":null}}},"fandom_discussions":{"enabled":false,"wiki_id":1885853,"wiki_url":"https://wikibot.fandom.com/","cooldown":60,"webhookURL":"https://discordapp.com/api/webhooks/111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","limit":5,"appearance":{"mode":"embed","embed":{"show_content":true}},"show_forums":[]}}""" + + +class TestMWFormatter(unittest.TestCase): + def setUp(self) -> None: + with open("settings.json", "r") as c_file: + self.current_file = c_file.read() + + def tearDown(self) -> None: + with open("settings.json", "w") as s_file: + s_file.write(self.current_file) + + def test_11311_migration(self): + with open("settings.json", "w") as s_file: + s_file.write(test_file) + importlib.import_module("src.migrations.11311") + with open("settings.json", "r") as c_file: + current_settings = c_file.read() + self.assertEqual(json.loads(result_file), json.loads(current_settings)) \ No newline at end of file diff --git a/test/test_abusefilter.py b/test/test_abusefilter.py new file mode 100644 index 0000000..678e084 --- /dev/null +++ b/test/test_abusefilter.py @@ -0,0 +1,58 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import importlib +import json +from src.configloader import settings +from src.api.context import Context +from src.api.util import default_message +from src.api.hooks import formatter_hooks +from src.misc import WIKI_SCRIPT_PATH +from test.test_utilities import inject_settings +from unittest.mock import PropertyMock +import unittest + + +def no_formatter(ctx: Context, change: dict) -> None: + raise NameError + + +inject_settings("appearance.mode", "embed") +importlib.import_module(settings.get('extensions_dir', 'extensions'), 'extensions') +formatter_hooks["no_formatter"] = no_formatter +with open("test/data/rc_objects.json", "r") as ob: + jsons = json.loads(ob.read()) +with open("test/data/rc_results.json", "r") as ob: + results = json.loads(ob.read()) + + +def get_objects(name: str): + return jsons.get(name), json.dumps(results.get(name)) + + +class TestMWFormatter(unittest.TestCase): + def test_abusefilter_embed(self): + test = default_message("abuselog", formatter_hooks) + ctx = PropertyMock() + ctx.message_type = "embed" + ctx.event_type = "abuselog" + ctx.event = "abuselog" + ctx.parsedcomment = "" + ctx.client.WIKI_SCRIPT_PATH = WIKI_SCRIPT_PATH + ctx.webhook_url = "https://example.com" + # ctx.client.return_value = Mock(spec=Client) + edit_c, results = get_objects("abuselog") + result = repr(test(ctx, edit_c)) + self.assertEqual(results, result) diff --git a/test/test_cargo.py b/test/test_cargo.py new file mode 100644 index 0000000..d51c48b --- /dev/null +++ b/test/test_cargo.py @@ -0,0 +1,65 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import importlib +import json +from src.configloader import settings +from src.api.context import Context +from src.api.util import default_message +from src.api.hooks import formatter_hooks +from src.misc import WIKI_SCRIPT_PATH, LinkParser +from test.test_utilities import inject_settings +from unittest.mock import PropertyMock +import unittest + + +def no_formatter(ctx: Context, change: dict) -> None: + raise NameError + + +inject_settings("appearance.mode", "embed") +importlib.import_module(settings.get('extensions_dir', 'extensions'), 'extensions') +formatter_hooks["no_formatter"] = no_formatter +with open("test/data/rc_objects.json", "r") as ob: + jsons = json.loads(ob.read()) +with open("test/data/rc_results.json", "r") as ob: + results = json.loads(ob.read()) + + +def get_objects(name: str): + return jsons.get(name), json.dumps(results.get(name)) + + +def parse_links(summary: str): + link_parser = LinkParser() + link_parser.feed(summary) + return link_parser.new_string + + +class TestMWFormatter(unittest.TestCase): + def test_cargo_embed(self): + test = default_message("cargo/createtable", formatter_hooks) + ctx = PropertyMock() + ctx.message_type = "embed" + ctx.event_type = "cargo/createtable" + ctx.event = "cargo/createtable" + ctx.client.parse_links = parse_links + ctx.parsedcomment = "" + ctx.client.WIKI_SCRIPT_PATH = WIKI_SCRIPT_PATH + ctx.webhook_url = "https://example.com" + # ctx.client.return_value = Mock(spec=Client) + edit_c, results = get_objects("cargo/createtable") + result = repr(test(ctx, edit_c)) + self.assertEqual(results, result) diff --git a/test/test_datadump.py b/test/test_datadump.py new file mode 100644 index 0000000..bac093c --- /dev/null +++ b/test/test_datadump.py @@ -0,0 +1,58 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import importlib +import json +from src.configloader import settings +from src.api.context import Context +from src.api.util import default_message +from src.api.hooks import formatter_hooks +from src.misc import WIKI_SCRIPT_PATH +from test.test_utilities import inject_settings +from unittest.mock import PropertyMock +import unittest + + +def no_formatter(ctx: Context, change: dict) -> None: + raise NameError + + +inject_settings("appearance.mode", "embed") +importlib.import_module(settings.get('extensions_dir', 'extensions'), 'extensions') +formatter_hooks["no_formatter"] = no_formatter +with open("test/data/rc_objects.json", "r") as ob: + jsons = json.loads(ob.read()) +with open("test/data/rc_results.json", "r") as ob: + results = json.loads(ob.read()) + + +def get_objects(name: str): + return jsons.get(name), json.dumps(results.get(name)) + + +class TestMWFormatter(unittest.TestCase): + def test_datadump_embed(self): + test = default_message("datadump/generate", formatter_hooks) + ctx = PropertyMock() + ctx.message_type = "embed" + ctx.event_type = "datadump/generate" + ctx.event = "datadump/generate" + ctx.parsedcomment = "" + ctx.client.WIKI_SCRIPT_PATH = WIKI_SCRIPT_PATH + ctx.webhook_url = "https://example.com" + # ctx.client.return_value = Mock(spec=Client) + edit_c, results = get_objects("datadump/generate") + result = repr(test(ctx, edit_c)) + self.assertEqual(results, result) diff --git a/test/test_disconnection.py b/test/test_disconnection.py new file mode 100644 index 0000000..6218f44 --- /dev/null +++ b/test/test_disconnection.py @@ -0,0 +1,31 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import unittest +from test.test_utilities import inject_settings +from src.wiki import Wiki + + +class login_Testing(unittest.TestCase): + wiki = Wiki(None, None) + + def test_connection_checker(self): + self.assertTrue(self.wiki.check_connection(looped=True)) + + def test_connection_tracker1(self): # expands this test + inject_settings("show_updown_messages", True) + self.wiki.downtimecredibility = 0 + self.wiki.downtime_controller(True) + self.assertTrue(self.wiki.downtimecredibility > 0) diff --git a/test/test_hooks.py b/test/test_hooks.py new file mode 100644 index 0000000..072731a --- /dev/null +++ b/test/test_hooks.py @@ -0,0 +1,82 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import unittest + +from src.api.context import Context +from src.discord.message import DiscordMessage, DiscordMessageMetadata +from src.api import formatter +from src.api.hook import pre_hook, post_hook +from src.api.hooks import formatter_hooks, pre_hooks, post_hooks + + +class ApiTesting(unittest.TestCase): + def setUp(self) -> None: + self.temp = formatter_hooks.copy() + + def tearDown(self) -> None: + formatter_hooks.update(self.temp) + + def test_embed_formatter_registration(self): + formatter_hooks.clear() + + @formatter.embed(event="test", mode="embed") + def test_formatter_registration(ctx: Context, change: dict) -> DiscordMessage: + pass + self.assertEqual(formatter_hooks["test"], test_formatter_registration) + + def test_compact_formatter_registration(self): + formatter_hooks.clear() + + @formatter.embed(event="test", mode="compact") + def test_formatter_registration(ctx: Context, change: dict) -> DiscordMessage: + pass + self.assertEqual(formatter_hooks["test"], test_formatter_registration) + + def test_overwrite_formatter_registration_warning(self): + formatter_hooks.clear() + + @formatter.embed(event="test", mode="compact") + def test_formatter_registration(ctx: Context, change: dict) -> DiscordMessage: + pass + + with self.assertLogs("src.api.formatter", level="WARNING"): + @formatter.embed(event="test", mode="compact") + def test_other_formatter_registration(ctx: Context, change: dict) -> DiscordMessage: + pass + + def test_formatter_aliasing(self): + formatter_hooks.clear() + + @formatter.embed(event="test", mode="compact", aliases=["test2", "test3"]) + def test_formatter_registration(ctx: Context, change: dict) -> DiscordMessage: + pass + self.assertEqual(formatter_hooks["test2"], test_formatter_registration) + + def test_pre_hook_registration(self): + pre_hooks.clear() + + @pre_hook + def test_prehook(some_data: Context, change: dict): + pass + self.assertEqual(pre_hooks[0], test_prehook) + + def test_post_hook_registration(self): + post_hooks.clear() + + @post_hook + def test_posthook(message: DiscordMessage, metadata: DiscordMessageMetadata, context: Context, change: dict): + pass + self.assertEqual(post_hooks[0], test_posthook) diff --git a/test/test_i18n.py b/test/test_i18n.py new file mode 100644 index 0000000..0cccef5 --- /dev/null +++ b/test/test_i18n.py @@ -0,0 +1,30 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +from test.test_utilities import inject_settings +import src.i18n +import unittest + + +class i18nTesting(unittest.TestCase): + def test_language_output_polish(self): + inject_settings("lang", "pl") + src.i18n.load_languages() # reload languages with new language + self.assertEqual(src.i18n.rcgcdw.gettext("Daily overview"), "Podsumowanie dnia") + + def test_language_output_english(self): + inject_settings("lang", "en") + src.i18n.load_languages() # reload languages with new language + self.assertEqual(src.i18n.rcgcdw.gettext("Daily overview"), "Daily overview") diff --git a/test/test_interwiki.py b/test/test_interwiki.py new file mode 100644 index 0000000..7022265 --- /dev/null +++ b/test/test_interwiki.py @@ -0,0 +1,58 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import importlib +import json +from src.configloader import settings +from src.api.context import Context +from src.api.util import default_message +from src.api.hooks import formatter_hooks +from src.misc import WIKI_SCRIPT_PATH +from test.test_utilities import inject_settings +from unittest.mock import PropertyMock +import unittest + + +def no_formatter(ctx: Context, change: dict) -> None: + raise NameError + + +inject_settings("appearance.mode", "embed") +importlib.import_module(settings.get('extensions_dir', 'extensions'), 'extensions') +formatter_hooks["no_formatter"] = no_formatter +with open("test/data/rc_objects.json", "r") as ob: + jsons = json.loads(ob.read()) +with open("test/data/rc_results.json", "r") as ob: + results = json.loads(ob.read()) + + +def get_objects(name: str): + return jsons.get(name), json.dumps(results.get(name)) + + +class TestMWFormatter(unittest.TestCase): + def test_interwiki_embed(self): + test = default_message("interwiki/iw_add", formatter_hooks) + ctx = PropertyMock() + ctx.message_type = "embed" + ctx.event_type = "interwiki/iw_add" + ctx.event = "interwiki/iw_add" + ctx.parsedcomment = "" + ctx.client.WIKI_SCRIPT_PATH = WIKI_SCRIPT_PATH + ctx.webhook_url = "https://example.com" + # ctx.client.return_value = Mock(spec=Client) + edit_c, results = get_objects("interwiki/iw_add") + result = repr(test(ctx, edit_c)) + self.assertEqual(results, result) diff --git a/test/test_mediawiki.py b/test/test_mediawiki.py new file mode 100644 index 0000000..2a0668b --- /dev/null +++ b/test/test_mediawiki.py @@ -0,0 +1,58 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import importlib +import json +from src.configloader import settings +from src.api.context import Context +from src.api.util import default_message +from src.api.hooks import formatter_hooks +from src.misc import WIKI_SCRIPT_PATH +from test.test_utilities import inject_settings +from unittest.mock import PropertyMock +import unittest + + +def no_formatter(ctx: Context, change: dict) -> None: + raise NameError + + +inject_settings("appearance.mode", "embed") +importlib.import_module(settings.get('extensions_dir', 'extensions'), 'extensions') +formatter_hooks["no_formatter"] = no_formatter +with open("test/data/rc_objects.json", "r") as ob: + jsons = json.loads(ob.read()) +with open("test/data/rc_results.json", "r") as ob: + results = json.loads(ob.read()) + + +def get_objects(name: str): + return jsons.get(name), json.dumps(results.get(name)) + + +class TestMWFormatter(unittest.TestCase): + def test_edit_embed(self): + test = default_message("edit", formatter_hooks) + ctx = PropertyMock() + ctx.message_type = "embed" + ctx.event_type = "edit" + ctx.event = "edit" + ctx.parsedcomment = "Work on new as" + ctx.client.WIKI_SCRIPT_PATH = WIKI_SCRIPT_PATH + ctx.webhook_url = "https://example.com" + # ctx.client.return_value = Mock(spec=Client) + edit_c, results = get_objects("edit") + result = repr(test(ctx, edit_c)) + self.assertEqual(results, result) diff --git a/test/test_misc.py b/test/test_misc.py new file mode 100644 index 0000000..c346fbd --- /dev/null +++ b/test/test_misc.py @@ -0,0 +1,76 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . +import json +import sys +from unittest import TestCase +from src.exceptions import MediaWikiError +from src.misc import datafile, data_template, weighted_average, prepare_paths, parse_mw_request_info +from os.path import exists + + +class TestDataFile(TestCase): + def test_generate_datafile(self): + datafile.generate_datafile() + self.assertTrue(exists(datafile.data_filename)) + with open(datafile.data_filename, "r") as df: + contents = df.read() + print(json.loads(contents)) + self.assertEqual(json.loads(contents), data_template) + + def test_load_datafile(self): + self.assertEqual(datafile.load_datafile(), data_template) + + # def test_save_datafile(self): + # datafile["discussion_id"] = 321388838283 + # datafile.save_datafile() + # with open(datafile.data_filename, "r") as df: + # contents = json.loads(df.read()) + # self.assertEqual(contents["discussion_id"], 321388838283) + + +class Test(TestCase): + def test_weighted_average(self): + self.assertEqual(weighted_average(3, 5, 30), 7.5) + + def test_prepare_paths(self): + self.assertEqual(prepare_paths("https://minecraft.fandom.com/blabhlldlasldllad", dry=True), "https://minecraft.fandom.com") + self.assertEqual(prepare_paths("https://minecraft.fandom.com/wiki/Minecraft_Wiki", dry=True), "https://minecraft.fandom.com") + self.assertEqual(prepare_paths("https://minecraft.fandom.com/", dry=True), "https://minecraft.fandom.com") + + def test_parse_mw_request_info(self): + warning_data = """{"batchcomplete":"","warnings":[{"code":"unrecognizedvalues","key":"apiwarn-unrecognizedvalues","params":["list",{"list":["recentchange"],"type":"comma"},1],"module":"query"},{"code":"unrecognizedparams","key":"apierror-unrecognizedparams","params":[{"list":{"3":"rcshow","4":"rcprop","5":"rclimit","6":"rctype"},"type":"comma"},4],"module":"main"}]}""" + warning_data = json.loads(warning_data) + error_data = """{"errors":[{"code":"missingparam","key":"apierror-missingparam-at-least-one-of","params":[{"list":["totitle","toid","torev","totext","torelative","toslots"],"type":"text"},6],"module":"compare"}],"*":"See https://minecraft.fandom.com/pl/api.php for API usage. Subscribe to the mediawiki-api-announce mailing list at <https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce> for notice of API deprecations and breaking changes."}""" + error_data = json.loads(error_data) + self.assertRaises(MediaWikiError, parse_mw_request_info, error_data, "dummy") + with self.assertLogs("rcgcdw.misc", level="WARNING"): + parse_mw_request_info(warning_data, "dummy") + # with self.assertNoLogs("rcgcdw.misc", level="WARNING"): # python 3.10 + # parse_mw_request_info(legit_data, "dummy") diff --git a/test/test_queue.py b/test/test_queue.py new file mode 100644 index 0000000..66b3161 --- /dev/null +++ b/test/test_queue.py @@ -0,0 +1,66 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import unittest +from typing import Tuple + +from src.discord.queue import MessageQueue +from src.discord.message import DiscordMessage, DiscordMessageMetadata + + +def create_dummy(id: int = 0, **kwargs) -> Tuple[DiscordMessage, DiscordMessageMetadata]: + dm = DiscordMessage(event_type="log/{}".format(id), message_type="embed", webhook_url="https://example.com/") + dmm = DiscordMessageMetadata("POST", log_id=kwargs.get("log_id", int(id)*10), page_id=kwargs.get("page_id", int(id)), + rev_id=kwargs.get("rev_id", int(id)*10), webhook_url=kwargs.get("webhook_url", "https://example.com/")) + return dm, dmm + + +class TestQueue(unittest.TestCase): + def test_add_message(self): + queue = MessageQueue() + for _ in range(100): + queue.add_message(create_dummy()) + self.assertEqual(len(queue), 100) + + def test_cut_messages(self): + queue = MessageQueue() + for num in range(100): + queue.add_message(create_dummy(id=num)) + queue.cut_messages(10) + self.assertEqual(list(queue)[0][1].page_id, 10) + + def test_compare_message_to_dict(self): + queue = MessageQueue() + passing = [create_dummy(id=103, page_id=3928, rev_id=228848), create_dummy(id=108, page_id=3928, rev_id=228853)] + failing = [create_dummy(id=105, page_id=39, rev_id=2288), create_dummy(id=110, page_id=392, rev_id=228)] + for msg in passing: + with self.subTest(): + self.assertTrue(queue.compare_message_to_dict(msg[1], {"page_id": 3928})) + for msg in failing: + with self.subTest(): + self.assertFalse(queue.compare_message_to_dict(msg[1], {"page_id": 3928})) + + def test_delete_all_with_matching_metadata(self): + queue = MessageQueue() + queue.add_message(create_dummy(id=103, page_id=500, rev_id=228844)) + for num in range(100): + queue.add_message(create_dummy(id=num)) + queue.add_message(create_dummy(id=105, page_id=3923, rev_id=228848)) + queue_correct = MessageQueue() + for num in range(100): + queue_correct.add_message(create_dummy(id=num)) + queue_correct.add_message(create_dummy(id=105, page_id=3923, rev_id=228848)) + queue.delete_all_with_matching_metadata(page_id=500) + self.assertEqual(len(queue), len(queue_correct)) # Could be better but I'm running out of time diff --git a/test/test_utilities.py b/test/test_utilities.py new file mode 100644 index 0000000..d5c73bf --- /dev/null +++ b/test/test_utilities.py @@ -0,0 +1,25 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . +from typing import Any + +import src.configloader + + +def inject_settings(setting: str, value: Any): + path = setting.split(".") + dic = src.configloader.settings + for key in path[:-1]: + dic = dic[key] + dic[path[-1]] = value \ No newline at end of file diff --git a/test/test_wiki_login.py b/test/test_wiki_login.py new file mode 100644 index 0000000..482afeb --- /dev/null +++ b/test/test_wiki_login.py @@ -0,0 +1,53 @@ +# This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). +# +# RcGcDw is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RcGcDw is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with RcGcDw. If not, see . + +import unittest +from src.configloader import settings +from test.test_utilities import inject_settings +from src.wiki import Wiki + + +def cleanup_func(func): + def wrap(*args, **kwargs): + login = settings["wiki_bot_login"] + password = settings["wiki_bot_password"] + func(*args, **kwargs) + inject_settings("wiki_bot_login", login) + inject_settings("wiki_bot_password", password) + return func + return wrap + + +class login_Testing(unittest.TestCase): + wiki = Wiki(None, None) + + def test_success(self): + self.wiki.logged_in = False + self.wiki.log_in() + self.assertTrue(self.wiki.logged_in) + + @cleanup_func + def test_failure1(self): + self.wiki.logged_in = False + inject_settings("wiki_bot_login", "asdkaodhasofaufbasf") + with self.assertLogs("rcgcdw.rc", level="ERROR"): + self.wiki.log_in() + + @cleanup_func + def test_failure2(self): + self.wiki.logged_in = False + inject_settings("wiki_bot_password", "lkkkkk") + with self.assertLogs("rcgcdw.rc", level="ERROR"): + self.wiki.log_in()