From 58d7050dc3c67a642ebc158819efb1ba19b39ef3 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Fri, 16 May 2025 23:36:49 +0300 Subject: [PATCH 01/22] pip: add requirements.txt --- requirements.txt | Bin 0 -> 446 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..29bdc4d881f0eac7284019564fa8abbdb0b9a220 GIT binary patch literal 446 zcmezWFOeaSp^_n!A)mpP!Ir^b zLk2^zsRj&s42BG5aCsHgzTf%F?OSb$9d$!0R7 zFyt{Lg5^MV8bbY3z);Ch!jQp`&ydHU%TU6Q%86}0{{oZ BLDv8P literal 0 HcmV?d00001 From 3518f4bbf025d5fa5f267c75b3ca5fd90773d761 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Fri, 16 May 2025 23:53:11 +0300 Subject: [PATCH 02/22] chore: remove unused imports --- inline_query.py | 1 - 1 file changed, 1 deletion(-) diff --git a/inline_query.py b/inline_query.py index d308db3..a9fba1c 100644 --- a/inline_query.py +++ b/inline_query.py @@ -1,6 +1,5 @@ from uuid import uuid4 -import requests from telegram import Update, InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent, \ InlineKeyboardMarkup from telegram.constants import ParseMode From 8909210e2e12561340651e2d2c6c701da6443b6b Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sat, 17 May 2025 00:05:45 +0300 Subject: [PATCH 03/22] fix: add is_banned check --- inline_query.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inline_query.py b/inline_query.py index a9fba1c..6dd8024 100644 --- a/inline_query.py +++ b/inline_query.py @@ -35,6 +35,25 @@ async def answer_query(update: Update, query: str, data) -> None: copyrights = humanize_tags_from_json(data['tag_string_copyright'], "unknown copyright") artists = humanize_tags_from_json(data['tag_string_artist'], "unknown artist") rating = format_rating(data['rating']) + + if data['is_banned']: + results = [ + InlineQueryResultArticle( + id=str(uuid4()), + title=f"ID: {query}", + description=f"{characters} ({copyrights}) drawn by {artists}", + input_message_content=InputTextMessageContent( + f"ID: {query} {rating}\n" + f"{characters} ({copyrights}) drawn by {artists}\n" + f"This post has been removed because of a takedown request or rule violation.", + parse_mode=ParseMode.HTML + ) + ) + ] + + await update.inline_query.answer(results) + return + keyboard = [ [ InlineKeyboardButton(f"Open in {app.name}", From bd39df5368f6d2a2f4543f2bd878e278df01dc06 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sat, 17 May 2025 00:07:30 +0300 Subject: [PATCH 04/22] i18n: use strings from Danbooru --- inline_query.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inline_query.py b/inline_query.py index 6dd8024..3e625a1 100644 --- a/inline_query.py +++ b/inline_query.py @@ -86,9 +86,10 @@ async def invalid_query(update: Update, query: str) -> None: InlineQueryResultArticle( id=str(uuid4()), title=f"ID: {query}", - description="not found.", + description="Error", input_message_content=InputTextMessageContent( - f"ID: {query}\nrequested post does not exist.", + f"ID: {query}\n" + f"That record was not found.", parse_mode=ParseMode.HTML ) ) From 93e3ca4f96740466842be8a66aa880b7603297a0 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sat, 17 May 2025 23:23:14 +0300 Subject: [PATCH 05/22] refactor: define parameter types *again* --- extensions.py | 4 ++-- main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions.py b/extensions.py index c6ac510..1ffcc22 100644 --- a/extensions.py +++ b/extensions.py @@ -1,7 +1,7 @@ import re -def humanize_tags_from_json(value: str, default: str): +def humanize_tags_from_json(value: str, default: str) -> str: if value != "": output = str() tags = value.split() @@ -14,7 +14,7 @@ def humanize_tags_from_json(value: str, default: str): return default -def format_rating(value: str): +def format_rating(value: str) -> str | None: match value: case "g": # Negative Squared Latin Capital Letter G diff --git a/main.py b/main.py index 4f19837..5bd891a 100644 --- a/main.py +++ b/main.py @@ -28,7 +28,7 @@ def main() -> None: application.run_polling(allowed_updates=commands.Update.ALL_TYPES) -def get_token(): +def get_token() -> None: if os.getenv("BOT_TOKEN") is not None: return os.getenv("BOT_TOKEN") From 89f5b35395140acaba077a01a931b111272ab80d Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 01:44:46 +0300 Subject: [PATCH 06/22] feat: add /info command, pt. 1 --- commands.py | 43 +++++++++++++++++++++++++++++++++++++++++++ extensions.py | 32 ++++++++++++++++++++++++++++++++ inline_query.py | 9 +++------ main.py | 1 + 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/commands.py b/commands.py index 5b001c8..7b27b25 100644 --- a/commands.py +++ b/commands.py @@ -4,6 +4,7 @@ from telegram.ext import ContextTypes import html_parser from config import * +from extensions import get_json, format_rating, format_status async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: @@ -45,3 +46,45 @@ async def about_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N parse_mode=ParseMode.HTML, reply_markup=reply_markup ) + + +async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + try: + post_id = context.args[0] + message = await context.bot.send_message(update.effective_chat.id, + f"{html_parser.bold("Information")}\n" + f"Fetching...", + parse_mode=ParseMode.HTML) + post_data = get_json(f"posts/{post_id}") + if post_data is None: + await update.message.reply_text( + f"{html_parser.bold("Error")}: That record was not found.", + parse_mode=ParseMode.HTML) + return + uploader_data = get_json(f"users/{post_data['uploader_id']}") + # well, we could check the uploader, but why would we do that? + + await context.bot.edit_message_text( + f"{html_parser.bold("Information")}\n" + f"ID: {html_parser.code(post_data['id'])}\n" + f"Uploader: {html_parser.hyperlink(uploader_data['name'], + f"http://{app.host}/users/{post_data['uploader_id']}")} " + f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=user:{uploader_data['name']}")}\n" + f"Date: {post_data['created_at']}\n" + f"Size: {post_data['media_asset']['file_size']} .{post_data['media_asset']['file_ext']} " + f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']}) " + f"{html_parser.hyperlink("»", f"http://{app.host}/media_assets/{post_data['media_asset']['id']}")}\n" + f"Source: {post_data['source']}\n" + f"Rating: {format_rating(post_data['rating'])}\n" + f"Score: {html_parser.hyperlink(post_data['score'], + f"http://{app.host}/post_votes?search[post_id]={post_data['id']}&variant=compact")} " + f"(+{post_data['up_score']} / -{post_data['down_score']})\n" + f"Favorites: {html_parser.hyperlink(post_data['fav_count'], + f"http://{app.host}/posts/{post_data['id']}/favorites")}\n" + f"Status: {format_status(post_data)}", + update.effective_chat.id, message.message_id, + parse_mode=ParseMode.HTML) + except (IndexError, ValueError): + await update.message.reply_text( + f"{html_parser.bold("Usage")}: {html_parser.code(f"/info <post ID>")}", + parse_mode=ParseMode.HTML) diff --git a/extensions.py b/extensions.py index 1ffcc22..baca79d 100644 --- a/extensions.py +++ b/extensions.py @@ -1,4 +1,8 @@ import re +import requests + +from typing import Any +from config import app def humanize_tags_from_json(value: str, default: str) -> str: @@ -28,3 +32,31 @@ def format_rating(value: str) -> str | None: case "e": # Negative Squared Latin Capital Letter E return "🅴" + + +def format_status(data) -> str: + is_active = True + is_pending = data['is_pending'] + is_flagged = data['is_flagged'] + is_deleted = data['is_deleted'] + is_banned = data['is_banned'] + if is_pending | is_flagged | is_deleted: + is_active = False + + status = [] + if is_active: + status.append("Active") + if is_pending: + status.append("Pending") + if is_flagged: + status.append("Flagged") + if is_banned: + status.append("Banned") + return " ".join(status) + +def get_json(pathname: str) -> Any | None: + r = requests.get(f"http://{app.host}/{pathname}.json") + + if r.status_code != 200: + return None + return r.json() diff --git a/inline_query.py b/inline_query.py index 3e625a1..c90425b 100644 --- a/inline_query.py +++ b/inline_query.py @@ -6,7 +6,7 @@ from telegram.constants import ParseMode from telegram.ext import ContextTypes from config import * -from extensions import humanize_tags_from_json, format_rating +from extensions import humanize_tags_from_json, format_rating, get_json # noinspection PyUnusedLocal @@ -19,14 +19,11 @@ async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No if not query.isdigit(): return - response = requests.get(f"http://{app.host}/posts/{query}.json") - - if response.status_code != 200: + data = get_json(f"posts/{query}") + if data is None: await invalid_query(update, query) return - data = response.json() - await answer_query(update, query, data) diff --git a/main.py b/main.py index 5bd891a..3bc63d2 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,7 @@ def main() -> None: application.add_handler(CommandHandler("start", commands.start_command)) application.add_handler(CommandHandler("help", commands.help_command)) application.add_handler(CommandHandler("about", commands.about_command)) + application.add_handler(CommandHandler("info", commands.info_command)) from inline_query import inline_query application.add_handler(InlineQueryHandler(inline_query)) From 35ea45d9c729ec74d2ead870bce837605e737a0c Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 10:12:35 +0300 Subject: [PATCH 07/22] fix: set encoding for _c.read Signed-off-by: mctaylors --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 0d97476..c6c1a8b 100644 --- a/config.py +++ b/config.py @@ -4,7 +4,7 @@ from typing import Optional import requests _c = configparser.ConfigParser() -_c.read("config.ini") +_c.read("config.ini", "utf-8") class _General: From f2b6a2ee8f01cf7cda5e3f892863e05e4e464ec8 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 10:30:53 +0300 Subject: [PATCH 08/22] chore: make return None statement explicit in format_rating Signed-off-by: mctaylors --- extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions.py b/extensions.py index 1ffcc22..edc3571 100644 --- a/extensions.py +++ b/extensions.py @@ -28,3 +28,4 @@ def format_rating(value: str) -> str | None: case "e": # Negative Squared Latin Capital Letter E return "🅴" + return None From b4e406d3515e76ed016cea04aee720310ddc9ca7 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 10:54:44 +0300 Subject: [PATCH 09/22] feat: add /info command, pt. 2 Signed-off-by: mctaylors --- commands.py | 58 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/commands.py b/commands.py index 7b27b25..c5ea1c2 100644 --- a/commands.py +++ b/commands.py @@ -1,4 +1,4 @@ -from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, LinkPreviewOptions from telegram.constants import ParseMode from telegram.ext import ContextTypes @@ -64,26 +64,46 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No uploader_data = get_json(f"users/{post_data['uploader_id']}") # well, we could check the uploader, but why would we do that? + keyboard = [ + [ + InlineKeyboardButton(f"Open in {app.name}", + url=f"https://{app.hostname}/posts/{post_id}") + ] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + # noinspection PyListCreation + m = [] + m.append(f"ID: {html_parser.code(post_data['id'])}") + m.append(f"Uploader: {html_parser.hyperlink(uploader_data['name'], + f"http://{app.host}/users/{post_data['uploader_id']}")} " + f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=user:{uploader_data['name']}")}") + m.append(f"Date: {post_data['created_at']}") + if post_data['approver_id'] is not None: + approver_data = get_json(f"users/{post_data['approver_id']}") + m.append(f"Approver: {html_parser.hyperlink(approver_data['name'], + f"http://{app.host}/users/{post_data['approver_id']}")} " + f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=approver:{approver_data['name']}")}") + m.append(f"Size: {post_data['media_asset']['file_size']} .{post_data['media_asset']['file_ext']} " + f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']}) " + f"{html_parser.hyperlink("»", f"http://{app.host}/media_assets/{post_data['media_asset']['id']}")}") + m.append(f"Source: {post_data['source'] if post_data['source'] != "" else "🚫"}") + m.append(f"Rating: {format_rating(post_data['rating'])}") + m.append(f"Score: {html_parser.hyperlink(post_data['score'], + f"http://{app.host}/post_votes?search[post_id]={post_data['id']}&variant=compact")} " + f"(+{post_data['up_score']} / -{post_data['down_score']})") + m.append(f"Favorites: {html_parser.hyperlink(post_data['fav_count'], + f"http://{app.host}/posts/{post_data['id']}/favorites")}") + m.append(f"Status: {format_status(post_data)}") + + link_preview_options = LinkPreviewOptions(True) + if not post_data['is_banned']: + link_preview_options = LinkPreviewOptions(url=post_data['large_file_url']) + await context.bot.edit_message_text( - f"{html_parser.bold("Information")}\n" - f"ID: {html_parser.code(post_data['id'])}\n" - f"Uploader: {html_parser.hyperlink(uploader_data['name'], - f"http://{app.host}/users/{post_data['uploader_id']}")} " - f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=user:{uploader_data['name']}")}\n" - f"Date: {post_data['created_at']}\n" - f"Size: {post_data['media_asset']['file_size']} .{post_data['media_asset']['file_ext']} " - f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']}) " - f"{html_parser.hyperlink("»", f"http://{app.host}/media_assets/{post_data['media_asset']['id']}")}\n" - f"Source: {post_data['source']}\n" - f"Rating: {format_rating(post_data['rating'])}\n" - f"Score: {html_parser.hyperlink(post_data['score'], - f"http://{app.host}/post_votes?search[post_id]={post_data['id']}&variant=compact")} " - f"(+{post_data['up_score']} / -{post_data['down_score']})\n" - f"Favorites: {html_parser.hyperlink(post_data['fav_count'], - f"http://{app.host}/posts/{post_data['id']}/favorites")}\n" - f"Status: {format_status(post_data)}", + f"{html_parser.bold("Information")}\n" + "\n".join(m), update.effective_chat.id, message.message_id, - parse_mode=ParseMode.HTML) + parse_mode=ParseMode.HTML, reply_markup=reply_markup, link_preview_options=link_preview_options) except (IndexError, ValueError): await update.message.reply_text( f"{html_parser.bold("Usage")}: {html_parser.code(f"/info <post ID>")}", From 84a897868d95f00d4f34c84dbfd2fd92015c761e Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 11:08:09 +0300 Subject: [PATCH 10/22] feat: use LinkPreviewOptions in answer_query Signed-off-by: mctaylors --- inline_query.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inline_query.py b/inline_query.py index 3e625a1..fa2e71a 100644 --- a/inline_query.py +++ b/inline_query.py @@ -1,7 +1,7 @@ from uuid import uuid4 from telegram import Update, InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent, \ - InlineKeyboardMarkup + InlineKeyboardMarkup, LinkPreviewOptions from telegram.constants import ParseMode from telegram.ext import ContextTypes @@ -70,9 +70,9 @@ async def answer_query(update: Update, query: str, data) -> None: thumbnail_url=data['preview_file_url'], input_message_content=InputTextMessageContent( f"ID: {query} {rating}\n" - f"{characters} ({copyrights}) " - f"drawn by {artists}", - parse_mode=ParseMode.HTML + f"{characters} ({copyrights}) drawn by {artists}", + parse_mode=ParseMode.HTML, + link_preview_options=LinkPreviewOptions(url=data['large_file_url']) ), reply_markup=InlineKeyboardMarkup(keyboard) ) From b503234b8570d557fcc5bd223d853de245823861 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Fri, 16 May 2025 23:36:49 +0300 Subject: [PATCH 11/22] pip: add requirements.txt Signed-off-by: mctaylors --- requirements.txt | Bin 0 -> 446 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..29bdc4d881f0eac7284019564fa8abbdb0b9a220 GIT binary patch literal 446 zcmezWFOeaSp^_n!A)mpP!Ir^b zLk2^zsRj&s42BG5aCsHgzTf%F?OSb$9d$!0R7 zFyt{Lg5^MV8bbY3z);Ch!jQp`&ydHU%TU6Q%86}0{{oZ BLDv8P literal 0 HcmV?d00001 From 05cbca750932f391e682ca363b1b32b4d42d5185 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Fri, 16 May 2025 23:53:11 +0300 Subject: [PATCH 12/22] chore: remove unused imports Signed-off-by: mctaylors --- inline_query.py | 1 - 1 file changed, 1 deletion(-) diff --git a/inline_query.py b/inline_query.py index d308db3..a9fba1c 100644 --- a/inline_query.py +++ b/inline_query.py @@ -1,6 +1,5 @@ from uuid import uuid4 -import requests from telegram import Update, InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent, \ InlineKeyboardMarkup from telegram.constants import ParseMode From ff86c78b2177cde512a74498f1128bcf1a3bc51e Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sat, 17 May 2025 00:05:45 +0300 Subject: [PATCH 13/22] fix: add is_banned check Signed-off-by: mctaylors --- inline_query.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inline_query.py b/inline_query.py index a9fba1c..6dd8024 100644 --- a/inline_query.py +++ b/inline_query.py @@ -35,6 +35,25 @@ async def answer_query(update: Update, query: str, data) -> None: copyrights = humanize_tags_from_json(data['tag_string_copyright'], "unknown copyright") artists = humanize_tags_from_json(data['tag_string_artist'], "unknown artist") rating = format_rating(data['rating']) + + if data['is_banned']: + results = [ + InlineQueryResultArticle( + id=str(uuid4()), + title=f"ID: {query}", + description=f"{characters} ({copyrights}) drawn by {artists}", + input_message_content=InputTextMessageContent( + f"ID: {query} {rating}\n" + f"{characters} ({copyrights}) drawn by {artists}\n" + f"This post has been removed because of a takedown request or rule violation.", + parse_mode=ParseMode.HTML + ) + ) + ] + + await update.inline_query.answer(results) + return + keyboard = [ [ InlineKeyboardButton(f"Open in {app.name}", From 7fdf18b5cb5620cbbb74db618629273685bafdf9 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sat, 17 May 2025 00:07:30 +0300 Subject: [PATCH 14/22] i18n: use strings from Danbooru Signed-off-by: mctaylors --- inline_query.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inline_query.py b/inline_query.py index 6dd8024..3e625a1 100644 --- a/inline_query.py +++ b/inline_query.py @@ -86,9 +86,10 @@ async def invalid_query(update: Update, query: str) -> None: InlineQueryResultArticle( id=str(uuid4()), title=f"ID: {query}", - description="not found.", + description="Error", input_message_content=InputTextMessageContent( - f"ID: {query}\nrequested post does not exist.", + f"ID: {query}\n" + f"That record was not found.", parse_mode=ParseMode.HTML ) ) From 0ee018e2ced33f5adf138d3e71679bc036951b89 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sat, 17 May 2025 23:23:14 +0300 Subject: [PATCH 15/22] refactor: define parameter types *again* Signed-off-by: mctaylors --- extensions.py | 4 ++-- main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions.py b/extensions.py index c6ac510..1ffcc22 100644 --- a/extensions.py +++ b/extensions.py @@ -1,7 +1,7 @@ import re -def humanize_tags_from_json(value: str, default: str): +def humanize_tags_from_json(value: str, default: str) -> str: if value != "": output = str() tags = value.split() @@ -14,7 +14,7 @@ def humanize_tags_from_json(value: str, default: str): return default -def format_rating(value: str): +def format_rating(value: str) -> str | None: match value: case "g": # Negative Squared Latin Capital Letter G diff --git a/main.py b/main.py index 4f19837..5bd891a 100644 --- a/main.py +++ b/main.py @@ -28,7 +28,7 @@ def main() -> None: application.run_polling(allowed_updates=commands.Update.ALL_TYPES) -def get_token(): +def get_token() -> None: if os.getenv("BOT_TOKEN") is not None: return os.getenv("BOT_TOKEN") From d646920210540938c7615efdca3061d051c848f8 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 10:12:35 +0300 Subject: [PATCH 16/22] fix: set encoding for _c.read Signed-off-by: mctaylors --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 0d97476..c6c1a8b 100644 --- a/config.py +++ b/config.py @@ -4,7 +4,7 @@ from typing import Optional import requests _c = configparser.ConfigParser() -_c.read("config.ini") +_c.read("config.ini", "utf-8") class _General: From 0cc77dd42c5f97325fedfe71e4d7351a8d2cbfd6 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 10:30:53 +0300 Subject: [PATCH 17/22] change: make return None statement explicit in format_rating Signed-off-by: mctaylors --- extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions.py b/extensions.py index 1ffcc22..edc3571 100644 --- a/extensions.py +++ b/extensions.py @@ -28,3 +28,4 @@ def format_rating(value: str) -> str | None: case "e": # Negative Squared Latin Capital Letter E return "🅴" + return None From df1735ef368ed92aedbe4b999c73a0ba9f57370b Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 11:08:09 +0300 Subject: [PATCH 18/22] feat: use LinkPreviewOptions in answer_query Signed-off-by: mctaylors --- inline_query.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inline_query.py b/inline_query.py index 3e625a1..fa2e71a 100644 --- a/inline_query.py +++ b/inline_query.py @@ -1,7 +1,7 @@ from uuid import uuid4 from telegram import Update, InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent, \ - InlineKeyboardMarkup + InlineKeyboardMarkup, LinkPreviewOptions from telegram.constants import ParseMode from telegram.ext import ContextTypes @@ -70,9 +70,9 @@ async def answer_query(update: Update, query: str, data) -> None: thumbnail_url=data['preview_file_url'], input_message_content=InputTextMessageContent( f"ID: {query} {rating}\n" - f"{characters} ({copyrights}) " - f"drawn by {artists}", - parse_mode=ParseMode.HTML + f"{characters} ({copyrights}) drawn by {artists}", + parse_mode=ParseMode.HTML, + link_preview_options=LinkPreviewOptions(url=data['large_file_url']) ), reply_markup=InlineKeyboardMarkup(keyboard) ) From f07a51f76a8c805ed46a54e6216ff8fb26462819 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 11:26:51 +0300 Subject: [PATCH 19/22] change: make inline query answer more compact Signed-off-by: mctaylors --- inline_query.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/inline_query.py b/inline_query.py index fa2e71a..a3f19c4 100644 --- a/inline_query.py +++ b/inline_query.py @@ -43,8 +43,7 @@ async def answer_query(update: Update, query: str, data) -> None: title=f"ID: {query}", description=f"{characters} ({copyrights}) drawn by {artists}", input_message_content=InputTextMessageContent( - f"ID: {query} {rating}\n" - f"{characters} ({copyrights}) drawn by {artists}\n" + f"#{query} {characters} ({copyrights}) drawn by {artists} {rating}\n" f"This post has been removed because of a takedown request or rule violation.", parse_mode=ParseMode.HTML ) @@ -69,8 +68,7 @@ async def answer_query(update: Update, query: str, data) -> None: description=f"{characters} ({copyrights}) drawn by {artists}", thumbnail_url=data['preview_file_url'], input_message_content=InputTextMessageContent( - f"ID: {query} {rating}\n" - f"{characters} ({copyrights}) drawn by {artists}", + f"#{query} {characters} ({copyrights}) drawn by {artists} {rating}", parse_mode=ParseMode.HTML, link_preview_options=LinkPreviewOptions(url=data['large_file_url']) ), @@ -88,7 +86,7 @@ async def invalid_query(update: Update, query: str) -> None: title=f"ID: {query}", description="Error", input_message_content=InputTextMessageContent( - f"ID: {query}\n" + f"#{query}\n" f"That record was not found.", parse_mode=ParseMode.HTML ) From c70d7fe3bee36b73b9de7ac44ab611d5c269d94e Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 11:41:39 +0300 Subject: [PATCH 20/22] refactor: completely rewrite humanize_tags_from_json Signed-off-by: mctaylors --- extensions.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/extensions.py b/extensions.py index edc3571..49b2d1a 100644 --- a/extensions.py +++ b/extensions.py @@ -3,14 +3,7 @@ import re def humanize_tags_from_json(value: str, default: str) -> str: if value != "": - output = str() - tags = value.split() - - for t in tags: - output += f"{re.sub('_\\(.*', '', t)}, " - output = output[:-2] - - return output + return ", ".join([re.sub('_\\(.*', '', t) for t in value.split()]) return default From b079d56250e376757669922f62dacb9a3ad5aec5 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 01:44:46 +0300 Subject: [PATCH 21/22] feat: add /info command, pt. 1 # Conflicts: # extensions.py --- commands.py | 43 +++++++++++++++++++++++++++++++++++++++++++ extensions.py | 32 ++++++++++++++++++++++++++++++++ inline_query.py | 9 +++------ main.py | 1 + 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/commands.py b/commands.py index 5b001c8..7b27b25 100644 --- a/commands.py +++ b/commands.py @@ -4,6 +4,7 @@ from telegram.ext import ContextTypes import html_parser from config import * +from extensions import get_json, format_rating, format_status async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: @@ -45,3 +46,45 @@ async def about_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N parse_mode=ParseMode.HTML, reply_markup=reply_markup ) + + +async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + try: + post_id = context.args[0] + message = await context.bot.send_message(update.effective_chat.id, + f"{html_parser.bold("Information")}\n" + f"Fetching...", + parse_mode=ParseMode.HTML) + post_data = get_json(f"posts/{post_id}") + if post_data is None: + await update.message.reply_text( + f"{html_parser.bold("Error")}: That record was not found.", + parse_mode=ParseMode.HTML) + return + uploader_data = get_json(f"users/{post_data['uploader_id']}") + # well, we could check the uploader, but why would we do that? + + await context.bot.edit_message_text( + f"{html_parser.bold("Information")}\n" + f"ID: {html_parser.code(post_data['id'])}\n" + f"Uploader: {html_parser.hyperlink(uploader_data['name'], + f"http://{app.host}/users/{post_data['uploader_id']}")} " + f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=user:{uploader_data['name']}")}\n" + f"Date: {post_data['created_at']}\n" + f"Size: {post_data['media_asset']['file_size']} .{post_data['media_asset']['file_ext']} " + f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']}) " + f"{html_parser.hyperlink("»", f"http://{app.host}/media_assets/{post_data['media_asset']['id']}")}\n" + f"Source: {post_data['source']}\n" + f"Rating: {format_rating(post_data['rating'])}\n" + f"Score: {html_parser.hyperlink(post_data['score'], + f"http://{app.host}/post_votes?search[post_id]={post_data['id']}&variant=compact")} " + f"(+{post_data['up_score']} / -{post_data['down_score']})\n" + f"Favorites: {html_parser.hyperlink(post_data['fav_count'], + f"http://{app.host}/posts/{post_data['id']}/favorites")}\n" + f"Status: {format_status(post_data)}", + update.effective_chat.id, message.message_id, + parse_mode=ParseMode.HTML) + except (IndexError, ValueError): + await update.message.reply_text( + f"{html_parser.bold("Usage")}: {html_parser.code(f"/info <post ID>")}", + parse_mode=ParseMode.HTML) diff --git a/extensions.py b/extensions.py index 49b2d1a..15bf219 100644 --- a/extensions.py +++ b/extensions.py @@ -1,4 +1,8 @@ import re +import requests + +from typing import Any +from config import app def humanize_tags_from_json(value: str, default: str) -> str: @@ -22,3 +26,31 @@ def format_rating(value: str) -> str | None: # Negative Squared Latin Capital Letter E return "🅴" return None + + +def format_status(data) -> str: + is_active = True + is_pending = data['is_pending'] + is_flagged = data['is_flagged'] + is_deleted = data['is_deleted'] + is_banned = data['is_banned'] + if is_pending | is_flagged | is_deleted: + is_active = False + + status = [] + if is_active: + status.append("Active") + if is_pending: + status.append("Pending") + if is_flagged: + status.append("Flagged") + if is_banned: + status.append("Banned") + return " ".join(status) + +def get_json(pathname: str) -> Any | None: + r = requests.get(f"http://{app.host}/{pathname}.json") + + if r.status_code != 200: + return None + return r.json() diff --git a/inline_query.py b/inline_query.py index a3f19c4..657e0b9 100644 --- a/inline_query.py +++ b/inline_query.py @@ -6,7 +6,7 @@ from telegram.constants import ParseMode from telegram.ext import ContextTypes from config import * -from extensions import humanize_tags_from_json, format_rating +from extensions import humanize_tags_from_json, format_rating, get_json # noinspection PyUnusedLocal @@ -19,14 +19,11 @@ async def inline_query(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No if not query.isdigit(): return - response = requests.get(f"http://{app.host}/posts/{query}.json") - - if response.status_code != 200: + data = get_json(f"posts/{query}") + if data is None: await invalid_query(update, query) return - data = response.json() - await answer_query(update, query, data) diff --git a/main.py b/main.py index 5bd891a..3bc63d2 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,7 @@ def main() -> None: application.add_handler(CommandHandler("start", commands.start_command)) application.add_handler(CommandHandler("help", commands.help_command)) application.add_handler(CommandHandler("about", commands.about_command)) + application.add_handler(CommandHandler("info", commands.info_command)) from inline_query import inline_query application.add_handler(InlineQueryHandler(inline_query)) From 270e1630d4b502a10d7c045a6e8c315b0afd0051 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 18 May 2025 10:54:44 +0300 Subject: [PATCH 22/22] feat: add /info command, pt. 2 Signed-off-by: mctaylors --- commands.py | 58 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/commands.py b/commands.py index 7b27b25..c5ea1c2 100644 --- a/commands.py +++ b/commands.py @@ -1,4 +1,4 @@ -from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, LinkPreviewOptions from telegram.constants import ParseMode from telegram.ext import ContextTypes @@ -64,26 +64,46 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No uploader_data = get_json(f"users/{post_data['uploader_id']}") # well, we could check the uploader, but why would we do that? + keyboard = [ + [ + InlineKeyboardButton(f"Open in {app.name}", + url=f"https://{app.hostname}/posts/{post_id}") + ] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + # noinspection PyListCreation + m = [] + m.append(f"ID: {html_parser.code(post_data['id'])}") + m.append(f"Uploader: {html_parser.hyperlink(uploader_data['name'], + f"http://{app.host}/users/{post_data['uploader_id']}")} " + f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=user:{uploader_data['name']}")}") + m.append(f"Date: {post_data['created_at']}") + if post_data['approver_id'] is not None: + approver_data = get_json(f"users/{post_data['approver_id']}") + m.append(f"Approver: {html_parser.hyperlink(approver_data['name'], + f"http://{app.host}/users/{post_data['approver_id']}")} " + f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=approver:{approver_data['name']}")}") + m.append(f"Size: {post_data['media_asset']['file_size']} .{post_data['media_asset']['file_ext']} " + f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']}) " + f"{html_parser.hyperlink("»", f"http://{app.host}/media_assets/{post_data['media_asset']['id']}")}") + m.append(f"Source: {post_data['source'] if post_data['source'] != "" else "🚫"}") + m.append(f"Rating: {format_rating(post_data['rating'])}") + m.append(f"Score: {html_parser.hyperlink(post_data['score'], + f"http://{app.host}/post_votes?search[post_id]={post_data['id']}&variant=compact")} " + f"(+{post_data['up_score']} / -{post_data['down_score']})") + m.append(f"Favorites: {html_parser.hyperlink(post_data['fav_count'], + f"http://{app.host}/posts/{post_data['id']}/favorites")}") + m.append(f"Status: {format_status(post_data)}") + + link_preview_options = LinkPreviewOptions(True) + if not post_data['is_banned']: + link_preview_options = LinkPreviewOptions(url=post_data['large_file_url']) + await context.bot.edit_message_text( - f"{html_parser.bold("Information")}\n" - f"ID: {html_parser.code(post_data['id'])}\n" - f"Uploader: {html_parser.hyperlink(uploader_data['name'], - f"http://{app.host}/users/{post_data['uploader_id']}")} " - f"{html_parser.hyperlink("»", f"http://{app.host}/posts?tags=user:{uploader_data['name']}")}\n" - f"Date: {post_data['created_at']}\n" - f"Size: {post_data['media_asset']['file_size']} .{post_data['media_asset']['file_ext']} " - f"({post_data['media_asset']['image_width']}x{post_data['media_asset']['image_height']}) " - f"{html_parser.hyperlink("»", f"http://{app.host}/media_assets/{post_data['media_asset']['id']}")}\n" - f"Source: {post_data['source']}\n" - f"Rating: {format_rating(post_data['rating'])}\n" - f"Score: {html_parser.hyperlink(post_data['score'], - f"http://{app.host}/post_votes?search[post_id]={post_data['id']}&variant=compact")} " - f"(+{post_data['up_score']} / -{post_data['down_score']})\n" - f"Favorites: {html_parser.hyperlink(post_data['fav_count'], - f"http://{app.host}/posts/{post_data['id']}/favorites")}\n" - f"Status: {format_status(post_data)}", + f"{html_parser.bold("Information")}\n" + "\n".join(m), update.effective_chat.id, message.message_id, - parse_mode=ParseMode.HTML) + parse_mode=ParseMode.HTML, reply_markup=reply_markup, link_preview_options=link_preview_options) except (IndexError, ValueError): await update.message.reply_text( f"{html_parser.bold("Usage")}: {html_parser.code(f"/info <post ID>")}",