28 Commits
Author SHA1 Message Date
john-okeefe a16c804692 feat(ui): show per-service login status while accounts are being checked
When the app starts it verifies all three services sequentially, which
could leave the other two login controls looking idle or trigger login
actions before their real state is known. Track which service is
currently being checked and reflect it in the header:

- Add a serviceLoggingIn store (plus setServiceLoggingIn and
  isServiceLoggingIn helpers) to GlobalVariablesAndHelperFunctions.
- Have the loginTo* functions and the startup CheckIf* functions toggle
  their service in the store (seed all three at startup, clear each as
  it resolves).
- In Header, the login buttons show a spinner with 'Checking AniList' /
  'Checking MAL' / 'Checking Simkl' and are disabled while that service
  is being checked, reverting to the normal 'AniList Login' style label
  once its state is known.
- In the avatar menu, the 'Login to X' row shows a spinner with the
  matching 'Checking X' text while that service is logging in.
2026-08-29 13:13:42 -04:00
john-okeefe d08756c36f fix(mal): distinguish silent token refresh from a fresh re-login
Two MAL login issues were caused by the refresh path being conflated
with the full browser OAuth flow:

1. A spurious 'It is now safe to close your browser tab' dialog was
   shown after a *silent* background token refresh, even though the
   browser was never opened. The dialog now belongs only to the
   browser-callback handler (handleMyAnimeListCallback); the refresh
   raises no dialog.

2. When both the access token and the refresh token were invalid, the
   app reported MAL as logged in without a username. Now, if a refresh
   fails (HttpClient error, non-2xx, or an empty access token), the
   stale tokens are cleared and a fresh browser OAuth login is
   automatically initiated instead of silently returning an empty user.

refreshMyAnimeListAuthorizationToken now returns a bool indicating
whether a fresh access token was actually obtained, letting
GetMyAnimeListLoggedInUser decide between retrying with the refreshed
token or falling back to a full re-login.
2026-08-29 13:13:37 -04:00
john-okeefe b42456f255 chore(gitignore): ignore build binaries and archive artifacts
Ignore compiled binaries and packaging archives that are generated
during a build, without hiding tracked build source files:

- build/bin: the compiled application binary
- *.tar / *.tar.gz: packaging archives (e.g. Wails .ptmp temp dirs)
- /AniTrack: the root app binary (fix the previous './AniTrack'
  pattern, which gitignore did not match)
2026-08-29 13:13:25 -04:00
john-okeefe 49ac5bef03 chore(wailsjs): regenerate Go bindings for the AniList Browse feature
Re-run the Wails binding generator so the frontend reflects the browse
Go types that were added in earlier commits (AniListBrowse plus the
Media / MediaList model refinements):

- App.d.ts / App.js: expose the new AniListBrowse() method
- models.ts: add the Media model, the anonymous tags element type,
  and type MediaList.media as Media instead of a generic object
2026-08-29 13:12:22 -04:00
john-okeefe b9f4c12839 chore(deps): upgrade Wails to v2.15.0 and bump golang.org/x dependencies
Reinstall the Go toolchain and rebuild wails against v2.15.0, then run
'go mod tidy' to update the dependency graph:

- github.com/wailsapp/wails/v2  v2.12.0 -> v2.15.0
- golang.org/x/crypto  v0.52.0 -> v0.53.0
- golang.org/x/net     v0.55.0 -> v0.56.0
- golang.org/x/sys     v0.45.0 -> v0.46.0
- golang.org/x/term    v0.43.0 -> v0.44.0
- golang.org/x/text    v0.37.0 -> v0.39.0

The regenerated Wails runtime bindings (runtime.d.ts / runtime.js) are
included as they surface new APIs shipped in v2.15: the cross-platform
notification API (InitializeNotifications, SendNotification, etc.) and
the EventsOffAll event helper.
2026-08-29 13:12:18 -04:00
john-okeefe 54c109ae3b rewrote standard media call and added browse function 2026-07-22 15:22:58 -04:00
john-okeefe d3bc93f6c9 tested for standardizing media call 2026-07-22 15:22:38 -04:00
john-okeefe 0384f570c5 added AniList Browse Fetch for new feature 2026-07-21 16:53:25 -04:00
john-okeefe 6925cd4e08 fixed gitignore to just ignore the Anitrack binary at root 2026-07-21 16:52:47 -04:00
john-okeefe 23960e0613 chore: upgrade Go to 1.25 and update dependencies
- Bump Go toolchain from 1.24.0 to 1.25.0
- Upgrade Wails from v2.10.1 to v2.12.0
- Upgrade tidwall/gjson from v1.18.0 to v1.19.0
- Upgrade labstack/echo from v4.13.3 to v4.15.2
- Upgrade labstack/gommon from v0.4.2 to v0.5.0
- Upgrade samber/lo from v1.49.1 to v1.53.0
- Upgrade gorilla/websocket to v1.5.3 (new transitive dep)
- Upgrade go-webview2 from v1.0.19 to v1.0.23
- Upgrade danieljoos/wincred from v1.2.2 to v1.2.3
- Upgrade godbus/dbus/v5 from v5.1.0 to v5.2.2
- Upgrade jchv/go-winloader to latest
- Upgrade mattn/go-isatty from v0.0.20 to v0.0.22
- Upgrade tidwall/match from v1.1.1 to v1.2.0
- Upgrade golang.org/x/crypto, net, sys, term to latest
- Add go-toast/v2 as new transitive dependency from Wails
2026-05-27 20:17:09 -04:00
john-okeefe 669b41c62c chore: bump version to 1.5.5 2026-05-27 20:02:16 -04:00
john-okeefe c54a11e7bd fix(build): improve Linux install script reliability and idempotency
- Fix tilde expansion: replace quoted '~' paths with $HOME so they
  actually resolve to the user's home directory instead of being
  treated literally
- Add existence checks before copying files so the script is
  idempotent and skips already-installed resources
- Add progress/status echo messages for each installation step
  so the user can see what is being done
2026-05-27 20:01:39 -04:00
john-okeefe b6bdee4df6 chore: bump version to 1.5.1 2026-05-27 17:41:27 -04:00
john-okeefe cbda217d18 chore: add compiled AniTrack binary to .gitignore
The project's compiled binary (AniTrack) was not covered by existing
gitignore patterns. Only platform-specific extensions like .exe, .dll,
.so, and .dylib were ignored. Add the bare binary name to prevent the
locally-built executable from appearing as an untracked file.
2026-05-27 17:41:19 -04:00
john-okeefe ceb756a1a8 chore: regenerate Wails TypeScript models for FlexString type
Auto-generated by wails generate module. The FlexString custom type
produces unconventional namespace names in the generated TypeScript but
does not affect runtime behavior.
2026-05-27 17:36:05 -04:00
john-okeefe 3621b66437 fix: handle MAL API returning numbers instead of strings for zero-value statistics
The MyAnimeList API inconsistently returns statistics status fields (watching,
completed, on_hold, dropped, plan_to_watch) as quoted strings for non-zero
values (e.g. "8217") but as bare numbers for zero values (e.g. 0). This caused
JSON unmarshal errors for anime with zero counts in any status field.

Introduce a FlexString custom type that implements json.Unmarshaler to accept
both JSON strings and JSON numbers, always storing the result as a string. The
type definition lives in MALTypes.go and the unmarshal logic in MALFunctions.go
to keep static types and behavior separate.
2026-05-27 17:28:54 -04:00
john-okeefe 7f92b1714e chore: bump version to 1.5.0
Update productVersion from 1.0.0 to 1.5.0 in Wails project
configuration to reflect the addition of AniList watchlist sorting,
extracted refresh button component, and pagination improvements.
2026-04-24 23:02:21 -04:00
john-okeefe cd142f7601 chore: remove release notes file 2026-04-24 23:01:29 -04:00
john-okeefe fe48e6a8c4 docs: add release notes for v1.0.0
Add comprehensive release notes documenting the first stable release of
AniTrack, including highlights such as webkit2gtk 4.1 Linux builds,
comprehensive error handling across all services, AniList watchlist
sorting, and various UI polish improvements.
2026-04-24 23:01:00 -04:00
john-okeefe 24d4d24edf fix(frontend): remove extraneous top margin from pagination controls
Remove the mt-5 class from the pagination container div to eliminate
unnecessary spacing between the pagination buttons and the per-page
selector.
2026-04-24 23:00:54 -04:00
john-okeefe b68abfc1c9 refactor(frontend): extract refresh button into standalone component and put sort dropdown in watchlist header
Move the refresh button out of the WatchList component header and into
its own RefreshWatchListButton.svelte component, placing it at the top
of the Home route page with right-alignment. This gives the refresh
control better visibility at the page level rather than being buried
inside the watchlist header.

Replace the removed refresh button in the WatchList header with the new
Sort dropdown component, giving users sort controls directly alongside
the watchlist title.
2026-04-24 23:00:50 -04:00
john-okeefe eadc96fb4f feat(frontend): add AniList watchlist sort dropdown component
Implement a Sort.svelte component that provides a dropdown menu allowing
users to dynamically reorder their AniList watchlist by various parameters
including media title, score, status, progress, popularity, and dates.

The component binds to the global aniListSort store and fetches updated
watchlist data from the backend whenever the user selects a new sort option,
providing immediate visual feedback.
2026-04-24 23:00:35 -04:00
john-okeefe 6a662cdf6e chore: bump version to 1.0.0
Update productVersion from 0.7.0 to 1.0.0 in Wails project
configuration to reflect the first major release milestone.
2026-04-24 19:39:08 -04:00
john-okeefe 2a9777872f chore: update author email address
Change contact email from jokeefe@fastmail.com to admin@linuxhg.com
in the Wails project configuration.
2026-04-24 19:38:56 -04:00
john-okeefe b03d4ab7e3 build: add Makefile with webkit2_41 build tag for Linux
Wails v2 defaults to linking against webkit2gtk-4.0, but modern Linux
distributions (e.g. Fedora) only ship webkit2gtk-4.1. The webkit2_41
build tag tells Wails to link against the correct library.

Provides two targets:
- make dev: run wails dev with the correct tag
- make build: build the production binary with the correct tag
- make clean: remove build artifacts
2026-04-24 17:10:29 -04:00
john-okeefe ff1b8fb742 chore: remove release notes for v0.6.5 2026-04-24 17:10:21 -04:00
john-okeefe cbd4c7cd01 docs: add release notes for v0.6.5
Add detailed release notes for the v0.6.5 patch release covering:
- Enhanced button disabled states in Anime and Pagination components
- Fixed media cover image sizing in WatchList
- AvatarMenu service status indicator
- TypeScript type safety fixes in Pagination event handlers
- Code formatting standardization across components
2026-04-24 17:10:03 -04:00
john-okeefe 335d757255 chore: remove release notes file
Release notes will be managed externally. Keeping repository focused on source code only.
2026-03-30 20:17:09 -04:00
33 changed files with 1957 additions and 619 deletions
+3 -1
View File
@@ -35,4 +35,6 @@ environment.go
http-client.private.env.json
# Build artifacts
build/*.tar.gz
*.tar
*.tar.gz
/AniTrack
+707 -202
View File
@@ -74,37 +74,83 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
mediaId
userId
media {
id
idMal
title {
romaji
english
native
}
description
coverImage {
large
}
season
seasonYear
status
episodes
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
genres
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
startDate {
year
month
day
}
endDate {
year
month
day
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
status
startedAt{
year
@@ -204,37 +250,84 @@ func (a *App) AniListSearch(query string) (interface{}, error) {
perPage
}
media (search: $search, type: $listType) {
id
idMal
title {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations{
nodes{
id
title{
userPreferred
romaji
english
native
}
description
coverImage {
large
}
season
seasonYear
status
episodes
nextAiringEpisode{
airingAt
timeUntilAiring
episode
}
genres
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
}
startDate{
year
month
day
}
endDate{
year
month
day
}
nextAiringEpisode{
airingAt
timeUntilAiring
episode
}
airingSchedule{
nodes{
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
}
}
`,
@@ -271,93 +364,140 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (An
Variables Variables `json:"variables"`
}{
Query: `
query(
$page: Int
$perPage: Int
$userId: Int
$listType: MediaType
$status: MediaListStatus
$sort:[MediaListSort]
) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
perPage
currentPage
lastPage
hasNextPage
}
mediaList(userId: $userId, type: $listType, status: $status, sort: $sort) {
id
mediaId
userId
media {
id
query (
$page: Int
$perPage: Int
$userId: Int
$listType: MediaType
$status: MediaListStatus
$sort: [MediaListSort]
) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
perPage
currentPage
lastPage
hasNextPage
}
mediaList(userId: $userId, type: $listType, status: $status, sort: $sort) {
id
mediaId
userId
media {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations{
nodes{
id
title{
userPreferred
romaji
english
native
}
}
}
startDate{
year
month
day
}
endDate{
year
month
day
}
nextAiringEpisode{
airingAt
timeUntilAiring
episode
}
genres
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
status
startedAt {
year
month
day
}
completedAt {
year
month
day
}
notes
progress
score
repeat
user {
id
name
avatar{
large
medium
}
statistics {
anime {
count
statuses {
status
count
}
airingSchedule{
nodes{
id
airingAt
timeUntilAiring
episode
mediaId
}
}
}
genres
tags{
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
}
}
status
startedAt {
year
month
day
}
completedAt {
year
month
day
}
notes
progress
score
repeat
user {
id
name
avatar {
large
medium
}
statistics {
anime {
count
statuses {
status
count
}
}
}
}
}
}
}
`,
Variables: Variables{
Page: page,
@@ -424,86 +564,138 @@ func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSi
Variables AniListUpdateVariables `json:"variables"`
}{
Query: `
mutation(
$mediaId:Int,
$progress:Int,
$status:MediaListStatus,
$score:Float,
$repeat:Int,
$notes:String,
$startedAt:FuzzyDateInput,
$completedAt:FuzzyDateInput,
){
SaveMediaListEntry(
mediaId:$mediaId,
progress:$progress,
status:$status,
score:$score,
repeat:$repeat,
notes:$notes,
startedAt:$startedAt
completedAt:$completedAt
){
id
mediaId
userId
media {
id
idMal
title {
romaji
english
native
}
description
coverImage {
large
}
season
seasonYear
status
episodes
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
isAdult
}
status
startedAt{
year
month
day
}
completedAt{
year
month
day
}
notes
progress
score
repeat
user {
id
name
avatar{
large
medium
}
statistics{
anime{
count
statuses{
status
count
}
}
}
}
}
}
mutation (
$mediaId: Int
$progress: Int
$status: MediaListStatus
$score: Float
$repeat: Int
$notes: String
$startedAt: FuzzyDateInput
$completedAt: FuzzyDateInput
) {
SaveMediaListEntry(
mediaId: $mediaId
progress: $progress
status: $status
score: $score
repeat: $repeat
notes: $notes
startedAt: $startedAt
completedAt: $completedAt
) {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
startDate {
year
month
day
}
endDate {
year
month
day
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
status
startedAt {
year
month
day
}
completedAt {
year
month
day
}
notes
progress
score
repeat
user {
id
name
avatar {
large
medium
}
statistics {
anime {
count
statuses {
status
count
}
}
}
}
}
}
`,
Variables: updateBody,
}
@@ -558,3 +750,316 @@ func (a *App) AniListDeleteEntry(mediaListId int) DeleteAniListReturn {
return post
}
func (a *App) AniListBrowse(
page int,
perPage int,
id int,
isAdult bool,
search string,
format []string,
status string,
countryOfOrigin string,
source string,
season string,
seasonYear int,
year string,
onList bool,
yearLesser int,
yearGreater int,
episodeLesser int,
episodeGreater int,
durationLesser int,
durationGreater int,
chapterLesser int,
chapterGreater int,
volumeLesser int,
volumeGreater int,
licensedBy []int,
isLicensed bool,
genres []string,
excludedGenres []string,
tags []string,
excludedTags []string,
minimumTagRank int,
sort []string) (AniListCurrentUserWatchList, error) {
// user := a.GetAniListLoggedInUser()
type Variables struct {
Page int `json:"page"`
PerPage int `json:"perPage"`
Id int `json:"id"`
IsAdult bool `json:"isAdult"`
Search string `json:"search"`
Format []string `json:"format"`
Status string `json:"status"`
CountryOfOrigin string `json:"countryOfOrigin"`
Source string `json:"source"`
Season string `json:"season"`
SeasonYear int `json:"seasonYear"`
Year string `json:"year"`
OnList bool `json:"onList"`
YearLesser int `json:"yearLesser"`
YearGreater int `json:"yearGreater"`
EpisodeLesser int `json:"episodeLesser"`
EpisodeGreater int `json:"episodeGreater"`
DurationLesser int `json:"durationLesser"`
DurationGreater int `json:"durationGreater"`
ChapterLesser int `json:"chapterLesser"`
ChapterGreater int `json:"chapterGreater"`
VolumeLesser int `json:"volumeLesser"`
VolumeGreater int `json:"volumeGreater"`
LicensedBy []int `json:"licensedBy"`
IsLicensed bool `json:"isLicensed"`
Genres []string `json:"genres"`
ExcludedGenres []string `json:"excludedGenres"`
Tags []string `json:"tags"`
ExcludedTags []string `json:"excludedTags"`
MinimumTagRank int `json:"minimumTagRank"`
Sort []string `json:"sort"`
}
body := struct {
Query string `json:"query"`
Variables Variables `json:"variables"`
}{
Query: `
query (
$page: Int = 1
$perPage: Int = 20
$id: Int
$isAdult: Boolean = false
$search: String
$format: [MediaFormat]
$status: MediaStatus
$countryOfOrigin: CountryCode
$source: MediaSource
$season: MediaSeason
$seasonYear: Int
$year: String
$onList: Boolean
$yearLesser: FuzzyDateInt
$yearGreater: FuzzyDateInt
$episodeLesser: Int
$episodeGreater: Int
$durationLesser: Int
$durationGreater: Int
$chapterLesser: Int
$chapterGreater: Int
$volumeLesser: Int
$volumeGreater: Int
$licensedBy: [Int]
$isLicensed: Boolean
$genres: [String]
$excludedGenres: [String]
$tags: [String]
$excludedTags: [String]
$minimumTagRank: Int
$sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
perPage
currentPage
lastPage
hasNextPage
}
media(
id: $id
type: ANIME
season: $season
format_in: $format
status: $status
countryOfOrigin: $countryOfOrigin
source: $source
search: $search
onList: $onList
seasonYear: $seasonYear
startDate_like: $year
startDate_lesser: $yearLesser
startDate_greater: $yearGreater
episodes_lesser: $episodeLesser
episodes_greater: $episodeGreater
duration_lesser: $durationLesser
duration_greater: $durationGreater
chapters_lesser: $chapterLesser
chapters_greater: $chapterGreater
volumes_lesser: $volumeLesser
volumes_greater: $volumeGreater
licensedById_in: $licensedBy
isLicensed: $isLicensed
genre_in: $genres
genre_not_in: $excludedGenres
tag_in: $tags
tag_not_in: $excludedTags
minimumTagRank: $minimumTagRank
sort: $sort
isAdult: $isAdult
) {
id
mediaId
userId
media {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
startDate {
year
month
day
}
endDate {
year
month
day
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
}
}
`,
Variables: Variables{
Page: page,
PerPage: perPage,
Id: id,
IsAdult: isAdult,
Search: search,
Format: format,
Status: status,
CountryOfOrigin: countryOfOrigin,
Source: source,
Season: season,
SeasonYear: seasonYear,
Year: year,
OnList: onList,
YearLesser: yearLesser,
YearGreater: yearGreater,
EpisodeLesser: episodeLesser,
EpisodeGreater: episodeGreater,
DurationLesser: durationLesser,
DurationGreater: durationGreater,
ChapterLesser: chapterLesser,
ChapterGreater: chapterGreater,
VolumeLesser: volumeLesser,
VolumeGreater: volumeGreater,
LicensedBy: licensedBy,
IsLicensed: isLicensed,
Genres: genres,
ExcludedGenres: excludedGenres,
Tags: tags,
ExcludedTags: excludedTags,
MinimumTagRank: minimumTagRank,
Sort: sort,
},
}
returnedBody, status := AniListQuery(body, true)
var badPost struct {
Errors []struct {
Message string `json:"message"`
Status int `json:"status"`
Locations []struct {
Line int `json:"line"`
Column int `json:"column"`
} `json:"locations"`
} `json:"errors"`
Data any `json:"data"`
}
var post AniListCurrentUserWatchList
if status == "200 OK" {
err := json.Unmarshal(returnedBody, &post)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
}
// Getting the real total, finding the real last page and storing that in the Page info
statuses := post.Data.Page.MediaList[0].User.Statistics.Anime.Statuses
var total int
for _, status := range statuses {
if status.Status == "CURRENT" {
total = status.Count
}
}
lastPage := total / perPage
post.Data.Page.PageInfo.Total = total
post.Data.Page.PageInfo.LastPage = lastPage
}
if status == "403 Forbidden" {
err := json.Unmarshal(returnedBody, &badPost)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return post, fmt.Errorf("API authentication error")
}
return post, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
}
if status != "200 OK" {
return post, fmt.Errorf("API request failed with status: %s", status)
}
return post, nil
}
+97 -35
View File
@@ -37,6 +37,20 @@ type AniListCurrentUserWatchList struct {
} `json:"data"`
}
type AniListBrowseList struct {
Data struct {
Page struct {
PageInfo struct {
Total int `json:"total"`
PerPage int `json:"perPage"`
CurrentPage int `json:"currentPage"`
LastPage int `json:"lastPage"`
HasNextPage bool `json:"hasNextPage"`
} `json:"pageInfo"`
Media []Media `json:"mediaList"`
} `json:"Page"`
} `json:"data"`
}
type AniListGetSingleAnime struct {
Data struct {
MediaList MediaList `json:"MediaList"`
@@ -49,43 +63,91 @@ type AniListUpdateReturn struct {
}
}
type Media struct {
ID int `json:"id"`
IDMal int `json:"idMal"`
Title struct {
UserPreferred string `json:"userPreferred"`
Romaji string `json:"romaji"`
English string `json:"english"`
Native string `json:"native"`
} `json:"title"`
Description string `json:"description"`
CoverImage struct {
ExtraLarge string
Large string `json:"large"`
Medium string
Color string
} `json:"coverImage"`
BannerImage string
Format string
Season string `json:"season"`
SeasonYear int `json:"seasonYear"`
Status string `json:"status"`
Episodes int `json:"episodes"`
Duration int
CountryOfOrigin string
Source string
Synonyms []string
AverageScore int
MeanScore int
Popularity int
Trending int
Favourites int
isFavourite bool
Relations struct {
nodes struct {
id int
Title struct {
UserPreferred string `json:"userPreferred"`
Romaji string `json:"romaji"`
English string `json:"english"`
Native string `json:"native"`
} `json:"title"`
}
}
StartDate struct {
Year int
Month int
Day int
}
EndDate struct {
Year int
Month int
Day int
}
NextAiringEpisode struct {
AiringAt int `json:"airingAt"`
TimeUntilAiring int `json:"timeUntilAiring"`
Episode int `json:"episode"`
} `json:"nextAiringEpisode"`
AiringSchedule struct {
Nodes struct {
Id int
AiringAt int
TimeUntilAiring int
Episode int
MediaId int
}
}
Genres []string `json:"genres"`
Tags []struct {
Id int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Rank int `json:"rank"`
IsMediaSpoiler bool `json:"isMediaSpoiler"`
IsAdult bool `json:"isAdult"`
} `json:"tags"`
IsAdult bool `json:"isAdult"`
}
type MediaList struct {
ID int `json:"id"`
MediaID int `json:"mediaId"`
UserID int `json:"userId"`
Media struct {
ID int `json:"id"`
IDMal int `json:"idMal"`
Title struct {
Romaji string `json:"romaji"`
English string `json:"english"`
Native string `json:"native"`
} `json:"title"`
Description string `json:"description"`
CoverImage struct {
Large string `json:"large"`
} `json:"coverImage"`
Season string `json:"season"`
SeasonYear int `json:"seasonYear"`
Status string `json:"status"`
Episodes int `json:"episodes"`
NextAiringEpisode struct {
AiringAt int `json:"airingAt"`
TimeUntilAiring int `json:"timeUntilAiring"`
Episode int `json:"episode"`
} `json:"nextAiringEpisode"`
Genres []string `json:"genres"`
Tags []struct {
Id int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Rank int `json:"rank"`
IsMediaSpoiler bool `json:"isMediaSpoiler"`
IsAdult bool `json:"isAdult"`
} `json:"tags"`
IsAdult bool `json:"isAdult"`
} `json:"media"`
ID int `json:"id"`
MediaID int `json:"mediaId"`
UserID int `json:"userId"`
Status string `json:"status"`
Media Media `json:"media"`
StartedAt struct {
Year int `json:"year"`
Month int `json:"month"`
+15
View File
@@ -11,6 +11,21 @@ import (
"strings"
)
// Unmarshalling accidental numbers received from MAL to strings
func (f *FlexString) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err == nil {
*f = FlexString(s)
return nil
}
var n json.Number
if err := json.Unmarshal(data, &n); err == nil {
*f = FlexString(string(n))
return nil
}
return fmt.Errorf("FlexString: invalid value")
}
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) {
client := &http.Client{}
+10 -6
View File
@@ -1,6 +1,10 @@
package main
import "time"
import (
"time"
)
type FlexString string
type MyAnimeListJWT struct {
TokenType string `json:"token_type"`
@@ -129,11 +133,11 @@ type MALAnime struct {
Statistics struct {
NumListUsers int `json:"num_list_users" ts_type:"numListUsers"`
Status struct {
Watching string `json:"watching" ts_type:"watching"`
Completed string `json:"completed" ts_type:"completed"`
OnHold string `json:"on_hold" ts_type:"onHold"`
Dropped string `json:"dropped" ts_type:"dropped"`
PlanToWatch string `json:"plan_to_watch" ts_type:"planToWatch"`
Watching FlexString `json:"watching" ts_type:"string"`
Completed FlexString `json:"completed" ts_type:"string"`
OnHold FlexString `json:"on_hold" ts_type:"string"`
Dropped FlexString `json:"dropped" ts_type:"string"`
PlanToWatch FlexString `json:"plan_to_watch" ts_type:"string"`
}
}
}
+30 -12
View File
@@ -235,7 +235,7 @@ func getMyAnimeListAuthorizationToken(content string, verifier *CodeVerifier) My
return post
}
func refreshMyAnimeListAuthorizationToken() {
func refreshMyAnimeListAuthorizationToken() bool {
dataForURLs := struct {
GrantType string `json:"grant_type"`
RefreshToken string `json:"refresh_token"`
@@ -260,13 +260,15 @@ func refreshMyAnimeListAuthorizationToken() {
response, err := http.NewRequest("POST", "https://myanimelist.net/v1/oauth2/token", strings.NewReader(data.Encode()))
if err != nil {
log.Printf("Failed at response, %s\n", err)
return false
}
response.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
res, resErr := client.Do(response)
if resErr != nil {
log.Printf("Failed at res, %s\n", err)
log.Printf("Failed at res, %s\n", resErr)
return false
}
defer res.Body.Close()
@@ -274,13 +276,22 @@ func refreshMyAnimeListAuthorizationToken() {
returnedBody, err := io.ReadAll(res.Body)
if err != nil {
log.Printf("Could not read returned body, %s\n", err)
return false
}
err = json.Unmarshal(returnedBody, &myAnimeListJwt)
var refreshed MyAnimeListJWT
err = json.Unmarshal(returnedBody, &refreshed)
if err != nil {
log.Printf("Failed at unmarshal, %s\n", err)
return false
}
if refreshed.AccessToken == "" {
return false
}
myAnimeListJwt = refreshed
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListTokenType",
Data: []byte(myAnimeListJwt.TokenType),
@@ -297,26 +308,33 @@ func refreshMyAnimeListAuthorizationToken() {
Key: "MyAnimeListRefreshToken",
Data: []byte(myAnimeListJwt.RefreshToken),
})
_, err = runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
Title: "MyAnimeList Authorization",
Message: "It is now safe to close your browser tab",
})
if err != nil {
fmt.Println(err)
}
return true
}
func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
a.MyAnimeListLogin()
user := createUser()
if user.Name == "" {
refreshMyAnimeListAuthorizationToken()
if user.Name == "" && !a.refreshMyAnimeListAndGetUser(&user) {
a.LogoutMyAnimeList()
a.MyAnimeListLogin()
user = createUser()
}
return user
}
func (a *App) refreshMyAnimeListAndGetUser(user *MyAnimeListUser) bool {
if !refreshMyAnimeListAuthorizationToken() {
return false
}
freshUser := createUser()
if freshUser.Name == "" {
return false
}
*user = freshUser
return true
}
func createUser() MyAnimeListUser {
client := &http.Client{}
+12
View File
@@ -0,0 +1,12 @@
TAGS := webkit2_41
.PHONY: dev build clean
dev:
wails dev -tags $(TAGS)
build:
wails build -tags $(TAGS)
clean:
rm -rf build/bin/*
-166
View File
@@ -1,166 +0,0 @@
# 🎉 AniTrack v0.7.0 Release Notes
## 🚨 Major Feature: Comprehensive Error Handling
This release brings **massive improvements** to application stability and user experience. The app will **no longer crash** when API services are down or experiencing errors!
---
## ✨ What's New
### 🛡️ Graceful API Error Handling
- **Application Stability**: App now remains running even when AniList, MAL, or Simkl APIs are down
- **Visual Error Feedback**: Beautiful modal dialogs display error details instead of silent crashes
- **Manual Retry**: Users can retry failed API connections with a single button click
- **Continue Using App**: Dismiss error messages and continue with limited functionality
### 🔧 Backend Improvements
#### AniList Integration
- Replaced all `log.Fatal()` calls with proper error returns
- Added error handling for API failures, network issues, and authentication errors
- `AniListSearch` now returns detailed error messages
- `GetAniListUserWatchingList` gracefully handles 403/404 responses
#### MyAnimeList Integration
- Updated `MALHelper` to return structured errors
- All MAL API functions now return errors instead of crashing:
- `GetMyAnimeList`
- `MyAnimeListUpdate`
- `GetMyAnimeListAnime`
- `DeleteMyAnimeListEntry`
- Fixed OAuth callback server to prevent shutdown on errors
#### Simkl Integration
- Updated `SimklHelper` with status code validation (200-299 range)
- All Simkl API functions now return errors:
- `SimklGetUserWatchlist`
- `SimklSyncEpisodes`
- `SimklSyncRating`
- `SimklSyncStatus`
- `SimklSearch`
- `SimklSyncRemove`
- Replaced `log.Fatal()` with proper error logging
### 💻 Frontend Improvements
#### New Error Management System
- **Centralized Error State**: New reactive stores for API errors
- `apiError` - Tracks current error with service, message, and status
- `isApiDown` - Quick flag for API availability
- **Error Helper Functions**:
- `setApiError()` - Set error state from any component
- `clearApiError()` - Reset error state
#### New Error Modal Component
- Auto-displays when API errors occur
- Shows service name, error message, and HTTP status code
- **"Retry Connection"** button - Attempts to reconnect to the failed service
- **"Dismiss"** button - Close modal and continue using the app
- Beautiful red-themed styling with Tailwind CSS
- Supports all three services (AniList, MAL, Simkl)
#### Updated User Interface
- Home page now displays "API Unavailable" state when services are down
- Helpful warning messages guide users during outages
- App structure remains visible and functional during errors
---
## 🐛 Bug Fixes
- **Critical**: Fixed app crashes when AniList API returns 403 Forbidden errors
- **Critical**: Fixed app crashes when AniList API is down or unreachable
- **Critical**: Fixed MAL OAuth server crashing on connection errors
- **Critical**: Fixed Simkl JSON marshaling errors crashing the app
- **Improved**: Better error messages for all API failures
---
## 📊 Technical Details
### Go Backend Changes
- **AniListFunctions.go**: Updated 3 functions with error handling
- **MALFunctions.go**: Updated 5 functions with error handling
- **MALUserFunctions.go**: Fixed server error handling
- **SimklFunctions.go**: Updated 7 functions with error handling
- **SimklUserFunctions.go**: Fixed 2 critical error handling issues
### Frontend Changes
- **ErrorModal.svelte**: New component (73 lines)
- **GlobalVariablesAndHelperFunctions.svelte**: Added error state system (27 lines)
- **App.svelte**: Integrated ErrorModal into main layout
- **CheckIfAniListLoggedInAndLoadWatchList.svelte**: Added error handling
- **Home.svelte**: Added API down state display
---
## 🎯 Impact
### Before (v0.6.5)
- ❌ App crashes to desktop when AniList API is down
- ❌ Users see only console error messages
- ❌ Must restart app after API failures
- ❌ No way to retry failed connections
### After (v0.7.0)
- ✅ App stays open during any API failures
- ✅ Beautiful modal dialogs explain what went wrong
- ✅ One-click retry to reconnect services
- ✅ Continue using app with limited functionality
---
## 🔄 Upgrading
No migration needed! This release is fully backward compatible.
Simply run:
```bash
git pull
wails build
```
Or download the latest release from the releases page.
---
## 🙏 Acknowledgments
This release addresses a critical stability issue that was affecting users during API service outages. Special thanks to the community for reporting these crashes and providing detailed error logs.
---
## 📝 Full Changelog
### Added
- Error modal component with retry/dismiss functionality
- Centralized error state management system
- API availability status tracking
- Visual feedback for all API failures
- Manual retry functionality for all services
### Changed
- AniList API functions to return errors instead of crashing
- MAL API functions to return errors instead of crashing
- Simkl API functions to return errors instead of crashing
- Error handling from console-only to UI modal dialogs
- App behavior from crash-on-error to graceful-degradation
### Fixed
- App crashes on AniList 403 errors
- App crashes on network failures
- MAL OAuth server shutdown on errors
- Simkl JSON marshaling crashes
- All `log.Fatal()` calls that terminated the app
### Removed
- `alert()` calls in favor of modal system
- Vite timestamp file (cleanup)
---
**Version**: 0.7.0
**Release Date**: March 2026
**Upgrade Difficulty**: ⭐ Easy (backward compatible)
**Recommended**: ⭐⭐⭐⭐⭐ Highly recommended for all users
@@ -16,7 +16,6 @@ headers {
}
body:graphql {
# Write your query or mutation here
query (
$page: Int
$perPage: Int
@@ -41,23 +40,79 @@ body:graphql {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
startDate {
year
month
day
}
endDate {
year
month
day
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
status
startedAt {
@@ -17,63 +17,161 @@ headers {
}
body:graphql {
mutation(
$mediaId:Int,
$progress:Int,
$status:MediaListStatus,
$score:Float,
$repeat:Int,
$notes:String,
$startedAt:FuzzyDateInput,
$completedAt:FuzzyDateInput,
){
SaveMediaListEntry(
mediaId:$mediaId,
progress:$progress,
status:$status,
score:$score,
repeat:$repeat,
notes:$notes,
startedAt:$startedAt
completedAt:$completedAt
){
mediaId
progress
status
score
repeat
notes
startedAt{
year
month
day
}
completedAt{
year
month
day
}
}
}
mutation (
$mediaId: Int
$progress: Int
$status: MediaListStatus
$score: Float
$repeat: Int
$notes: String
$startedAt: FuzzyDateInput
$completedAt: FuzzyDateInput
) {
SaveMediaListEntry(
mediaId: $mediaId
progress: $progress
status: $status
score: $score
repeat: $repeat
notes: $notes
startedAt: $startedAt
completedAt: $completedAt
) {
id
mediaId
userId
media {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
startDate {
year
month
day
}
endDate {
year
month
day
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
status
startedAt {
year
month
day
}
completedAt {
year
month
day
}
notes
progress
score
repeat
user {
id
name
avatar {
large
medium
}
statistics {
anime {
count
statuses {
status
count
}
}
}
}
}
}
}
body:graphql:vars {
{
"mediaId":170998,
"progress":5,
"status":"CURRENT",
"score":9.0,
"repeat":0,
"notes":",malSync::eyJ1IjoiaHR0cHM6Ly93d3cuY3J1bmNoeXJvbGwuY29tL3Nlcmllcy9HVkRIWDg1Wk4vI3NlYXNvbj1HNjNWQzJHUUsiLCJwIjoiIn0=::",
"startedAt":{
"year":2024,
"month":7,
"day":10
},
"completedAt":{
"year": 0,
"month":0,
"day":0
}
"mediaId": 170998,
"progress": 5,
"status": "CURRENT",
"score": 9,
"repeat": 0,
"notes": ",malSync::eyJ1IjoiaHR0cHM6Ly93d3cuY3J1bmNoeXJvbGwuY29tL3Nlcmllcy9HVkRIWDg1Wk4vI3NlYXNvbj1HNjNWQzJHUUsiLCJwIjoiIn0=::",
"startedAt": {
"year": 2024,
"month": 7,
"day": 10
},
"completedAt": {
"year": 0,
"month": 0,
"day": 0
}
}
}
@@ -0,0 +1,191 @@
meta {
name: SeasonBasedBrowse
type: graphql
seq: 1
}
post {
url: https://graphql.anilist.co
body: graphql
auth: inherit
}
body:graphql {
query (
$page: Int = 1
$perPage: Int = 20
$id: Int
$type: MediaType
$isAdult: Boolean = false
$search: String
$format: [MediaFormat]
$status: MediaStatus
$countryOfOrigin: CountryCode
$source: MediaSource
$season: MediaSeason
$seasonYear: Int
$year: String
$onList: Boolean
$yearLesser: FuzzyDateInt
$yearGreater: FuzzyDateInt
$episodeLesser: Int
$episodeGreater: Int
$durationLesser: Int
$durationGreater: Int
$chapterLesser: Int
$chapterGreater: Int
$volumeLesser: Int
$volumeGreater: Int
$licensedBy: [Int]
$isLicensed: Boolean
$genres: [String]
$excludedGenres: [String]
$tags: [String]
$excludedTags: [String]
$minimumTagRank: Int
$sort: [MediaSort] = [POPULARITY_DESC, SCORE_DESC]
) {
Page(page: $page, perPage: $perPage) {
pageInfo {
total
perPage
currentPage
lastPage
hasNextPage
}
media(
id: $id
type: $type
season: $season
format_in: $format
status: $status
countryOfOrigin: $countryOfOrigin
source: $source
search: $search
onList: $onList
seasonYear: $seasonYear
startDate_like: $year
startDate_lesser: $yearLesser
startDate_greater: $yearGreater
episodes_lesser: $episodeLesser
episodes_greater: $episodeGreater
duration_lesser: $durationLesser
duration_greater: $durationGreater
chapters_lesser: $chapterLesser
chapters_greater: $chapterGreater
volumes_lesser: $volumeLesser
volumes_greater: $volumeGreater
licensedById_in: $licensedBy
isLicensed: $isLicensed
genre_in: $genres
genre_not_in: $excludedGenres
tag_in: $tags
tag_not_in: $excludedTags
minimumTagRank: $minimumTagRank
sort: $sort
isAdult: $isAdult
) {
id
idMal
title {
userPreferred
romaji
english
native
}
description
coverImage {
extraLarge
large
medium
color
}
startDate {
year
month
day
}
endDate {
year
month
day
}
bannerImage
format
season
seasonYear
status
episodes
duration
countryOfOrigin
source
synonyms
averageScore
meanScore
popularity
trending
favourites
isFavourite
relations {
nodes {
id
title {
userPreferred
romaji
english
native
}
}
}
nextAiringEpisode {
airingAt
timeUntilAiring
episode
}
airingSchedule {
nodes {
id
airingAt
timeUntilAiring
episode
mediaId
}
}
genres
tags {
id
name
description
rank
isMediaSpoiler
isAdult
}
isAdult
}
}
}
}
body:graphql:vars {
{
"page": 1,
"perPage": 20,
"season": "SUMMER",
"seasonYear": 2026,
"type": "ANIME",
"excludedTags": [
"Ecchi",
"LGBTQ+ Themes",
"Yuri"
],
"excludedGenres": [
"Boy's Love"
]
}
}
settings {
encodeUrl: true
timeout: 0
}
@@ -0,0 +1,8 @@
meta {
name: AniListCalendar
seq: 4
}
auth {
mode: inherit
}
+17 -4
View File
@@ -1,26 +1,39 @@
#!/bin/bash
# copy desktop file
if [ -e "~/.local/share/applications/AniTrack.desktop" ]; then
if [ ! -f "$HOME/.local/share/applications/AniTrack.desktop" ]; then
if [ -d "~/.local/share/applications/" ]; then
echo "Copying desktop file..."
cp ./AniTrack.desktop ~/.local/share/applications/
else
mkdir -p ~/.local/share/applications/
echo "Copying desktop file..."
cp ./AniTrack.desktop ~/.local/share/applications/
fi
else
echo "Desktop file already installed..."
fi
# copy icons to xdg folders
for size in 32 48 64 128; do
xdg-icon-resource install --novendor --context apps --size $size ./icon/$size/AniTrack.png AniTrack
if [ ! -f $HOME/.local/share/icons/hicolor/${size}x${size}/apps/AniTrack.png ]; then
echo "Installing ${size} icon size..."
xdg-icon-resource install --novendor --context apps --size $size ./icon/$size/AniTrack.png AniTrack
else
echo "${size} icon size already exists..."
fi
done
# copy AniTrack Binary to $HOME/Applications/
if ! [ -d "~/Applications" ]; then
if ! [ -d "$HOME/Applications" ]; then
mkdir -p ~/Applications
echo "Installing app to ~/Applications..."
cp ./bin/AniTrack ~/Applications/
elif ! [[ -e ~/Applications/AniTrack ]]; then
elif ! [[ -e $HOME/Applications/AniTrack ]]; then
echo "Installing app to ~/Applications"
cp ./bin/AniTrack ~/Applications/
else
echo "AniTrack already in Applications..."
fi
echo "AniTrack has been successfully installed."
+3
View File
@@ -9,6 +9,7 @@
simklPrimary,
malWatchList,
simklWatchList,
serviceLoggingIn,
} from "./helperModules/GlobalVariablesAndHelperFunctions.svelte";
import { onMount } from "svelte";
import Router from "svelte-spa-router";
@@ -35,6 +36,8 @@
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
serviceLoggingIn.set(["anilist", "mal", "simkl"]);
!isAniListLoggedIn && (await CheckIfAniListLoggedInAndLoadWatchList());
!isMALLoggedIn && (await CheckIfMALLoggedInAndSetUser());
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
+57 -28
View File
@@ -14,6 +14,7 @@
logoutOfAniList,
logoutOfMAL,
logoutOfSimkl,
serviceLoggingIn,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import * as runtime from "../../wailsjs/runtime";
import type { MyAnimeListUser } from "../mal/types/MALTypes";
@@ -26,6 +27,7 @@
let isAniListLoggedIn: boolean;
let isSimklLoggedIn: boolean;
let isMALLoggedIn: boolean;
let loggingIn: string[] = [];
aniListUser.subscribe((value) => (currentAniListUser = value));
malUser.subscribe((value) => (currentMALUser = value));
@@ -33,6 +35,7 @@
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
serviceLoggingIn.subscribe((value) => (loggingIn = value));
function dropdownUser(): void {
let dropdown = document.querySelector("#userDropdown");
@@ -98,15 +101,24 @@
</li>
{:else}
<li>
<button
on:click={() => {
dropdownUser();
loginToAniList();
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
<span class="maple-font text-lg mr-4">A</span>Login to AniList
</button>
{#if loggingIn.includes("anilist")}
<span class="flex items-center px-4 py-2 w-full truncate">
<span
class="inline-block w-4 h-4 mr-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"
></span>
<span class="maple-font text-lg mr-4">A</span>Checking AniList
</span>
{:else}
<button
on:click={() => {
dropdownUser();
loginToAniList();
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
<span class="maple-font text-lg mr-4">A</span>Login to AniList
</button>
{/if}
</li>
{/if}
{#if isMALLoggedIn}
@@ -120,15 +132,24 @@
</li>
{:else}
<li>
<button
on:click={() => {
dropdownUser();
loginToMAL();
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
<span class="maple-font text-lg mr-4">M</span>Login to MyAnimeList
</button>
{#if loggingIn.includes("mal")}
<span class="flex items-center px-4 py-2 w-full truncate">
<span
class="inline-block w-4 h-4 mr-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"
></span>
<span class="maple-font text-lg mr-4">M</span>Checking MyAnimeList
</span>
{:else}
<button
on:click={() => {
dropdownUser();
loginToMAL();
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
<span class="maple-font text-lg mr-4">M</span>Login to MyAnimeList
</button>
{/if}
</li>
{/if}
{#if isSimklLoggedIn}
@@ -143,15 +164,24 @@
</li>
{:else}
<li>
<button
on:click={() => {
dropdownUser();
loginToSimkl();
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
<span class="maple-font text-lg mr-4">S</span>Login to Simkl
</button>
{#if loggingIn.includes("simkl")}
<span class="flex items-center px-4 py-2 w-full truncate">
<span
class="inline-block w-4 h-4 mr-4 border-2 border-gray-300 border-t-transparent rounded-full animate-spin"
></span>
<span class="maple-font text-lg mr-4">S</span>Checking Simkl
</span>
{:else}
<button
on:click={() => {
dropdownUser();
loginToSimkl();
}}
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
>
<span class="maple-font text-lg mr-4">S</span>Login to Simkl
</button>
{/if}
</li>
{/if}
</ul>
@@ -174,4 +204,3 @@
</div>
</div>
</div>
+39 -9
View File
@@ -7,6 +7,7 @@
loginToSimkl,
malLoggedIn,
simklLoggedIn,
serviceLoggingIn,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import AvatarMenu from "./AvatarMenu.svelte";
import logo from "../assets/images/AniTrackLogo.svg";
@@ -15,10 +16,12 @@
let isAniListLoggedIn: boolean;
let isSimklLoggedIn: boolean;
let isMALLoggedIn: boolean;
let loggingIn: string[] = [];
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
serviceLoggingIn.subscribe((value) => (loggingIn = value));
</script>
<nav class="border-gray-200 bg-gray-900">
@@ -74,23 +77,50 @@
>
<li>
{#if !isAniListLoggedIn}
<button on:click={loginToAniList}>
<!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
AniList Login
<button
disabled={loggingIn.includes("anilist")}
on:click={loginToAniList}
>
{#if loggingIn.includes("anilist")}
<span
class="inline-block w-4 h-4 mr-2 border-2 border-gray-300 border-t-transparent rounded-full animate-spin align-middle"
></span
>Checking AniList
{:else}
AniList Login
{/if}
</button>
{/if}
{#if !isMALLoggedIn}
<button on:click={loginToMAL}>
<!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
MyAnimeList Login
<button
disabled={loggingIn.includes("mal")}
on:click={loginToMAL}
>
{#if loggingIn.includes("mal")}
<span
class="inline-block w-4 h-4 mr-2 border-2 border-gray-300 border-t-transparent rounded-full animate-spin align-middle"
></span
>Checking MAL
{:else}
MyAnimeList Login
{/if}
</button>
{/if}
</li>
<li>
{#if !isSimklLoggedIn}
<button on:click={loginToSimkl}>
<!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
Simkl Login
<button
disabled={loggingIn.includes("simkl")}
on:click={loginToSimkl}
>
{#if loggingIn.includes("simkl")}
<span
class="inline-block w-4 h-4 mr-2 border-2 border-gray-300 border-t-transparent rounded-full animate-spin align-middle"
></span
>Checking Simkl
{:else}
Simkl Login
{/if}
</button>
{/if}
</li>
@@ -118,7 +118,7 @@
</ul>
</nav>
{/if}
<div class="flex mt-5">
<div class="flex">
<div class="w-20 mx-auto">
<select
bind:value={perPage}
@@ -0,0 +1,18 @@
<script lang="ts">
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import { loading } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
</script>
<div class="flex justify-end">
<button
type="button"
class="py-2 px-4 mt-4 mr-4 bg-gray-700 rounded-lg"
on:click={async () => {
loading.set(true);
await CheckIfAniListLoggedInAndLoadWatchList();
loading.set(false);
}}
>
Refresh WatchList
</button>
</div>
+77
View File
@@ -0,0 +1,77 @@
<script lang="ts">
import {
aniListSort,
watchListPage,
animePerPage,
aniListWatchlist,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import { MediaListSort } from "../anilist/types/AniListTypes";
import { GetAniListUserWatchingList } from "../../wailsjs/go/main/App";
const sortTypes = [
{ value: MediaListSort.MediaId, name: "Media Id Asc" },
{ value: MediaListSort.MediaIdDesc, name: "Media Id Desc" },
{ value: MediaListSort.Score, name: "Score Asc" },
{ value: MediaListSort.ScoreDesc, name: "Score Desc" },
{ value: MediaListSort.Status, name: "Status Asc" },
{ value: MediaListSort.StatusDesc, name: "Status Desc" },
{ value: MediaListSort.Progress, name: "Progress Asc" },
{ value: MediaListSort.ProgressDesc, name: "Progress Desc" },
{ value: MediaListSort.ProgressVolumes, name: "Progress Valumes Asc" },
{ value: MediaListSort.ProgressVolumesDesc, name: "Progress Valumes Desc" },
{ value: MediaListSort.Repeat, name: "Repeat Asc" },
{ value: MediaListSort.RepeatDesc, name: "Repeat Desc" },
{ value: MediaListSort.Priority, name: "Priority Asc" },
{ value: MediaListSort.PriorityDesc, name: "Priority Desc" },
{ value: MediaListSort.StartedOn, name: "Started On Asc" },
{ value: MediaListSort.StartedOnDesc, name: "Started On Desc" },
{ value: MediaListSort.FinishedOn, name: "Finished On Asc" },
{ value: MediaListSort.FinishedOnDesc, name: "Finished On Desc" },
{ value: MediaListSort.AddedTime, name: "Added Time Asc" },
{ value: MediaListSort.AddedTimeDesc, name: "Added Time Desc" },
{ value: MediaListSort.UpdatedTime, name: "Updated Time Asc" },
{ value: MediaListSort.UpdatedTimeDesc, name: "Updated Time Desc" },
{ value: MediaListSort.MediaTitleRomaji, name: "Media Title Romaji Asc" },
{
value: MediaListSort.MediaTitleRomajiDesc,
name: "Media Title Romaji Desc",
},
{ value: MediaListSort.MediaTitleEnglish, name: "Media Title English Asc" },
{
value: MediaListSort.MediaTitleEnglishDesc,
name: "Media Title English Desc",
},
{ value: MediaListSort.MediaTitleNative, name: "Media Title Native Asc" },
{
value: MediaListSort.MediaTitleNativeDesc,
name: "Media Title Native Desc",
},
{ value: MediaListSort.MediaPopularity, name: "Media Popularity Asc" },
{ value: MediaListSort.MediaPopularityDesc, name: "Media Popularity Desc" },
];
let sort: string;
aniListSort.subscribe((value) => (sort = value));
console.log(sort);
async function changeWatchListSort() {
const result = await GetAniListUserWatchingList(
$watchListPage,
$animePerPage,
$aniListSort,
);
aniListWatchlist.set(result);
}
</script>
<select
id="sort"
class="border rounded-lg block p-1.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
bind:value={$aniListSort}
on:change={() => changeWatchListSort()}
>
<option value="" disabled>Sort WatchList</option>
{#each sortTypes as sort}
<option value={sort.value}>{sort.name}</option>
{/each}
</select>
+2 -11
View File
@@ -10,6 +10,7 @@
import { Rating } from "flowbite-svelte";
import loader from "../helperFunctions/loader";
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
import Sort from "../helperComponents/Sort.svelte";
let isAniListLoggedIn: boolean;
let aniListWatchListLoaded: AniListCurrentUserWatchList;
@@ -25,17 +26,7 @@
>
<div class="flex justify-between items-center mb-4">
<h1 class="text-left text-xl font-bold">Your AniList WatchList</h1>
<button
type="button"
class="py-2 px-4 bg-gray-700 rounded-lg"
on:click={async () => {
loading.set(true);
await CheckIfAniListLoggedInAndLoadWatchList();
loading.set(false);
}}
>
Refresh WatchList
</button>
<Sort />
</div>
<div
@@ -14,6 +14,7 @@
aniListSort,
clearApiError,
setApiError,
serviceLoggingIn,
} from "./GlobalVariablesAndHelperFunctions.svelte";
let isAniListPrimary: boolean;
@@ -60,6 +61,7 @@
}
};
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
serviceLoggingIn.update((s) => [...s, "anilist"]);
try {
const loggedIn = await CheckIfAniListLoggedIn();
if (loggedIn) {
@@ -76,6 +78,8 @@
true,
);
aniListLoggedIn.set(false);
} finally {
serviceLoggingIn.update((s) => s.filter((item) => item !== "anilist"));
}
};
</script>
@@ -1,14 +1,21 @@
<script lang="ts" context="module">
import {CheckIfMyAnimeListLoggedIn, GetMyAnimeList, GetMyAnimeListLoggedInUser} from "../../wailsjs/go/main/App";
import {malUser, malPrimary, malWatchList, malLoggedIn} from "./GlobalVariablesAndHelperFunctions.svelte"
import {malUser, malPrimary, malWatchList, malLoggedIn, serviceLoggingIn} from "./GlobalVariablesAndHelperFunctions.svelte"
import type { MyAnimeListUser } from "../mal/types/MALTypes";
let isMalPrimary: boolean
malPrimary.subscribe(value => isMalPrimary = value)
export const CheckIfMALLoggedInAndSetUser = async () => {
serviceLoggingIn.update((s) => [...s, "mal"])
await CheckIfMyAnimeListLoggedIn().then(loggedIn => {
if (loggedIn) {
GetMyAnimeListLoggedInUser().then(user => {
if (!user.name) {
malUser.set({} as MyAnimeListUser)
malLoggedIn.set(false)
return
}
malUser.set(user)
if (isMalPrimary) {
GetMyAnimeList(1000).then(watchList => {
@@ -20,6 +27,8 @@
}
})
}
}).finally(() => {
serviceLoggingIn.update((s) => s.filter(item => item !== "mal"))
})
}
</script>
@@ -1,11 +1,12 @@
<script lang="ts" context="module">
import {CheckIfSimklLoggedIn, GetSimklLoggedInUser, SimklGetUserWatchlist} from "../../wailsjs/go/main/App";
import { simklLoggedIn, simklUser, simklPrimary, simklWatchList } from "./GlobalVariablesAndHelperFunctions.svelte";
import { simklLoggedIn, simklUser, simklPrimary, simklWatchList, serviceLoggingIn } from "./GlobalVariablesAndHelperFunctions.svelte";
let isSimklPrimary: boolean
simklPrimary.subscribe(value => isSimklPrimary = value)
export const CheckIfSimklLoggedInAndSetUser = async () => {
serviceLoggingIn.update((s) => [...s, "simkl"])
await CheckIfSimklLoggedIn().then(loggedIn => {
if (loggedIn) {
GetSimklLoggedInUser().then(user => {
@@ -24,6 +25,8 @@
}
})
}
}).finally(() => {
serviceLoggingIn.update((s) => s.filter(item => item !== "simkl"))
})
}
</script>
@@ -39,6 +39,7 @@
export let aniListLoggedIn = writable(false);
export let simklLoggedIn = writable(false);
export let malLoggedIn = writable(false);
export const serviceLoggingIn = writable([] as string[]);
export let simklWatchList = writable({} as SimklWatchList);
export let aniListPrimary = writable(true);
export let simklPrimary = writable(false);
@@ -148,39 +149,70 @@
return "";
}
export function loginToSimkl(): void {
GetSimklLoggedInUser().then((user) => {
if (Object.keys(user).length === 0) {
simklLoggedIn.set(false);
} else {
simklUser.set(user);
SimklGetUserWatchlist().then((result) => {
simklWatchList.set(result);
simklLoggedIn.set(true);
});
export function setServiceLoggingIn(service: string, isLoggingIn: boolean): void {
serviceLoggingIn.update((services) => {
if (isLoggingIn) {
return services.includes(service) ? services : [...services, service];
}
return services.filter((s) => s !== service);
});
}
export function isServiceLoggingIn(service: string): boolean {
let loggingIn = false;
serviceLoggingIn.subscribe((services) => {
loggingIn = services.includes(service);
})();
return loggingIn;
}
export function loginToSimkl(): void {
setServiceLoggingIn("simkl", true);
GetSimklLoggedInUser()
.then((user) => {
if (Object.keys(user).length === 0) {
simklLoggedIn.set(false);
} else {
simklUser.set(user);
SimklGetUserWatchlist().then((result) => {
simklWatchList.set(result);
simklLoggedIn.set(true);
});
}
})
.finally(() => setServiceLoggingIn("simkl", false));
}
export function loginToAniList(): void {
GetAniListLoggedInUser().then((result) => {
aniListUser.set(result);
if (isAniListPrimary) {
GetAniListUserWatchingList(page, perPage, sort).then((result) => {
aniListWatchlist.set(result);
setServiceLoggingIn("anilist", true);
GetAniListLoggedInUser()
.then((result) => {
aniListUser.set(result);
if (isAniListPrimary) {
GetAniListUserWatchingList(page, perPage, sort).then((result) => {
aniListWatchlist.set(result);
aniListLoggedIn.set(true);
});
} else {
aniListLoggedIn.set(true);
});
} else {
aniListLoggedIn.set(true);
}
});
}
})
.finally(() => setServiceLoggingIn("anilist", false));
}
export function loginToMAL(): void {
GetMyAnimeListLoggedInUser().then((result) => {
malUser.set(result);
malLoggedIn.set(true);
});
setServiceLoggingIn("mal", true);
GetMyAnimeListLoggedInUser()
.then((result) => {
if (!result.name) {
malUser.set({} as MyAnimeListUser);
malLoggedIn.set(false);
return;
}
malUser.set(result);
malLoggedIn.set(true);
})
.finally(() => setServiceLoggingIn("mal", false));
}
export function logoutOfAniList(): void {
+2
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import Pagination from "../helperComponents/Pagination.svelte";
import WatchList from "../helperComponents/WatchList.svelte";
import RefreshWatchListButton from "../helperComponents/RefreshWatchListButton.svelte";
import {
aniListLoggedIn,
aniListPrimary,
@@ -45,6 +46,7 @@
</div>
</div>
{:else if isAniListLoggedIn && isAniListPrimary}
<RefreshWatchListButton />
<div class="container py-10">
<Pagination />
<WatchList />
+2
View File
@@ -2,6 +2,8 @@
// This file is automatically generated. DO NOT EDIT
import {main} from '../models';
export function AniListBrowse(arg1:number,arg2:number,arg3:number,arg4:boolean,arg5:string,arg6:Array<string>,arg7:string,arg8:string,arg9:string,arg10:string,arg11:number,arg12:string,arg13:boolean,arg14:number,arg15:number,arg16:number,arg17:number,arg18:number,arg19:number,arg20:number,arg21:number,arg22:number,arg23:number,arg24:Array<number>,arg25:boolean,arg26:Array<string>,arg27:Array<string>,arg28:Array<string>,arg29:Array<string>,arg30:number,arg31:Array<string>):Promise<main.AniListCurrentUserWatchList>;
export function AniListDeleteEntry(arg1:number):Promise<main.DeleteAniListReturn>;
export function AniListLogin():Promise<void>;
+4
View File
@@ -2,6 +2,10 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AniListBrowse(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31) {
return window['go']['main']['App']['AniListBrowse'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31);
}
export function AniListDeleteEntry(arg1) {
return window['go']['main']['App']['AniListDeleteEntry'](arg1);
}
+178 -5
View File
@@ -230,8 +230,7 @@ export namespace main {
background: background;
related_anime: relatedAnime;
recommendations: recommendations;
// Go type: struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching string "json:\"watching\" ts_type:\"watching\""; Completed string "json:\"completed\" ts_type:\"completed\""; OnHold string "json:\"on_hold\" ts_type:\"onHold\""; Dropped string "json:\"dropped\" ts_type:\"dropped\""; PlanToWatch string "json:\"plan_to_watch\" ts_type:\"planToWatch\"" } }
Statistics: any;
Statistics: struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching main.;
static createFrom(source: any = {}) {
return new MALAnime(source);
@@ -360,13 +359,125 @@ export namespace main {
this.updated_at = source["updated_at"];
}
}
export class {
id: number;
name: string;
description: string;
rank: number;
isMediaSpoiler: boolean;
isAdult: boolean;
static createFrom(source: any = {}) {
return new (source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.name = source["name"];
this.description = source["description"];
this.rank = source["rank"];
this.isMediaSpoiler = source["isMediaSpoiler"];
this.isAdult = source["isAdult"];
}
}
export class Media {
id: number;
idMal: number;
// Go type: struct { UserPreferred string "json:\"userPreferred\""; Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" }
title: any;
description: string;
// Go type: struct { ExtraLarge string; Large string "json:\"large\""; Medium string; Color string }
coverImage: any;
BannerImage: string;
Format: string;
season: string;
seasonYear: number;
status: string;
episodes: number;
Duration: number;
CountryOfOrigin: string;
Source: string;
Synonyms: string[];
AverageScore: number;
MeanScore: number;
Popularity: number;
Trending: number;
Favourites: number;
// Go type: struct { nodes struct { id int; Title struct { UserPreferred string "json:\"userPreferred\""; Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\"" } }
Relations: any;
// Go type: struct { Year int; Month int; Day int }
StartDate: any;
// Go type: struct { Year int; Month int; Day int }
EndDate: any;
// Go type: struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" }
nextAiringEpisode: any;
// Go type: struct { Nodes struct { Id int; AiringAt int; TimeUntilAiring int; Episode int; MediaId int } }
AiringSchedule: any;
genres: string[];
tags: [];
isAdult: boolean;
static createFrom(source: any = {}) {
return new Media(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.idMal = source["idMal"];
this.title = this.convertValues(source["title"], Object);
this.description = source["description"];
this.coverImage = this.convertValues(source["coverImage"], Object);
this.BannerImage = source["BannerImage"];
this.Format = source["Format"];
this.season = source["season"];
this.seasonYear = source["seasonYear"];
this.status = source["status"];
this.episodes = source["episodes"];
this.Duration = source["Duration"];
this.CountryOfOrigin = source["CountryOfOrigin"];
this.Source = source["Source"];
this.Synonyms = source["Synonyms"];
this.AverageScore = source["AverageScore"];
this.MeanScore = source["MeanScore"];
this.Popularity = source["Popularity"];
this.Trending = source["Trending"];
this.Favourites = source["Favourites"];
this.Relations = this.convertValues(source["Relations"], Object);
this.StartDate = this.convertValues(source["StartDate"], Object);
this.EndDate = this.convertValues(source["EndDate"], Object);
this.nextAiringEpisode = this.convertValues(source["nextAiringEpisode"], Object);
this.AiringSchedule = this.convertValues(source["AiringSchedule"], Object);
this.genres = source["genres"];
this.tags = this.convertValues(source["tags"], );
this.isAdult = source["isAdult"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class MediaList {
id: number;
mediaId: number;
userId: number;
// Go type: struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\""; Genres []string "json:\"genres\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
media: any;
status: string;
media: Media;
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }
startedAt: any;
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }
@@ -387,8 +498,8 @@ export namespace main {
this.id = source["id"];
this.mediaId = source["mediaId"];
this.userId = source["userId"];
this.media = this.convertValues(source["media"], Object);
this.status = source["status"];
this.media = this.convertValues(source["media"], Media);
this.startedAt = this.convertValues(source["startedAt"], Object);
this.completedAt = this.convertValues(source["completedAt"], Object);
this.notes = source["notes"];
@@ -624,6 +735,43 @@ export namespace struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Tit
}
export namespace struct { NumListUsers int "json:\"num_list_users\" ts_type:\"numListUsers\""; Status struct { Watching main {
export class {
num_list_users: numListUsers;
Status: struct { Watching main.;
static createFrom(source: any = {}) {
return new (source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.num_list_users = source["num_list_users"];
this.Status = this.convertValues(source["Status"], Object);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time {
export class {
@@ -653,3 +801,28 @@ export namespace struct { Status string "json:\"status\" ts_type:\"status\""; Sc
}
export namespace struct { Watching main {
export class {
watching: string;
completed: string;
on_hold: string;
dropped: string;
plan_to_watch: string;
static createFrom(source: any = {}) {
return new (source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.watching = source["watching"];
this.completed = source["completed"];
this.on_hold = source["on_hold"];
this.dropped = source["dropped"];
this.plan_to_watch = source["plan_to_watch"];
}
}
}
+82 -1
View File
@@ -246,4 +246,85 @@ export function OnFileDropOff() :void
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void
export function ResolveFilePaths(files: File[]): void
// Notification types
export interface NotificationOptions {
id: string;
title: string;
subtitle?: string; // macOS and Linux only
body?: string;
categoryId?: string;
data?: { [key: string]: any };
}
export interface NotificationAction {
id?: string;
title?: string;
destructive?: boolean; // macOS-specific
}
export interface NotificationCategory {
id?: string;
actions?: NotificationAction[];
hasReplyField?: boolean;
replyPlaceholder?: string;
replyButtonTitle?: string;
}
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
// Initializes the notification service for the application.
// This must be called before sending any notifications.
export function InitializeNotifications(): Promise<void>;
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
// Cleans up notification resources and releases any held connections.
export function CleanupNotifications(): Promise<void>;
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
// Checks if notifications are available on the current platform.
export function IsNotificationAvailable(): Promise<boolean>;
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
// Requests notification authorization from the user (macOS only).
export function RequestNotificationAuthorization(): Promise<boolean>;
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
// Checks the current notification authorization status (macOS only).
export function CheckNotificationAuthorization(): Promise<boolean>;
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
// Sends a basic notification with the given options.
export function SendNotification(options: NotificationOptions): Promise<void>;
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
// Sends a notification with action buttons. Requires a registered category.
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
// Registers a notification category that can be used with SendNotificationWithActions.
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
// Removes a previously registered notification category.
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
// Removes all pending notifications from the notification center.
export function RemoveAllPendingNotifications(): Promise<void>;
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
// Removes a specific pending notification by its identifier.
export function RemovePendingNotification(identifier: string): Promise<void>;
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
// Removes all delivered notifications from the notification center.
export function RemoveAllDeliveredNotifications(): Promise<void>;
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
// Removes a specific delivered notification by its identifier.
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
// Removes a notification by its identifier (cross-platform convenience function).
export function RemoveNotification(identifier: string): Promise<void>;
+60
View File
@@ -48,6 +48,10 @@ export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
@@ -235,4 +239,60 @@ export function CanResolveFilePaths() {
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}
export function InitializeNotifications() {
return window.runtime.InitializeNotifications();
}
export function CleanupNotifications() {
return window.runtime.CleanupNotifications();
}
export function IsNotificationAvailable() {
return window.runtime.IsNotificationAvailable();
}
export function RequestNotificationAuthorization() {
return window.runtime.RequestNotificationAuthorization();
}
export function CheckNotificationAuthorization() {
return window.runtime.CheckNotificationAuthorization();
}
export function SendNotification(options) {
return window.runtime.SendNotification(options);
}
export function SendNotificationWithActions(options) {
return window.runtime.SendNotificationWithActions(options);
}
export function RegisterNotificationCategory(category) {
return window.runtime.RegisterNotificationCategory(category);
}
export function RemoveNotificationCategory(categoryId) {
return window.runtime.RemoveNotificationCategory(categoryId);
}
export function RemoveAllPendingNotifications() {
return window.runtime.RemoveAllPendingNotifications();
}
export function RemovePendingNotification(identifier) {
return window.runtime.RemovePendingNotification(identifier);
}
export function RemoveAllDeliveredNotifications() {
return window.runtime.RemoveAllDeliveredNotifications();
}
export function RemoveDeliveredNotification(identifier) {
return window.runtime.RemoveDeliveredNotification(identifier);
}
export function RemoveNotification(identifier) {
return window.runtime.RemoveNotification(identifier);
}
+19 -17
View File
@@ -1,49 +1,51 @@
module AniTrack
go 1.24.0
go 1.25.0
require (
github.com/99designs/keyring v1.2.2
github.com/tidwall/gjson v1.18.0
github.com/wailsapp/wails/v2 v2.10.1
github.com/tidwall/gjson v1.19.0
github.com/wailsapp/wails/v2 v2.15.0
)
require (
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect
github.com/dvsekhvalnov/jose2go v1.8.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.13.3 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/labstack/echo/v4 v4.15.2 // indirect
github.com/labstack/gommon v0.5.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/samber/lo v1.53.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.19 // indirect
github.com/wailsapp/go-webview2 v1.0.23 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.39.0 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.9.1 => /home/nymusicman/go/pkg/mod
+39 -38
View File
@@ -1,11 +1,13 @@
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvsekhvalnov/jose2go v1.8.0 h1:LqkkVKAlHFfH9LOEl5fe4p/zL02OhWE7pCufMBG2jLA=
@@ -14,22 +16,23 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/labstack/echo/v4 v4.15.2 h1:nnh2sCzGCVYnU+wCisMPiYapEg/QVo/gcI9ePKg5/T4=
github.com/labstack/echo/v4 v4.15.2/go.mod h1:Xzp1Ns1RA2c9fY7nSgUJkpkUZGNbEIVHZbtbOMPktBI=
github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c=
github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
@@ -45,8 +48,8 @@ github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -60,17 +63,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
@@ -79,30 +81,29 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
github.com/wailsapp/wails/v2 v2.15.0 h1:u7cHK+UesZOlYxyJxfYLteaCPhws6UsZoDdqUejuX6Q=
github.com/wailsapp/wails/v2 v2.15.0/go.mod h1:scxrgwfsv6yR6fE6cCF+Flfl+JeU+SR87T9x4kILJ6M=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+2 -2
View File
@@ -8,10 +8,10 @@
"frontend:dev:serverUrl": "auto",
"author": {
"name": "John O'Keefe",
"email": "jokeefe@fastmail.com"
"email": "admin@linuxhg.com"
},
"info": {
"productName": "AniTrack",
"productVersion": "0.7.0"
"productVersion": "1.5.5"
}
}