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)*",