commit 6ec797a387755a457e62064a1609daeaa9c0786f Author: mctaylors Date: Sun Mar 2 00:57:07 2025 +0300 Initial commit Signed-off-by: mctaylors diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a56dfdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +.venv/ +build/ +dist/ +__pycache__/ +*.spec \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/commands.py b/commands.py new file mode 100644 index 0000000..872c8df --- /dev/null +++ b/commands.py @@ -0,0 +1,46 @@ +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram.constants import ParseMode +from telegram.ext import ContextTypes + + +async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.message.reply_text( + f"hello, i'm {context.bot.first_name}, an inline image grabber.\n\n" + f"to get help, use /help", + parse_mode=ParseMode.HTML + ) + + +async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + await update.message.reply_text( + f"how to use {context.bot.first_name}\n\n" + f"1. open your message box and type:\n" + f"@{context.bot.username} <post ID>\n" + f"2. click on the box that has popped up\n" + f"3. ???\n" + f"4. done!", + parse_mode=ParseMode.HTML + ) + + +async def about_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + from main import config + reply_markup = None + source_url = config.get('Source', 'Url') + if source_url.startswith('http://') or source_url.startswith('https://'): + keyboard = [ + [ + InlineKeyboardButton(f"source code", url=source_url) + ] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + f"about {context.bot.first_name}\n" + f"{context.bot.first_name} is an inline image grabber written in python that grabs images from Danbooru " + f"(or other similar services).\n\n" + f"currently configured instance:\n" + f"{config.get('General', 'Name')} ({config.get('General', 'Domain')})", + parse_mode=ParseMode.HTML, + reply_markup=reply_markup + ) diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..ae34941 --- /dev/null +++ b/config.ini @@ -0,0 +1,6 @@ +[General] +Name = Danbooru +Domain = danbooru.donmai.us + +[Source] +Url = \ No newline at end of file diff --git a/extensions.py b/extensions.py new file mode 100644 index 0000000..9b68c40 --- /dev/null +++ b/extensions.py @@ -0,0 +1,14 @@ +import re + + +def humanize_tags_from_json(value, default): + if value != "": + output = str() + tags = value.split() + + for t in tags: + output += f"{re.sub('_\\(.*', '', t)}, " + output = output[:-2] + + return output + return default diff --git a/inline_query.py b/inline_query.py new file mode 100644 index 0000000..4e376f6 --- /dev/null +++ b/inline_query.py @@ -0,0 +1,78 @@ +from uuid import uuid4 + +import requests +from telegram import Update, InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent, \ + InlineKeyboardMarkup +from telegram.constants import ParseMode +from telegram.ext import ContextTypes + +from extensions import humanize_tags_from_json + + +# noinspection PyUnusedLocal +async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + query = update.inline_query.query + + if not query: + return + + if not query.isdigit(): + return + + from main import config + response = requests.get(f"https://{config.get('General', 'Domain')}/posts/{query}.json") + + if response.status_code != 200: + await invalid_query(update, query) + return + + data = response.json() + + await answer_query(update, query, config, data) + + +async def answer_query(update, query, config, data): + characters = humanize_tags_from_json(data['tag_string_character'], "no characters") + copyrights = humanize_tags_from_json(data['tag_string_copyright'], "unknown copyright") + artists = humanize_tags_from_json(data['tag_string_artist'], "unknown artist") + keyboard = [ + [ + InlineKeyboardButton(f"Open in {config.get('General', 'Name')}", + url=f"https://{config.get('General', 'Domain')}/posts/{query}"), + InlineKeyboardButton("View original", url=data['file_url']) + ] + ] + + results = [ + InlineQueryResultArticle( + id=str(uuid4()), + title=f"ID: {query}", + description=f"{characters} ({copyrights}) drawn by {artists}", + thumbnail_url=data['preview_file_url'], + input_message_content=InputTextMessageContent( + f"ID: {query}\n" + f"{characters} ({copyrights}) " + f"drawn by {artists}", + parse_mode=ParseMode.HTML + ), + reply_markup=InlineKeyboardMarkup(keyboard) + ) + ] + + await update.inline_query.answer(results) + + +async def invalid_query(update, query): + results = [ + InlineQueryResultArticle( + id=str(uuid4()), + title=f"ID: {query}", + description="not found.", + input_message_content=InputTextMessageContent( + f"ID: {query}\nrequested post does not exist.", + parse_mode=ParseMode.HTML + ) + ) + ] + + await update.inline_query.answer(results) diff --git a/main.py b/main.py new file mode 100644 index 0000000..fe4e817 --- /dev/null +++ b/main.py @@ -0,0 +1,48 @@ +import configparser +import logging +import os + +import requests +from telegram.ext import Application, CommandHandler, InlineQueryHandler + +from commands import * +from inline_query import inline_query + +logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO +) +# set higher logging level for httpx to avoid all GET and POST requests being logged +logging.getLogger("httpx").setLevel(logging.WARNING) + +logger = logging.getLogger(__name__) + +config = configparser.ConfigParser() +config.read('config.ini') + + +def main() -> None: + validate_config() + application = Application.builder().token(os.getenv("BOT_TOKEN")).build() + + application.add_handler(CommandHandler("start", start_command)) + application.add_handler(CommandHandler("help", help_command)) + application.add_handler(CommandHandler("about", about_command)) + + application.add_handler(InlineQueryHandler(inline_query)) + + application.run_polling(allowed_updates=Update.ALL_TYPES) + + +def validate_config(): + # noinspection PyBroadException + try: + response = requests.get(f"https://{config.get('General', 'Domain')}/profile.json") + if response.status_code != 200: + raise + except: + print('Unable validate Domain in config.ini.') + exit(1) + + +if __name__ == "__main__": + main()