From 5d4ff660c379a9f22c7592a0865040d09da0c785 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 8 Jun 2025 09:45:25 +0300 Subject: [PATCH 1/5] feat: add /user command Signed-off-by: mctaylors --- commands.py | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++ extensions.py | 10 ++- main.py | 1 + 3 files changed, 218 insertions(+), 3 deletions(-) diff --git a/commands.py b/commands.py index 2fe6886..ed6f98a 100644 --- a/commands.py +++ b/commands.py @@ -203,3 +203,213 @@ async def info_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ), parse_mode=ParseMode.HTML, ) + + +async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + try: + name_matches = context.args[0] + message = await context.bot.send_message( + update.effective_chat.id, + "\n".join([html_parser.bold(name_matches), "Fetching..."]), + parse_mode=ParseMode.HTML, + ) + user_search_data = get_json(f"users", [f"search[name_matches]={name_matches}"]) + if len(user_search_data) == 0: + await context.bot.edit_message_text( + " ".join([html_parser.bold("Error:"), "That record was not found."]), + update.effective_chat.id, + message.message_id, + parse_mode=ParseMode.HTML, + ) + return + user_id = user_search_data[0]["id"] + user_data = get_json(f"users/{user_id}") + + keyboard = [ + [ + InlineKeyboardButton( + f"Open in {app.name}", + url=f"{app.protocol}://{app.hostname}/users/{user_id}", + ) + ] + ] + reply_markup = InlineKeyboardMarkup(keyboard) + + m = [" ".join(["ID:", str(user_data["id"])])] + created_at = datetime.fromisoformat(user_data["created_at"]) + m.append(" ".join(["Join Date:", created_at.strftime("%Y-%m-%d")])) + m.append(" ".join(["Level:", user_data["level_string"]])) + m.append( + " ".join( + [ + "Uploads:", + html_parser.hyperlink( + user_data["post_upload_count"], + f"{app.protocol}://{app.hostname}/posts?tags=user:{user_data['name']}", + ), + ] + ) + ) + fav_post_count = get_json(f"counts/posts", [f"tags=fav:{user_data['name']}"])[ + "counts" + ]["posts"] + m.append( + " ".join( + [ + "Favorites:", + html_parser.hyperlink( + fav_post_count, + f"{app.protocol}://{app.hostname}/posts?tags=ordfav:{user_data['name']}", + ), + ] + ) + ) + m.append( + " ".join( + [ + "Favorite Groups:", + html_parser.hyperlink( + user_data["favorite_group_count"], + f"{app.protocol}://{app.hostname}/favorite_groups?search[creator_name]={user_data['name']}", + ), + ] + ) + ) + m.append( + " ".join( + [ + "Post Changes:", + html_parser.hyperlink( + user_data["post_update_count"], + f"{app.protocol}://{app.hostname}/post_versions?search[updater_name]={user_data['name']}", + ), + ] + ) + ) + noteupdater_post_count = get_json( + f"counts/posts", [f"tags=noteupdater:{user_data['name']}"] + )["counts"]["posts"] + m.append( + " ".join( + [ + "Note Changes:", + html_parser.hyperlink( + user_data["note_update_count"], + f"{app.protocol}://{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", + ), + "posts", + ] + ) + ) + m.append( + " ".join( + [ + "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']}", + ), + ] + ) + ) + m.append( + " ".join( + [ + "Artist Changes:", + html_parser.hyperlink( + user_data["artist_version_count"], + f"{app.protocol}://{app.hostname}/artist_versions?search[updater_name]={user_data['name']}", + ), + ] + ) + ) + m.append( + " ".join( + [ + "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']}", + ), + ] + ) + ) + m.append( + " ".join( + [ + "Forum Posts:", + html_parser.hyperlink( + user_data["forum_post_count"], + f"{app.protocol}://{app.hostname}/forum_posts?search[creator_name]={user_data['name']}", + ), + ] + ) + ) + commenter_post_count = get_json( + f"counts/posts", [f"tags=commenter:{user_data['name']}"] + )["counts"]["posts"] + m.append( + " ".join( + [ + "Comments:", + html_parser.hyperlink( + user_data["comment_count"], + f"{app.protocol}://{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", + ), + "posts", + ] + ) + ) + m.append( + " ".join( + [ + "Appeals:", + html_parser.hyperlink( + user_data["appeal_count"], + f"{app.protocol}://{app.hostname}/post_appeals?search[creator_name]={user_data['name']}", + ), + ] + ) + ) + m.append( + " ".join( + [ + "Feedback:", + html_parser.hyperlink( + " ".join( + [ + f"positive:{user_data['positive_feedback_count']}", + f"neutral:{user_data['neutral_feedback_count']}", + f"negative:{user_data['negative_feedback_count']}", + ] + ), + f"{app.protocol}://{app.hostname}/user_feedbacks?search[user_name]={user_data['name']}", + ), + ] + ) + ) + await context.bot.edit_message_text( + "\n".join([html_parser.bold(user_data["name"])] + m), + update.effective_chat.id, + message.message_id, + parse_mode=ParseMode.HTML, + reply_markup=reply_markup, + disable_web_page_preview=True + ) + except (IndexError, ValueError): + await update.message.reply_text( + " ".join( + [html_parser.bold("Usage:"), html_parser.code("/user <username>")] + ), + parse_mode=ParseMode.HTML, + ) diff --git a/extensions.py b/extensions.py index ddb8a62..ace09a0 100644 --- a/extensions.py +++ b/extensions.py @@ -1,7 +1,7 @@ import re import requests -from typing import Any +from typing import Any, Optional from config import app @@ -57,8 +57,12 @@ def format_status(data) -> str: return " ".join(status) -def get_json(pathname: str) -> Any | None: - r = requests.get(f"http://{app.host}/{pathname}.json") +def get_json(pathname: str, query: Optional[list] = None) -> Any | None: + url = [f"http://{app.host}/{pathname}.json"] + if query is not None: + url.append("?") + url.append("&".join(query)) + r = requests.get("".join(url)) if r.status_code != 200: return None diff --git a/main.py b/main.py index 2cf568d..2779b01 100644 --- a/main.py +++ b/main.py @@ -23,6 +23,7 @@ def main() -> None: application.add_handler(CommandHandler("help", commands.help_command)) application.add_handler(CommandHandler("about", commands.about_command)) application.add_handler(CommandHandler("info", commands.info_command)) + application.add_handler(CommandHandler("user", commands.user_command)) from inline_query import inline_query -- 2.50.0 From 56f1026341aff9d535bf6c1185647850ecc55b9d Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 8 Jun 2025 10:03:40 +0300 Subject: [PATCH 2/5] feat: add ban reason in /user Signed-off-by: mctaylors --- commands.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/commands.py b/commands.py index ed6f98a..d9b01a6 100644 --- a/commands.py +++ b/commands.py @@ -8,7 +8,7 @@ from telegram.constants import ParseMode from telegram.ext import ContextTypes import html_parser -from datetime import datetime +from datetime import datetime, timedelta from config import * from extensions import get_json, format_rating, format_status, humanize_filesize @@ -250,6 +250,19 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ] ) ) + if user_data["is_banned"]: + ban_data = get_json( + f"bans", [f"search[user_id]={user_data['id']}"] + )[0] + m.append( + " ".join( + [ + "Ban reason:", + ban_data["reason"], + f"(banned for {timedelta(seconds=ban_data['duration'])})" + ] + ) + ) fav_post_count = get_json(f"counts/posts", [f"tags=fav:{user_data['name']}"])[ "counts" ]["posts"] -- 2.50.0 From 6b2cf74eaef40b8009129c85d424be2d6c8cee71 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 8 Jun 2025 10:43:42 +0300 Subject: [PATCH 3/5] feat: add avg in feedback, add post votes Signed-off-by: mctaylors --- commands.py | 55 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/commands.py b/commands.py index d9b01a6..c85b51c 100644 --- a/commands.py +++ b/commands.py @@ -251,15 +251,13 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ) ) if user_data["is_banned"]: - ban_data = get_json( - f"bans", [f"search[user_id]={user_data['id']}"] - )[0] + ban_data = get_json(f"bans", [f"search[user_id]={user_data['id']}"])[0] m.append( " ".join( [ "Ban reason:", ban_data["reason"], - f"(banned for {timedelta(seconds=ban_data['duration'])})" + f"(banned for {timedelta(seconds=ban_data['duration'])})", ] ) ) @@ -277,6 +275,32 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ] ) ) + upvote_post_count = get_json( + f"counts/posts", [f"tags=upvote:{user_data['name']}"] + )["counts"]["posts"] + downvote_post_count = get_json( + f"counts/posts", [f"tags=downvote:{user_data['name']}"] + )["counts"]["posts"] + vote_post_count = upvote_post_count + downvote_post_count + m.append( + " ".join( + [ + "Post Votes:", + html_parser.hyperlink( + f"up:{upvote_post_count}", + f"{app.protocol}://{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", + ), + html_parser.hyperlink( + f"total:{vote_post_count}", + f"{app.protocol}://{app.hostname}/post_votes?search[user_name]={user_data['name']}", + ), + ] + ) + ) m.append( " ".join( [ @@ -394,18 +418,25 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ] ) ) + avg_feedback_count = user_data["positive_feedback_count"] - user_data["negative_feedback_count"] m.append( " ".join( [ "Feedback:", html_parser.hyperlink( - " ".join( - [ - f"positive:{user_data['positive_feedback_count']}", - f"neutral:{user_data['neutral_feedback_count']}", - f"negative:{user_data['negative_feedback_count']}", - ] - ), + f"positive:{user_data['positive_feedback_count']}", + f"{app.protocol}://{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", + ), + 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", + ), + html_parser.hyperlink( + f"avg:{avg_feedback_count}", f"{app.protocol}://{app.hostname}/user_feedbacks?search[user_name]={user_data['name']}", ), ] @@ -417,7 +448,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No message.message_id, parse_mode=ParseMode.HTML, reply_markup=reply_markup, - disable_web_page_preview=True + disable_web_page_preview=True, ) except (IndexError, ValueError): await update.message.reply_text( -- 2.50.0 From 1d80adc8d8af6d896d1de93f6e5072f55fe6c7b3 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 8 Jun 2025 10:54:36 +0300 Subject: [PATCH 4/5] change: reformat post votes and feedback Signed-off-by: mctaylors --- commands.py | 54 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/commands.py b/commands.py index c85b51c..c3abbb8 100644 --- a/commands.py +++ b/commands.py @@ -287,17 +287,19 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No [ "Post Votes:", html_parser.hyperlink( - f"up:{upvote_post_count}", - f"{app.protocol}://{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", - ), - html_parser.hyperlink( - f"total:{vote_post_count}", + vote_post_count, f"{app.protocol}://{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", + ), + html_parser.hyperlink( + f"down:{downvote_post_count}", + f"{app.protocol}://{app.hostname}/post_votes?search[user_name]={user_data['name']}&search[score]=-1", + ), + ])})", ] ) ) @@ -418,27 +420,33 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ] ) ) - avg_feedback_count = user_data["positive_feedback_count"] - user_data["negative_feedback_count"] + total_feedback_count = ( + user_data["positive_feedback_count"] + + user_data["neutral_feedback_count"] + + user_data["negative_feedback_count"] + ) m.append( " ".join( [ "Feedback:", 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", - ), - 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", - ), - 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", - ), - html_parser.hyperlink( - f"avg:{avg_feedback_count}", + total_feedback_count, f"{app.protocol}://{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", + ), + 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", + ), + 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", + ), + ])})" ] ) ) -- 2.50.0 From c1987ab48a3fbd6df5c1760d761cb40283564084 Mon Sep 17 00:00:00 2001 From: mctaylors Date: Sun, 8 Jun 2025 10:56:57 +0300 Subject: [PATCH 5/5] change: use html_parser.code when showing ID Signed-off-by: mctaylors --- commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.py b/commands.py index c3abbb8..4f93aa3 100644 --- a/commands.py +++ b/commands.py @@ -235,7 +235,7 @@ async def user_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No ] reply_markup = InlineKeyboardMarkup(keyboard) - m = [" ".join(["ID:", str(user_data["id"])])] + m = [" ".join(["ID:", html_parser.code(user_data["id"])])] created_at = datetime.fromisoformat(user_data["created_at"]) m.append(" ".join(["Join Date:", created_at.strftime("%Y-%m-%d")])) m.append(" ".join(["Level:", user_data["level_string"]])) -- 2.50.0