Merge branch 'testing' into 'master'

Testing

Closes #22 and #81

See merge request piotrex43/RcGcDw!50
This commit is contained in:
Frisk 2019-05-24 18:54:40 +00:00
commit de20f63cf2
29 changed files with 2081 additions and 1302 deletions

234
configbuilder.py Normal file
View file

@ -0,0 +1,234 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Recent changes Gamepedia compatible Discord webhook is a project for using a webhook as recent changes page from MediaWiki.
# Copyright (C) 2018 Frisk
# This program 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.
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
import json, time, sys, re
try:
import requests
except ModuleNotFoundError:
print("The requests module couldn't be found. Please install requests library using pip install requests.")
sys.exit(0)
if "--help" in sys.argv:
print("""Usage: python configbuilder.py [OPTIONS]
Options:
--basic - Prepares only the most basic settings necessary to run the script, leaves a lot of customization options on default
--advanced - Prepares advanced settings with great range of customization
--annoying - Asks for every single value available""")
sys.exit(0)
def default_or_custom(answer, default):
if answer == "":
return default
else:
return answer
def yes_no(answer):
if answer.lower() == "y":
return True
elif answer.lower() == "n":
return False
else:
raise ValueError
print("Welcome in RcGcDw config builder! You can accept the default value if provided in the question by using Enter key without providing any other input.\nWARNING! Your current settings.json will be overwritten if you continue!")
try: # load settings
with open("settings.json.example") as sfile:
settings = json.load(sfile)
except FileNotFoundError:
if yes_no(default_or_custom(input("Template config (settings.json.example) could not be found. Download the most recent stable one from master branch? (https://gitlab.com/piotrex43/RcGcDw/raw/master/settings.json.example)? (Y/n)"), "y")):
settings = requests.get("https://gitlab.com/piotrex43/RcGcDw/raw/master/settings.json.example").json()
def basic():
while True:
if set_cooldown():
break
while True:
if set_wiki():
break
while True:
if set_lang():
break
while True:
if set_webhook():
break
while True:
if set_wikiname():
break
while True:
if set_displaymode():
break
def advanced():
def set_cooldown():
option = default_or_custom(input("Interval for fetching recent changes in seconds (min. 10, default 30).\n"), 30)
try:
option = int(option)
if option < 10:
print("Please give a value higher than 9!")
return False
else:
settings["cooldown"] = option
return True
except ValueError:
print("Please give a valid number.")
return False
def set_wiki():
option = input("Please give the wiki you want to be monitored. (for example 'minecraft' or 'terraria-pl' are valid options)\n")
if option.startswith("http"):
regex = re.search(r"http(?:s|):\/\/(.*?)\.gamepedia.com", option)
if regex.group(1):
option = regex.group(1)
wiki_request = requests.get("https://{}.gamepedia.com".format(option), timeout=10, allow_redirects=False)
if wiki_request.status_code == 404 or wiki_request.status_code == 302:
print("Wiki at https://{}.gamepedia.com does not exist, are you sure you have entered the wiki correctly?".format(option))
return False
else:
settings["wiki"] = option
return True
def set_lang():
option = default_or_custom(input(
"Please provide a language code for translation of the script. Translations available: en, de, ru, pt-br, fr, pl. (default en)\n"), "en")
if option in ["en", "de", "ru", "pt-br", "fr", "pl"]:
settings["lang"] = option
return True
return False
def set_webhook():
option = input(
"Webhook URL is required. You can get it on Discord by following instructions on this page: https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks\n")
if option.startswith("https://discordapp.com/api/webhooks/"):
test_webhook = requests.get(option)
if test_webhook.status_code != 200:
print("The webhook URL does not seem right. Reason: {}".format(test_webhook.json()["message"]))
return False
else:
settings["webhookURL"] = option
return True
else:
print("The webhook URL should start with https://discordapp.com/api/webhooks/, are you sure it's the right URL?")
return False
def set_wikiname():
option = input("Please provide any wiki name for the wiki (can be whatever, but should be a full name of the wiki, for example \"Minecraft Wiki\")\n") # TODO Fetch the wiki yourself using api by default
settings["wikiname"] = option
return True
def set_displaymode():
option = default_or_custom(input(
"Please choose the display mode for the feed. More on how they look like on https://gitlab.com/piotrex43/RcGcDw/wikis/Presentation. Valid values: compact or embed. Default: embed\n"), "embed").lower()
if option in ["embed", "compact"]:
settings["appearance"]["mode"] = option
return True
print("Invalid mode selected!")
return False
def set_limit():
option = default_or_custom(input("Limit for amount of changes fetched every {} seconds. (default: 10, minimum: 1, the less active wiki is the lower the value should be)\n".format(settings["cooldown"])), 10)
try:
option = int(option)
if option < 2:
print("Please give a value higher than 1!")
return False
else:
settings["limit"] = option
return True
except ValueError:
print("Please give a valid number.")
return False
def set_refetch_limit():
option = default_or_custom(input("Limit for amount of changes fetched every time the script starts. (default: 28, minimum: {})\n".format(settings["limit"])), 28)
try:
option = int(option)
if option < settings["limit"]:
print("Please give a value higher than {}!".format(settings["limit"]))
return False
else:
settings["limit"] = option
return True
except ValueError:
print("Please give a valid number.")
return False
def set_updown_messages():
try:
option = yes_no(default_or_custom(input("Should the script send messages when the wiki becomes unavailable? (Y/n)"), "y"))
settings["show_updown_messages"] = option
return True
except ValueError:
print("Response not valid, please use y (yes) or n (no)")
return False
def set_downup_avatars():
option = default_or_custom(input("Provide a link for a custom webhook avatar when the wiki goes DOWN. (default: no avatar)"), "") #TODO Add a check for the image
settings["avatars"]["connection_failed"] = option
option = default_or_custom(
input("Provide a link for a custom webhook avatar when the connection to the wiki is RESTORED. (default: no avatar)"),
"") # TODO Add a check for the image
settings["avatars"]["connection_failed"] = option
return True
def set_ignored_events():
option = default_or_custom(
input("Provide a list of entry types that are supposed to be ignored. Separate them using commas. Example: external, edit, upload/overwrite. (default: external)"), "external") # TODO Add a check for the image
entry_types = []
for etype in option.split(","):
entry_types.append(etype.strip())
settings["ignored"] = entry_types
def set_overview():
try:
option = yes_no(default_or_custom(input("Should the script send daily overviews of the actions done on the wiki for past 24 hours? (y/N)"), "n"))
settings["overview"] = option
return True
except ValueError:
print("Response not valid, please use y (yes) or n (no)")
return False
def set_overview_time():
try:
option = default_or_custom(input("At what time should the daily overviews be sent? (script uses local machine time, the format of the time should be HH:MM, default is 00:00)"), "00:00")
re.match(r"$\d{2}:\d{2}^")
settings["overview"] = option
return True
except ValueError:
print("Response not valid, please use y (yes) or n (no)")
return False
try:
basic()
with open("settings.json", "w") as settings_file:
settings_file.write(json.dumps(settings, indent=4))
if "--advanced" in sys.argv:
print("Basic part of the config has been completed. Starting the advanced part...")
advanced()
print("Responses has been saved! Your settings.json should be now valid and bot ready to run.")
except KeyboardInterrupt:
if not yes_no(default_or_custom(input("\nSave the config before exiting? (y/N)"),"n")):
sys.exit(0)
else:
with open("settings.json", "w") as settings_file:
settings_file.write(json.dumps(settings, indent=4))

12
configloader.py Normal file
View file

@ -0,0 +1,12 @@
import json, sys, logging
try: # load settings
with open("settings.json") 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.7") # 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)

Binary file not shown.

View file

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

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

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

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

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

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

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

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

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

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

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

24
misc.pot Normal file
View file

@ -0,0 +1,24 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-20 17:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: misc.py:76
msgid ""
"\n"
"__And more__"
msgstr ""

222
misc.py Normal file
View file

@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
# Recent changes Gamepedia compatible Discord webhook is a project for using a webhook as recent changes page from MediaWiki.
# Copyright (C) 2018 Frisk
# This program 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.
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
import json, logging, sys, re
from html.parser import HTMLParser
from configloader import settings
import gettext
# Initialize translation
t = gettext.translation('misc', localedir='locale', languages=[settings["lang"]])
_ = t.gettext
# Create a custom logger
misc_logger = logging.getLogger("rcgcdw.misc")
data_template = {"rcid": 99999999999,
"daily_overview": {"edits": None, "new_files": None, "admin_actions": None, "bytes_changed": None,
"new_articles": None, "unique_editors": None, "day_score": None, "days_tracked": 0}}
def generate_datafile():
"""Generate a data.json file from a template."""
try:
with open("data.json", 'w') 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.")
sys.exit(1)
def load_datafile() -> object:
"""Read a data.json file and return a dictionary with contents
:rtype: object
"""
try:
with open("data.json") as data:
return json.loads(data.read())
except FileNotFoundError:
generate_datafile()
misc_logger.info("The data file could not be found. Generating a new one...")
return data_template
def save_datafile(data):
"""Overwrites the data.json file with given dictionary"""
try:
with open("data.json", "w") as data_file:
data_file.write(json.dumps(data, indent=4))
except PermissionError:
misc_logger.critical("Could not modify a data file (no permissions). No way to store last edit.")
sys.exit(1)
def weighted_average(value, weight, new_value):
"""Calculates weighted average of value number with weight weight and new_value with weight 1"""
return round(((value * weight) + new_value) / (weight + 1), 2)
def link_formatter(link):
"""Formats a link to not embed it"""
return "<" + re.sub(r"([ \)])", "\\\\\\1", link) + ">"
class ContentParser(HTMLParser):
more = _("\n__And more__")
current_tag = ""
small_prev_ins = ""
small_prev_del = ""
ins_length = len(more)
del_length = len(more)
added = False
def handle_starttag(self, tagname, attribs):
if tagname == "ins" or tagname == "del":
self.current_tag = tagname
if tagname == "td" and 'diff-addedline' in attribs[0]:
self.current_tag = tagname + "a"
if tagname == "td" and 'diff-deletedline' in attribs[0]:
self.current_tag = tagname + "d"
if tagname == "td" and 'diff-marker' in attribs[0]:
self.added = True
def handle_data(self, data):
data = re.sub(r"(`|_|\*|~|<|>|{|}|@|/|\|)", "\\\\\\1", data, 0)
if self.current_tag == "ins" and self.ins_length <= 1000:
self.ins_length += len("**" + data + '**')
if self.ins_length <= 1000:
self.small_prev_ins = self.small_prev_ins + "**" + data + '**'
else:
self.small_prev_ins = self.small_prev_ins + self.more
if self.current_tag == "del" and self.del_length <= 1000:
self.del_length += len("~~" + data + '~~')
if self.del_length <= 1000:
self.small_prev_del = self.small_prev_del + "~~" + data + '~~'
else:
self.small_prev_del = self.small_prev_del + self.more
if (self.current_tag == "afterins" or self.current_tag == "tda") and self.ins_length <= 1000:
self.ins_length += len(data)
if self.ins_length <= 1000:
self.small_prev_ins = self.small_prev_ins + data
else:
self.small_prev_ins = self.small_prev_ins + self.more
if (self.current_tag == "afterdel" or self.current_tag == "tdd") and self.del_length <= 1000:
self.del_length += len(data)
if self.del_length <= 1000:
self.small_prev_del = self.small_prev_del + data
else:
self.small_prev_del = self.small_prev_del + self.more
if self.added:
if data == '+' and self.ins_length <= 1000:
self.ins_length += 1
if self.ins_length <= 1000:
self.small_prev_ins = self.small_prev_ins + '\n'
else:
self.small_prev_ins = self.small_prev_ins + self.more
if data == '' and self.del_length <= 1000:
self.del_length += 1
if self.del_length <= 1000:
self.small_prev_del = self.small_prev_del + '\n'
else:
self.small_prev_del = self.small_prev_del + self.more
self.added = False
def handle_endtag(self, tagname):
if tagname == "ins":
self.current_tag = "afterins"
elif tagname == "del":
self.current_tag = "afterdel"
else:
self.current_tag = ""
class LinkParser(HTMLParser):
new_string = ""
recent_href = ""
def handle_starttag(self, tag, attrs):
for attr in attrs:
if attr[0] == 'href':
self.recent_href = attr[1]
if self.recent_href.startswith("//"):
self.recent_href = "https:{rest}".format(rest=self.recent_href)
elif not self.recent_href.startswith("http"):
self.recent_href = "https://{wiki}.gamepedia.com".format(wiki=settings["wiki"]) + self.recent_href
self.recent_href = self.recent_href.replace(")", "\\)")
def handle_data(self, data):
if self.recent_href:
self.new_string = self.new_string + "[{}](<{}>)".format(data, self.recent_href)
self.recent_href = ""
else:
self.new_string = self.new_string + data
def handle_comment(self, data):
self.new_string = self.new_string + data
def handle_endtag(self, tag):
misc_logger.debug(self.new_string)
def safe_read(request, *keys):
if request is None:
return None
try:
request = request.json()
for item in keys:
request = request[item]
except KeyError:
misc_logger.warning(
"Failure while extracting data from request on key {key} in {change}".format(key=item, change=request))
return None
except ValueError:
misc_logger.warning("Failure while extracting data from request in {change}".format(change=request))
return None
return request
def handle_discord_http(code, formatted_embed, result):
if 300 > code > 199: # message went through
return 0
elif code == 400: # HTTP BAD REQUEST result.status_code, data, result, header
misc_logger.error(
"Following message has been rejected by Discord, please submit a bug on our bugtracker adding it:")
misc_logger.error(formatted_embed)
misc_logger.error(result.text)
return 1
elif code == 401 or code == 404: # HTTP UNAUTHORIZED AND NOT FOUND
misc_logger.error("Webhook URL is invalid or no longer in use, please replace it with proper one.")
sys.exit(1)
elif code == 429:
misc_logger.error("We are sending too many requests to the Discord, slowing down...")
return 2
elif 499 < code < 600:
misc_logger.error(
"Discord have trouble processing the event, and because the HTTP code returned is {} it means we blame them.".format(
code))
return 3
def add_to_dict(dictionary, key):
if key in dictionary:
dictionary[key] += 1
else:
dictionary[key] = 1
return dictionary

File diff suppressed because it is too large Load diff

461
rcgcdw.py

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
"header": { "header": {
"user-agent": "RcGcDw/{version}" "user-agent": "RcGcDw/{version}"
}, },
"limit": 11, "limit": 10,
"webhookURL": "https://discordapp.com/api/webhooks/111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "webhookURL": "https://discordapp.com/api/webhooks/111111111111111111/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"limitrefetch": 28, "limitrefetch": 28,
"wikiname": "Minecraft Wiki", "wikiname": "Minecraft Wiki",
@ -16,7 +16,6 @@
"embed": "" "embed": ""
}, },
"ignored": ["external"], "ignored": ["external"],
"verbose_level": 0,
"show_updown_messages": true, "show_updown_messages": true,
"overview": false, "overview": false,
"overview_time": "00:00", "overview_time": "00:00",
@ -27,10 +26,37 @@
"wiki_bot_login": "", "wiki_bot_login": "",
"wiki_bot_password": "", "wiki_bot_password": "",
"show_added_categories": true, "show_added_categories": true,
"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":{ "appearance":{
"mode": "embed", "mode": "embed",
"embed": { "embed": {
"daily_overview": { "show_edit_changes": false,
"daily_overview": {
"color": 16312092, "color": 16312092,
"icon":"" "icon":""
}, },