From 1df328a3049603855466fdd97c1db87e9d77a997 Mon Sep 17 00:00:00 2001 From: fujie Date: Mon, 16 Mar 2026 01:54:05 +0800 Subject: [PATCH] feat(stats): enhance plugin contribution tracking and update statistics display --- scripts/openwebui_stats.py | 150 +++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 71 deletions(-) diff --git a/scripts/openwebui_stats.py b/scripts/openwebui_stats.py index 75775fe..d4d1b1c 100644 --- a/scripts/openwebui_stats.py +++ b/scripts/openwebui_stats.py @@ -89,12 +89,15 @@ class OpenWebUIStats: "action", "filter", "pipe", + "pipeline", "tool", "function", "prompt", "model", ] + NON_PLUGIN_TYPES = {"post", "review", "comment"} + TYPE_ALIASES = { "tools": "tool", } @@ -104,6 +107,11 @@ class OpenWebUIStats: normalized = str(post_type or "").strip().lower() return self.TYPE_ALIASES.get(normalized, normalized) + def _is_published_plugin(self, post_type: str, downloads: int) -> bool: + """Treat marketplace items with downloads > 0 as published plugins.""" + normalized = self._normalize_post_type(post_type) + return downloads > 0 and normalized not in self.NON_PLUGIN_TYPES + def load_history(self) -> list: """Load history records (merge Gist + local file, keep the one with more records)""" gist_history = [] @@ -199,7 +207,10 @@ class OpenWebUIStats: "total_saves": stats["total_saves"], "followers": stats.get("user", {}).get("followers", 0), "points": stats.get("user", {}).get("total_points", 0), - "contributions": stats.get("user", {}).get("contributions", 0), + "contributions": stats.get( + "plugin_contributions", + stats.get("user", {}).get("contributions", 0), + ), "posts": {p["slug"]: p["downloads"] for p in stats.get("posts", [])}, } @@ -268,8 +279,11 @@ class OpenWebUIStats: - prev.get("followers", 0), "points": stats.get("user", {}).get("total_points", 0) - prev.get("points", 0), - "contributions": stats.get("user", {}).get("contributions", 0) - - prev.get("contributions", 0), + "contributions": stats.get( + "plugin_contributions", + stats.get("user", {}).get("contributions", 0), + ) + - prev.get("contributions", prev.get("plugin_contributions", 0)), "posts": { p["slug"]: p["downloads"] - prev.get("posts", {}).get(p["slug"], p["downloads"]) @@ -601,6 +615,7 @@ class OpenWebUIStats: "total_downvotes": 0, "total_saves": 0, "total_comments": 0, + "plugin_contributions": 0, "by_type": {}, "posts": [], "user": {}, # User info @@ -629,19 +644,21 @@ class OpenWebUIStats: meta = plugin_obj.get("meta", {}) or {} manifest = meta.get("manifest", {}) or {} - # Accumulate statistics post_downloads = post.get("downloads", 0) post_views = post.get("views", 0) + post_saves = post.get("saveCount", 0) + is_published_plugin = self._is_published_plugin(post_type, post_downloads) - stats["total_downloads"] += post_downloads stats["total_upvotes"] += post.get("upvotes", 0) stats["total_downvotes"] += post.get("downvotes", 0) - stats["total_saves"] += post.get("saveCount", 0) stats["total_comments"] += post.get("commentCount", 0) - # Key: only count views for posts with actual downloads (exclude post/review types) - if post_type not in ("post", "review") and post_downloads > 0: + # Plugin-only marketplace totals: published items with downloads > 0. + if is_published_plugin: + stats["total_downloads"] += post_downloads stats["total_views"] += post_views + stats["total_saves"] += post_saves + stats["plugin_contributions"] += 1 if post_type not in stats["by_type"]: stats["by_type"][post_type] = 0 stats["by_type"][post_type] += 1 @@ -661,8 +678,9 @@ class OpenWebUIStats: "downloads": post.get("downloads", 0), "views": post.get("views", 0), "upvotes": post.get("upvotes", 0), - "saves": post.get("saveCount", 0), + "saves": post_saves, "comments": post.get("commentCount", 0), + "is_published_plugin": is_published_plugin, "created_at": created_at.strftime("%Y-%m-%d"), "updated_at": updated_at.strftime("%Y-%m-%d"), "url": f"https://openwebui.com/posts/{post.get('slug', '')}", @@ -688,10 +706,11 @@ class OpenWebUIStats: print("๐Ÿ“ˆ Overview") print("-" * 40) print(f" ๐Ÿ“ Posts: {stats['total_posts']}") - print(f" โฌ‡๏ธ Total Downloads: {stats['total_downloads']}") - print(f" ๐Ÿ‘๏ธ Total Views: {stats['total_views']}") + print(f" ๐Ÿงฉ Published Plugins: {stats['plugin_contributions']}") + print(f" โฌ‡๏ธ Plugin Downloads: {stats['total_downloads']}") + print(f" ๐Ÿ‘๏ธ Plugin Views: {stats['total_views']}") print(f" ๐Ÿ‘ Total Upvotes: {stats['total_upvotes']}") - print(f" ๐Ÿ’พ Total Saves: {stats['total_saves']}") + print(f" ๐Ÿ’พ Plugin Saves: {stats['total_saves']}") print(f" ๐Ÿ’ฌ Total Comments: {stats['total_comments']}") print() @@ -745,13 +764,14 @@ class OpenWebUIStats: "overview_title": "## ๐Ÿ“ˆ ๆ€ป่งˆ", "overview_header": "| ๆŒ‡ๆ ‡ | ๆ•ฐๅ€ผ |", "posts": "๐Ÿ“ ๅ‘ๅธƒๆ•ฐ้‡", - "downloads": "โฌ‡๏ธ ๆ€ปไธ‹่ฝฝ้‡", - "views": "๐Ÿ‘๏ธ ๆ€ปๆต่งˆ้‡", + "downloads": "โฌ‡๏ธ ๆ’ไปถๆ€ปไธ‹่ฝฝ้‡", + "views": "๐Ÿ‘๏ธ ๆ’ไปถๆ€ปๆต่งˆ้‡", "upvotes": "๐Ÿ‘ ๆ€ป็‚น่ตžๆ•ฐ", - "saves": "๐Ÿ’พ ๆ€ปๆ”ถ่—ๆ•ฐ", + "saves": "๐Ÿ’พ ๆ’ไปถๆ€ปๆ”ถ่—ๆ•ฐ", "comments": "๐Ÿ’ฌ ๆ€ป่ฏ„่ฎบๆ•ฐ", "author_points": "โญ ไฝœ่€…ๆ€ป็งฏๅˆ†", "author_followers": "๐Ÿ‘ฅ ็ฒ‰ไธๆ•ฐ้‡", + "author_contributions": "๐Ÿงฉ ๅทฒๅ‘ๅธƒๆ’ไปถๆ•ฐ", "type_title": "## ๐Ÿ“‚ ๆŒ‰็ฑปๅž‹ๅˆ†็ฑป", "list_title": "## ๐Ÿ“‹ ๅ‘ๅธƒๅˆ—่กจ", "list_header": "| ๆŽ’ๅ | ๆ ‡้ข˜ | ็ฑปๅž‹ | ็‰ˆๆœฌ | ไธ‹่ฝฝ | ๆต่งˆ | ็‚น่ตž | ๆ”ถ่— | ๆ›ดๆ–ฐๆ—ฅๆœŸ |", @@ -762,13 +782,14 @@ class OpenWebUIStats: "overview_title": "## ๐Ÿ“ˆ Overview", "overview_header": "| Metric | Value |", "posts": "๐Ÿ“ Total Posts", - "downloads": "โฌ‡๏ธ Total Downloads", - "views": "๐Ÿ‘๏ธ Total Views", + "downloads": "โฌ‡๏ธ Total Plugin Downloads", + "views": "๐Ÿ‘๏ธ Total Plugin Views", "upvotes": "๐Ÿ‘ Total Upvotes", - "saves": "๐Ÿ’พ Total Saves", + "saves": "๐Ÿ’พ Total Plugin Saves", "comments": "๐Ÿ’ฌ Total Comments", "author_points": "โญ Author Points", "author_followers": "๐Ÿ‘ฅ Followers", + "author_contributions": "๐Ÿงฉ Published Plugins", "type_title": "## ๐Ÿ“‚ By Type", "list_title": "## ๐Ÿ“‹ Posts List", "list_header": "| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |", @@ -815,6 +836,9 @@ class OpenWebUIStats: md.append( f"| {t['author_followers']} | {self.get_badge('followers', stats, user, delta)} |" ) + md.append( + f"| {t['author_contributions']} | {self.get_badge('contributions', stats, user, delta)} |" + ) md.append("") @@ -914,6 +938,12 @@ class OpenWebUIStats: "color": "blue", "namedLogo": "openwebui", }, + "views": { + "schemaVersion": 1, + "label": "views", + "message": format_number(stats["total_views"]), + "color": "blueviolet", + }, "plugins": { "schemaVersion": 1, "label": "plugins", @@ -932,6 +962,18 @@ class OpenWebUIStats: "message": format_number(stats.get("user", {}).get("total_points", 0)), "color": "orange", }, + "saves": { + "schemaVersion": 1, + "label": "saves", + "message": format_number(stats["total_saves"]), + "color": "lightgrey", + }, + "contributions": { + "schemaVersion": 1, + "label": "contributions", + "message": str(stats.get("plugin_contributions", 0)), + "color": "green", + }, "upvotes": { "schemaVersion": 1, "label": "upvotes", @@ -960,8 +1002,6 @@ class OpenWebUIStats: if not (self.gist_token and self.gist_id): return - delta = self.get_stat_delta(stats) - # Define badge config {key: (label, value, color)} badges_config = { "downloads": ("Downloads", stats["total_downloads"], "brightgreen"), @@ -980,7 +1020,7 @@ class OpenWebUIStats: ), "contributions": ( "Contributions", - stats.get("user", {}).get("contributions", 0), + stats.get("plugin_contributions", 0), "green", ), "posts": ("Posts", stats["total_posts"], "informational"), @@ -988,22 +1028,12 @@ class OpenWebUIStats: files_payload = {} for key, (label, val, color) in badges_config.items(): - diff = delta.get(key, 0) - if isinstance(diff, dict): - diff = 0 # Avoid dict vs int comparison error with 'posts' key - - message = f"{val}" - if diff > 0: - message += f" (+{diff}๐Ÿš€)" - elif diff < 0: - message += f" ({diff})" - # Build Shields.io endpoint JSON # ๅ‚่€ƒ: https://shields.io/badges/endpoint-badge badge_data = { "schemaVersion": 1, "label": label, - "message": message, + "message": f"{val}", "color": color, } @@ -1013,22 +1043,18 @@ class OpenWebUIStats: } # Generate top 6 plugins badges (based on slots p1, p2...) - post_deltas = delta.get("posts", {}) - for i, post in enumerate(stats.get("posts", [])[:6]): + top_plugin_posts = [ + post for post in stats.get("posts", []) if post.get("is_published_plugin") + ][:6] + for i, post in enumerate(top_plugin_posts): idx = i + 1 - diff = post_deltas.get(post["slug"], 0) - - # Downloads badge - dl_msg = f"{post['downloads']}" - if diff > 0: - dl_msg += f" (+{diff}๐Ÿš€)" files_payload[f"badge_p{idx}_dl.json"] = { "content": json.dumps( { "schemaVersion": 1, "label": "Downloads", - "message": dl_msg, + "message": f"{post['downloads']}", "color": "brightgreen", } ) @@ -1062,19 +1088,13 @@ class OpenWebUIStats: # ็”Ÿๆˆๆ‰€ๆœ‰ๅธ–ๅญ็š„ไธชไฝ“ๅพฝ็ซ  (็”จไบŽ่ฏฆ็ป†ๆŠฅ่กจ) for post in stats.get("posts", []): slug_hash = self._safe_key(post["slug"]) - diff = post_deltas.get(post["slug"], 0) - - # 1. Downloads - dl_msg = f"{post['downloads']}" - if diff > 0: - dl_msg += f" (+{diff}๐Ÿš€)" files_payload[f"badge_post_{slug_hash}_dl.json"] = { "content": json.dumps( { "schemaVersion": 1, "label": "Downloads", - "message": dl_msg, + "message": f"{post['downloads']}", "color": "brightgreen", } ) @@ -1163,19 +1183,11 @@ class OpenWebUIStats: is_post: bool = False, style: str = "flat", ) -> str: - """่Žทๅ– Shields.io ๅพฝ็ซ  URL (ๅŒ…ๅซๅขž้‡ๆ˜พ็คบ)""" + """่Žทๅ– Shields.io ๅพฝ็ซ  URLใ€‚""" import urllib.parse gist_user = "Fu-Jie" - def _fmt_delta(k: str) -> str: - val = delta.get(k, 0) - if isinstance(val, dict): - return "" - if val > 0: - return f"
(+{val}๐Ÿš€)" - return "" - if not self.gist_id: if is_post: return "**-**" @@ -1185,14 +1197,14 @@ class OpenWebUIStats: if key == "points": val = user.get("total_points", 0) if key == "contributions": - val = user.get("contributions", 0) + val = stats.get("plugin_contributions", user.get("contributions", 0)) if key == "posts": val = stats.get("total_posts", 0) if key == "saves": val = stats.get("total_saves", 0) if key.startswith("updated"): return f"๐Ÿ• {get_beijing_time().strftime('%Y-%m-%d %H:%M')}" - return f"**{val}**{_fmt_delta(key)}" + return f"**{val}**" raw_url = f"https://gist.githubusercontent.com/{gist_user}/{self.gist_id}/raw/badge_{key}.json" encoded_url = urllib.parse.quote(raw_url, safe="") @@ -1208,30 +1220,26 @@ class OpenWebUIStats: stats: ็ปŸ่ฎกๆ•ฐๆฎ lang: ่ฏญ่จ€ ("zh" ไธญๆ–‡, "en" ่‹ฑๆ–‡) """ - # ่Žทๅ– Top 6 ๆ’ไปถ - top_plugins = stats["posts"][:6] + # ่Žทๅ– Top 6 ๅทฒๅ‘ๅธƒๆ’ไปถ + top_plugins = [ + post for post in stats["posts"] if post.get("is_published_plugin") + ][:6] delta = self.get_stat_delta(stats) - def fmt_delta(key: str) -> str: - val = delta.get(key, 0) - if val > 0: - return f"
(+{val}๐Ÿš€)" - return "" - # ไธญ่‹ฑๆ–‡ๆ–‡ๆœฌ texts = { "zh": { "title": "## ๐Ÿ“Š ็คพๅŒบ็ปŸ่ฎก", - "author_header": "| ๐Ÿ‘ค ไฝœ่€… | ๐Ÿ‘ฅ ็ฒ‰ไธ | โญ ็งฏๅˆ† | ๐Ÿ† ่ดก็Œฎ |", - "header": "| ๐Ÿ“ ๅ‘ๅธƒ | โฌ‡๏ธ ไธ‹่ฝฝ | ๐Ÿ‘๏ธ ๆต่งˆ | ๐Ÿ‘ ็‚น่ตž | ๐Ÿ’พ ๆ”ถ่— |", + "author_header": "| ๐Ÿ‘ค ไฝœ่€… | ๐Ÿ‘ฅ ็ฒ‰ไธ | โญ ็งฏๅˆ† | ๐Ÿงฉ ๆ’ไปถ่ดก็Œฎ |", + "header": "| ๐Ÿ“ ๅ‘ๅธƒ | โฌ‡๏ธ ๆ’ไปถไธ‹่ฝฝ | ๐Ÿ‘๏ธ ๆ’ไปถๆต่งˆ | ๐Ÿ‘ ็‚น่ตž | ๐Ÿ’พ ๆ’ไปถๆ”ถ่— |", "top6_title": "### ๐Ÿ”ฅ ็ƒญ้—จๆ’ไปถ Top 6", "top6_header": "| ๆŽ’ๅ | ๆ’ไปถ | ็‰ˆๆœฌ | ไธ‹่ฝฝ | ๆต่งˆ | ๐Ÿ“… ๆ›ดๆ–ฐ |", "full_stats": "*ๅฎŒๆ•ด็ปŸ่ฎกไธŽ่ถ‹ๅŠฟๅ›พ่ฏทๆŸฅ็œ‹ [็คพๅŒบ็ปŸ่ฎกๆŠฅๅ‘Š](./docs/community-stats.zh.md)*", }, "en": { "title": "## ๐Ÿ“Š Community Stats", - "author_header": "| ๐Ÿ‘ค Author | ๐Ÿ‘ฅ Followers | โญ Points | ๐Ÿ† Contributions |", - "header": "| ๐Ÿ“ Posts | โฌ‡๏ธ Downloads | ๐Ÿ‘๏ธ Views | ๐Ÿ‘ Upvotes | ๐Ÿ’พ Saves |", + "author_header": "| ๐Ÿ‘ค Author | ๐Ÿ‘ฅ Followers | โญ Points | ๐Ÿงฉ Plugin Contributions |", + "header": "| ๐Ÿ“ Posts | โฌ‡๏ธ Plugin Downloads | ๐Ÿ‘๏ธ Plugin Views | ๐Ÿ‘ Upvotes | ๐Ÿ’พ Plugin Saves |", "top6_title": "### ๐Ÿ”ฅ Top 6 Popular Plugins", "top6_header": "| Rank | Plugin | Version | Downloads | Views | ๐Ÿ“… Updated |", "full_stats": "*See full stats and charts in [Community Stats Report](./docs/community-stats.md)*",