Initial commit, discussions work in progress, split off most important functions from rcgcdw.py since we need them for modularization

This commit is contained in:
Frisk 2020-04-04 14:29:18 +02:00
parent c5f215abc7
commit ed111974df
No known key found for this signature in database
GPG key ID: 213F7C15068AF8AC
4 changed files with 133 additions and 81 deletions

View file

@ -10,3 +10,4 @@ try: # load settings
except FileNotFoundError: except FileNotFoundError:
logging.critical("No config file could be found. Please make sure settings.json is in the directory.") logging.critical("No config file could be found. Please make sure settings.json is in the directory.")
sys.exit(1) sys.exit(1)

35
discussions.py Normal file
View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Recent changes Goat compatible Discord webhook is a project for using a webhook as recent changes page from MediaWiki.
# Copyright (C) 2020 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 logging, gettext
from configloader import settings
from misc import datafile, WIKI_SCRIPT_PATH
# Initialize translation
t = gettext.translation('discussions', localedir='locale', languages=[settings["lang"]])
_ = t.gettext
# Create a custom logger
discussion_logger = logging.getLogger("rcgcdw.disc")
fetch_url = "https://services.fandom.com/discussion/{wikiid}/posts?sortDirection=descending&sortKey=creation_date&limit={limit}".format(wikiid=settings["fandom_discussions"]["wiki_id"], limit=settings["fandom_discussions"]["limit"])
def fetch_discussions():
pass

116
misc.py
View file

@ -18,6 +18,9 @@
import json, logging, sys, re import json, logging, sys, re
from html.parser import HTMLParser from html.parser import HTMLParser
from urllib.parse import urlparse, urlunparse
import requests
from configloader import settings from configloader import settings
import gettext import gettext
@ -30,43 +33,53 @@ _ = t.gettext
misc_logger = logging.getLogger("rcgcdw.misc") misc_logger = logging.getLogger("rcgcdw.misc")
data_template = {"rcid": 99999999999, data_template = {"rcid": 99999999999, "discussion_id": 0,
"daily_overview": {"edits": None, "new_files": None, "admin_actions": None, "bytes_changed": None, "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}} "new_articles": None, "unique_editors": None, "day_score": None, "days_tracked": 0}}
WIKI_API_PATH: str = ""
WIKI_ARTICLE_PATH: str = ""
WIKI_SCRIPT_PATH: str = ""
WIKI_JUST_DOMAIN: str = ""
def generate_datafile(): class DataFile:
"""Generate a data.json file from a template.""" """Data class which instance of is shared by multiple modules to remain consistent and do not cause too many IO operations."""
try: def __init__(self):
with open("data.json", 'w') as data: self.data = self.load_datafile()
data.write(json.dumps(data_template, indent=4))
except PermissionError: @staticmethod
misc_logger.critical("Could not create a data file (no permissions). No way to store last edit.") def generate_datafile():
sys.exit(1) """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(self) -> dict:
"""Read a data.json file and return a dictionary with contents
:rtype: dict
"""
try:
with open("data.json") as data:
return json.loads(data.read())
except FileNotFoundError:
self.generate_datafile()
misc_logger.info("The data file could not be found. Generating a new one...")
return data_template
def save_datafile(self):
"""Overwrites the data.json file with given dictionary"""
try:
with open("data.json", "w") as data_file:
data_file.write(json.dumps(self.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 load_datafile() -> dict: datafile = DataFile()
"""Read a data.json file and return a dictionary with contents
:rtype: dict
"""
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): def weighted_average(value, weight, new_value):
"""Calculates weighted average of value number with weight weight and new_value with weight 1""" """Calculates weighted average of value number with weight weight and new_value with weight 1"""
@ -192,3 +205,44 @@ def add_to_dict(dictionary, key):
else: else:
dictionary[key] = 1 dictionary[key] = 1
return dictionary return dictionary
def prepare_paths():
global WIKI_API_PATH
global WIKI_ARTICLE_PATH
global WIKI_SCRIPT_PATH
global WIKI_JUST_DOMAIN
"""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"""
def quick_try_url(url):
"""Quickly test if URL is the proper script path,
False if it appears invalid
dictionary when it appears valid"""
try:
request = requests.get(url, timeout=5)
if request.status_code == requests.codes.ok:
if request.json()["query"]["general"] is not None:
return request
return False
except (KeyError, requests.exceptions.ConnectionError):
return False
try:
parsed_url = urlparse(settings["wiki_url"])
except KeyError:
misc_logger.critical("wiki_url is not specified in the settings. Please provide the wiki url in the settings and start the script again.")
sys.exit(1)
for url_scheme in (settings["wiki_url"], settings["wiki_url"].split("wiki")[0], urlunparse((*parsed_url[0:2], "", "", "", ""))): # check different combinations, it's supposed to be idiot-proof
tested = quick_try_url(url_scheme + "/api.php?action=query&format=json&meta=siteinfo")
if tested:
WIKI_API_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/api.php"
WIKI_SCRIPT_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/"
WIKI_ARTICLE_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["articlepath"]
WIKI_JUST_DOMAIN = urlunparse((*parsed_url[0:2], "", "", "", ""))
break
else:
misc_logger.critical("Could not verify wikis paths. Please make sure you have given the proper wiki URL in settings.json and your Internet connection is working.")
sys.exit(1)
prepare_paths()

View file

@ -26,9 +26,13 @@ from html.parser import HTMLParser
import misc import misc
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from collections import defaultdict, Counter from collections import defaultdict, Counter
from urllib.parse import quote_plus, urlparse, urlunparse from urllib.parse import quote_plus
from configloader import settings from configloader import settings
from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, misc_logger from misc import link_formatter, ContentParser, safe_read, handle_discord_http, add_to_dict, datafile, \
WIKI_API_PATH, WIKI_ARTICLE_PATH, WIKI_SCRIPT_PATH, WIKI_JUST_DOMAIN
if settings["fandom_discussions"]["enabled"]:
pass
if __name__ != "__main__": # return if called as a module if __name__ != "__main__": # return if called as a module
logging.critical("The file is being executed as a module. Please execute the script using the console.") logging.critical("The file is being executed as a module. Please execute the script using the console.")
@ -53,7 +57,7 @@ except FileNotFoundError:
lang.install() lang.install()
ngettext = lang.ngettext ngettext = lang.ngettext
storage = misc.load_datafile() storage = datafile.data
# Remove previous data holding file if exists and limitfetch allows # Remove previous data holding file if exists and limitfetch allows
@ -61,7 +65,7 @@ if settings["limitrefetch"] != -1 and os.path.exists("lastchange.txt") is True:
with open("lastchange.txt", 'r') as sfile: with open("lastchange.txt", 'r') as sfile:
logger.info("Converting old lastchange.txt file into new data storage data.json...") logger.info("Converting old lastchange.txt file into new data storage data.json...")
storage["rcid"] = int(sfile.read().strip()) storage["rcid"] = int(sfile.read().strip())
misc.save_datafile(storage) datafile.save_datafile()
os.remove("lastchange.txt") os.remove("lastchange.txt")
# A few initial vars # A few initial vars
@ -69,10 +73,6 @@ if settings["limitrefetch"] != -1 and os.path.exists("lastchange.txt") is True:
logged_in = False logged_in = False
supported_logs = ["protect/protect", "protect/modify", "protect/unprotect", "upload/overwrite", "upload/upload", "delete/delete", "delete/delete_redir", "delete/restore", "delete/revision", "delete/event", "import/upload", "import/interwiki", "merge/merge", "move/move", "move/move_redir", "protect/move_prot", "block/block", "block/unblock", "block/reblock", "rights/rights", "rights/autopromote", "abusefilter/modify", "abusefilter/create", "interwiki/iw_add", "interwiki/iw_edit", "interwiki/iw_delete", "curseprofile/comment-created", "curseprofile/comment-edited", "curseprofile/comment-deleted", "curseprofile/comment-purged", "curseprofile/profile-edited", "curseprofile/comment-replied", "contentmodel/change", "sprite/sprite", "sprite/sheet", "sprite/slice", "managetags/create", "managetags/delete", "managetags/activate", "managetags/deactivate", "tag/update", "cargo/createtable", "cargo/deletetable", "cargo/recreatetable", "cargo/replacetable", "upload/revert"] supported_logs = ["protect/protect", "protect/modify", "protect/unprotect", "upload/overwrite", "upload/upload", "delete/delete", "delete/delete_redir", "delete/restore", "delete/revision", "delete/event", "import/upload", "import/interwiki", "merge/merge", "move/move", "move/move_redir", "protect/move_prot", "block/block", "block/unblock", "block/reblock", "rights/rights", "rights/autopromote", "abusefilter/modify", "abusefilter/create", "interwiki/iw_add", "interwiki/iw_edit", "interwiki/iw_delete", "curseprofile/comment-created", "curseprofile/comment-edited", "curseprofile/comment-deleted", "curseprofile/comment-purged", "curseprofile/profile-edited", "curseprofile/comment-replied", "contentmodel/change", "sprite/sprite", "sprite/sheet", "sprite/slice", "managetags/create", "managetags/delete", "managetags/activate", "managetags/deactivate", "tag/update", "cargo/createtable", "cargo/deletetable", "cargo/recreatetable", "cargo/replacetable", "upload/revert"]
profile_fields = {"profile-location": _("Location"), "profile-aboutme": _("About me"), "profile-link-google": _("Google link"), "profile-link-facebook":_("Facebook link"), "profile-link-twitter": _("Twitter link"), "profile-link-reddit": _("Reddit link"), "profile-link-twitch": _("Twitch link"), "profile-link-psn": _("PSN link"), "profile-link-vk": _("VK link"), "profile-link-xbl": _("XBL link"), "profile-link-steam": _("Steam link"), "profile-link-discord": _("Discord handle"), "profile-link-battlenet": _("Battle.net handle")} profile_fields = {"profile-location": _("Location"), "profile-aboutme": _("About me"), "profile-link-google": _("Google link"), "profile-link-facebook":_("Facebook link"), "profile-link-twitter": _("Twitter link"), "profile-link-reddit": _("Reddit link"), "profile-link-twitch": _("Twitch link"), "profile-link-psn": _("PSN link"), "profile-link-vk": _("VK link"), "profile-link-xbl": _("XBL link"), "profile-link-steam": _("Steam link"), "profile-link-discord": _("Discord handle"), "profile-link-battlenet": _("Battle.net handle")}
WIKI_API_PATH: str = ""
WIKI_ARTICLE_PATH: str = ""
WIKI_SCRIPT_PATH: str = ""
WIKI_JUST_DOMAIN: str = ""
class LinkParser(HTMLParser): class LinkParser(HTMLParser):
@ -112,44 +112,6 @@ LinkParser = LinkParser()
class MWError(Exception): class MWError(Exception):
pass pass
def prepare_paths():
global WIKI_API_PATH
global WIKI_ARTICLE_PATH
global WIKI_SCRIPT_PATH
global WIKI_JUST_DOMAIN
"""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"""
def quick_try_url(url):
"""Quickly test if URL is the proper script path,
False if it appears invalid
dictionary when it appears valid"""
try:
request = requests.get(url, timeout=5)
if request.status_code == requests.codes.ok:
if request.json()["query"]["general"] is not None:
return request
return False
except (KeyError, requests.exceptions.ConnectionError):
return False
try:
parsed_url = urlparse(settings["wiki_url"])
except KeyError:
logger.critical("wiki_url is not specified in the settings. Please provide the wiki url in the settings and start the script again.")
sys.exit(1)
for url_scheme in (settings["wiki_url"], settings["wiki_url"].split("wiki")[0], urlunparse((*parsed_url[0:2], "", "", "", ""))): # check different combinations, it's supposed to be idiot-proof
tested = quick_try_url(url_scheme + "/api.php?action=query&format=json&meta=siteinfo")
if tested:
WIKI_API_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/api.php"
WIKI_SCRIPT_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["scriptpath"] + "/"
WIKI_ARTICLE_PATH = urlunparse((*parsed_url[0:2], "", "", "", "")) + tested.json()["query"]["general"]["articlepath"]
WIKI_JUST_DOMAIN = urlunparse((*parsed_url[0:2], "", "", "", ""))
break
else:
logger.critical("Could not verify wikis paths. Please make sure you have given the proper wiki URL in settings.json and your Internet connection is working.")
sys.exit(1)
def create_article_path(article: str) -> str: def create_article_path(article: str) -> str:
"""Takes the string and creates an URL with it as the article name""" """Takes the string and creates an URL with it as the article name"""
@ -1069,7 +1031,7 @@ def daily_overview_sync(edits, files, admin, changed_bytes, new_articles, unique
storage["daily_overview"].update({"edits": edits_avg, "new_files": files_avg, "admin_actions": admin_avg, "bytes_changed": changed_bytes_avg, storage["daily_overview"].update({"edits": edits_avg, "new_files": files_avg, "admin_actions": admin_avg, "bytes_changed": changed_bytes_avg,
"new_articles": new_articles_avg, "unique_editors": unique_contributors_avg, "day_score": day_score_avg}) "new_articles": new_articles_avg, "unique_editors": unique_contributors_avg, "day_score": day_score_avg})
storage["daily_overview"]["days_tracked"] += 1 storage["daily_overview"]["days_tracked"] += 1
misc.save_datafile(storage) datafile.save_datafile()
return edits, files, admin, changed_bytes, new_articles, unique_contributors, day_score return edits, files, admin, changed_bytes, new_articles, unique_contributors, day_score
def day_overview(): def day_overview():
@ -1261,7 +1223,7 @@ class Recent_Changes_Class(object):
if settings["limitrefetch"] != -1 and self.recent_id != self.file_id and self.recent_id != 0: # if saving to database is disabled, don't save the recent_id if settings["limitrefetch"] != -1 and self.recent_id != self.file_id and self.recent_id != 0: # if saving to database is disabled, don't save the recent_id
self.file_id = self.recent_id self.file_id = self.recent_id
storage["rcid"] = self.recent_id storage["rcid"] = self.recent_id
misc.save_datafile(storage) datafile.save_datafile()
logger.debug("Most recent rcid is: {}".format(self.recent_id)) logger.debug("Most recent rcid is: {}".format(self.recent_id))
return self.recent_id return self.recent_id
@ -1453,7 +1415,6 @@ else:
sys.exit(1) sys.exit(1)
# Log in and download wiki information # Log in and download wiki information
prepare_paths()
try: try:
if settings["wiki_bot_login"] and settings["wiki_bot_password"]: if settings["wiki_bot_login"] and settings["wiki_bot_password"]:
recent_changes.log_in() recent_changes.log_in()
@ -1468,6 +1429,7 @@ recent_changes.fetch(amount=settings["limitrefetch"] if settings["limitrefetch"]
schedule.every(settings["cooldown"]).seconds.do(recent_changes.fetch) schedule.every(settings["cooldown"]).seconds.do(recent_changes.fetch)
if 1 == 2: # additional translation strings in unreachable code if 1 == 2: # additional translation strings in unreachable code
# noinspection PyUnreachableCode
print(_("director"), _("bot"), _("editor"), _("directors"), _("sysop"), _("bureaucrat"), _("reviewer"), print(_("director"), _("bot"), _("editor"), _("directors"), _("sysop"), _("bureaucrat"), _("reviewer"),
_("autoreview"), _("autopatrol"), _("wiki_guardian"), ngettext("second", "seconds", 1), ngettext("minute", "minutes", 1), ngettext("hour", "hours", 1), ngettext("day", "days", 1), ngettext("week", "weeks", 1), ngettext("month", "months",1), ngettext("year", "years", 1), ngettext("millennium", "millennia", 1), ngettext("decade", "decades", 1), ngettext("century", "centuries", 1)) _("autoreview"), _("autopatrol"), _("wiki_guardian"), ngettext("second", "seconds", 1), ngettext("minute", "minutes", 1), ngettext("hour", "hours", 1), ngettext("day", "days", 1), ngettext("week", "weeks", 1), ngettext("month", "months",1), ngettext("year", "years", 1), ngettext("millennium", "millennia", 1), ngettext("decade", "decades", 1), ngettext("century", "centuries", 1))