Skip to content

Commit 763a950

Browse files
committed
v0.6.0 — Update-Check via GitHub Releases API
1 parent b4d5e0b commit 763a950

10 files changed

Lines changed: 235 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## v0.6.0 (2026-05-09)
4+
- Feature: **Update-Check** gegen GitHub Releases API. Einmal taeglich (per `chrome.alarms`) prueft der Background-Worker, ob ein neueres Release verfuegbar ist. Bei Update wird die Versionsnummer in Sidebar-Footer und Options-Header fett+gelb dargestellt und ist klickbar (oeffnet Release-Seite).
5+
- Options: Neuer Abschnitt "Update-Check" mit Toggle (default an) und "Jetzt suchen"-Button fuer manuelle Pruefung.
6+
- Permissions: Neu `alarms` (fuer den taeglichen Check) und `https://api.github.com/*` (fuer den Releases-Endpoint). Chrome wird beim Update einmalig um Bestaetigung der erweiterten Permissions bitten.
7+
- Privacy: README praezisiert — der Update-Check ist die einzige externe Anfrage und kann abgeschaltet werden.
8+
9+
## v0.5.3 (2026-05-09)
10+
- UX: Versionsnummer im Sidebar-Footer (dezent rechts) und im Options-Header (neben "Einstellungen"). Beides liest live aus `manifest.json` ueber `chrome.runtime.getManifest()` — keine doppelte Pflege bei Releases.
11+
312
## v0.5.2 (2026-05-09)
413
- Bugfix: Community-Namen wurden auf Detail-, Settings- und Leaderboard-Seiten mit dem Seiten-`<h1>` ueberschrieben. Folge: Eintraege wie "Change password", "GPT 5.5" oder Post-Titel landeten als Community-Namen im Round-Robin. Ab jetzt werden Namen nur noch auf der Community-Root-Seite erfasst.
514
- Selbstheilung: Nav-Scan ueberschreibt bestehende Namen aktiv aus der zuverlaessigen Skool-Drawer-Komponente. Falsche Namen aus pre-v0.5.2 korrigieren sich beim naechsten Skool-Besuch automatisch — ohne dass der User etwas tun muss.

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ Klick auf das Extension-Icon &rarr; **Einstellungen öffnen**, oder aus der Side
4747

4848
## Privacy
4949

50-
Die Extension macht **keine** externen Netzwerk-Requests. Alle Daten (Keywords, Communities, Bookmarks, History) liegen ausschließlich in `chrome.storage.sync` und `chrome.storage.local` im Browser. Skool bekommt nicht mit, dass die Extension läuft &mdash; sie liest nur den DOM-Inhalt, den du sowieso siehst, und fügt eine Sidebar hinzu.
50+
Alle Daten (Keywords, Communities, Bookmarks, History) liegen ausschließlich in `chrome.storage.sync` und `chrome.storage.local` im Browser. Skool bekommt nicht mit, dass die Extension läuft &mdash; sie liest nur den DOM-Inhalt, den du sowieso siehst, und fügt eine Sidebar hinzu.
51+
52+
Eine einzige Ausnahme: einmal täglich fragt der Background-Worker bei der GitHub-Releases-API (`api.github.com`) nach, ob eine neuere Version vorliegt. Es werden keine Nutzerdaten gesendet, nur die Anfrage selbst (mit Standard-User-Agent von Chrome). Diesen Update-Check kannst du in den Optionen abschalten.
5153

5254
## Entwicklung / Contributing
5355

background.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,63 @@ chrome.runtime.onInstalled.addListener(async () => {
1616
if (existing[k] === undefined) patch[k] = v;
1717
}
1818
if (Object.keys(patch).length) await chrome.storage.sync.set(patch);
19+
checkForUpdates();
1920
});
2021

22+
chrome.runtime.onStartup.addListener(() => {
23+
checkForUpdates();
24+
});
25+
26+
// Update-Check: einmal taeglich gegen GitHub Releases API. Per Setting
27+
// `checkForUpdates` deaktivierbar. Ergebnis wandert in storage.local und
28+
// wird vom Content-Script (Footer) und der Options-Page gerendert.
29+
const UPDATE_REPO = "Marcuss28/skool-helper";
30+
const UPDATE_ALARM = "skool-helper-update-check";
31+
32+
chrome.alarms.create(UPDATE_ALARM, { periodInMinutes: 24 * 60 });
33+
chrome.alarms.onAlarm.addListener((alarm) => {
34+
if (alarm.name === UPDATE_ALARM) checkForUpdates();
35+
});
36+
37+
function semverGt(a, b) {
38+
const pa = String(a || "0").split(".").map(n => parseInt(n, 10) || 0);
39+
const pb = String(b || "0").split(".").map(n => parseInt(n, 10) || 0);
40+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
41+
const av = pa[i] || 0;
42+
const bv = pb[i] || 0;
43+
if (av > bv) return true;
44+
if (av < bv) return false;
45+
}
46+
return false;
47+
}
48+
49+
async function checkForUpdates() {
50+
try {
51+
const { checkForUpdates: enabled = true } = await chrome.storage.sync.get({ checkForUpdates: true });
52+
if (!enabled) return;
53+
54+
const r = await fetch(`https://api.github.com/repos/${UPDATE_REPO}/releases/latest`, {
55+
headers: { Accept: "application/vnd.github+json" }
56+
});
57+
if (!r.ok) return;
58+
const data = await r.json();
59+
const latestTag = (data.tag_name || "").replace(/^v/, "");
60+
const currentVersion = chrome.runtime.getManifest().version;
61+
62+
await chrome.storage.local.set({
63+
updateInfo: {
64+
latestVersion: latestTag,
65+
url: data.html_url || `https://github.com/${UPDATE_REPO}/releases/latest`,
66+
checkedAt: Date.now(),
67+
updateAvailable: semverGt(latestTag, currentVersion)
68+
}
69+
});
70+
} catch (e) {
71+
// Netzwerk-Fehler / Rate-Limit: leise ignorieren, beim naechsten Lauf
72+
// wird's nochmal probiert.
73+
}
74+
}
75+
2176
// Tastatur-Shortcut: Alt+Shift+S togglet die Sidebar global (sync-Storage,
2277
// damit der content-Script das Storage-Change-Event mitbekommt).
2378
if (chrome.commands && chrome.commands.onCommand) {
@@ -53,5 +108,12 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
53108
chrome.runtime.openOptionsPage();
54109
}
55110

111+
if (msg.type === "check-for-updates") {
112+
checkForUpdates().then(() => {
113+
try { sendResponse({ ok: true }); } catch (e) {}
114+
});
115+
return true; // async response
116+
}
117+
56118
return false;
57119
});

content.css

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
1-
/* Skool Helper – Styles (v0.5.2) */
1+
/* Skool Helper – Styles (v0.6.0) */
2+
3+
/* Update-Verfuegbar: fett + gelb, klickbar */
4+
#skool-helper-sidebar .sh-footer-version-update {
5+
color: #fbbf24 !important;
6+
font-weight: 700;
7+
text-decoration: none;
8+
cursor: pointer;
9+
}
10+
#skool-helper-sidebar a.sh-footer-version-update:hover {
11+
text-decoration: underline;
12+
}
13+
14+
/* Versions-Anzeige im Footer: dezent, rechts vom Hauptext */
15+
#skool-helper-sidebar .sh-footer-version {
16+
color: #6b7280;
17+
font-size: 10px;
18+
font-variant-numeric: tabular-nums;
19+
margin-left: 6px;
20+
flex: 0 0 auto;
21+
}
22+
#skool-helper-sidebar .sh-footer-main {
23+
flex: 1 1 auto;
24+
text-align: center;
25+
}
26+
227

328
/* Round-Robin Punkte-Badge: dezent, in der Zeile zwischen Name und Time */
429
#skool-helper-sidebar .sh-rr-points {

content.js

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
/**
2-
* Skool Helper – Content Script (v0.5.2)
2+
* Skool Helper – Content Script (v0.6.0)
33
*
4+
* v0.6.0: Update-Check (opt-in, default an) — Background-Worker prueft 1x
5+
* taeglich gegen die GitHub-Releases-API, ob ein neueres Release
6+
* verfuegbar ist. Bei Update wird die Versionsnummer im Footer
7+
* fett+gelb und klickbar (oeffnet Release-Seite). Per Toggle in
8+
* den Options abschaltbar. Neue Permissions: alarms +
9+
* host_permission api.github.com.
10+
* v0.5.3: Versionsnummer sichtbar — im Sidebar-Footer (klein, rechts neben
11+
* dem Standardtext) und im Options-Header (neben "Einstellungen").
12+
* Liest aus chrome.runtime.getManifest().version, damit nur die
13+
* manifest.json bei Releases angefasst werden muss.
414
* v0.5.2: Bugfix — Community-Namen wurden auf Detail-/Settings-/Leaderboard-
515
* Seiten mit dem Seitentitel ueberschrieben (Folge: "Change password",
616
* Post-Titel etc. tauchten als Community-Namen im Round-Robin auf).
@@ -123,10 +133,11 @@
123133
state.showTimer = sync.showTimer === true;
124134
state.showEngagement = sync.showEngagement === true;
125135

126-
const local = await chrome.storage.local.get({ communities: {}, bookmarks: {}, postHistory: {} });
136+
const local = await chrome.storage.local.get({ communities: {}, bookmarks: {}, postHistory: {}, updateInfo: null });
127137
state.communities = local.communities || {};
128138
state.bookmarks = local.bookmarks || {};
129139
state.postHistory = local.postHistory || {};
140+
state.updateInfo = local.updateInfo || null;
130141
prunePostHistory();
131142
} catch (err) {
132143
console.warn("[Skool Helper] Konnte Config nicht laden:", err);
@@ -286,6 +297,10 @@
286297
state.communities = changes.communities.newValue || {};
287298
renderRoundRobin();
288299
}
300+
if (area === "local" && changes.updateInfo) {
301+
state.updateInfo = changes.updateInfo.newValue || null;
302+
renderFooterStats();
303+
}
289304
});
290305

291306
function currentCommunitySlug() {
@@ -1227,6 +1242,14 @@
12271242
});
12281243
}
12291244

1245+
// Versions-String wird einmalig aus manifest.json gelesen und gecached.
1246+
let __versionStr = "";
1247+
try {
1248+
if (chrome.runtime && chrome.runtime.getManifest) {
1249+
__versionStr = "v" + chrome.runtime.getManifest().version;
1250+
}
1251+
} catch (e) {}
1252+
12301253
function renderFooterStats() {
12311254
if (!sidebarEl) return;
12321255
const foot = sidebarEl.querySelector("#sh-footer");
@@ -1246,10 +1269,21 @@
12461269
const totalMatches = Object.values(state.sessionCounts).reduce((a, b) => a + b, 0);
12471270
parts.push(`${todayVisited} Comm · ${totalMatches} Treffer`);
12481271
}
1249-
if (parts.length > 0) {
1250-
foot.textContent = parts.join(" · ");
1272+
const main = parts.length > 0 ? parts.join(" · ") : "Keywords & Communities in den Einstellungen";
1273+
const update = state.updateInfo;
1274+
const hasUpdate = update && update.updateAvailable === true && update.latestVersion;
1275+
if (__versionStr) {
1276+
const versionClass = hasUpdate ? "sh-footer-version sh-footer-version-update" : "sh-footer-version";
1277+
const tooltip = hasUpdate
1278+
? `Update verfuegbar: v${update.latestVersion} — klicken zum Oeffnen`
1279+
: "Aktuelle Version";
1280+
const versionLabel = hasUpdate ? `${__versionStr} →` : __versionStr;
1281+
const versionMarkup = hasUpdate
1282+
? `<a class="${versionClass}" href="${escapeHtml(update.url || "")}" target="_blank" rel="noopener" title="${escapeHtml(tooltip)}">${escapeHtml(versionLabel)}</a>`
1283+
: `<span class="${versionClass}" title="${escapeHtml(tooltip)}">${escapeHtml(versionLabel)}</span>`;
1284+
foot.innerHTML = `<span class="sh-footer-main">${escapeHtml(main)}</span> ${versionMarkup}`;
12511285
} else {
1252-
foot.textContent = "Keywords & Communities in den Einstellungen";
1286+
foot.textContent = main;
12531287
}
12541288
}
12551289

defaults.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ globalThis.SKOOL_HELPER_DEFAULTS = {
3232
excludedKeywords: [],
3333
excludedAuthors: [],
3434
showTimer: false,
35-
showEngagement: false
35+
showEngagement: false,
36+
checkForUpdates: true
3637
};

manifest.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
{
22
"manifest_version": 3,
33
"name": "Skool Helper – Keyword Feed & Highlights",
4-
"version": "0.5.2",
4+
"version": "0.6.0",
55
"description": "Hebt relevante Skool-Beiträge anhand deiner Keywords hervor, zeigt einen Community Round-Robin (welche Communities heute noch offen sind), bietet Kommentar-Ideen. Jede Interaktion bleibt manuell.",
66
"permissions": [
77
"storage",
88
"notifications",
9-
"activeTab"
9+
"activeTab",
10+
"alarms"
1011
],
1112
"host_permissions": [
1213
"https://www.skool.com/*",
13-
"https://skool.com/*"
14+
"https://skool.com/*",
15+
"https://api.github.com/*"
1416
],
1517
"content_scripts": [
1618
{

options.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ body {
88
line-height: 1.5;
99
}
1010
header h1 { margin: 0 0 4px; font-size: 22px; }
11+
header h1 #version-badge {
12+
font-size: 13px;
13+
font-weight: 400;
14+
color: #9ca3af;
15+
margin-left: 8px;
16+
vertical-align: middle;
17+
font-variant-numeric: tabular-nums;
18+
}
19+
header h1 #version-badge .version-update {
20+
color: #fbbf24;
21+
font-weight: 700;
22+
text-decoration: none;
23+
}
24+
header h1 #version-badge .version-update:hover {
25+
text-decoration: underline;
26+
}
1127
header p { margin: 0 0 24px; color: #9ca3af; }
1228
main { max-width: 760px; }
1329
section {

options.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</head>
88
<body>
99
<header>
10-
<h1>Skool Helper – Einstellungen</h1>
10+
<h1>Skool Helper – Einstellungen <span id="version-badge"></span></h1>
1111
<p>Keywords, Kommentar-Vorlagen, Communities und Benachrichtigungen konfigurieren. Änderungen am Sprachfilter und an der Sprache einer Community werden automatisch gespeichert.</p>
1212
</header>
1313

@@ -131,6 +131,23 @@ <h2>Tastatur-Shortcut</h2>
131131
</p>
132132
</section>
133133

134+
<section>
135+
<h2>Update-Check</h2>
136+
<p class="hint">
137+
Prüft einmal täglich gegen die GitHub-Releases-API, ob ein neueres Release verfügbar ist.
138+
Wenn ja, wird die Versionsnummer im Sidebar-Footer und im Header dieser Seite fett und gelb dargestellt — ein Klick öffnet die Release-Seite.
139+
Es werden keine Daten gesendet, nur der Versionsstand abgefragt.
140+
</p>
141+
<label class="check">
142+
<input type="checkbox" id="checkForUpdates" />
143+
Täglich nach neuen Versionen suchen
144+
</label>
145+
<div class="row-actions">
146+
<button id="check-now">Jetzt suchen</button>
147+
<span id="update-status" class="status"></span>
148+
</div>
149+
</section>
150+
134151
<div class="actions">
135152
<button id="save" class="primary">Speichern</button>
136153
<button id="reset">Zurücksetzen</button>

options.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ async function load() {
1818
if ($("excludedAuthors")) $("excludedAuthors").value = (sync.excludedAuthors || []).join("\n");
1919
if ($("showTimer")) $("showTimer").checked = sync.showTimer === true;
2020
if ($("showEngagement")) $("showEngagement").checked = sync.showEngagement === true;
21+
if ($("checkForUpdates")) $("checkForUpdates").checked = sync.checkForUpdates !== false;
2122

2223
const local = await chrome.storage.local.get({ communities: {} });
2324
renderCommunities(local.communities || {});
@@ -105,7 +106,8 @@ async function save() {
105106
excludedKeywords: parseList(($("excludedKeywords") || {value: ""}).value).map(s => s.toLowerCase()),
106107
excludedAuthors: parseList(($("excludedAuthors") || {value: ""}).value).map(s => s.toLowerCase()),
107108
showTimer: $("showTimer") ? $("showTimer").checked === true : false,
108-
showEngagement: $("showEngagement") ? $("showEngagement").checked === true : false
109+
showEngagement: $("showEngagement") ? $("showEngagement").checked === true : false,
110+
checkForUpdates: $("checkForUpdates") ? $("checkForUpdates").checked === true : true
109111
};
110112
await chrome.storage.sync.set(payload);
111113

@@ -259,7 +261,24 @@ async function importBackupFromFile(file) {
259261
}
260262
}
261263

264+
async function applyUpdateBadge() {
265+
try {
266+
const v = chrome.runtime.getManifest().version;
267+
const badge = document.getElementById("version-badge");
268+
if (!badge) return;
269+
const { updateInfo } = await chrome.storage.local.get({ updateInfo: null });
270+
const hasUpdate = updateInfo && updateInfo.updateAvailable === true && updateInfo.latestVersion;
271+
if (hasUpdate) {
272+
badge.innerHTML = `<a href="${updateInfo.url || `https://github.com/Marcuss28/skool-helper/releases/latest`}" target="_blank" rel="noopener" class="version-update" title="Update verfügbar: v${updateInfo.latestVersion}">v${v} → v${updateInfo.latestVersion}</a>`;
273+
} else {
274+
badge.textContent = "v" + v;
275+
}
276+
} catch (e) {}
277+
}
278+
262279
document.addEventListener("DOMContentLoaded", () => {
280+
applyUpdateBadge();
281+
263282
load();
264283
$("save").addEventListener("click", save);
265284
$("reset").addEventListener("click", reset);
@@ -293,4 +312,39 @@ document.addEventListener("DOMContentLoaded", () => {
293312
showStatus("Sprachfilter übernommen");
294313
});
295314
}
315+
316+
const checkUpdatesChk = $("checkForUpdates");
317+
if (checkUpdatesChk) {
318+
checkUpdatesChk.addEventListener("change", async () => {
319+
await chrome.storage.sync.set({ checkForUpdates: checkUpdatesChk.checked === true });
320+
showStatus("Update-Check " + (checkUpdatesChk.checked ? "aktiviert" : "deaktiviert"));
321+
});
322+
}
323+
const checkNowBtn = $("check-now");
324+
if (checkNowBtn) {
325+
checkNowBtn.addEventListener("click", () => {
326+
const status = $("update-status");
327+
if (status) status.textContent = "Suche…";
328+
chrome.runtime.sendMessage({ type: "check-for-updates" }, async () => {
329+
await applyUpdateBadge();
330+
const { updateInfo } = await chrome.storage.local.get({ updateInfo: null });
331+
if (status) {
332+
if (updateInfo && updateInfo.updateAvailable) {
333+
status.textContent = `Update verfügbar: v${updateInfo.latestVersion}`;
334+
} else if (updateInfo && updateInfo.latestVersion) {
335+
status.textContent = `Aktuelle Version: v${updateInfo.latestVersion}`;
336+
} else {
337+
status.textContent = "Konnte nicht prüfen (Netzwerk?)";
338+
}
339+
setTimeout(() => { status.textContent = ""; }, 4000);
340+
}
341+
});
342+
});
343+
}
344+
345+
// Storage-Listener: wenn Background den Update-Check abgeschlossen hat,
346+
// Badge live aktualisieren ohne Reload.
347+
chrome.storage.onChanged.addListener((changes, area) => {
348+
if (area === "local" && changes.updateInfo) applyUpdateBadge();
349+
});
296350
});

0 commit comments

Comments
 (0)