diff --git a/.gitignore b/.gitignore index a56dfdd..309f911 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ build/ dist/ __pycache__/ -*.spec \ No newline at end of file +*.spec +config.ini \ No newline at end of file diff --git a/commands.py b/commands.py index 3ea3f17..132df5d 100644 --- a/commands.py +++ b/commands.py @@ -8,8 +8,8 @@ from telegram.constants import ParseMode from telegram.ext import ContextTypes import html_parser +import config from datetime import datetime, timedelta -from config import general, app from extensions import get_json, format_rating, format_status, humanize_filesize @@ -43,8 +43,8 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No async def about_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: reply_markup = None - if general.sourceurl is not None: - keyboard = [[InlineKeyboardButton(f"source code", url=general.sourceurl)]] + if config.general.sourceurl is not None: + keyboard = [[InlineKeyboardButton(f"source code", url=config.general.sourceurl)]] reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text( @@ -54,7 +54,7 @@ async def about_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N f"{context.bot.first_name} is an inline image grabber written in python that grabs images from " "Danbooru (or other similar services).\n", html_parser.bold("currently configured instance:"), - f"{html_parser.italic(app.name)} ({app.hostname})", + f"{html_parser.italic(config.app.name)} ({config.app.hostname})", ] ), parse_mode=ParseMode.HTML, @@ -85,8 +85,8 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No keyboard = [ [ InlineKeyboardButton( - f"Open in {app.name}", - url=f"{app.protocol}://{app.hostname}/posts/{post_id}", + f"Open in {config.app.name}", + url=f"{config.app.protocol}://{config.app.hostname}/posts/{post_id}", ) ] ] @@ -99,11 +99,11 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Uploader:", html_parser.hyperlink( uploader_data["name"], - f"{app.protocol}://{app.hostname}/users/{post_data['uploader_id']}", + f"{config.app.protocol}://{config.app.hostname}/users/{post_data['uploader_id']}", ), html_parser.hyperlink( "»", - f"{app.protocol}://{app.hostname}/posts?tags=user:{uploader_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=user:{uploader_data['name']}", ), ] ), @@ -115,7 +115,7 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Date:", html_parser.hyperlink( created_at.strftime("%Y-%m-%d %X (%z)"), - f"{app.protocol}://{app.hostname}/posts?tags=date:{created_at.strftime("%Y-%m-%d")}", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=date:{created_at.strftime("%Y-%m-%d")}", ), ] ) @@ -128,11 +128,11 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Approver:", html_parser.hyperlink( approver_data["name"], - f"{app.protocol}://{app.hostname}/users/{post_data['approver_id']}", + f"{config.app.protocol}://{config.app.hostname}/users/{post_data['approver_id']}", ), html_parser.hyperlink( "»", - f"{app.protocol}://{app.hostname}/posts?tags=approver:{approver_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=approver:{approver_data['name']}", ), ] ) @@ -148,7 +148,7 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']})", html_parser.hyperlink( "»", - f"{app.protocol}://{app.hostname}/media_assets/{post_data['media_asset']['id']}", + f"{config.app.protocol}://{config.app.hostname}/media_assets/{post_data['media_asset']['id']}", ), ] ) @@ -165,7 +165,7 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Score:", html_parser.hyperlink( post_data["score"], - f"{app.protocol}://{app.hostname}/post_votes?search[post_id]={post_data['id']}&variant=compact", + f"{config.app.protocol}://{config.app.hostname}/post_votes?search[post_id]={post_data['id']}&variant=compact", ), f"(+{post_data['up_score']} / -{post_data['down_score']})", ] @@ -177,7 +177,7 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Favorites:", html_parser.hyperlink( post_data["fav_count"], - f"{app.protocol}://{app.hostname}/posts/{post_data['id']}/favorites", + f"{config.app.protocol}://{config.app.hostname}/posts/{post_data['id']}/favorites", ), ] ) @@ -228,8 +228,8 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No keyboard = [ [ InlineKeyboardButton( - f"Open in {app.name}", - url=f"{app.protocol}://{app.hostname}/users/{user_id}", + f"Open in {config.app.name}", + url=f"{config.app.protocol}://{config.app.hostname}/users/{user_id}", ) ] ] @@ -245,7 +245,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Uploads:", html_parser.hyperlink( user_data["post_upload_count"], - f"{app.protocol}://{app.hostname}/posts?tags=user:{user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=user:{user_data['name']}", ), ] ) @@ -270,7 +270,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Favorites:", html_parser.hyperlink( fav_post_count, - f"{app.protocol}://{app.hostname}/posts?tags=ordfav:{user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=ordfav:{user_data['name']}", ), ] ) @@ -288,16 +288,16 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Post Votes:", html_parser.hyperlink( vote_post_count, - f"{app.protocol}://{app.hostname}/post_votes?search[user_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/post_votes?search[user_name]={user_data['name']}", ), f"({' '.join([ html_parser.hyperlink( f"up:{upvote_post_count}", - f"{app.protocol}://{app.hostname}/post_votes?search[user_name]={user_data['name']}&search[score]=1", + f"{config.app.protocol}://{config.app.hostname}/post_votes?search[user_name]={user_data['name']}&search[score]=1", ), html_parser.hyperlink( f"down:{downvote_post_count}", - f"{app.protocol}://{app.hostname}/post_votes?search[user_name]={user_data['name']}&search[score]=-1", + f"{config.app.protocol}://{config.app.hostname}/post_votes?search[user_name]={user_data['name']}&search[score]=-1", ), ])})", ] @@ -309,7 +309,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Favorite Groups:", html_parser.hyperlink( user_data["favorite_group_count"], - f"{app.protocol}://{app.hostname}/favorite_groups?search[creator_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/favorite_groups?search[creator_name]={user_data['name']}", ), ] ) @@ -320,7 +320,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Post Changes:", html_parser.hyperlink( user_data["post_update_count"], - f"{app.protocol}://{app.hostname}/post_versions?search[updater_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/post_versions?search[updater_name]={user_data['name']}", ), ] ) @@ -334,12 +334,12 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Note Changes:", html_parser.hyperlink( user_data["note_update_count"], - f"{app.protocol}://{app.hostname}/note_versions?search[updater_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/note_versions?search[updater_name]={user_data['name']}", ), "in", html_parser.hyperlink( noteupdater_post_count, - f"{app.protocol}://{app.hostname}/posts?tags=noteupdater:{user_data['name']}+order:note", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=noteupdater:{user_data['name']}+order:note", ), "posts", ] @@ -351,7 +351,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Wiki Page Changes:", html_parser.hyperlink( user_data["wiki_page_version_count"], - f"{app.protocol}://{app.hostname}/wiki_page_versions?search[updater_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/wiki_page_versions?search[updater_name]={user_data['name']}", ), ] ) @@ -362,7 +362,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Artist Changes:", html_parser.hyperlink( user_data["artist_version_count"], - f"{app.protocol}://{app.hostname}/artist_versions?search[updater_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/artist_versions?search[updater_name]={user_data['name']}", ), ] ) @@ -373,7 +373,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Commentary Changes:", html_parser.hyperlink( user_data["artist_commentary_version_count"], - f"{app.protocol}://{app.hostname}/artist_commentary_versions?search[updater_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/artist_commentary_versions?search[updater_name]={user_data['name']}", ), ] ) @@ -384,7 +384,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Forum Posts:", html_parser.hyperlink( user_data["forum_post_count"], - f"{app.protocol}://{app.hostname}/forum_posts?search[creator_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/forum_posts?search[creator_name]={user_data['name']}", ), ] ) @@ -398,12 +398,12 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Comments:", html_parser.hyperlink( user_data["comment_count"], - f"{app.protocol}://{app.hostname}/comments?group_by=comment&search[creator_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/comments?group_by=comment&search[creator_name]={user_data['name']}", ), "in", html_parser.hyperlink( commenter_post_count, - f"{app.protocol}://{app.hostname}/posts?tags=commenter:{user_data['name']}+order:comment_bumped", + f"{config.app.protocol}://{config.app.hostname}/posts?tags=commenter:{user_data['name']}+order:comment_bumped", ), "posts", ] @@ -415,7 +415,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Appeals:", html_parser.hyperlink( user_data["appeal_count"], - f"{app.protocol}://{app.hostname}/post_appeals?search[creator_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/post_appeals?search[creator_name]={user_data['name']}", ), ] ) @@ -431,20 +431,20 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "Feedback:", html_parser.hyperlink( total_feedback_count, - f"{app.protocol}://{app.hostname}/user_feedbacks?search[user_name]={user_data['name']}", + f"{config.app.protocol}://{config.app.hostname}/user_feedbacks?search[user_name]={user_data['name']}", ), f"({' '.join([ html_parser.hyperlink( f"positive:{user_data['positive_feedback_count']}", - f"{app.protocol}://{app.hostname}/user_feedbacks?search[user_name]={user_data['name']}&search[category]=positive", + f"{config.app.protocol}://{config.app.hostname}/user_feedbacks?search[user_name]={user_data['name']}&search[category]=positive", ), html_parser.hyperlink( f"neutral:{user_data['neutral_feedback_count']}", - f"{app.protocol}://{app.hostname}/user_feedbacks?search[user_name]={user_data['name']}&search[category]=neutral", + f"{config.app.protocol}://{config.app.hostname}/user_feedbacks?search[user_name]={user_data['name']}&search[category]=neutral", ), html_parser.hyperlink( f"negative:{user_data['negative_feedback_count']}", - f"{app.protocol}://{app.hostname}/user_feedbacks?search[user_name]={user_data['name']}&search[category]=negative", + f"{config.app.protocol}://{config.app.hostname}/user_feedbacks?search[user_name]={user_data['name']}&search[category]=negative", ), ])})" ] diff --git a/config.ini b/config.ini deleted file mode 100644 index 44b06d3..0000000 --- a/config.ini +++ /dev/null @@ -1,21 +0,0 @@ -[General] -; HTTP API token of Telegram bot. -; Can also be set with the BOT_TOKEN environment variable. -Token = -; (Optional) URL for "source code" button in /about. -; If not set, button will not appear in "about" menu. -SourceUrl = git.mctaylors.ru - -[App] -; Application name (e.g. Danbooru). -Name = Danbooru -; Host (without http://) used to access the application API. -Host = danbooru.donmai.us -; (Optional) Public hostname (without http://) for example, the “Open in [App.Name]” button. -; Useful if the bot is hosted on the same server as the application. -; If not set, [App.Host] will be used instead. -HostName = -; Prefer the secure HTTPS protocol for generating links that use a public hostname. -; If True, bot will generate HTTPS links. -; If False, bot will generate HTTP links. -PreferSecureProtocol = True \ No newline at end of file diff --git a/config.py b/config.py index 4351bda..4c5d2f1 100644 --- a/config.py +++ b/config.py @@ -1,10 +1,72 @@ import configparser +from os import path from typing import Optional import requests +config_file = "config.ini" + _c = configparser.ConfigParser() -_c.read("config.ini", "utf-8") +_c.optionxform = str + + +def init_config() -> None: + if _validate_config() is False: + print("Config is invalid.") + exit(1) + _c.read("config.ini", "utf-8") + global general, app + general = _General() + app = _App() + + +def _validate_config() -> bool: + default_config = { + "General": {"Token": "", "SourceUrl": ""}, + "App": { + "Name": "Danbooru", + "Host": "danbooru.donmai.us", + "HostName": "", + "PreferSecureProtocol": True, + }, + } + + if not path.exists(config_file): + for section, options in default_config.items(): + _c[section] = options + with open(config_file, "w") as cf: + _c.write(cf) + print(f"{config_file} generated successfully.") + else: + _c.read(config_file, "utf-8") + + is_valid = True + for section, options in default_config.items(): + if not _c.has_section(section): + print(f"Missing section: [{section}]") + is_valid = False + for option in options: + if not _c.has_option(section, option): + print(f"Missing option: '{option}' in section [{section}]") + is_valid = False + if not is_valid: + return False + + if _c.get("App", "Name") == "": + print(f"Empty option: 'Name' in section [App]") + return False + + try: + response = requests.get( + f"http://{_c.get("App", "Host")}/status.json", timeout=10 + ) + if response.status_code != 200: + raise requests.exceptions.InvalidURL + except requests.exceptions.RequestException: + print(f"Unable to validate: 'Host' in section [App]") + return False + + return True class _General: @@ -28,13 +90,6 @@ class _App: def __init__(self): self.name = _c.get("App", "Name") self.host = _c.get("App", "Host") - try: - response = requests.get(f"http://{self.host}/status.json", timeout=10) - if response.status_code != 200: - raise requests.exceptions.InvalidURL - except requests.exceptions.RequestException: - print("Unable validate App.Host in config.ini.") - exit(1) self.hostname = _c.get("App", "HostName") if not self.hostname: self.hostname = self.host @@ -43,5 +98,5 @@ class _App: self.protocol = "https" -general = _General() -app = _App() +general = None +app = None diff --git a/extensions.py b/extensions.py index ace09a0..350e813 100644 --- a/extensions.py +++ b/extensions.py @@ -2,7 +2,7 @@ import re import requests from typing import Any, Optional -from config import app +import config def humanize_tags_from_json(value: str, default: str) -> str: @@ -58,7 +58,7 @@ def format_status(data) -> str: def get_json(pathname: str, query: Optional[list] = None) -> Any | None: - url = [f"http://{app.host}/{pathname}.json"] + url = [f"http://{config.app.host}/{pathname}.json"] if query is not None: url.append("?") url.append("&".join(query)) diff --git a/inline_query.py b/inline_query.py index eaacfb4..9395db4 100644 --- a/inline_query.py +++ b/inline_query.py @@ -13,7 +13,7 @@ from telegram.constants import ParseMode from telegram.ext import ContextTypes import html_parser -from config import app +import config from extensions import humanize_tags_from_json, format_rating, get_json @@ -111,7 +111,7 @@ async def answer_query( html_parser.bold(artists), ] ), - f"{app.protocol}://{app.hostname}/posts/{query}", + f"{config.app.protocol}://{config.app.hostname}/posts/{query}", ), rating, ] diff --git a/main.py b/main.py index acce5d9..3c64cfa 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,9 @@ import logging import os +import config from telegram.ext import CommandHandler, InlineQueryHandler, ApplicationBuilder -from config import general - logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO ) @@ -15,6 +14,8 @@ logger = logging.getLogger(__name__) def main() -> None: + config.init_config() + application = ApplicationBuilder().token(get_token()).build() import commands @@ -39,11 +40,11 @@ def get_token() -> str: if os.getenv("BOT_TOKEN") is not None: return os.getenv("BOT_TOKEN") - if general.token != "": - return general.token + if config.general.token != "": + return config.general.token print( - "Set BOT_TOKEN environment variable or use General.Token in config.ini to set bot token." + f"Set BOT_TOKEN environment variable or use 'Token' option in section [General] to set bot token." ) exit(1)