Skip to content

Commit 07d9035

Browse files
authored
Update generate.py
1 parent f68659a commit 07d9035

File tree

1 file changed

+113
-70
lines changed

1 file changed

+113
-70
lines changed

scripts/generate.py

Lines changed: 113 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -87,69 +87,124 @@ def _escape_inline(text: Any) -> str:
8787
return text.strip()
8888

8989

90-
def _generate_recent_card(entry: Dict[str, Any]) -> str:
90+
def md_escape(s: Any) -> str:
9191
"""
92-
Generate a single plugin card as a <td> block:
93-
- creator avatar
94-
- name + 'plugin' label
95-
- meta line (MC, creator, added_at)
96-
- stars / downloads / updated badges
92+
Match the Top 6 helper: escape < and > so HTML isn't broken.
9793
"""
98-
name = _escape_inline(entry.get("name", "Unknown"))
99-
repo = _escape_inline(entry.get("repo", ""))
94+
s = s or ""
95+
if not isinstance(s, str):
96+
s = str(s)
97+
return s.replace("<", "&lt;").replace(">", "&gt;")
10098

101-
mc = _escape_inline(entry.get("mc_versions", ""))
102-
added_at = _escape_inline(entry.get("added_at", ""))
10399

104-
creator_obj = entry.get("creator") or {}
105-
creator_name = _escape_inline(creator_obj.get("name", ""))
106-
creator_url = _escape_inline(creator_obj.get("url", ""))
107-
creator_avatar = _escape_inline(creator_obj.get("avatar", ""))
100+
# ---------- Avatar helpers (copied from Top 6 script) ----------
108101

109-
parts: List[str] = []
110-
parts.append(' <td valign="top" width="50%">')
102+
def _is_github_avatar(url: str) -> bool:
103+
if not url:
104+
return False
105+
return ("avatars.githubusercontent.com" in url) or bool(
106+
re.search(r"github\.com/.+\.png$", url)
107+
)
111108

112-
# Creator avatar
113-
if creator_avatar:
114-
parts.append(
115-
f' <img src="{creator_avatar}" alt="{creator_name} avatar" '
116-
f'width="120" height="120"><br>'
117-
)
118109

119-
# Title line
120-
if repo:
121-
parts.append(
122-
f' <a href="https://github.com/{repo}"><strong>{name}</strong></a> '
123-
f"<code>plugin</code><br>"
124-
)
125-
else:
126-
parts.append(f" <strong>{name}</strong> <code>plugin</code><br>")
127-
128-
# Meta line: MC / creator / added date
129-
meta_bits: List[str] = []
130-
if mc:
131-
meta_bits.append(f"<code>MC: {mc}</code>")
132-
if creator_name:
133-
if creator_url:
134-
meta_bits.append(f'by <a href="{creator_url}"><strong>{creator_name}</strong></a>')
110+
def _sharp_github_avatar(url: str, px: int = 400) -> str:
111+
"""Force a crisp GitHub avatar by requesting a larger size, displayed at 100x100."""
112+
if not url:
113+
return url
114+
if "avatars.githubusercontent.com" in url:
115+
return url + (f"&s={px}" if "?" in url else f"?s={px}")
116+
if re.search(r"github\.com/.+\.png$", url):
117+
return url + (f"&size={px}" if "?" in url else f"?size={px}")
118+
return url
119+
120+
121+
# ---------- Recently-added cards (Top-6 style) ----------
122+
123+
def _recent_items_for_cards(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
124+
"""
125+
Normalize YAML plugin entries into the mini dicts that our card
126+
renderer expects, mirroring the Top 6 structure:
127+
- repo, name, desc
128+
- creatorAvatar (hi-res GitHub if possible)
129+
- ownerAvatar fallback (GitHub owner avatar)
130+
- addedUnix: unix timestamp from added_at
131+
"""
132+
items: List[Dict[str, Any]] = []
133+
for e in entries:
134+
repo = (e.get("repo") or "").strip()
135+
if not repo or "/" not in repo:
136+
continue
137+
138+
creator = e.get("creator") or {}
139+
creator_avatar = creator.get("avatar")
140+
141+
# Only keep creator avatar if it's GitHub-hosted (can request hi-res)
142+
if _is_github_avatar(creator_avatar):
143+
creator_avatar = _sharp_github_avatar(creator_avatar, 400)
135144
else:
136-
meta_bits.append(f"by <strong>{creator_name}</strong>")
137-
if added_at:
138-
meta_bits.append(f"added <code>{added_at}</code>")
139-
140-
if meta_bits:
141-
parts.append(" " + " · ".join(meta_bits) + "<br>")
142-
143-
# Badge row: stars / downloads / updated
144-
if repo:
145-
parts.append(
146-
f' <img src="https://img.shields.io/github/stars/{repo}?style=flat&amp;label=stars"> '
147-
f'<img src="https://img.shields.io/github/downloads/{repo}/total?style=flat&amp;label=downloads"> '
148-
f'<img src="https://img.shields.io/github/release-date/{repo}?label=updated">'
145+
creator_avatar = None # force fallback to owner avatar for sharpness
146+
147+
owner = repo.split("/", 1)[0]
148+
owner_avatar = f"https://avatars.githubusercontent.com/{owner}?s=400"
149+
150+
# Date -> unix for "added" badge
151+
added_at_raw = e.get("added_at")
152+
dt = _parse_date_safe(added_at_raw) if added_at_raw else datetime.min
153+
added_unix = int(dt.timestamp()) if dt != datetime.min else None
154+
155+
items.append(
156+
{
157+
"repo": repo,
158+
"name": e.get("name"),
159+
"desc": e.get("description", ""),
160+
"creatorAvatar": creator_avatar,
161+
"ownerAvatar": owner_avatar,
162+
"addedUnix": added_unix,
163+
}
149164
)
165+
return items
150166

151-
parts.append(" </td>")
152-
return "\n".join(parts)
167+
168+
def _render_recent_cards(items: List[Dict[str, Any]]) -> str:
169+
"""
170+
Render a list of items using the exact same layout as the Top 6
171+
cards in README (avatar, title + `plugin`, description, badges),
172+
but using `addedUnix` for a green 'added' date badge.
173+
"""
174+
if not items:
175+
return ""
176+
177+
TWO_COL_WIDTH = "50%"
178+
cells: List[str] = []
179+
180+
for t in items:
181+
# Prefer creator avatar only if it's GitHub-hosted (sharp). Otherwise use owner avatar (400px).
182+
img = t.get("creatorAvatar") or t.get("ownerAvatar")
183+
repo = t["repo"]
184+
name = md_escape(t.get("name") or repo.split("/")[1])
185+
desc = md_escape(t.get("desc") or "")
186+
187+
added_unix = t.get("addedUnix")
188+
189+
cell = f"""
190+
<td align="left" valign="top" width="{TWO_COL_WIDTH}">
191+
<a href="https://github.com/{repo}"><img src="{img}" alt="{name}" width="100" height="100" style="border-radius:12px;"></a>
192+
<div><strong><a href="https://github.com/{repo}">{name}</a></strong>&nbsp;<code>plugin</code></div>
193+
<div style="margin:4px 0 6px 0;">{(desc or "&nbsp;")}</div>
194+
<div>
195+
<img alt="stars" src="https://img.shields.io/github/stars/{repo}?style=flat">
196+
&nbsp;<img alt="downloads" src="https://img.shields.io/github/downloads/{repo}/total?style=flat">"""
197+
if added_unix is not None:
198+
cell += f"""
199+
&nbsp;<img alt="added" src="https://img.shields.io/date/{added_unix}?label=added&style=flat">"""
200+
cell += """
201+
</div>
202+
</td>""".rstrip()
203+
204+
cells.append(cell)
205+
206+
rows = ["<tr>" + "".join(cells[i : i + 2]) + "</tr>" for i in range(0, len(cells), 2)]
207+
return "\n".join(["<table>", *rows, "</table>"])
153208

154209

155210
def generate_recent_plugins_md(entries: List[Dict[str, Any]]) -> str:
@@ -160,31 +215,19 @@ def generate_recent_plugins_md(entries: List[Dict[str, Any]]) -> str:
160215
<!--- Recently Added Plugins Start -->
161216
<!--- Recently Added Plugins End -->
162217
163-
Renders as a 2-column card grid similar to the "Top 6 Plugins" section
164-
in the README.
218+
Renders as a 2-column card grid using the Top 6 card layout.
165219
"""
166220
if not entries:
167221
return "No recently added plugins found.\n"
168222

223+
items = _recent_items_for_cards(entries)
224+
cards_html = _render_recent_cards(items)
225+
169226
lines: List[str] = []
170227
lines.append("> These are the six most recently added plugins (based on `added_at`).")
171228
lines.append("")
172-
173-
lines.append("<table>")
174-
for i in range(0, len(entries), 2):
175-
left = _generate_recent_card(entries[i])
176-
if i + 1 < len(entries):
177-
right = _generate_recent_card(entries[i + 1])
178-
else:
179-
right = " <td></td>"
180-
181-
lines.append(" <tr>")
182-
lines.append(left)
183-
lines.append(right)
184-
lines.append(" </tr>")
185-
lines.append("</table>")
229+
lines.append(cards_html)
186230
lines.append("")
187-
188231
return "\n".join(lines)
189232

190233

0 commit comments

Comments
 (0)