From 5d0c175ad484a6291e82dd4ed654fbc1b1eaf022 Mon Sep 17 00:00:00 2001 From: Weblate Date: Fri, 30 Jul 2021 10:44:59 +0000 Subject: [PATCH 01/30] Translated using Weblate (Chinese (Simplified) (zh-hans)) Currently translated at 100.0% (292 of 292 strings) Translated using Weblate (Ukrainian) Currently translated at 45.8% (134 of 292 strings) Translated using Weblate (Russian) Currently translated at 100.0% (292 of 292 strings) Translated using Weblate (Portuguese (Brazil) (pt-br)) Currently translated at 100.0% (292 of 292 strings) Translated using Weblate (Spanish) Currently translated at 55.4% (162 of 292 strings) Translated using Weblate (German) Currently translated at 100.0% (292 of 292 strings) Translated using Weblate (Chinese (Simplified) (zh-hans)) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Chinese (Traditional) (zh-hant)) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Russian) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Portuguese (Brazil) (pt-br)) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (German) Currently translated at 100.0% (2 of 2 strings) Translated using Weblate (Polish) Currently translated at 100.0% (2 of 2 strings) Co-authored-by: Eduaddad Co-authored-by: Frisk The Evil Goat Overlord Co-authored-by: MakandIv Co-authored-by: MarkusRost Co-authored-by: Tamara Carvallo Co-authored-by: Weblate Co-authored-by: lakejason0 Translate-URL: https://translate.wikibot.de/projects/rcgcdw/formatters/de/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/formatters/es/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/formatters/pt-br/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/formatters/ru/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/formatters/uk/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/formatters/zh-hans/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/de/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/pl/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/pt-br/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/ru/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/uk/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/zh-hans/ Translate-URL: https://translate.wikibot.de/projects/rcgcdw/redaction/zh-hant/ Translation: RcGcDw/formatters Translation: RcGcDw/redaction --- locale/de/LC_MESSAGES/formatters.mo | Bin 39694 -> 39708 bytes locale/de/LC_MESSAGES/formatters.po | 12 +-- locale/de/LC_MESSAGES/redaction.mo | Bin 493 -> 536 bytes locale/de/LC_MESSAGES/redaction.po | 12 +-- locale/es/LC_MESSAGES/formatters.mo | Bin 18108 -> 21933 bytes locale/es/LC_MESSAGES/formatters.po | 112 ++++++++++------------- locale/pl/LC_MESSAGES/redaction.mo | Bin 453 -> 588 bytes locale/pl/LC_MESSAGES/redaction.po | 17 ++-- locale/pt-br/LC_MESSAGES/formatters.mo | Bin 38806 -> 38805 bytes locale/pt-br/LC_MESSAGES/formatters.po | 4 +- locale/pt-br/LC_MESSAGES/redaction.mo | Bin 508 -> 561 bytes locale/pt-br/LC_MESSAGES/redaction.po | 10 +- locale/ru/LC_MESSAGES/formatters.mo | Bin 48556 -> 48555 bytes locale/ru/LC_MESSAGES/formatters.po | 6 +- locale/ru/LC_MESSAGES/redaction.mo | Bin 610 -> 618 bytes locale/ru/LC_MESSAGES/redaction.po | 10 +- locale/uk/LC_MESSAGES/formatters.mo | Bin 17756 -> 18740 bytes locale/uk/LC_MESSAGES/formatters.po | 57 +++++------- locale/uk/LC_MESSAGES/redaction.mo | Bin 581 -> 634 bytes locale/uk/LC_MESSAGES/redaction.po | 8 +- locale/zh-hans/LC_MESSAGES/formatters.mo | Bin 37119 -> 37118 bytes locale/zh-hans/LC_MESSAGES/formatters.po | 4 +- locale/zh-hans/LC_MESSAGES/redaction.mo | Bin 562 -> 569 bytes locale/zh-hans/LC_MESSAGES/redaction.po | 8 +- locale/zh-hant/LC_MESSAGES/redaction.mo | Bin 564 -> 571 bytes locale/zh-hant/LC_MESSAGES/redaction.po | 8 +- 26 files changed, 119 insertions(+), 149 deletions(-) diff --git a/locale/de/LC_MESSAGES/formatters.mo b/locale/de/LC_MESSAGES/formatters.mo index 77027f8ce00194dfe5fc0b0d76197e33f223124a..5b622a68203663c18d3a9f9a3aeb794d79e07773 100644 GIT binary patch delta 2619 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 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 1321f364a1a6ca0e76c233a4c3e003dadb32f8bc..3850a03a7c4fc15705001ea259d7621b5867372a 100644 GIT binary patch delta 280 zcmaFMJcFhFo)F7a1|VPtVi_Pd0b*7l_5orLNC09sAWj5gE+EbT;vPl@hGHPi3B(Lc z3=F(LiVH{sg~8@Pg+UZhLuN`!Y92#f9fVO=#}H7IpOu4;ruQ(^M zB)`Z?!7bFsN5Rk0*HyvB)z{I}$HCFXCCD{6c;aJ4;S#92RK4=d?98P661|kkVvHwb Y&Gd}87|MWlm!u|VmjE3HX9L{_0A>sy?jIdPGk zn~{N$fv%yUu91O)fu)s^k+uO4aQP$_m*|ERCFT|9B$nhCSt\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 ddf39e76351faf7f45224751d51062a1752f78b7..0bd9a5ab4d752fa5136ffd76c089fc47b86bb742 100644 GIT binary patch delta 5722 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\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 1050bd69633cb7f90118dc3093486865c7943d7b..4354871de09ae46dc4009ebe0210585f1de2d6e7 100644 GIT binary patch delta 391 zcmX|-&rZTX5XKil4?P+aFCNx|iDk6OO+5iS00T521U_;INzjdN zZe_j&3ij_D5CqSx!YBxMjM6k~(iF8tGUC1>jezv`xsXv3o4B1!A{pZ2j$ilfd2uCA zmJg2AAzq8v7QShjUgPw!)pRtMNkw{s#d5+_B20XJ>$sy?jIdPGk zxsid9fv%yUuA!NNk&%_Dp|*jcfdQ9KVsVLXNKs;5aZX}Mevy>|P$VzCG%-C@HzYMN z*Gge>Fk^;fgsyvPUTP6UML>RPN@j_Ik)Da3;pAP6N{kwl?=Whr7%9}$DCDUE4Ye~g c(NwTkFjTNoFw(T\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 cab7332131d11522f6b6c53aab91730343c2d09e..3dbd6ddb5cc91d00e6080737978fbd8f53b6f04e 100644 GIT binary patch delta 2581 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 HX_@CgxYU3b 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 d155f9d69d6fe07d0c412e1265d5ab5c3cb53faf..ecd853befcc9cce985830d5e18eec7fb5316db83 100644 GIT binary patch delta 269 zcmeyvypg5;o)F7a1|VPtVi_Pd0b*7l_5orLNC09sAWj5gE+EbT;?;}{48=eiBreYc z5my7!Kw+>sK#Bp#g@BCAl$6vwhPpZkqppr2peR2pHMvCBGetLyEw!jPGe2+Q2Qdzf zpk()CmvYUC#ai}}u8u*vzW%x{jtUIs)X)url{XJ%(6<(KHCOjcw(D{iJ|#Kn-G PT$)po4|EiS0dy1qJG(z8 delta 246 zcmdnU@`t(po)F7a1|VPpVi_RT0b*7lwgF-g2moSsAPxlLnT!k!kwBUQh(80_Km^tY zq!1t|H8;O3HH9IdC_gJTxkT49MK>(9s5mn}&q^UE**)2%oGU1`Ait\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 467a17e51b06418d1d437bf4000a0a15e89ca0a1..4754ff02a96adb302bce13a786e564c86caa1a5c 100644 GIT binary patch delta 2577 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 DKXb4| 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 b31ff267c94dd846e80e5745ee760bfbe211ba73..058c06ed8f728dbec88ec7616645528b6350bfd7 100644 GIT binary patch delta 175 zcmaFF@`|PYo)F7a1|VPtVi_Pd0b*7l_5orLNC09sAWj5gE+EbT;v0+%48=g22Z()` z7#IYAbR>`l3WLo7QVc*Y1Y~5Uq@?CC)YU;4b#)8@Mfq8&$tAj;DY{`3V~sZMn8v7V crf0;(aIx{iu8R#9yDv6f*ay^r%mr!#0MX\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 284fd68d905c45920e6c2ff76b8ca2d682e6df6e..07eef9d75f7d352c245262187f6dd05485b3143d 100644 GIT binary patch delta 3981 zcmb8xdr(x@9l-Ik0`iCo@(7|Jm*t^R7Kk8Kf&p~b2jvkFnqXq$vMjhl7TjG$MY=qy zW^9d}+=w%2(`XXSyCJSdk!VI+o3!R}x1FTZF(&<^)5hs!l1^(oW~S4Af4htAv^KG4 z_}$Mrch5cdJa_qMpXtP7CjZCD(FYZ2FOf+!M=Ny_zZb`awDC5jrlCEe8)s7PkCgZ0 zbjnX)53MiZ6tt5h=5KpxJ*Md-nA;yOHsg_umG3amnD zcqiuLAj;>*und2N3ovemQg>h}CgTQ_`n_0yqgc%Q)mbtcH$KCMaR%MWz+IS!qsX6{ z;F5)xP(HtkOE8LI=U^pD!w=#-+>N>T3f_)Eln#B0Qa_n#%;){8jEp?E9_4`{G~-d6 zi*I8#{sCo%Vi}HfK*J`iKzVLIN{3%VdHzFOgx7H)7Bc_K@II887)8HSc!P{I_&LU6 ze5z7uI1}amJhWg9(w6E$>F_Yhi(f!_?p>6TeSp$|zoCqHHjT-WSTXcG%3d2zWB#X- zd5Rk{vSTO>y@?D~y@eY724xAp#O;{CeBO&A*ov2N8!k>)YB|1)LHr!+@jQ*o-brLM z_h2nn;SVyHe`)A4H>5)8Y^CbZjazX7SL2;?lv;y3@ihJvy*R)yy6{iv!3}JX9cZB3 zuVww*_&Qc%KI_ zrfEmnL_?_Ileh%m!JlJH0WZb(QP$kbne4=aI1T@f6>|O)_;H*-T3VHlTF$N&G(VS09qG zU?&$D=|Pmu@&VqB$vn+F)mmJM{V4S>U@d+Txqnx2_%saTYVLo86_{V5R5@-${?ws} zZ=?Um+_+Ar8lS&2{Nk(l5aohJ;STLV8R5@xJ*v|1X}BL{ijQI$&gLRZ(}+}6Z74H$ z5M}00q3oG!C>={&%>45%Rl7L+YM;TW#Mk8uSSFbvrnn~`mzcH<0u5m|OM zj!F0l@`(BZrTqfdk*@-EA0EYCbm8qwn19(6gDl7+_$l6xTNv(lO?;!_cFL_BR+;)= zqCA+v`bc>MAH!cn${QG_?4f5dfb&?tcD#%mv6}T=MY~6Ekn&Z3b@+kJ?4$$S_zYjf zf#phl8|O3ZNAWq7wJx_R)q+DP9r!iY<6Ik~!)}z%FJc|ep;8-e!fd>RnfOPXh5k6^ zcM6#toQioUQ@9YNLS>}fg0i+9k@61Aqr4C0`B#yRua09JUO`!!-=i$qHI%haVrS;! z0%Udks)3AD*o_PP7v{ zh{@DPrkRkLl(d)khrZXVLPh@T!F!1LL{H@25{x1G3E9oN37H{D(+T;GkfoCSA~PuK ze;XlfGl@5nth*#x7EYjI>nMLOmo)^-!@on}pWS?v6c;9ReTb_ZNjcX$30dp8L;}HH zQga9=v5A;RMk*{E%VB)$ql zDV1wUBtOZ+WEK+@L=(YJKq$3x&68Y0oy*~Ld!2!ygioT=SJ*wBJ(|zn*6q~H{UHa; zYK1^|%8s%nX=v93H3rD_hfBQ;O%>9bffA{gspq{f8-|Np6p`)8+L!J=EUnb$S9N zsSiY@-L#XsJ$1G|n0_^<{p*G!H3P0$+SxU9#b*UmlZUnG4Vdl zvB~c4bb7UBd#5*=i*C(+J-MaJV|Te-cDFXa$+5~|?Yl$&GJ8Q)tF5NVQr~E?)@Z96 zE9%!QT;JGWOQ^GZeU|3PF{}rJ&sOCdtb4BSnJatG4G2iFIp5x%T0+-4o~F2_Jnan zzk6w!UR`*7p>f(cVT>6Qcw7q(lYLDJ!9Br8Bb(&LWBQP#B+y}ri;6!)wNu7fV@!Xq zaMrR5FWuNH7bdje0ppBDo9BW7S^D-kSgb delta 3255 zcmZYBd2AF_9Ki9n^r8hyDQC;kX}3TN-RY)@Zi_ado#N;Z+`Fh zru|^2<-&88@TJt)R~4;`7)IQUQR)C*NZTF?8jI)$NR7k9D*}3fb}>Jzr#En!z{{h5z2*k;z-O?XvTz-az&9}iPoQMvJj#Ur!CFiv z|8fHl$^@T97k1-VyoeQ;$YaY5t5D9jqFiS$%IbcI;em8cnJ2zS8{@x_|4L)QWTpiu zUs#HnxDw@wHlw8a2+HSwK&heMF&+~KL?0jp<@>p)p%Y1lsvSW7*V0+Zfkk*8mtX<2 z>cH(d31i9QS**g@m^N6c8f?N!Jcjc9+gOb?q^%uy<8UF^w$1QuRQ!9+|$Nu?F#LSs-aP=wT>Dn|MIG>pT!*asJ&q_`e=SQSM1 z{&v)G7s{>)AE)DBD!-uA!eA;(w4oH`WRwdwqTDcqnfMHnG_?<9f+w*OPova80+lH> zk&PM_qwI#oSb}~$Bm4g#oe&2Cl&Y-hRcyi%DvzzK_M+_j!|29eaTDf_W(VLQEXL%V z=)|UB1>;p%iu-XQUN*;BRPIg2b(q2ZRV^hvmjeN`<0+hjH&ABkW>3A0>rkFBiM*BL zOq_sQk!z{XaT;F7nOJO#jyK^F#%FLkTDgflPz#RZ{^|f72VOGmo6nYF>_#{KiE={+ zACd{QqCDAQT#8q522OHDGx-7*GPby)OE3=kuV(N=maYpWLwhhR#c_d-%;*-TptB&l z_LH!Ru@@)fVNAyx$f~K{+$9;E$f_wf%J&*i;l<;U<4S6`<4*hrr3RXqbp(II2HZ1VsTCMQx~jNN13u08 z5RWULx3OL?;d|JP?n?5%k&3-JdBiaeSc}yNanrzo3f{cZY!s}$Y%q}z99MgM9T4mo-OGcCBnn_SH zYL#(>y20gUzt&WcC7epgc9WfOuNl8vWFYrnO0*JnL_LvC@KQ7W%mSkjmtjvC&>9FS zGHDsaC_;*B1Cc=Ro-+P|0=0tZFCCgIit#swblC(dEZZuMkSZNXv=KGLEMg#$MYxGY z1TQe7*uZ#G_CnbWQiwjH4>6Qrn;XAg*`>yB!(3cKNOMFnijp0bWV~49*u2QHUe{vv z)hP$#r#3W)ybbz}R87}X6C$2|9hS(ysqb6#uLtDmBL;TsV>8nte-E4(qx;h?Mz*FO zkJX>bSP)6p&RX=_nMd`Hvc@=F4wu8`blRLHnxnX^uz0M4mQ?KthHUc#9$&D<6Y>Yj zwAr3D9$&+Z4y{}tY0Vzp>0QYQue~wQ99&}$wt4Ek`E3FJYHxigm>;Nbs&D9o<+{(> znPIh=|1>Rn#@^=FwEIW(pRHMz96dg}H0;v4x-{QNr^7M9S(vL;YEG?8bLEatT5PNG z`n-YYRBHTQo?CO-3+>LN`NVv<^e=69^v(H$BZ>JVVj^2zpY_l;78OK(EK0RRMi>7S i8>uPJu|z(p*b%S4F=e4%GbJukG4-t;k*ig+E&l, 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 d2d153765423a6447fb34a0291c85eaf54b4d480..0ce9c614befc91e2036ecf884795e527f741c042 100644 GIT binary patch delta 183 zcmX@g@{6VZo)F7a1|VPtVi_Pd0b*7l_5orLNC09sAWj5gE+EbT;ya8C48=fN2#5oi z7#PHWbTW_z3WLo7QVc*Y1Y~5Uq@?CC)YU;4b#)8@Mfq8&$tAj;DY{`BQ_nK$=^1e` fT-blH;lhrKtrzxP*mPmTg}oQ{0kvQX0<{AGoXj#T delta 117 zcmeyxa+D?Fo)F7a1|VPpVi_RT0b*7lwgF-g2moSsAPxlLTZ{}0kw97ih)sYRfLyRX pAcX)ysk!-OsVN(aZZQgTF\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 bada41bc8fc4268e72e2647dc00d5d24013eb26b..4c502bfe73d17659087bf78307a74bc996745cb5 100644 GIT binary patch delta 2583 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\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 0ff0ab97d008d6d056f5db7c55231f334b4f0364..814888ba9163e4210d984da1d35f77fe00eb1b31 100644 GIT binary patch delta 169 zcmdnQvXiC$o)F7a1|VPtVi_Pd0b*7l_5orLNC09sAWj5gE+EbT;H{jboHG Y(=*~?c)ESl%UKg%Oz#J(hO>bh0A)@je*gdg delta 147 zcmdnVvWX?&o)F7a1|VPpVi_RT0b*t#wgF-g@Bm_VAoc{}Ym5vGfk2uQh&6y3fLyRX zAjQBCl$x7gmYOoL$VV+i*SRP)u_QA;PuC@}B-Kj6$iT=z*U(7U&{)CH#LCz}+W-hQ Z?oMRnH_|iYVtBrM_sf|}p6qI4007\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 d6b721a6227f7af1fa0d03737a0646b79c34f53e..0e4fa39be2b07eb32784097f4d332b4e6ded2b27 100644 GIT binary patch delta 169 zcmdnOvYVy;o)F7a1|VPtVi_Pd0b*7l_5orLNC09sAWj5gE+EbT;`NLS48=g26Nr_W z7#Mhgv@Vba3WLo7QVc*Y1Y~5Uq@?CC)YU;4b#)8@Mfq8&$tAj;DY{`3V;wdgi)WNI Y(=*~?c)ESl%UK&=Oz#J(hO>bh0B#H<&Hw-a delta 147 zcmdnZvV|q!o)F7a1|VPpVi_RT0b*t#wgF-g@Bm_VAoc{}8;lGLfk2uQh_!$kfLyRX zAjQBCl$x7gmYOoL$X6{y*SRP)u_QA;PuC@}B-Kj6$iT=z*U(7U&{)CH#LCz}+W-hQ Z?nz?gH_|iYVtBrM_sf|}p6qI4008169M%8; 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 "移除了" From 5d7647e862e8295d4d68ed6e9a1c2b24d81f9c8f Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 14 Oct 2021 12:29:45 +0200 Subject: [PATCH 02/30] Fix #227 and bump version --- setup.py | 2 +- src/configloader.py | 2 +- src/discussions.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) 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/configloader.py b/src/configloader.py index 002c890..1b32d27 100644 --- a/src/configloader.py +++ b/src/configloader.py @@ -28,7 +28,7 @@ def load_settings(): if settings["limitrefetch"] < settings["limit"] and settings["limitrefetch"] != -1: settings["limitrefetch"] = settings["limit"] if "user-agent" in settings["header"]: - settings["header"]["user-agent"] = settings["header"]["user-agent"].format(version="1.14") # set the version in the useragent + settings["header"]["user-agent"] = settings["header"]["user-agent"].format(version="1.14.0.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) diff --git a/src/discussions.py b/src/discussions.py index 6ef5094..f50ad48 100644 --- a/src/discussions.py +++ b/src/discussions.py @@ -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) From b6065a8188fdab6aa51f2223a517a4fbc7a73e7b Mon Sep 17 00:00:00 2001 From: Frisk Date: Fri, 12 Nov 2021 16:34:21 +0100 Subject: [PATCH 03/30] Fix issue when the content is revdeleted before we fetch diff (just don't provide diffs on MediaWiki errors) --- extensions/base/mediawiki.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From b63aadb74d70f09ccf5ad5c1759085c9f12ab974 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 18 Nov 2021 17:22:16 +0100 Subject: [PATCH 04/30] Added #233 and fixed #234 --- settings.json.example | 1 + src/configloader.py | 14 ++++++-------- src/misc.py | 2 +- src/rcgcdw.py | 5 +++-- 4 files changed, 11 insertions(+), 11 deletions(-) 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/src/configloader.py b/src/configloader.py index 1b32d27..9bbeb8e 100644 --- a/src/configloader.py +++ b/src/configloader.py @@ -16,19 +16,18 @@ import json import logging import sys - global settings +from src.argparser import command_args 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.0.1") # set the version in the useragent + 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.0.2") # 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 +44,3 @@ def load_settings(): load_settings() - diff --git a/src/misc.py b/src/misc.py index 79e5881..dece17a 100644 --- a/src/misc.py +++ b/src/misc.py @@ -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(settings.get("datafile_path", "data.json"), "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.") diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 612fc95..3dcc690 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -19,16 +19,16 @@ # WARNING! SHITTY CODE AHEAD. ENTER ONLY IF YOU ARE SURE YOU CAN TAKE IT # You have been warned -import time, logging.config, requests, datetime, math, os.path, schedule, sys, re, importlib +import time, logging.config, requests, datetime, math, os.path, schedule, sys, re, importlib, argparse import src.misc +import src.configloader from collections import defaultdict, Counter, OrderedDict 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.api.util import create_article_path, default_message from src.discord.queue import send_to_discord @@ -37,6 +37,7 @@ from src.exceptions import MWError, ServerError, MediaWikiError, BadRequest, Cli from src.i18n import rcgcdw from src.wiki import Wiki +settings = src.configloader.settings _ = rcgcdw.gettext ngettext = rcgcdw.ngettext From b52cc80b615eabb65ec7e169f8a3c011512c3696 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 18 Nov 2021 17:27:02 +0100 Subject: [PATCH 05/30] Fixes to previous commit --- src/misc.py | 14 +++++++------- src/rcgcdw.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/misc.py b/src/misc.py index dece17a..348c9ad 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_filename = settings.get("datafile_path", "data.json") self.data = self.load_datafile() - misc_logger.debug("Current contents of data.json {}".format(self.data)) + misc_logger.debug("Current contents of {} {}".format(self.data_filename, self.data)) self.changed = 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(settings.get("datafile_path", "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 diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 3dcc690..626adb4 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -24,7 +24,7 @@ import time, logging.config, requests, datetime, math, os.path, schedule, sys, r 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 @@ -41,7 +41,7 @@ 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 f7b60ec87d5cb3b2e01c8886d13a9c28bbb6e063 Mon Sep 17 00:00:00 2001 From: Frisk Date: Fri, 19 Nov 2021 12:42:36 +0100 Subject: [PATCH 06/30] Added argparser which I forgot in last commits --- src/argparser.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/argparser.py diff --git a/src/argparser.py b/src/argparser.py new file mode 100644 index 0000000..a70cb55 --- /dev/null +++ b/src/argparser.py @@ -0,0 +1,21 @@ +# 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 = parser.parse_args() From e9049b922a9717dab749261c284b9843a9f09447 Mon Sep 17 00:00:00 2001 From: Frisk Date: Fri, 19 Nov 2021 12:50:52 +0100 Subject: [PATCH 07/30] do less writes --- src/rcgcdw.py | 2 +- src/wiki.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 626adb4..acc6e3f 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -41,7 +41,7 @@ settings = src.configloader.settings _ = rcgcdw.gettext ngettext = rcgcdw.ngettext -TESTING = command_args.test # 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: diff --git a/src/wiki.py b/src/wiki.py index 3114487..1cd98b8 100644 --- a/src/wiki.py +++ b/src/wiki.py @@ -108,8 +108,8 @@ class Wiki(object): last_check = self.fetch_changes(amount=amount) if last_check is not None: if settings["limitrefetch"] != -1: - storage["rcid"] = last_check[0] if last_check[0] else storage["rcid"] - storage["abuse_log_id"] = last_check[1] if last_check[1] else storage["abuse_log_id"] + storage["rcid"] = last_check[0] if last_check[0] and last_check[0] != storage["rcid"] else storage["rcid"] + storage["abuse_log_id"] = last_check[1] if last_check[1] and last_check[1] != storage["abuse_log_id"] else storage["abuse_log_id"] storage.save_datafile() else: self.memory_id = last_check From 49868527cdac30fd407e1f470397948a7379605d Mon Sep 17 00:00:00 2001 From: Frisk Date: Fri, 19 Nov 2021 13:03:59 +0100 Subject: [PATCH 08/30] Fixed test --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0edcec6..ae19232 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,7 +37,7 @@ pytest: script: - source venv/bin/activate - pip3.7 install -U pytest - - pytest --junitxml=report.xml + - pytest --junit-xml=report.xml artifacts: when: always reports: From 8631a41ef6a911e8e89630b9c179d20dd68cb9cf Mon Sep 17 00:00:00 2001 From: Frisk Date: Fri, 19 Nov 2021 13:35:36 +0100 Subject: [PATCH 09/30] Actually decrease amount of unnecessary writes --- src/misc.py | 5 +++-- src/wiki.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/misc.py b/src/misc.py index 348c9ad..c87c94d 100644 --- a/src/misc.py +++ b/src/misc.py @@ -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: diff --git a/src/wiki.py b/src/wiki.py index 1cd98b8..3114487 100644 --- a/src/wiki.py +++ b/src/wiki.py @@ -108,8 +108,8 @@ class Wiki(object): last_check = self.fetch_changes(amount=amount) if last_check is not None: if settings["limitrefetch"] != -1: - storage["rcid"] = last_check[0] if last_check[0] and last_check[0] != storage["rcid"] else storage["rcid"] - storage["abuse_log_id"] = last_check[1] if last_check[1] and last_check[1] != storage["abuse_log_id"] else storage["abuse_log_id"] + storage["rcid"] = last_check[0] if last_check[0] else storage["rcid"] + storage["abuse_log_id"] = last_check[1] if last_check[1] else storage["abuse_log_id"] storage.save_datafile() else: self.memory_id = last_check From f4703e692cbdfa5793b2d3dd88f646b34a51788d Mon Sep 17 00:00:00 2001 From: Frisk Date: Tue, 28 Dec 2021 15:08:56 +0100 Subject: [PATCH 10/30] added initial testing framework using mockserver to imitate client-server connection --- test/mockserver/configs/settings1.json | 557 ++++++++++++++++++ test/mockserver/data/response_error.json | 14 + test/mockserver/data/response_image.json | 32 + test/mockserver/data/response_init.json | 461 +++++++++++++++ .../data/response_recentchanges.json | 209 +++++++ .../data/response_recentchanges2.json | 78 +++ test/mockserver/data/response_siteinfo.json | 98 +++ test/mockserver/data/response_userinfo.json | 22 + test/mockserver/results/results1.json | 0 test/mockserver/server.py | 113 ++++ test/mockserver/start.py | 55 ++ 11 files changed, 1639 insertions(+) create mode 100644 test/mockserver/configs/settings1.json create mode 100644 test/mockserver/data/response_error.json create mode 100644 test/mockserver/data/response_image.json create mode 100644 test/mockserver/data/response_init.json create mode 100644 test/mockserver/data/response_recentchanges.json create mode 100644 test/mockserver/data/response_recentchanges2.json create mode 100644 test/mockserver/data/response_siteinfo.json create mode 100644 test/mockserver/data/response_userinfo.json create mode 100644 test/mockserver/results/results1.json create mode 100644 test/mockserver/server.py create mode 100644 test/mockserver/start.py 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/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..201eca5 --- /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": 811, + "newlen": 771, + "timestamp": "2021-12-26T12:13:38Z", + "parsedcomment": "Unfo 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": 771, + "newlen": 811, + "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..e69de29 diff --git a/test/mockserver/server.py b/test/mockserver/server.py new file mode 100644 index 0000000..22db113 --- /dev/null +++ b/test/mockserver/server.py @@ -0,0 +1,113 @@ +# 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 pprint +from http.server import BaseHTTPRequestHandler, HTTPServer +import json +import urllib.parse +import requests + +response_jsons: dict[str, dict] = {} + + +def load_response(name: str): + with open("data/{}.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 ["response_recentchanges", "response_recentchanges2", "response_init", "response_error", "response_siteinfo" + "response_image", "response_userinfo"]] + + +messages_collector = [] + + +# Return server response based on some output from Minecraft Wiki +class MockServerRequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + # 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 len(messages_collector) == 0: + # Limit amount of events accordingly to required amount just in case + response_jsons["response_recentchanges"]["query"]["recentchanges"] = get_response("response_recentchanges")["query"]["recentchanges"][0:int(query.get("rclimit", 20))] + response_content = json.dumps(get_response("response_recentchanges")) + else: + response_jsons["response_recentchanges2"]["query"]["recentchanges"] = get_response("response_recentchanges2")["query"]["recentchanges"][0:int(query.get("rclimit", 20))] + response_content = json.dumps(get_response("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("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("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("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("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("response_error")) + self.wfile.write(response_content.encode('utf-8')) + + def do_POST(self): + self.read_ok_collect() + + def do_PATCH(self): + self.read_ok_collect() + + def do_DELETE(self): + self.read_ok_collect() + + def read_ok_collect(self): + content_length = int(self.headers['Content-Length']) + patch_data = self.rfile.read(content_length) + messages_collector.append(patch_data.decode('utf-8')) + 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): + mock_server = HTTPServer(('localhost', port), MockServerRequestHandler) + try: + print("Server started successfully at http://localhost:{}".format(port)) + mock_server.serve_forever() + except KeyboardInterrupt: + print("Shutting down...") + print(pprint.pprint(messages_collector)) + pass diff --git a/test/mockserver/start.py b/test/mockserver/start.py new file mode 100644 index 0000000..a8627f9 --- /dev/null +++ b/test/mockserver/start.py @@ -0,0 +1,55 @@ +# 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 + data_file.write(json.dumps(data_file_data, indent=4)) + +# Start mock server +server.start_mock_server(8080) + +# Revert file changes +if not command_args.no_client: + shutil.copy(backup_filename, old_config) \ No newline at end of file From 241e2844eec23e0499717bf616f348de6fefdb38 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 30 Dec 2021 15:46:18 +0100 Subject: [PATCH 11/30] Added test files and completed(?) scripts --- src/rcgcdw.py | 2 +- .../data/response_diff20748742075224.json | 5 ++ .../data/response_diff20752242075225.json | 5 ++ .../data/response_diff20752302075231.json | 5 ++ .../data/response_diff2325552075232.json | 5 ++ .../data/response_diff2325562075234.json | 5 ++ .../data/response_recentchanges.json | 10 +-- test/mockserver/results/results1.json | 1 + test/mockserver/server.py | 69 ++++++++++++------- test/mockserver/start.py | 5 +- 10 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 test/mockserver/data/response_diff20748742075224.json create mode 100644 test/mockserver/data/response_diff20752242075225.json create mode 100644 test/mockserver/data/response_diff20752302075231.json create mode 100644 test/mockserver/data/response_diff2325552075232.json create mode 100644 test/mockserver/data/response_diff2325562075234.json diff --git a/src/rcgcdw.py b/src/rcgcdw.py index acc6e3f..f5cd6d9 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -19,7 +19,7 @@ # WARNING! SHITTY CODE AHEAD. ENTER ONLY IF YOU ARE SURE YOU CAN TAKE IT # You have been warned -import time, logging.config, requests, datetime, math, os.path, schedule, sys, re, importlib, argparse +import time, logging.config, requests, datetime, math, os.path, schedule, sys, re, importlib import src.misc import src.configloader 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_recentchanges.json b/test/mockserver/data/response_recentchanges.json index 201eca5..6693225 100644 --- a/test/mockserver/data/response_recentchanges.json +++ b/test/mockserver/data/response_recentchanges.json @@ -139,10 +139,10 @@ "rcid": 2793424, "user": "Good User", "anon": "", - "oldlen": 811, - "newlen": 771, + "oldlen": 771, + "newlen": 811, "timestamp": "2021-12-26T12:13:38Z", - "parsedcomment": "Unfo revision by 192.168.1.1. Reason: Mad cats", + "parsedcomment": "Undo revision by 192.168.1.1. Reason: Mad cats", "tags": [ "visualeditor" ] @@ -157,8 +157,8 @@ "rcid": 2793423, "user": "192.168.1.1", "anon": "", - "oldlen": 771, - "newlen": 811, + "oldlen": 811, + "newlen": 771, "timestamp": "2021-12-26T12:09:18Z", "parsedcomment": "Hahahahhaha, you will never understand my genius!", "tags": [ diff --git a/test/mockserver/results/results1.json b/test/mockserver/results/results1.json index e69de29..ace744f 100644 --- a/test/mockserver/results/results1.json +++ 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/server.py b/test/mockserver/server.py index 22db113..05fa389 100644 --- a/test/mockserver/server.py +++ b/test/mockserver/server.py @@ -12,7 +12,6 @@ # # You should have received a copy of the GNU General Public License # along with RcGcDw. If not, see . -import pprint from http.server import BaseHTTPRequestHandler, HTTPServer import json import urllib.parse @@ -20,9 +19,11 @@ import requests response_jsons: dict[str, dict] = {} +class EndOfContent(Exception): + pass def load_response(name: str): - with open("data/{}.json".format(name), "r") as response_file: + with open("data/response_{}.json".format(name), "r") as response_file: response_json: dict = json.loads(response_file.read()) response_jsons[name] = response_json @@ -31,16 +32,16 @@ def get_response(name: str): return response_jsons.get(name) -[load_response(x) for x in ["response_recentchanges", "response_recentchanges2", "response_init", "response_error", "response_siteinfo" - "response_image", "response_userinfo"]] +[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])} @@ -48,51 +49,61 @@ class MockServerRequestHandler(BaseHTTPRequestHandler): # Regular pooled query for recentchanges if query.get("list") == "recentchanges": self.send_essentials_ok() - if len(messages_collector) == 0: + if askedfor is False: # Limit amount of events accordingly to required amount just in case - response_jsons["response_recentchanges"]["query"]["recentchanges"] = get_response("response_recentchanges")["query"]["recentchanges"][0:int(query.get("rclimit", 20))] - response_content = json.dumps(get_response("response_recentchanges")) + 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["response_recentchanges2"]["query"]["recentchanges"] = get_response("response_recentchanges2")["query"]["recentchanges"][0:int(query.get("rclimit", 20))] - response_content = json.dumps(get_response("response_recentchanges2")) + 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("response_init")) + 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("response_siteinfo")) + 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("response_image")) + 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("response_userinfo")) + 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("response_error")) + 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() + self.read_ok_collect(method="POST") def do_PATCH(self): - self.read_ok_collect() + self.read_ok_collect(method="PATCH") def do_DELETE(self): - self.read_ok_collect() + self.read_ok_collect(method="DELETE") - def read_ok_collect(self): + def read_ok_collect(self, method: str): content_length = int(self.headers['Content-Length']) patch_data = self.rfile.read(content_length) - messages_collector.append(patch_data.decode('utf-8')) + 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')) @@ -102,12 +113,22 @@ class MockServerRequestHandler(BaseHTTPRequestHandler): self.end_headers() -def start_mock_server(port): +def start_mock_server(port, config): mock_server = HTTPServer(('localhost', port), MockServerRequestHandler) try: print("Server started successfully at http://localhost:{}".format(port)) - mock_server.serve_forever() + while 1: + if len(messages_collector) < 13: + mock_server.handle_request() + else: + raise EndOfContent except KeyboardInterrupt: print("Shutting down...") - print(pprint.pprint(messages_collector)) - pass + 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 index a8627f9..395e089 100644 --- a/test/mockserver/start.py +++ b/test/mockserver/start.py @@ -42,13 +42,14 @@ if not command_args.no_client: 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: + 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) +server.start_mock_server(8080, command_args) # Revert file changes if not command_args.no_client: From 3e48281285315ab1b4f5d4987434e16e15d18372 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sat, 1 Jan 2022 13:45:53 +0100 Subject: [PATCH 12/30] add second config --- test/mockserver/configs/settings2.json | 557 +++++++++++++++++++++++++ test/mockserver/results/results2.json | 1 + test/mockserver/server.py | 2 +- 3 files changed, 559 insertions(+), 1 deletion(-) create mode 100644 test/mockserver/configs/settings2.json create mode 100644 test/mockserver/results/results2.json diff --git a/test/mockserver/configs/settings2.json b/test/mockserver/configs/settings2.json new file mode 100644 index 0000000..1cf300b --- /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": 0, + "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/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 index 05fa389..e43f4ba 100644 --- a/test/mockserver/server.py +++ b/test/mockserver/server.py @@ -118,7 +118,7 @@ def start_mock_server(port, config): try: print("Server started successfully at http://localhost:{}".format(port)) while 1: - if len(messages_collector) < 13: + 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 From bf9e4bc855a619ae7a622da88d3d2ca0cd5f3b51 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 2 Jan 2022 14:39:20 +0100 Subject: [PATCH 13/30] Added some more testing cases --- src/misc.py | 17 ++++++----- test/test_misc.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 test/test_misc.py diff --git a/src/misc.py b/src/misc.py index c87c94d..c744280 100644 --- a/src/misc.py +++ b/src/misc.py @@ -48,10 +48,10 @@ 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_filename = settings.get("datafile_path", "data.json") - self.data = self.load_datafile() + 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 = False + self.changed: bool = False def generate_datafile(self): """Generate a data.json file from a template.""" @@ -256,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/test/test_misc.py b/test/test_misc.py new file mode 100644 index 0000000..90397a7 --- /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") From adb3b4207425c5224fa9a449daed6c2ab19e0d66 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 2 Jan 2022 15:31:11 +0100 Subject: [PATCH 14/30] Fixed #235 --- src/discord/redaction.py | 27 +++++++++++++++++++++++++-- src/rcgcdw.py | 4 +++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/discord/redaction.py b/src/discord/redaction.py index b7aac13..fd57440 100644 --- a/src/discord/redaction.py +++ b/src/discord/redaction.py @@ -15,6 +15,8 @@ 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.queue import send_to_discord, messagequeue @@ -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,24 @@ def redact_messages(ids: list, entry_type: int, to_censor: dict): send_to_discord(DiscordMessageRaw(message, settings["webhookURL"]+"/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. + This does not fix the problem of leaky diffs in entirety (because we are not doing diff comparisons), however the chances of fixing in most cases is pretty high + + :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 diff --git a/src/rcgcdw.py b/src/rcgcdw.py index f5cd6d9..b95ab41 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -45,7 +45,7 @@ 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"]) @@ -262,6 +262,8 @@ def rc_processor(change, changed_categories): logparams = change.get('logparams', {"ids": []}) if settings["appearance"]["mode"] == "embed": redact_messages(logparams.get("ids", []), 0, logparams.get("new", {})) + if "content" in logparams.get("new", {}): # 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)) From d9ae3306cf0f1e04177d357364a48f39b0c14f61 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 2 Jan 2022 20:56:50 +0100 Subject: [PATCH 15/30] Applied fixes to previous commit (thanks Markus!) --- src/discord/redaction.py | 3 +-- src/rcgcdw.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/discord/redaction.py b/src/discord/redaction.py index fd57440..d897d3a 100644 --- a/src/discord/redaction.py +++ b/src/discord/redaction.py @@ -96,7 +96,6 @@ def redact_messages(ids: Union[List[Union[str, int]], set[Union[int, str]]], ent 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. - This does not fix the problem of leaky diffs in entirety (because we are not doing diff comparisons), however the chances of fixing in most cases is pretty high :arg ids - list :arg pageid - int @@ -112,4 +111,4 @@ def find_middle_next(ids: List[str], pageid: int) -> set: 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 + return result - set(ids) diff --git a/src/rcgcdw.py b/src/rcgcdw.py index b95ab41..40ddb4a 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -262,7 +262,7 @@ def rc_processor(change, changed_categories): logparams = change.get('logparams', {"ids": []}) if settings["appearance"]["mode"] == "embed": redact_messages(logparams.get("ids", []), 0, logparams.get("new", {})) - if "content" in logparams.get("new", {}): # Also redact revisions in the middle and next ones in case of content (diffs leak) + 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", []): From 977894036a9edb62badf3c56feccbe9d1062ab6e Mon Sep 17 00:00:00 2001 From: Frisk Date: Sun, 2 Jan 2022 20:59:17 +0100 Subject: [PATCH 16/30] Cleaned up unused imports --- src/configloader.py | 2 +- src/discord/redaction.py | 2 +- src/discussions.py | 2 +- src/fileio/database.py | 1 - src/rcgcdw.py | 4 ++-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/configloader.py b/src/configloader.py index 9bbeb8e..b7cfe38 100644 --- a/src/configloader.py +++ b/src/configloader.py @@ -16,8 +16,8 @@ import json import logging import sys -global settings from src.argparser import command_args +global settings def load_settings(): diff --git a/src/discord/redaction.py b/src/discord/redaction.py index d897d3a..28c2575 100644 --- a/src/discord/redaction.py +++ b/src/discord/redaction.py @@ -18,7 +18,7 @@ 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 diff --git a/src/discussions.py b/src/discussions.py index f50ad48..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 diff --git a/src/fileio/database.py b/src/fileio/database.py index 888d22a..f84d6ce 100644 --- a/src/fileio/database.py +++ b/src/fileio/database.py @@ -15,7 +15,6 @@ import sqlite3 import logging -import json from src.configloader import settings logger = logging.getLogger("rcgcdw.fileio.database") diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 40ddb4a..5f58ec2 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -29,11 +29,11 @@ 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.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 From 71b7188d9797b201fab3b89f7e2b4c1260052d58 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 6 Jan 2022 15:36:53 +0100 Subject: [PATCH 17/30] Added resilience against file database corruption/deletion on error_tolerance > 1 --- src/fileio/database.py | 33 ++++++++++++++++++++++++++++++--- test/__init__.py | 0 2 files changed, 30 insertions(+), 3 deletions(-) delete mode 100644 test/__init__.py diff --git a/src/fileio/database.py b/src/fileio/database.py index f84d6ce..8b63a96 100644 --- a/src/fileio/database.py +++ b/src/fileio/database.py @@ -20,6 +20,26 @@ 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...") @@ -59,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: @@ -68,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/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 From 68303dd18bb03d9b09165068fe82211b937c5b89 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 6 Jan 2022 15:42:33 +0100 Subject: [PATCH 18/30] Actually fix pytest tests --- src/argparser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/argparser.py b/src/argparser.py index a70cb55..6bb0b96 100644 --- a/src/argparser.py +++ b/src/argparser.py @@ -18,4 +18,5 @@ 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 = parser.parse_args() +command_args, unknown = parser.parse_known_args() + From 149a1128399e4abfdc56a66609a5ba52dd1faa59 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 6 Jan 2022 15:45:22 +0100 Subject: [PATCH 19/30] Actually actually fix pytest tests --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ae19232..a3966ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,7 +37,7 @@ pytest: script: - source venv/bin/activate - pip3.7 install -U pytest - - pytest --junit-xml=report.xml + - PYTHONPATH=. pytest --junit-xml=report.xml artifacts: when: always reports: From 56fd7aefb781d9552f24aa6b9297410201fe2f1f Mon Sep 17 00:00:00 2001 From: Frisk Date: Sat, 8 Jan 2022 13:37:33 +0100 Subject: [PATCH 20/30] Added tests for registering formatters and hooks --- src/api/formatter.py | 1 + test/test_hooks.py | 76 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 test/test_hooks.py 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/test/test_hooks.py b/test/test_hooks.py new file mode 100644 index 0000000..4969e5d --- /dev/null +++ b/test/test_hooks.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 . + +import unittest + +from api.context import Context +from 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 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) From fe18b2e9623da2bc28f1dcf05185c0c428fdde27 Mon Sep 17 00:00:00 2001 From: Frisk Date: Sat, 8 Jan 2022 13:55:28 +0100 Subject: [PATCH 21/30] Fix test --- test/test_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_hooks.py b/test/test_hooks.py index 4969e5d..ff11377 100644 --- a/test/test_hooks.py +++ b/test/test_hooks.py @@ -15,7 +15,7 @@ import unittest -from api.context import Context +from src.api.context import Context from discord.message import DiscordMessage, DiscordMessageMetadata from src.api import formatter from src.api.hook import pre_hook, post_hook From 54bf7716c9bd033e6a933399fe9b5457be124cee Mon Sep 17 00:00:00 2001 From: Frisk Date: Sat, 8 Jan 2022 14:02:24 +0100 Subject: [PATCH 22/30] Uh oh --- test/test_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_hooks.py b/test/test_hooks.py index ff11377..d969dd0 100644 --- a/test/test_hooks.py +++ b/test/test_hooks.py @@ -16,7 +16,7 @@ import unittest from src.api.context import Context -from discord.message import DiscordMessage, DiscordMessageMetadata +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 From 4450d7e642696e62a642fb4385344d5db968d0c9 Mon Sep 17 00:00:00 2001 From: Frisk Date: Wed, 12 Jan 2022 13:55:08 +0100 Subject: [PATCH 23/30] Added more tests and removed broken codequality test --- .gitlab-ci.yml | 3 -- test/mockserver/configs/settings2.json | 2 +- test/test_disconnection.py | 31 ++++++++++++ test/test_i18n.py | 30 ++++++++++++ test/test_queue.py | 66 ++++++++++++++++++++++++++ test/test_utilities.py | 25 ++++++++++ test/test_wiki_login.py | 53 +++++++++++++++++++++ 7 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 test/test_disconnection.py create mode 100644 test/test_i18n.py create mode 100644 test/test_queue.py create mode 100644 test/test_utilities.py create mode 100644 test/test_wiki_login.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a3966ab..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" diff --git a/test/mockserver/configs/settings2.json b/test/mockserver/configs/settings2.json index 1cf300b..4bdaecc 100644 --- a/test/mockserver/configs/settings2.json +++ b/test/mockserver/configs/settings2.json @@ -21,7 +21,7 @@ "show_updown_messages": true, "ignored_namespaces": [], "extensions_dir": "extensions", - "error_tolerance": 0, + "error_tolerance": 2, "overview": true, "overview_time": "00:00", "send_empty_overview": true, 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_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_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() From f06b782d9d4e7c79cd16da6ba3c134d411aa87c9 Mon Sep 17 00:00:00 2001 From: Frisk Date: Wed, 12 Jan 2022 16:45:07 +0100 Subject: [PATCH 24/30] Added new test --- test/data/rc_objects.json | 17 ++++++++++++ test/data/rc_results.json | 3 ++ test/test_formatters.py | 58 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 test/data/rc_objects.json create mode 100644 test/data/rc_results.json create mode 100644 test/test_formatters.py diff --git a/test/data/rc_objects.json b/test/data/rc_objects.json new file mode 100644 index 0000000..c6863ec --- /dev/null +++ b/test/data/rc_objects.json @@ -0,0 +1,17 @@ +{ + "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"] + } +} \ 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..14825b2 --- /dev/null +++ b/test/data/rc_results.json @@ -0,0 +1,3 @@ +{ + "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": "Usuni\u0119to", "value": "__Tylko znaki niedrukowane__", "inline": true}, {"name": "Dodano", "value": "__Tylko znaki niedrukowane__", "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"}]} +} \ No newline at end of file diff --git a/test/test_formatters.py b/test/test_formatters.py new file mode 100644 index 0000000..2aa5a39 --- /dev/null +++ b/test/test_formatters.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.client import Client +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 Mock, 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) From f2b468d6e18e91ea4557251899f66cc9898631f3 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 13 Jan 2022 17:17:33 +0100 Subject: [PATCH 25/30] Added new tests (speedy) --- test/data/rc_objects.json | 78 ++++++++++++++++++- test/data/rc_results.json | 6 +- test/test_abusefilter.py | 59 ++++++++++++++ test/test_cargo.py | 66 ++++++++++++++++ test/test_datadump.py | 59 ++++++++++++++ test/test_hooks.py | 6 ++ test/test_interwiki.py | 59 ++++++++++++++ .../{test_formatters.py => test_mediawiki.py} | 4 +- test/test_misc.py | 12 +-- 9 files changed, 339 insertions(+), 10 deletions(-) create mode 100644 test/test_abusefilter.py create mode 100644 test/test_cargo.py create mode 100644 test/test_datadump.py create mode 100644 test/test_interwiki.py rename test/{test_formatters.py => test_mediawiki.py} (96%) diff --git a/test/data/rc_objects.json b/test/data/rc_objects.json index c6863ec..d20fa71 100644 --- a/test/data/rc_objects.json +++ b/test/data/rc_objects.json @@ -1,5 +1,5 @@ { - "edit": { + "edit": { "type": "edit", "ns": 0, "title": "Some page", @@ -13,5 +13,81 @@ "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 index 14825b2..4c62b3a 100644 --- a/test/data/rc_results.json +++ b/test/data/rc_results.json @@ -1,3 +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": "Usuni\u0119to", "value": "__Tylko znaki niedrukowane__", "inline": true}, {"name": "Dodano", "value": "__Tylko znaki niedrukowane__", "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"}]} + "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": "Usuni\u0119to", "value": "__Tylko znaki niedrukowane__", "inline": true}, {"name": "Dodano", "value": "__Tylko znaki niedrukowane__", "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": "Utworzono tabel\u0119 Cargo \u201eTableTest\u201d"}]}, + "abuselog": {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": null, "title": "User4 wywo\u0142a\u0142(a) \"Prevent vandalism\"", "fields": [{"name": "Wykonano", "value": "Edycja", "inline": false}, {"name": "Podj\u0119te dzia\u0142ania", "value": "Otagowano edycj\u0119", "inline": false}, {"name": "Tytu\u0142", "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": "Dodano wpis do tabeli interwiki", "description": "Prefix: testonlypleaseignore, strona: https://notawiki.notaplatform.com/wiki/$1 | "}]} } \ No newline at end of file diff --git a/test/test_abusefilter.py b/test/test_abusefilter.py new file mode 100644 index 0000000..517461d --- /dev/null +++ b/test/test_abusefilter.py @@ -0,0 +1,59 @@ +# 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)) + print(result) + self.assertEqual(results, result) diff --git a/test/test_cargo.py b/test/test_cargo.py new file mode 100644 index 0000000..68024c0 --- /dev/null +++ b/test/test_cargo.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 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)) + print(result) + self.assertEqual(results, result) diff --git a/test/test_datadump.py b/test/test_datadump.py new file mode 100644 index 0000000..f3ddfa9 --- /dev/null +++ b/test/test_datadump.py @@ -0,0 +1,59 @@ +# 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)) + print(result) + self.assertEqual(results, result) diff --git a/test/test_hooks.py b/test/test_hooks.py index d969dd0..072731a 100644 --- a/test/test_hooks.py +++ b/test/test_hooks.py @@ -23,6 +23,12 @@ 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() diff --git a/test/test_interwiki.py b/test/test_interwiki.py new file mode 100644 index 0000000..4e740fb --- /dev/null +++ b/test/test_interwiki.py @@ -0,0 +1,59 @@ +# 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)) + print(result) + self.assertEqual(results, result) diff --git a/test/test_formatters.py b/test/test_mediawiki.py similarity index 96% rename from test/test_formatters.py rename to test/test_mediawiki.py index 2aa5a39..2a0668b 100644 --- a/test/test_formatters.py +++ b/test/test_mediawiki.py @@ -17,12 +17,11 @@ import importlib import json from src.configloader import settings from src.api.context import Context -from src.api.client import Client 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 Mock, PropertyMock +from unittest.mock import PropertyMock import unittest @@ -42,6 +41,7 @@ with open("test/data/rc_results.json", "r") as ob: 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) diff --git a/test/test_misc.py b/test/test_misc.py index 90397a7..c346fbd 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -47,12 +47,12 @@ class TestDataFile(TestCase): 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) + # 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): From c87c066861953582184f1b58309896dd960995af Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 13 Jan 2022 17:18:40 +0100 Subject: [PATCH 26/30] Added some additional typing information --- src/discord/queue.py | 10 +++++----- src/i18n.py | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/discord/queue.py b/src/discord/queue.py index 9777287..dfa56f1 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 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 From 5d48533e69f84c3eeb9a90e1ce683fd345aff865 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 13 Jan 2022 17:23:26 +0100 Subject: [PATCH 27/30] Added some additional typing information and some spacing fixes --- src/api/hooks.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 From 9196a9cbbe6bd27e9b79c543fb2c7a2b7571dc70 Mon Sep 17 00:00:00 2001 From: Frisk Date: Fri, 14 Jan 2022 10:01:44 +0100 Subject: [PATCH 28/30] Added migration test, fixes in previous tests --- src/configloader.py | 3 ++- src/rcgcdw.py | 1 + src/wiki.py | 4 ++-- test/data/rc_results.json | 8 ++++---- test/test_11311_migration.py | 39 ++++++++++++++++++++++++++++++++++++ test/test_abusefilter.py | 1 - test/test_cargo.py | 1 - test/test_datadump.py | 1 - test/test_interwiki.py | 1 - 9 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 test/test_11311_migration.py diff --git a/src/configloader.py b/src/configloader.py index b7cfe38..545db53 100644 --- a/src/configloader.py +++ b/src/configloader.py @@ -23,11 +23,12 @@ global settings def load_settings(): global settings try: # load settings + 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.0.2") # set the version in the useragent + 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) diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 5f58ec2..1c60c1a 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -341,6 +341,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/data/rc_results.json b/test/data/rc_results.json index 4c62b3a..2d8f91f 100644 --- a/test/data/rc_results.json +++ b/test/data/rc_results.json @@ -1,7 +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": "Usuni\u0119to", "value": "__Tylko znaki niedrukowane__", "inline": true}, {"name": "Dodano", "value": "__Tylko znaki niedrukowane__", "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"}]}, + "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": "Utworzono tabel\u0119 Cargo \u201eTableTest\u201d"}]}, - "abuselog": {"allowed_mentions": {"parse": []}, "avatar_url": "", "embeds": [{"color": null, "title": "User4 wywo\u0142a\u0142(a) \"Prevent vandalism\"", "fields": [{"name": "Wykonano", "value": "Edycja", "inline": false}, {"name": "Podj\u0119te dzia\u0142ania", "value": "Otagowano edycj\u0119", "inline": false}, {"name": "Tytu\u0142", "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": "Dodano wpis do tabeli interwiki", "description": "Prefix: testonlypleaseignore, strona: https://notawiki.notaplatform.com/wiki/$1 | "}]} + "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/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 index 517461d..678e084 100644 --- a/test/test_abusefilter.py +++ b/test/test_abusefilter.py @@ -55,5 +55,4 @@ class TestMWFormatter(unittest.TestCase): # ctx.client.return_value = Mock(spec=Client) edit_c, results = get_objects("abuselog") result = repr(test(ctx, edit_c)) - print(result) self.assertEqual(results, result) diff --git a/test/test_cargo.py b/test/test_cargo.py index 68024c0..d51c48b 100644 --- a/test/test_cargo.py +++ b/test/test_cargo.py @@ -62,5 +62,4 @@ class TestMWFormatter(unittest.TestCase): # ctx.client.return_value = Mock(spec=Client) edit_c, results = get_objects("cargo/createtable") result = repr(test(ctx, edit_c)) - print(result) self.assertEqual(results, result) diff --git a/test/test_datadump.py b/test/test_datadump.py index f3ddfa9..bac093c 100644 --- a/test/test_datadump.py +++ b/test/test_datadump.py @@ -55,5 +55,4 @@ class TestMWFormatter(unittest.TestCase): # ctx.client.return_value = Mock(spec=Client) edit_c, results = get_objects("datadump/generate") result = repr(test(ctx, edit_c)) - print(result) self.assertEqual(results, result) diff --git a/test/test_interwiki.py b/test/test_interwiki.py index 4e740fb..7022265 100644 --- a/test/test_interwiki.py +++ b/test/test_interwiki.py @@ -55,5 +55,4 @@ class TestMWFormatter(unittest.TestCase): # ctx.client.return_value = Mock(spec=Client) edit_c, results = get_objects("interwiki/iw_add") result = repr(test(ctx, edit_c)) - print(result) self.assertEqual(results, result) From 1247e5f40804228cf564d9d0e63423fa9c12f732 Mon Sep 17 00:00:00 2001 From: MarkusRost <2701034-MarkusRost@users.noreply.gitlab.com> Date: Wed, 2 Feb 2022 08:11:39 +0000 Subject: [PATCH 29/30] Don't run redaction on archives --- src/rcgcdw.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 1c60c1a..6d66fc3 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -260,13 +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", {})) - 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)) + 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) From 23b5a5db1c5b3e4033c1f5df71d318956c2a8628 Mon Sep 17 00:00:00 2001 From: Frisk Date: Thu, 24 Feb 2022 07:16:43 +0100 Subject: [PATCH 30/30] Added fix to possible None comparison on HTTP status check --- src/discord/queue.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/discord/queue.py b/src/discord/queue.py index dfa56f1..771507c 100644 --- a/src/discord/queue.py +++ b/src/discord/queue.py @@ -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 \ No newline at end of file