mirror of
https://github.com/sbondCo/Watcharr.git
synced 2026-08-07 15:25:29 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a53066b72 | |||
| 9ebc46d7d4 | |||
| 0456eefe3b | |||
| 3b85331f3b | |||
| 56fe1152c5 | |||
| 699f19badb | |||
| e415f77e5a | |||
| 124323cc59 | |||
| ee716a9dc8 | |||
| c4694e577f | |||
| d8f914ddca | |||
| 4d145d593b | |||
| 889aa30150 | |||
| 490192b386 | |||
| 9a9cf1e3fb | |||
| 300b063b9a | |||
| 99be784453 | |||
| 2f6cf1c0db | |||
| 23e686385c | |||
| cbe445b7d9 | |||
| 087071a8af | |||
| a0a74e8cdc | |||
| a16769e0db | |||
| 565d90dbf0 | |||
| b6d6e66f04 | |||
| 0b4f3c8604 | |||
| 0a5a4e39f5 | |||
| 2d8def18ed | |||
| d53f62e96c | |||
| 4dd0b419e0 | |||
| 41948f15ba | |||
| 49f0739503 | |||
| 1c763e9761 | |||
| 18a30639b9 | |||
| a3847ccb3c | |||
| 5f5eb3e9f2 |
+1
-1
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# Backend
|
||||
#
|
||||
FROM golang:1.22-alpine AS server
|
||||
FROM golang:1.24-alpine AS server
|
||||
|
||||
WORKDIR /server
|
||||
COPY server/*.go server/go.* ./
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# MyAnimeList
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Text File (.txt list)
|
||||
|
||||
:::info Backup?
|
||||
You may consider a backup of your server before starting any import. They are not easily reversible, though we do our best to ensure they are accurate and bug free!
|
||||
:::
|
||||
|
||||
The text file (.txt) import is of an arbitrary format (the one I used for years before creating Watcharr).
|
||||
|
||||
Hopefully it is useful for others with similar files or in scenarios where its the easiest to generate for an import (though if possible, when generating a backup from another service data manually, matching a Watcharr export would enable keeping more data).
|
||||
|
||||
## Format
|
||||
|
||||
Each line is a new entry. The name of the content (show/movie) must be provided, the year can be optionally provided surrounded by brackets. Doesn't support specifying if name is for a show or movie, the importer will only automatically match on full search matches, if there are multiple results, you will be asked to pick the correct one.
|
||||
|
||||
```
|
||||
<name> [(<year>)]
|
||||
```
|
||||
|
||||
## An example
|
||||
|
||||
```
|
||||
The Terminator (1984)
|
||||
Breaking Bad
|
||||
A Fistful of Dollars
|
||||
Reacher (2022)
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Trakt
|
||||
|
||||
:::info Backup?
|
||||
You may consider a backup of your server before starting any import. They are not easily reversible, though we do our best to ensure they are accurate and bug free!
|
||||
:::
|
||||
|
||||
**Note:** Your Trakt profile must be public _during_ this process. You are free to private it again once it completes.
|
||||
|
||||
1. Provide your Trakt username in the text box.
|
||||
2. Press `Start Import`.
|
||||
|
||||
This will be a long process, possibly a couple hours depending on how large of a Trakt history you have. If you think it has frozen or isn't working, try checking your server logs to see if it is doing anything.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
# Watcharr
|
||||
|
||||
:::info Backup?
|
||||
You may consider a backup of your server before starting any import. They are not easily reversible, though we do our best to ensure they are accurate and bug free!
|
||||
:::
|
||||
|
||||
:::warning May not be what you want
|
||||
Watcharr import/export is a feature intended for end users. If you are the server admin, it is recommended that you copy your server files directly to any new server you are creating (it'll be much faster and easier). [See backup guide](/docs/server_config/backup).
|
||||
:::
|
||||
|
||||
1. Provide the Watcharr export file (that you generated from your profile page).
|
||||
2. Review the import table, if everything looks good, start the import.
|
||||
Generated
+400
-311
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "watcharr",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -15,27 +15,27 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/kit": "^2.21.0",
|
||||
"@types/papaparse": "^5.3.15",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||
"@typescript-eslint/parser": "^8.18.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"@vite-pwa/sveltekit": "^0.6.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-svelte": "^2.45.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.2.6",
|
||||
"sass": "^1.83.0",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"sass": "^1.89.0",
|
||||
"svelte": "^5.17.3",
|
||||
"svelte-check": "^4.1.3",
|
||||
"svelte-eslint-parser": "^0.42.0",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.7"
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.9.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"papaparse": "^5.4.1"
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ func (i *IGDB) req(host string, ep string, p map[string]string, b string, resp i
|
||||
return errors.New("using igdbHost without a clientID or accessToken")
|
||||
}
|
||||
|
||||
slog.Debug("IGDB->req: Creating a request.", "ep", ep, "body", b)
|
||||
|
||||
base, err := url.Parse(host)
|
||||
if err != nil {
|
||||
return errors.New("failed to parse api uri")
|
||||
@@ -181,6 +183,24 @@ func (i *IGDB) Search(q string) (GameSearchResponse, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Should return same details as `Search`, we both are for search page only minimal details required.
|
||||
func (i *IGDB) SearchById(id string) (GameSearchResponse, error) {
|
||||
slog.Debug("IGDB Search called", "id", id)
|
||||
var resp GameSearchResponse
|
||||
err := i.req(
|
||||
igdbHost,
|
||||
"/games",
|
||||
map[string]string{},
|
||||
"fields name, cover.image_id, version_title, summary, first_release_date; where id = "+id+";",
|
||||
&resp,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("IGDB Search request failed!", "error", err)
|
||||
return GameSearchResponse{}, errors.New("request failed")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (i *IGDB) GameDetails(id string) (GameDetailsResponse, error) {
|
||||
slog.Debug("IGDB GameDetails called", "id", id)
|
||||
var resp []GameDetailsResponse
|
||||
|
||||
+20
-21
@@ -1,16 +1,16 @@
|
||||
module github.com/sbondCo/Watcharr
|
||||
|
||||
go 1.22
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/gin-contrib/cache v1.3.1
|
||||
github.com/gin-contrib/cors v1.7.3
|
||||
github.com/gin-contrib/cors v1.7.5
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-co-op/gocron/v2 v2.14.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/go-co-op/gocron/v2 v2.16.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/joho/godotenv v1.5.1
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
@@ -18,23 +18,23 @@ require (
|
||||
|
||||
require (
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||
github.com/bytedance/sonic v1.12.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gomodule/redigo v1.9.2 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
@@ -46,11 +46,10 @@ require (
|
||||
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
+38
-39
@@ -2,42 +2,41 @@ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8b
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do=
|
||||
github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8=
|
||||
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
|
||||
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/cache v1.3.1 h1:EWjkOaLocs5fGt9feQaI7rt1GZbDyatFXEUh2/s3ZI8=
|
||||
github.com/gin-contrib/cache v1.3.1/go.mod h1:6Tme0p3QEF/Ck/KUcq7h/OAqZvUDjHRH1DtQbNgfIX0=
|
||||
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
|
||||
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
|
||||
github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-co-op/gocron/v2 v2.14.2 h1:S6CbI7MVfD3S/aPJNLoSg2YcGyEqzEMwUopDejuT4Oc=
|
||||
github.com/go-co-op/gocron/v2 v2.14.2/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2 h1:r08P663ikXiulLT9XaabkLypL/W9MoCIbqgQoAutyX4=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2/go.mod h1:4YTLGCCAH75A5RlQ6q+h+VacO7CgjkgP0EJ+BEOXRSI=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -51,13 +50,13 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
@@ -89,11 +88,13 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
@@ -102,21 +103,19 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
+21
-1
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -399,7 +400,26 @@ func (b *BaseRouter) addGameRoutes() {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "a query was not provided"})
|
||||
return
|
||||
}
|
||||
games, err := igdb.Search(query)
|
||||
decodedQuery, err := url.QueryUnescape(query)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "query parameter invalid"})
|
||||
return
|
||||
}
|
||||
games, err := igdb.Search(decodedQuery)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, games)
|
||||
}))
|
||||
|
||||
// Search for game by id (for search page, same minimal details as /search returned)
|
||||
gamer.GET("/search/:id", cache.CachePage(b.ms, exp, func(c *gin.Context) {
|
||||
if c.Param("id") == "" {
|
||||
c.Status(400)
|
||||
return
|
||||
}
|
||||
games, err := igdb.SearchById(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
|
||||
@@ -645,6 +645,54 @@
|
||||
d="M368 192h-16v-80a96 96 0 10-192 0v80h-16a64.07 64.07 0 00-64 64v176a64.07 64.07 0 0064 64h224a64.07 64.07 0 0064-64V256a64.07 64.07 0 00-64-64zm-48 0H192v-80a64 64 0 11128 0z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "github"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 24 25"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.301 0h.093c2.242 0 4.34.613 6.137 1.68l-.055-.031a12.35 12.35 0 0 1 4.449 4.422l.031.058a12.2 12.2 0 0 1 1.654 6.166c0 5.406-3.483 10-8.327 11.658l-.087.026a.72.72 0 0 1-.642-.113l.002.001a.62.62 0 0 1-.208-.466v-.014v.001l.008-1.226q.008-1.178.008-2.154a2.84 2.84 0 0 0-.833-2.274a11 11 0 0 0 1.718-.305l-.076.017a6.5 6.5 0 0 0 1.537-.642l-.031.017a4.5 4.5 0 0 0 1.292-1.058l.006-.007a4.9 4.9 0 0 0 .84-1.645l.009-.035a7.9 7.9 0 0 0 .329-2.281l-.001-.136v.007l.001-.072a4.73 4.73 0 0 0-1.269-3.23l.003.003c.168-.44.265-.948.265-1.479a4.25 4.25 0 0 0-.404-1.814l.011.026a2.1 2.1 0 0 0-1.31.181l.012-.005a8.6 8.6 0 0 0-1.512.726l.038-.022l-.609.384c-.922-.264-1.981-.416-3.075-.416s-2.153.152-3.157.436l.081-.02q-.256-.176-.681-.433a9 9 0 0 0-1.272-.595l-.066-.022A2.17 2.17 0 0 0 5.837 5.1l.013-.002a4.2 4.2 0 0 0-.393 1.788c0 .531.097 1.04.275 1.509l-.01-.029a4.72 4.72 0 0 0-1.265 3.303v-.004l-.001.13c0 .809.12 1.591.344 2.327l-.015-.057c.189.643.476 1.202.85 1.693l-.009-.013a4.4 4.4 0 0 0 1.267 1.062l.022.011c.432.252.933.465 1.46.614l.046.011c.466.125 1.024.227 1.595.284l.046.004c-.431.428-.718 1-.784 1.638l-.001.012a3 3 0 0 1-.699.236l-.021.004c-.256.051-.549.08-.85.08h-.066h.003a1.9 1.9 0 0 1-1.055-.348l.006.004a2.84 2.84 0 0 1-.881-.986l-.007-.015a2.6 2.6 0 0 0-.768-.827l-.009-.006a2.3 2.3 0 0 0-.776-.38l-.016-.004l-.32-.048a1.05 1.05 0 0 0-.471.074l.007-.003q-.128.072-.08.184q.058.128.145.225l-.001-.001q.092.108.205.19l.003.002l.112.08c.283.148.516.354.693.603l.004.006c.191.237.359.505.494.792l.01.024l.16.368c.135.402.38.738.7.981l.005.004c.3.234.662.402 1.057.478l.016.002c.33.064.714.104 1.106.112h.007q.069.002.15.002q.392 0 .767-.062l-.027.004l.368-.064q0 .609.008 1.418t.008.873v.014c0 .185-.08.351-.208.466h-.001a.72.72 0 0 1-.645.111l.005.001C3.486 22.286.006 17.692.006 12.285c0-2.268.612-4.393 1.681-6.219l-.032.058a12.35 12.35 0 0 1 4.422-4.449l.058-.031a11.9 11.9 0 0 1 6.073-1.645h.098h-.005zm-7.64 17.666q.048-.112-.112-.192q-.16-.048-.208.032q-.048.112.112.192q.144.096.208-.032m.497.545q.112-.08-.032-.256q-.16-.144-.256-.048q-.112.08.032.256q.159.157.256.047zm.48.72q.144-.112 0-.304q-.128-.208-.272-.096q-.144.08 0 .288t.272.112m.672.673q.128-.128-.064-.304q-.192-.192-.32-.048q-.144.128.064.304q.192.192.32.044zm.913.4q.048-.176-.208-.256q-.24-.064-.304.112t.208.24q.24.097.304-.096m1.009.08q0-.208-.272-.176q-.256 0-.256.176q0 .208.272.176q.256.001.256-.175zm.929-.16q-.032-.176-.288-.144q-.256.048-.224.24t.288.128t.225-.224z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "website"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 2048 2048"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M1024 0q141 0 272 36t245 103t207 160t160 208t103 245t37 272q0 141-36 272t-103 245t-160 207t-208 160t-245 103t-272 37q-141 0-272-36t-245-103t-207-160t-160-208t-103-244t-37-273q0-141 36-272t103-245t160-207t208-160T751 37t273-37m0 1920q123 0 237-32t214-90t182-141t140-181t91-214t32-238q0-123-32-237t-90-214t-141-182t-181-140t-214-91t-238-32q-123 0-237 32t-214 90t-182 141t-140 181t-91 214t-32 238q0 123 32 237t90 214t141 182t181 140t214 91t238 32m597-880l48-144h75l-85 256h-75l-48-144l-48 144h-75l-85-256h75l48 144l48-144h74zm-464-144h75l-85 256h-75l-48-144l-48 144h-75l-85-256h75l48 144l48-144h74l48 144zm-512 0h75l-85 256h-75l-48-144l-48 144h-75l-85-256h75l48 144l48-144h74l48 144z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "tmdb"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 185.04 133.4"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M51.06,66.7h0A17.67,17.67,0,0,1,68.73,49h-.1A17.67,17.67,0,0,1,86.3,66.7h0A17.67,17.67,0,0,1,68.63,84.37h.1A17.67,17.67,0,0,1,51.06,66.7Zm82.67-31.33h32.9A17.67,17.67,0,0,0,184.3,17.7h0A17.67,17.67,0,0,0,166.63,0h-32.9A17.67,17.67,0,0,0,116.06,17.7h0A17.67,17.67,0,0,0,133.73,35.37Zm-113,98h63.9A17.67,17.67,0,0,0,102.3,115.7h0A17.67,17.67,0,0,0,84.63,98H20.73A17.67,17.67,0,0,0,3.06,115.7h0A17.67,17.67,0,0,0,20.73,133.37Zm83.92-49h6.25L125.5,49h-8.35l-8.9,23.2h-.1L99.4,49H90.5Zm32.45,0h7.8V49h-7.8Zm22.2,0h24.95V77.2H167.1V70h15.35V62.8H167.1V56.2h16.25V49h-24ZM10.1,35.4h7.8V6.9H28V0H0V6.9H10.1ZM39,35.4h7.8V20.1H61.9V35.4h7.8V0H61.9V13.2H46.75V0H39Zm41.25,0h25V28.2H88V21h15.35V13.8H88V7.2h16.25V0h-24Zm-79,49H9V57.25h.1l9,27.15H24l9.3-27.15h.1V84.4h7.8V49H29.45l-8.2,23.1h-.1L13,49H1.2Zm112.09,49H126a24.59,24.59,0,0,0,7.56-1.15,19.52,19.52,0,0,0,6.35-3.37,16.37,16.37,0,0,0,4.37-5.5A16.91,16.91,0,0,0,146,115.8a18.5,18.5,0,0,0-1.68-8.25,15.1,15.1,0,0,0-4.52-5.53A18.55,18.55,0,0,0,133.07,99,33.54,33.54,0,0,0,125,98H113.29Zm7.81-28.2h4.6a17.43,17.43,0,0,1,4.67.62,11.68,11.68,0,0,1,3.88,1.88,9,9,0,0,1,2.62,3.18,9.87,9.87,0,0,1,1,4.52,11.92,11.92,0,0,1-1,5.08,8.69,8.69,0,0,1-2.67,3.34,10.87,10.87,0,0,1-4,1.83,21.57,21.57,0,0,1-5,.55H121.1Zm36.14,28.2h14.5a23.11,23.11,0,0,0,4.73-.5,13.38,13.38,0,0,0,4.27-1.65,9.42,9.42,0,0,0,3.1-3,8.52,8.52,0,0,0,1.2-4.68,9.16,9.16,0,0,0-.55-3.2,7.79,7.79,0,0,0-1.57-2.62,8.38,8.38,0,0,0-2.45-1.85,10,10,0,0,0-3.18-1v-.1a9.28,9.28,0,0,0,4.43-2.82,7.42,7.42,0,0,0,1.67-5,8.34,8.34,0,0,0-1.15-4.65,7.88,7.88,0,0,0-3-2.73,12.9,12.9,0,0,0-4.17-1.3,34.42,34.42,0,0,0-4.63-.32h-13.2Zm7.8-28.8h5.3a10.79,10.79,0,0,1,1.85.17,5.77,5.77,0,0,1,1.7.58,3.33,3.33,0,0,1,1.23,1.13,3.22,3.22,0,0,1,.47,1.82,3.63,3.63,0,0,1-.42,1.8,3.34,3.34,0,0,1-1.13,1.2,4.78,4.78,0,0,1-1.57.65,8.16,8.16,0,0,1-1.78.2H165Zm0,14.15h5.9a15.12,15.12,0,0,1,2.05.15,7.83,7.83,0,0,1,2,.55,4,4,0,0,1,1.58,1.17,3.13,3.13,0,0,1,.62,2,3.71,3.71,0,0,1-.47,1.95,4,4,0,0,1-1.23,1.3,4.78,4.78,0,0,1-1.67.7,8.91,8.91,0,0,1-1.83.2h-7Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "igdb"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M24 6.228H0v11.543a89 89 0 0 1 2.271-.333a74 74 0 0 1 17.038-.28c1.57.153 3.134.363 4.69.614zm-.706.707v10.013a74.8 74.8 0 0 0-22.588 0V6.934zM7.729 8.84a2.62 2.62 0 0 0-1.857.72a2.55 2.55 0 0 0-.73 1.33c-.098.5-.063 1.03.112 1.51c.177.488.515.917.954 1.196c.547.354 1.224.472 1.865.401a3.24 3.24 0 0 0 1.786-.777c-.003-.724.002-1.449-.002-2.173c-.725.004-1.45-.002-2.174.003c.003.317 0 .634.001.951h1.105c.002.236 0 .473.002.71a1.7 1.7 0 0 1-.932.298c-.32.02-.65-.05-.922-.225a1.46 1.46 0 0 1-.59-.744c-.18-.499-.134-1.085.163-1.53c.23-.355.619-.61 1.043-.647a1.8 1.8 0 0 1 1.012.206c.152.082.286.192.424.295q.344-.42.692-.838a3 3 0 0 0-.595-.403c-.418-.212-.892-.285-1.357-.283m11.66.086q-.14.002-.28 0c-.68.002-1.359-.004-2.038.003c.003 1.666 0 3.332.002 4.998h2.497q.36-.003.709-.097c.276-.076.546-.208.742-.422c.194-.208.297-.492.304-.776c.016-.278-.032-.572-.195-.804c-.175-.252-.453-.408-.734-.514c.211-.122.407-.285.521-.505c.134-.246.149-.535.117-.807a1.16 1.16 0 0 0-.436-.73c-.264-.207-.599-.304-.93-.334a3 3 0 0 0-.279-.012m-16.715 0v5.002h1.102V8.927q-.552-.002-1.102 0zm8.524 0v5.002h2.016a2.9 2.9 0 0 0 1.07-.211a2.44 2.44 0 0 0 1.174-.993c.34-.555.429-1.244.292-1.876a2.37 2.37 0 0 0-.828-1.338c-.478-.387-1.096-.577-1.707-.584zm6.949.967c.392.002.784-.001 1.176.002c.183.011.38.054.51.19c.11.112.136.28.112.43a.44.44 0 0 1-.22.316a1.1 1.1 0 0 1-.483.116c-.365.002-.73-.001-1.094.001l-.001-1.054zm-5.031.026c.28 0 .567.053.815.19c.274.149.491.396.607.685c.113.272.138.574.107.865a1.46 1.46 0 0 1-.335.786a1.43 1.43 0 0 1-.865.466c-.168.031-.34.022-.51.023h-.632V9.92zm5.03 1.948h1.36c.174.006.354.035.505.127a.45.45 0 0 1 .212.308c.025.15.004.32-.099.44c-.102.12-.258.176-.409.2c-.172.032-.348.02-.522.022c-.35-.001-.698.002-1.047-.001z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
+22
-1
@@ -100,10 +100,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
a.menu-footer,
|
||||
button.menu-footer {
|
||||
font-size: 11px;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
transition: inherit;
|
||||
color: $text-color-accent;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
display: inline;
|
||||
font-weight: initial;
|
||||
width: auto;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
text-decoration: underline;
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: gray;
|
||||
color: $text-color-accent;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
+123
-15
@@ -3,6 +3,7 @@
|
||||
TMDBSeasonDetails,
|
||||
TMDBShowSeason,
|
||||
Watched,
|
||||
WatchedSeason,
|
||||
WatchedSeasonAddResponse,
|
||||
WatchedStatus,
|
||||
} from "@/types";
|
||||
@@ -12,9 +13,10 @@
|
||||
import SeasonsListEpisode from "./SeasonsListEpisode.svelte";
|
||||
import PosterStatus from "./poster/PosterStatus.svelte";
|
||||
import { notify } from "./util/notify";
|
||||
import { get } from "svelte/store";
|
||||
import { store } from "@/store.svelte";
|
||||
import PosterRating from "./poster/PosterRating.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
import { watchedStatuses } from "./util/helpers";
|
||||
|
||||
interface Props {
|
||||
tvId: number;
|
||||
@@ -168,6 +170,24 @@
|
||||
function handleStarClick(rating: number, seasonNumber: number) {
|
||||
updateWatchedSeason(seasonNumber, undefined, rating);
|
||||
}
|
||||
|
||||
function checkSeasonStatus(
|
||||
watchedSeasons: WatchedSeason[] | undefined,
|
||||
currentSeason: TMDBShowSeason,
|
||||
): WatchedStatus | undefined {
|
||||
if (watchedSeasons) {
|
||||
const watchedSeason = watchedSeasons.find(
|
||||
(ws) => ws.seasonNumber === currentSeason.season_number,
|
||||
);
|
||||
console.debug(
|
||||
"checkSeasonStatus:",
|
||||
currentSeason.season_number,
|
||||
watchedSeason?.status,
|
||||
);
|
||||
return watchedSeason?.status;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ctr">
|
||||
@@ -179,12 +199,33 @@
|
||||
activeSeason = season.season_number;
|
||||
}}
|
||||
>
|
||||
<h1>{season.name}</h1>
|
||||
{#if season.air_date}
|
||||
<h2>{new Date(Date.parse(season.air_date)).getFullYear()}</h2>
|
||||
{:else if season.season_number > 0}
|
||||
<h2>TBD</h2>
|
||||
{/if}
|
||||
<div>
|
||||
<h1 class="season-name">{season.name}</h1>
|
||||
{#if season.episode_count > 0}
|
||||
<h2 class="season-episodes">{season.episode_count} Episodes</h2>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#if season.air_date}
|
||||
<h2 class="season-date">
|
||||
{new Date(Date.parse(season.air_date)).getFullYear()}
|
||||
</h2>
|
||||
{:else if season.season_number > 0}
|
||||
<h2>TBD</h2>
|
||||
{/if}
|
||||
|
||||
{#if watchedItem}
|
||||
{@const status = checkSeasonStatus(
|
||||
watchedItem.watchedSeasons,
|
||||
season,
|
||||
)}
|
||||
{#if status}
|
||||
<div class="plain season-status">
|
||||
<Icon i={watchedStatuses[status]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
<div class="last"></div>
|
||||
@@ -274,41 +315,97 @@
|
||||
button {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 18px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border: 2px solid #302d2d;
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
min-width: 160px;
|
||||
max-width: 220px;
|
||||
transition: background-color 100ms ease;
|
||||
|
||||
& > div {
|
||||
&:first-of-type {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-left: auto;
|
||||
margin-bottom: auto;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.season-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.season-name,
|
||||
.season-episodes {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.season-date,
|
||||
.season-status {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.season-episodes {
|
||||
color: $text-color-accent;
|
||||
}
|
||||
|
||||
.season-status {
|
||||
fill: $text-color;
|
||||
|
||||
:global(svg) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: sans-serif;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
color: white;
|
||||
background-color: black;
|
||||
color: $bg-color;
|
||||
background-color: $text-color;
|
||||
|
||||
.season-status {
|
||||
fill: $bg-color;
|
||||
}
|
||||
|
||||
.season-episodes {
|
||||
color: $bg-color-accent;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
|
||||
.season-status {
|
||||
fill: $bg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,12 +455,23 @@
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
:global(body.nav-shown) ul.seasons {
|
||||
top: $nav-height;
|
||||
}
|
||||
|
||||
ul.seasons {
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
position: unset;
|
||||
height: unset;
|
||||
justify-content: center;
|
||||
justify-content: unset;
|
||||
overflow: auto;
|
||||
min-width: unset;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
padding: 10px 0;
|
||||
z-index: 5;
|
||||
@include nav-blur;
|
||||
|
||||
button {
|
||||
&:first-of-type {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<script lang="ts">
|
||||
import Icon from "../Icon.svelte";
|
||||
import Modal from "../Modal.svelte";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Modal title="About" {onClose} maxWidth="400px">
|
||||
<div class="content">
|
||||
<div class="inner">
|
||||
<p>
|
||||
An open source project that helps you keep the content you are watching
|
||||
or playing organized and places you in control.
|
||||
</p>
|
||||
<div class="horizontal-icon-list">
|
||||
<a
|
||||
href="https://github.com/sbondCo/Watcharr"
|
||||
target="_blank"
|
||||
title="Github"
|
||||
>
|
||||
<Icon i="github" wh={60} />
|
||||
Source<br />
|
||||
Code
|
||||
</a>
|
||||
<a
|
||||
href="https://watcharr.app/"
|
||||
target="_blank"
|
||||
title="Watcharr Website & Documentation"
|
||||
>
|
||||
<Icon i="website" wh={60} />
|
||||
Website<br />
|
||||
& Docs
|
||||
</a>
|
||||
</div>
|
||||
<h5 class="norm">Watcharr uses the following media databases:</h5>
|
||||
<div class="horizontal-icon-list">
|
||||
<a href="https://www.themoviedb.org/" target="_blank" title="TMDB">
|
||||
<Icon i="tmdb" wh={60} />
|
||||
</a>
|
||||
<a href="https://www.igdb.com/" target="_blank" title="IGDB">
|
||||
<Icon i="igdb" wh={60} />
|
||||
</a>
|
||||
</div>
|
||||
<h5 class="norm">
|
||||
<!-- Linking to the file in `dev` branch would break every old version of
|
||||
Watcharr if the file were ever moved in the future. Using the current version
|
||||
as the branch should ensure this file is always accessible (but only as up to
|
||||
date as the current version) -->
|
||||
<a
|
||||
href="https://github.com/sbondCo/Watcharr/blob/v{__WATCHARR_VERSION__}/ATTRIBUTION.md"
|
||||
target="_blank"
|
||||
>
|
||||
See our attribution file.
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
p {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
h5.norm {
|
||||
margin: 8px 0px;
|
||||
|
||||
> a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
div.horizontal-icon-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 5px;
|
||||
|
||||
> a {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin: 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
transition: opacity 150ms ease-in;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@
|
||||
store.wlDetailedView = store.wlDetailedView.filter((a) => a !== d);
|
||||
} else {
|
||||
store.wlDetailedView.push(d);
|
||||
store.wlDetailedView = store.wlDetailedView;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { clearWatcharrData } from "../logout";
|
||||
import { notify } from "../util/notify";
|
||||
import AboutModal from "./AboutModal.svelte";
|
||||
|
||||
let user = $derived(store.userInfo);
|
||||
let proxyUserLogoutShown = $state(false);
|
||||
let aboutModalOpen = $state(false);
|
||||
|
||||
function logout() {
|
||||
if (user?.type === UserType.Proxy) {
|
||||
@@ -61,9 +63,13 @@
|
||||
notify({ id: nid, type: "error", text: "Failed to get link" });
|
||||
}
|
||||
}
|
||||
|
||||
function closeAbout() {
|
||||
aboutModalOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu conf={{ arrowRight: "10px" }}>
|
||||
<Menu conf={{ width: "140px", arrowRight: "10px" }}>
|
||||
{#if user?.username}
|
||||
<h5 title={user.username}>Hi {user.username}!</h5>
|
||||
{/if}
|
||||
@@ -85,5 +91,26 @@
|
||||
{#if proxyUserLogoutShown}
|
||||
<ProxyUserLogoutModal onClose={() => (proxyUserLogoutShown = false)} />
|
||||
{/if}
|
||||
<span>v{__WATCHARR_VERSION__}</span>
|
||||
<span>
|
||||
<button
|
||||
class="menu-footer"
|
||||
onclick={() => {
|
||||
aboutModalOpen = !aboutModalOpen;
|
||||
}}
|
||||
>
|
||||
about
|
||||
</button>
|
||||
|
|
||||
<a
|
||||
class="menu-footer"
|
||||
href="https://github.com/sbondCo/Watcharr/releases"
|
||||
target="_blank"
|
||||
>
|
||||
v{__WATCHARR_VERSION__}
|
||||
</a>
|
||||
</span>
|
||||
</Menu>
|
||||
|
||||
{#if aboutModalOpen}
|
||||
<AboutModal onClose={closeAbout} />
|
||||
{/if}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { store } from "@/store.svelte";
|
||||
import {
|
||||
UserPermission,
|
||||
type Icon,
|
||||
type MediaType,
|
||||
type TMDBContentCreditsCrew,
|
||||
type Theme,
|
||||
type TokenClaims,
|
||||
type Watched,
|
||||
type WatchedStatus,
|
||||
@@ -294,26 +292,6 @@ export function getOrdinalSuffix(i: number) {
|
||||
return "th";
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle site wide theme.
|
||||
* @param theme The theme to switch to.
|
||||
* @param updateStore Should the store be updated to new theme?
|
||||
* **If set to `false`, state should be manually updated.**
|
||||
*/
|
||||
export function toggleTheme(theme: Theme, updateStore = true) {
|
||||
if (theme === "dark") {
|
||||
document.documentElement.classList.add("theme-dark");
|
||||
if (updateStore) {
|
||||
store.appTheme = "dark";
|
||||
}
|
||||
} else {
|
||||
document.documentElement.classList.remove("theme-dark");
|
||||
if (updateStore) {
|
||||
store.appTheme = "light";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTokenPayload(): TokenClaims | undefined {
|
||||
try {
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// App Theme logic.
|
||||
|
||||
import { browser } from "$app/environment";
|
||||
import { store } from "@/store.svelte";
|
||||
import type { Theme } from "@/types";
|
||||
|
||||
/**
|
||||
* Utility function to handle media queries for theme preferences.
|
||||
*/
|
||||
|
||||
const prefersDarkThemeQuery: MediaQueryList | undefined = browser
|
||||
? window.matchMedia("(prefers-color-scheme: dark)")
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
* prefersDarkThemeQuery onChange handler.
|
||||
*/
|
||||
function prefersDarkThemeChanged(e: MediaQueryListEvent) {
|
||||
console.info(
|
||||
"prefersDarkThemeChanged: User preferred theme has changed. prefersDark:",
|
||||
e.matches,
|
||||
);
|
||||
document.documentElement.classList.toggle("theme-dark", e.matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle site wide theme.
|
||||
* @param theme The theme to switch to.
|
||||
* @param updateStore Should the store be updated to new theme?
|
||||
* **If set to `false`, state should be manually updated.**
|
||||
*/
|
||||
export function toggleTheme(theme: Theme, updateStore = true) {
|
||||
if (updateStore) {
|
||||
store.appTheme = theme;
|
||||
}
|
||||
|
||||
switch (theme) {
|
||||
case "dark":
|
||||
document.documentElement.classList.add("theme-dark");
|
||||
break;
|
||||
case "light":
|
||||
document.documentElement.classList.remove("theme-dark");
|
||||
break;
|
||||
case "system":
|
||||
document.documentElement.classList.toggle(
|
||||
"theme-dark",
|
||||
prefersDarkThemeQuery?.matches,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefersDarkThemeQuery) {
|
||||
// Always remove first before adding to avoid cases
|
||||
// where we add multiple events at same time.
|
||||
prefersDarkThemeQuery.removeEventListener(
|
||||
"change",
|
||||
prefersDarkThemeChanged,
|
||||
);
|
||||
// If using system theme, add change listener (since
|
||||
// system theme supports live updating when user
|
||||
// preference changes on os or wherever).
|
||||
if (theme === "system") {
|
||||
prefersDarkThemeQuery.addEventListener("change", prefersDarkThemeChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@mixin nav-blur {
|
||||
backdrop-filter: blur(2.5px) saturate(120%);
|
||||
background-color: var(--nav-color);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "./mixins.scss";
|
||||
@import "./vars.scss";
|
||||
|
||||
@font-face {
|
||||
|
||||
@@ -43,13 +43,25 @@
|
||||
|
||||
function handleSearch(ev: KeyboardEvent) {
|
||||
if (
|
||||
ev.key === "ContextMenu" ||
|
||||
ev.key === "Home" ||
|
||||
ev.key === "End" ||
|
||||
ev.key === "PageDown" ||
|
||||
ev.key === "PageUp" ||
|
||||
ev.key === "NumLock" ||
|
||||
ev.key === "Escape" ||
|
||||
ev.key === "Tab" ||
|
||||
ev.key === "CapsLock" ||
|
||||
ev.key === "OS" ||
|
||||
ev.key === "ArrowLeft" ||
|
||||
ev.key === "ArrowRight" ||
|
||||
ev.key === "ArrowUp" ||
|
||||
ev.key === "ArrowDown"
|
||||
ev.key === "ArrowDown" ||
|
||||
ev.key === "Control" ||
|
||||
ev.key === "Alt" ||
|
||||
ev.key === "AltGraph" ||
|
||||
ev.key === "Shift" ||
|
||||
ev.key === "Meta"
|
||||
)
|
||||
return;
|
||||
clearTimeout(searchTimeout);
|
||||
@@ -162,6 +174,33 @@
|
||||
scroll = window.scrollY;
|
||||
}
|
||||
|
||||
function focusSearch() {
|
||||
try {
|
||||
if (!mainSearchEl) {
|
||||
console.warn("focusSearch: mainSearchEl not defined!");
|
||||
return;
|
||||
}
|
||||
if (document.activeElement === mainSearchEl) {
|
||||
console.debug("focusSearch: mainSearchEl is already focused.");
|
||||
return;
|
||||
}
|
||||
mainSearchEl.focus();
|
||||
} catch (err) {
|
||||
console.error("focusSearch: Failed!", err);
|
||||
}
|
||||
}
|
||||
|
||||
function handleGlobalKeybind(ev: KeyboardEvent) {
|
||||
switch (ev.key.toLowerCase()) {
|
||||
case "s":
|
||||
if (ev.ctrlKey) {
|
||||
ev.preventDefault();
|
||||
focusSearch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
afterNavigate(() => {
|
||||
decideOnNavSplit();
|
||||
closeAllSubMenus();
|
||||
@@ -172,10 +211,12 @@
|
||||
decideOnNavSplit();
|
||||
window.addEventListener("resize", decideOnNavSplit);
|
||||
window.document.addEventListener("scroll", docOnScroll);
|
||||
window.document.addEventListener("keydown", handleGlobalKeybind);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", decideOnNavSplit);
|
||||
window.document.removeEventListener("scroll", docOnScroll);
|
||||
window.document.removeEventListener("keydown", handleGlobalKeybind);
|
||||
};
|
||||
} else {
|
||||
console.error(
|
||||
@@ -341,9 +382,8 @@
|
||||
top: 0;
|
||||
gap: 3px;
|
||||
z-index: 99990;
|
||||
backdrop-filter: blur(2.5px) saturate(120%);
|
||||
background-color: $nav-color;
|
||||
transition: top 200ms ease-in-out;
|
||||
@include nav-blur;
|
||||
|
||||
&:global(.scrolled-down) {
|
||||
top: -110px;
|
||||
|
||||
@@ -430,7 +430,7 @@
|
||||
// Build toImport array
|
||||
const toImport: ImportedList[] = [];
|
||||
const fileText = await readFile(new FileReader(), file);
|
||||
const jsonData = JSON.parse(fileText)["media"] as any[];
|
||||
const jsonData = JSON.parse(fileText)["metadata"] as any[];
|
||||
for (const v of jsonData) {
|
||||
if (
|
||||
!v.source_id ||
|
||||
@@ -491,11 +491,9 @@
|
||||
? v.reviews[0].review?.text
|
||||
: "",
|
||||
|
||||
// Ryot does not support overall rating for shows
|
||||
rating:
|
||||
v.lot === "movie" && v.reviews?.length
|
||||
? validifyRating(Number(v.reviews[0].rating))
|
||||
: undefined,
|
||||
rating: v.reviews?.length
|
||||
? validifyRating(Number(v.reviews[0].rating))
|
||||
: undefined,
|
||||
|
||||
datesWatched:
|
||||
v.lot === "movie" && v.seen_history?.length
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
/import/process is for processing the
|
||||
selected files data. Here it will be
|
||||
displayed and imported.
|
||||
@@ -166,17 +166,33 @@
|
||||
if (year) {
|
||||
l.year = String(year.getFullYear());
|
||||
}
|
||||
if (type === "movie") {
|
||||
l.type = "movie";
|
||||
} else if (type === "tv series") {
|
||||
l.type = "tv";
|
||||
} else if (type === "tv episode") {
|
||||
l.type = "tv_episode";
|
||||
} else {
|
||||
console.warn("Skipping item with invalid type", `(${type})`, el);
|
||||
anySkipped = true;
|
||||
continue;
|
||||
|
||||
switch (type) {
|
||||
case "movie":
|
||||
case "video":
|
||||
case "tv movie":
|
||||
case "short":
|
||||
l.type = "movie";
|
||||
break;
|
||||
case "tv series":
|
||||
case "tv mini series":
|
||||
case "tv special":
|
||||
case "tv short":
|
||||
l.type = "tv";
|
||||
break;
|
||||
case "tv episode":
|
||||
l.type = "tv_episode";
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
"Skipping item with invalid type",
|
||||
`(${type})`,
|
||||
el,
|
||||
);
|
||||
anySkipped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (imdbId) {
|
||||
l.imdbId = imdbId;
|
||||
}
|
||||
@@ -709,7 +725,9 @@
|
||||
{#if importMultiItem}
|
||||
<Modal
|
||||
title="Multiple Results Found"
|
||||
desc="Select the correct item for {importMultiItem.original.name}"
|
||||
desc="Select the correct item for {importMultiItem.original
|
||||
.name} {importMultiItem.original.year &&
|
||||
'(' + importMultiItem.original.year + ')'}"
|
||||
onClose={() => {
|
||||
importMultiItem?.callback("closed results modal");
|
||||
importMultiItem = undefined;
|
||||
|
||||
@@ -7,11 +7,7 @@
|
||||
import Stat from "@/lib/stats/Stat.svelte";
|
||||
import Stats from "@/lib/stats/Stats.svelte";
|
||||
import { updateUserSetting } from "@/lib/util/api";
|
||||
import {
|
||||
getOrdinalSuffix,
|
||||
monthsShort,
|
||||
toggleTheme,
|
||||
} from "@/lib/util/helpers";
|
||||
import { getOrdinalSuffix, monthsShort } from "@/lib/util/helpers";
|
||||
import { store } from "@/store.svelte";
|
||||
import { UserType, type Image, type Profile } from "@/types";
|
||||
import axios from "axios";
|
||||
@@ -21,6 +17,7 @@
|
||||
import SyncModal from "./modals/SyncModal.svelte";
|
||||
import RegionDropDown from "@/lib/RegionDropDown.svelte";
|
||||
import RatingSetting from "@/lib/rating/RatingSetting.svelte";
|
||||
import { toggleTheme } from "@/lib/util/theme";
|
||||
|
||||
let user = $derived(store.userInfo);
|
||||
let settings = $derived(store.userSettings);
|
||||
@@ -218,6 +215,13 @@
|
||||
<div class="theme">
|
||||
<h4 class="norm">Theme</h4>
|
||||
<div class="row">
|
||||
<button
|
||||
class={`plain${selectedTheme === "system" ? " selected" : ""}`}
|
||||
id="system"
|
||||
onclick={() => toggleTheme("system")}
|
||||
>
|
||||
<span>system</span>
|
||||
</button>
|
||||
<button
|
||||
class={`plain${selectedTheme === "light" ? " selected" : ""}`}
|
||||
id="light"
|
||||
@@ -504,6 +508,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
&#system {
|
||||
background: linear-gradient(to right bottom, white 50%, black 50.3%);
|
||||
outline-color: black;
|
||||
|
||||
span {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
-webkit-text-stroke: 0.5px white;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
outline-color: gold !important;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function searchGamesById(id: string) {
|
||||
try {
|
||||
const games = await axios.get<GameSearch[]>(`/game/search/${id}`, {
|
||||
signal: reqController.signal,
|
||||
});
|
||||
return {
|
||||
data: games?.data?.map((g) => ({
|
||||
...g,
|
||||
media_type: "game",
|
||||
})) as GameWithMediaType[],
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`searchGamesById: failed!`, id, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function searchExternalId(id: string, provider: string) {
|
||||
try {
|
||||
return await axios.get<ContentSearch>(
|
||||
@@ -181,6 +198,8 @@
|
||||
let p = spl[0]?.toLowerCase();
|
||||
switch (p) {
|
||||
// Default names that are supported right out of the box
|
||||
case "movie": // tmdb id target
|
||||
case "tv": // tmdb id target
|
||||
case "imdb":
|
||||
case "tvdb":
|
||||
case "youtube":
|
||||
@@ -189,6 +208,7 @@
|
||||
case "instagram":
|
||||
case "twitter":
|
||||
case "tiktok":
|
||||
case "igdb":
|
||||
break;
|
||||
// Any aliases we want to support
|
||||
case "i":
|
||||
@@ -205,6 +225,12 @@
|
||||
case "thetvdb":
|
||||
p = "tvdb";
|
||||
break;
|
||||
case "game":
|
||||
p = "igdb";
|
||||
break;
|
||||
case "series":
|
||||
p = "tv";
|
||||
break;
|
||||
// If none match, then is invalid provider.
|
||||
default:
|
||||
console.info("checkForExternalIdSearch: Invalid provider found:", p);
|
||||
@@ -237,42 +263,74 @@
|
||||
reqController = new AbortController();
|
||||
try {
|
||||
if (isExtSearch) {
|
||||
console.log("Search: Performing external id search.");
|
||||
const resp = await searchExternalId(
|
||||
extProvider.id,
|
||||
extProvider.provider,
|
||||
);
|
||||
const data = resp.data;
|
||||
if (!data || !data.results) {
|
||||
console.warn("Search: No results from external id search.");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
data.results.length === 1 &&
|
||||
data.results[0].media_type &&
|
||||
data.results[0].id
|
||||
) {
|
||||
console.info(
|
||||
"Search: Only one result from external id search. Redirecting..",
|
||||
data.results[0],
|
||||
);
|
||||
const mediaType = data.results[0].media_type;
|
||||
if (
|
||||
mediaType !== "movie" &&
|
||||
mediaType !== "tv" &&
|
||||
mediaType !== "person"
|
||||
) {
|
||||
console.info(
|
||||
"Search: Unsupported media type found in only result.. not redirecting.",
|
||||
mediaType,
|
||||
);
|
||||
} else {
|
||||
goto(`/${data.results[0].media_type}/${data.results[0].id}`);
|
||||
if (extProvider.provider === "igdb") {
|
||||
console.log("Search: Performing igdb id search.");
|
||||
const resp = await searchGamesById(extProvider.id);
|
||||
const data = resp.data;
|
||||
const resultsAmt = data.length;
|
||||
if (!data || resultsAmt <= 0) {
|
||||
console.warn("Search: No results from game id search.");
|
||||
return;
|
||||
}
|
||||
if (resultsAmt === 1) {
|
||||
console.info(
|
||||
"Search: Only one result from game id search. Redirecting..",
|
||||
data[0],
|
||||
);
|
||||
|
||||
goto(`/game/${data[0].id}`);
|
||||
return;
|
||||
}
|
||||
allSearchResults.push(...data);
|
||||
} else if (
|
||||
extProvider.provider === "tv" ||
|
||||
extProvider.provider === "movie"
|
||||
) {
|
||||
// HACK I can't be bothered doing a test api call here,
|
||||
// assuming that people paste the id in, this should work
|
||||
// without the debounce going to an incomplete id.
|
||||
// Flesh out if anyone has issues.
|
||||
goto(`/${extProvider.provider}/${extProvider.id}`);
|
||||
return;
|
||||
} else {
|
||||
// Else call tmdb `external id` endpoint
|
||||
console.log("Search: Performing external id search.");
|
||||
const resp = await searchExternalId(
|
||||
extProvider.id,
|
||||
extProvider.provider,
|
||||
);
|
||||
const data = resp.data;
|
||||
if (!data || !data.results) {
|
||||
console.warn("Search: No results from external id search.");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
data.results.length === 1 &&
|
||||
data.results[0].media_type &&
|
||||
data.results[0].id
|
||||
) {
|
||||
console.info(
|
||||
"Search: Only one result from external id search. Redirecting..",
|
||||
data.results[0],
|
||||
);
|
||||
const mediaType = data.results[0].media_type;
|
||||
if (
|
||||
mediaType !== "movie" &&
|
||||
mediaType !== "tv" &&
|
||||
mediaType !== "person"
|
||||
) {
|
||||
console.info(
|
||||
"Search: Unsupported media type found in only result.. not redirecting.",
|
||||
mediaType,
|
||||
);
|
||||
} else {
|
||||
goto(`/${data.results[0].media_type}/${data.results[0].id}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
allSearchResults.push(...data.results);
|
||||
}
|
||||
console.info("Search: Multiple results from external id search.");
|
||||
allSearchResults.push(...data.results);
|
||||
searchResults = allSearchResults;
|
||||
curPage++;
|
||||
} else if (activeSearchFilter) {
|
||||
|
||||
+4
-7
@@ -12,7 +12,7 @@ import type {
|
||||
} from "./types";
|
||||
import type { Notification } from "./lib/util/notify";
|
||||
import { browser } from "$app/environment";
|
||||
import { toggleTheme } from "./lib/util/helpers";
|
||||
import { toggleTheme } from "./lib/util/theme";
|
||||
|
||||
export const defaultSort = ["DATEADDED", "DOWN"];
|
||||
|
||||
@@ -54,7 +54,7 @@ const _store: Store = $state({
|
||||
notifications: [],
|
||||
activeSort: defaultSort,
|
||||
activeFilters: { type: [], status: [] },
|
||||
appTheme: "light",
|
||||
appTheme: "system",
|
||||
importedList: undefined,
|
||||
parsedImportedList: undefined,
|
||||
searchQuery: "",
|
||||
@@ -186,7 +186,7 @@ export const clearAllStores = () => {
|
||||
store.watchedList = [];
|
||||
store.notifications = [];
|
||||
store.activeSort = defaultSort;
|
||||
store.appTheme = "light";
|
||||
store.appTheme = "system";
|
||||
store.importedList = undefined;
|
||||
store.parsedImportedList = undefined;
|
||||
store.searchQuery = "";
|
||||
@@ -244,10 +244,7 @@ function rehydrateStore() {
|
||||
$state.snapshot(store.appTheme),
|
||||
);
|
||||
} else {
|
||||
let defTheme: Theme = "light";
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
defTheme = "dark";
|
||||
}
|
||||
let defTheme: Theme = "system";
|
||||
_store.appTheme = defTheme;
|
||||
toggleTheme(defTheme, false);
|
||||
console.debug(
|
||||
|
||||
+6
-2
@@ -57,9 +57,13 @@ export type Icon =
|
||||
| "sparkles"
|
||||
| "tag"
|
||||
| "ticket"
|
||||
| "lock-closed";
|
||||
| "lock-closed"
|
||||
| "github"
|
||||
| "website"
|
||||
| "tmdb"
|
||||
| "igdb";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
export type Theme = "light" | "dark" | "system";
|
||||
|
||||
export type WLDetailedViewOption =
|
||||
| "statusRating"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
:root {
|
||||
--bg-color: white;
|
||||
--bg-color-accent: rgb(180, 180, 180);
|
||||
--nav-color: rgba(255, 255, 255, 0.8);
|
||||
--text-color: black;
|
||||
--text-color-accent: rgb(90, 90, 90);
|
||||
--accent-color: rgba(128, 128, 128, 0.226);
|
||||
--accent-color-hover: rgba(46, 46, 46);
|
||||
--backdrop-filter: blur(4px) grayscale(80%);
|
||||
@@ -14,8 +16,10 @@
|
||||
|
||||
:root.theme-dark {
|
||||
--bg-color: rgb(15, 15, 15);
|
||||
--bg-color-accent: rgb(70, 70, 70);
|
||||
--nav-color: rgba(15, 15, 15, 0.438);
|
||||
--text-color: white;
|
||||
--text-color-accent: rgb(180, 180, 180);
|
||||
--accent-color: rgba(46, 46, 46);
|
||||
--accent-color-hover: rgba(255, 255, 255, 0.8);
|
||||
--backdrop-filter: blur(0.5px) grayscale(50%);
|
||||
@@ -27,7 +31,9 @@
|
||||
}
|
||||
|
||||
$bg-color: var(--bg-color);
|
||||
$bg-color-accent: var(--bg-color-accent);
|
||||
$text-color: var(--text-color);
|
||||
$text-color-accent: var(--text-color-accent);
|
||||
$placeholder-color: var(--placeholder-color);
|
||||
$accent-color: var(--accent-color);
|
||||
$accent-color-hover: var(--accent-color-hover);
|
||||
|
||||
Reference in New Issue
Block a user