diff --git a/app.go b/app.go index c612167..d1d1203 100644 --- a/app.go +++ b/app.go @@ -268,7 +268,7 @@ func (a *App) GetAppVersion() VersionInfo { } } -func (a *App) interactiveAuth(client kv.Client, message map[string]interface{}) bool { +func (a *App) interactiveAuth(client kv.Client, message map[string]any) bool { callbackID := fmt.Sprintf("auth-callback-%d", client.UID()) authResult := make(chan bool) runtime.EventsOnce(a.ctx, callbackID, func(optional ...any) { diff --git a/database/database.go b/database/database.go index 8cf1a52..ec4f83d 100644 --- a/database/database.go +++ b/database/database.go @@ -64,7 +64,7 @@ func (mod *LocalDBClient) Close() error { } func (mod *LocalDBClient) GetKey(key string) (string, error) { - res, err := mod.makeRequest(kv.CmdReadKey, map[string]interface{}{"key": key}) + res, err := mod.makeRequest(kv.CmdReadKey, map[string]any{"key": key}) if err != nil { return "", err } @@ -72,14 +72,14 @@ func (mod *LocalDBClient) GetKey(key string) (string, error) { } func (mod *LocalDBClient) PutKey(key string, data string) error { - _, err := mod.makeRequest(kv.CmdWriteKey, map[string]interface{}{"key": key, "data": data}) + _, err := mod.makeRequest(kv.CmdWriteKey, map[string]any{"key": key, "data": data}) return err } func (mod *LocalDBClient) SubscribePrefix(fn kv.SubscriptionCallback, prefixes ...string) (err error, cancelFn CancelFunc) { var ids []int64 for _, prefix := range prefixes { - _, err = mod.makeRequest(kv.CmdSubscribePrefix, map[string]interface{}{"prefix": prefix}) + _, err = mod.makeRequest(kv.CmdSubscribePrefix, map[string]any{"prefix": prefix}) if err != nil { return err, nil } @@ -93,7 +93,7 @@ func (mod *LocalDBClient) SubscribePrefix(fn kv.SubscriptionCallback, prefixes . } func (mod *LocalDBClient) SubscribeKey(key string, fn func(string)) (err error, cancelFn CancelFunc) { - _, err = mod.makeRequest(kv.CmdSubscribeKey, map[string]interface{}{"key": key}) + _, err = mod.makeRequest(kv.CmdSubscribeKey, map[string]any{"key": key}) if err != nil { return err, nil } @@ -108,7 +108,7 @@ func (mod *LocalDBClient) SubscribeKey(key string, fn func(string)) (err error, } } -func (mod *LocalDBClient) GetJSON(key string, dst interface{}) error { +func (mod *LocalDBClient) GetJSON(key string, dst any) error { res, err := mod.GetKey(key) if err != nil { return err @@ -120,19 +120,19 @@ func (mod *LocalDBClient) GetJSON(key string, dst interface{}) error { } func (mod *LocalDBClient) GetAll(prefix string) (map[string]string, error) { - res, err := mod.makeRequest(kv.CmdReadPrefix, map[string]interface{}{"prefix": prefix}) + res, err := mod.makeRequest(kv.CmdReadPrefix, map[string]any{"prefix": prefix}) if err != nil { return nil, err } out := make(map[string]string) - for key, value := range res.Data.(map[string]interface{}) { + for key, value := range res.Data.(map[string]any) { out[key] = value.(string) } return out, nil } -func (mod *LocalDBClient) PutJSON(key string, data interface{}) error { +func (mod *LocalDBClient) PutJSON(key string, data any) error { byt, err := json.Marshal(data) if err != nil { return err @@ -141,8 +141,8 @@ func (mod *LocalDBClient) PutJSON(key string, data interface{}) error { return mod.PutKey(key, string(byt)) } -func (mod *LocalDBClient) PutJSONBulk(kvs map[string]interface{}) error { - encoded := make(map[string]interface{}) +func (mod *LocalDBClient) PutJSONBulk(kvs map[string]any) error { + encoded := make(map[string]any) for k, v := range kvs { byt, err := json.Marshal(v) if err != nil { @@ -160,13 +160,13 @@ func (mod *LocalDBClient) RemoveKey(key string) error { return mod.PutKey(key, "") } -func (mod *LocalDBClient) makeRequest(cmd string, data map[string]interface{}) (kv.Response, error) { +func (mod *LocalDBClient) makeRequest(cmd string, data map[string]any) (kv.Response, error) { req, chn := mod.client.MakeRequest(cmd, data) mod.hub.SendMessage(req) return getResponse(<-chn) } -func getResponse(response interface{}) (kv.Response, error) { +func getResponse(response any) (kv.Response, error) { switch c := response.(type) { case kv.Response: return c, nil diff --git a/frontend/src/ui/components/Sidebar.tsx b/frontend/src/ui/components/Sidebar.tsx index 7fc73bf..2496ad4 100644 --- a/frontend/src/ui/components/Sidebar.tsx +++ b/frontend/src/ui/components/Sidebar.tsx @@ -153,6 +153,39 @@ function SidebarLink({ route: { title, url, icon } }: { route: Route }) { ); } +function parseVersion(semanticVersion: string) { + const [version, prerelease] = semanticVersion.split('-', 2); + const [major, minor, patch] = version.split('.').map((x) => parseInt(x, 10)); + return { major, minor, patch, prerelease }; +} + +function hasLatestOrBeta(current: string, latest: string): boolean { + // If current version has no prerelease tag, just do a string check + if (!current.includes('-', 6)) { + return current.startsWith(latest); + } + + // Split MAJOR/MINOR/PATCH and check each + const parsedCurrent = parseVersion(current); + const parsedLatest = parseVersion(latest); + + if ( + parsedCurrent.major > parsedLatest.major || + parsedCurrent.minor > parsedLatest.minor || + parsedCurrent.patch > parsedLatest.patch + ) { + return true; + } + + // If latest has no prerelease, we assume stable + if (!parsedLatest.prerelease) { + return true; + } + + // Sort by prerelease (this breaks with high numbers but hopefully we won't get to alpha.10) + return parsedCurrent.prerelease > parsedLatest.prerelease; +} + export default function Sidebar({ sections, }: SidebarProps): React.ReactElement { @@ -164,22 +197,40 @@ export default function Sidebar({ null, ); const dev = version && version.startsWith('v0.0.0'); + const prerelease = !dev && version.includes('-', 6); async function fetchLastVersion() { try { - const req = await fetch( - `https://api.github.com/repos/${APPREPO}/releases/latest`, - { - headers: { - Accept: 'application/vnd.github.v3+json', + // For prerelease builds, use the list endpoint to get the latest prerelease + if (prerelease) { + const req = await fetch( + `https://api.github.com/repos/${APPREPO}/releases`, + { + headers: { + Accept: 'application/vnd.github.v3+json', + }, }, - }, - ); - const data = (await req.json()) as { html_url: string; name: string }; - setLastVersion({ - url: data.html_url, - name: data.name, - }); + ); + const data = (await req.json()) as { html_url: string; name: string }[]; + setLastVersion({ + url: data[0].html_url, + name: data[0].name, + }); + } else { + const req = await fetch( + `https://api.github.com/repos/${APPREPO}/releases/latest`, + { + headers: { + Accept: 'application/vnd.github.v3+json', + }, + }, + ); + const data = (await req.json()) as { html_url: string; name: string }; + setLastVersion({ + url: data.html_url, + name: data.name, + }); + } } catch (e) { // TODO Report error nicely console.warn('Failed checking upstream for latest version', e); @@ -209,7 +260,7 @@ export default function Sidebar({ {!dev && version && lastVersion && - !version.startsWith(lastVersion.name) && ( + !hasLatestOrBeta(version, lastVersion.name) && ( {t('menu.messages.update-available')} diff --git a/main.go b/main.go index ff2d7ea..a09dadc 100644 --- a/main.go +++ b/main.go @@ -147,7 +147,7 @@ func cliMain(ctx *cli.Context) error { } return dialog != "Yes" }, - Bind: []interface{}{ + Bind: []any{ app, }, }) diff --git a/twitch/bot.alerts.go b/twitch/bot.alerts.go index 5e7b586..0529ba6 100644 --- a/twitch/bot.alerts.go +++ b/twitch/bot.alerts.go @@ -174,7 +174,7 @@ func SetupAlerts(bot *Bot) *BotAlertsModule { } writeTemplate(bot, tpl, sub) } - addPendingSub := func(ev interface{}) { + addPendingSub := func(ev any) { switch sub := ev.(type) { case helix.EventSubChannelSubscribeEvent: pendingMux.Lock()