| 1 |
// GitHub trending repos. Approximates trending by searching the GitHub |
| 2 |
// search API for repos with >100 stars, sorted by star count. Optional |
| 3 |
// language filter; no auth required. Formats star counts as "Nk" display. |
| 4 |
|
| 5 |
const GH_API = "https://api.github.com/search/repositories"; |
| 6 |
|
| 7 |
fn id() { |
| 8 |
"github_trending" |
| 9 |
} |
| 10 |
|
| 11 |
fn name() { |
| 12 |
"GitHub Trending" |
| 13 |
} |
| 14 |
|
| 15 |
fn capabilities() { |
| 16 |
#{ |
| 17 |
supports_pagination: true |
| 18 |
} |
| 19 |
} |
| 20 |
|
| 21 |
fn config_schema() { |
| 22 |
#{ |
| 23 |
description: "Trending GitHub repositories by stars gained recently. Filter by language.", |
| 24 |
fields: [ |
| 25 |
#{ |
| 26 |
key: "language", |
| 27 |
label: "Language", |
| 28 |
field_type: "text", |
| 29 |
description: "Filter by language (e.g., rust, python, javascript). Leave blank for all.", |
| 30 |
placeholder: "rust" |
| 31 |
}, |
| 32 |
#{ |
| 33 |
key: "timeframe", |
| 34 |
label: "Time Window", |
| 35 |
field_type: "select", |
| 36 |
required: true, |
| 37 |
description: "How far back to look for star activity", |
| 38 |
default_value: "weekly", |
| 39 |
options: ["daily", "weekly", "monthly"] |
| 40 |
}, |
| 41 |
#{ |
| 42 |
key: "limit", |
| 43 |
label: "Repos to show", |
| 44 |
field_type: "text", |
| 45 |
description: "Number of repos to fetch (max 30)", |
| 46 |
default_value: "20", |
| 47 |
placeholder: "20" |
| 48 |
} |
| 49 |
] |
| 50 |
} |
| 51 |
} |
| 52 |
|
| 53 |
fn fetch(config, cursor) { |
| 54 |
let limit = 20; |
| 55 |
if config.limit != () { |
| 56 |
let parsed = parse_int(config.limit); |
| 57 |
if parsed != () && parsed > 0 { |
| 58 |
if parsed > 30 { |
| 59 |
limit = 30; |
| 60 |
} else { |
| 61 |
limit = parsed; |
| 62 |
} |
| 63 |
} |
| 64 |
} |
| 65 |
|
| 66 |
let page = 1; |
| 67 |
if cursor != () { |
| 68 |
let parsed = parse_int(cursor); |
| 69 |
if parsed != () && parsed > 0 { |
| 70 |
page = parsed; |
| 71 |
} |
| 72 |
} |
| 73 |
|
| 74 |
// Build date query for "created after" threshold |
| 75 |
let timeframe = "weekly"; |
| 76 |
if config.timeframe != () { |
| 77 |
timeframe = config.timeframe; |
| 78 |
} |
| 79 |
|
| 80 |
// Use stars>100 as a proxy for trending (GitHub search API) |
| 81 |
let query = "stars:>100"; |
| 82 |
|
| 83 |
if config.language != () && config.language != "" { |
| 84 |
query += " language:" + config.language; |
| 85 |
} |
| 86 |
|
| 87 |
// Sort by stars to approximate trending |
| 88 |
let url = GH_API + "?q=" + query + "&sort=stars&order=desc&per_page=" + limit + "&page=" + page; |
| 89 |
|
| 90 |
let data = http_get_json(url); |
| 91 |
|
| 92 |
if data == () { |
| 93 |
return #{ items: [], has_more: false }; |
| 94 |
} |
| 95 |
|
| 96 |
let repos = data.items; |
| 97 |
if repos == () { |
| 98 |
return #{ items: [], has_more: false }; |
| 99 |
} |
| 100 |
|
| 101 |
let items = []; |
| 102 |
for repo in repos { |
| 103 |
let item = parse_repo(repo); |
| 104 |
if item != () { |
| 105 |
items.push(item); |
| 106 |
} |
| 107 |
} |
| 108 |
|
| 109 |
let has_more = items.len() >= limit; |
| 110 |
let result = #{ |
| 111 |
items: items, |
| 112 |
has_more: has_more |
| 113 |
}; |
| 114 |
|
| 115 |
if has_more { |
| 116 |
result.next_cursor = "" + (page + 1); |
| 117 |
} |
| 118 |
|
| 119 |
result |
| 120 |
} |
| 121 |
|
| 122 |
fn parse_repo(repo) { |
| 123 |
if repo.full_name == () { |
| 124 |
return (); |
| 125 |
} |
| 126 |
|
| 127 |
let name = repo.full_name; |
| 128 |
let description = ""; |
| 129 |
if repo.description != () { |
| 130 |
description = repo.description; |
| 131 |
} |
| 132 |
|
| 133 |
let stars = 0; |
| 134 |
if repo.stargazers_count != () { |
| 135 |
stars = repo.stargazers_count; |
| 136 |
} |
| 137 |
|
| 138 |
let forks = 0; |
| 139 |
if repo.forks_count != () { |
| 140 |
forks = repo.forks_count; |
| 141 |
} |
| 142 |
|
| 143 |
let language = ""; |
| 144 |
if repo.language != () { |
| 145 |
language = repo.language; |
| 146 |
} |
| 147 |
|
| 148 |
let url = ""; |
| 149 |
if repo.html_url != () { |
| 150 |
url = repo.html_url; |
| 151 |
} |
| 152 |
|
| 153 |
let owner = "Unknown"; |
| 154 |
if repo.owner != () { |
| 155 |
if repo.owner.login != () { |
| 156 |
owner = repo.owner.login; |
| 157 |
} |
| 158 |
} |
| 159 |
|
| 160 |
let created = timestamp_now(); |
| 161 |
if repo.pushed_at != () { |
| 162 |
created = repo.pushed_at; |
| 163 |
} |
| 164 |
|
| 165 |
let repo_id = 0; |
| 166 |
if repo.id != () { |
| 167 |
repo_id = repo.id; |
| 168 |
} |
| 169 |
|
| 170 |
// Format stars count |
| 171 |
let stars_display = "" + stars; |
| 172 |
if stars >= 1000 { |
| 173 |
stars_display = "" + (stars / 1000) + "k"; |
| 174 |
} |
| 175 |
|
| 176 |
let secondary = stars_display + " stars · " + forks + " forks"; |
| 177 |
if language != "" { |
| 178 |
secondary += " · " + language; |
| 179 |
} |
| 180 |
|
| 181 |
// Build body |
| 182 |
let body = ""; |
| 183 |
if description != "" { |
| 184 |
body = description + "\n\n"; |
| 185 |
} |
| 186 |
body += "**Stars:** " + stars + "\n"; |
| 187 |
body += "**Forks:** " + forks + "\n"; |
| 188 |
if language != "" { |
| 189 |
body += "**Language:** " + language + "\n"; |
| 190 |
} |
| 191 |
|
| 192 |
// Tags |
| 193 |
let tags = ["github"]; |
| 194 |
if language != "" { |
| 195 |
tags.push(language); |
| 196 |
} |
| 197 |
|
| 198 |
#{ |
| 199 |
id: #{ source: "github_trending", item_id: "" + repo_id }, |
| 200 |
bite: #{ |
| 201 |
author: owner, |
| 202 |
text: truncate(name + ": " + description, 100), |
| 203 |
secondary: secondary, |
| 204 |
indicator: "⭐" |
| 205 |
}, |
| 206 |
content: #{ |
| 207 |
title: name, |
| 208 |
body: body, |
| 209 |
url: url |
| 210 |
}, |
| 211 |
meta: #{ |
| 212 |
source_name: "GitHub Trending", |
| 213 |
published_at: created, |
| 214 |
score: stars, |
| 215 |
tags: tags |
| 216 |
} |
| 217 |
} |
| 218 |
} |
| 219 |
|