Skip to content

Commit 58aeddb

Browse files
committed
feat(simkl): add simkl ratings for show & movies
1 parent 55dfa9f commit 58aeddb

12 files changed

+287
-63
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"dependencies": {
5454
"@dvcol/base-http-client": "^1.13.1",
5555
"@dvcol/common-utils": "^1.11.1",
56-
"@dvcol/simkl-http-client": "^1.1.0",
56+
"@dvcol/simkl-http-client": "^1.1.1",
5757
"@dvcol/tmdb-http-client": "^1.3.4",
5858
"@dvcol/trakt-http-client": "^1.4.9",
5959
"@dvcol/web-extension-utils": "^3.3.2",

pnpm-lock.yaml

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
3+
<path
4+
fill="none"
5+
stroke="currentColor"
6+
stroke-dasharray="32"
7+
stroke-dashoffset="32"
8+
stroke-linecap="round"
9+
stroke-linejoin="round"
10+
stroke-width="1.5"
11+
d="M12 3L9.65 8.76L3.44 9.22L8.2 13.24L6.71 19.28L12 16M12 3L14.35 8.76L20.56 9.22L15.8 13.24L17.29 19.28L12 16"
12+
>
13+
<animate fill="freeze" attributeName="stroke-dashoffset" dur="0.5s" values="32;0" />
14+
</path>
15+
<path
16+
fill="currentColor"
17+
fill-opacity="0"
18+
d="M12 3L9.65 8.76L3.44 9.22L8.2 13.24L6.71 19.28L12 16Z"
19+
>
20+
<animate
21+
fill="freeze"
22+
attributeName="fill-opacity"
23+
begin="0.5s"
24+
dur="0.5s"
25+
values="0;1"
26+
/>
27+
</path>
28+
</svg>
29+
</template>

src/components/views/panel/PanelMovieStatistics.vue

+38-15
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import { computed, onMounted, type PropType, toRefs, watch } from 'vue';
99
1010
import type { RatingItem } from '~/models/rating.model';
1111
12-
import IconTrakt from '~/components/icons/IconTrakt.vue';
1312
import PanelStatistics from '~/components/views/panel/PanelStatistics.vue';
1413
import { ResolveExternalLinks } from '~/settings/external.links';
1514
import { useRatingsStore } from '~/stores/data/ratings.store';
15+
import { useSimklStore } from '~/stores/data/simkl.store';
1616
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
1717
import { useI18n } from '~/utils/i18n.utils';
18+
import { DataSource } from '~/utils/icon.utils';
1819
1920
const i18n = useI18n('panel', 'statistics');
2021
@@ -53,21 +54,43 @@ const ratingUrl = computed(() => {
5354
});
5455
});
5556
56-
const ratings = computed<RatingItem[]>(
57-
() =>
58-
[
59-
{
60-
name: i18n('trakt', 'common', 'source', 'name'),
61-
icon: IconTrakt,
62-
rating: {
63-
votes: movie?.value?.votes,
64-
rating: movie?.value?.rating,
65-
url: ratingUrl.value,
66-
loading: movieLoading.value,
67-
},
57+
const { getMovie, getMovieLoading } = useSimklStore();
58+
const simklMovie = computed(() => {
59+
if (!movie?.value?.ids?.imdb) return;
60+
return getMovie(movie.value.ids.imdb).value;
61+
});
62+
const simklMovieLoading = computed(() => {
63+
if (!movie?.value?.ids?.imdb) return;
64+
return getMovieLoading(movie.value.ids.imdb).value;
65+
});
66+
67+
const ratings = computed<RatingItem[]>(() => {
68+
const _ratings: RatingItem[] = [];
69+
_ratings.push({
70+
name: i18n('trakt', 'common', 'source', 'name'),
71+
icon: DataSource.Trakt,
72+
rating: {
73+
votes: movie?.value?.votes,
74+
rating: movie?.value?.rating,
75+
url: ratingUrl.value,
76+
loading: movieLoading.value,
77+
},
78+
});
79+
if (!simklMovie.value?.ratings) return _ratings;
80+
Object.entries(simklMovie.value.ratings).forEach(([key, value]) => {
81+
_ratings.push({
82+
name: i18n(key, 'common', 'source', 'name'),
83+
icon: key,
84+
rating: {
85+
votes: value.votes,
86+
rating: value.rating,
87+
loading: simklMovieLoading.value,
6888
},
69-
] satisfies RatingItem[],
70-
);
89+
});
90+
});
91+
92+
return _ratings;
93+
});
7194
7295
const onScoreEdit = async (_score: TraktSyncRatingValue) => {
7396
if (!movie?.value?.ids?.trakt) return;

src/components/views/panel/PanelRatings.vue

+11-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { DropdownOption } from 'naive-ui';
77
import type { RatingItem } from '~/models/rating.model';
88
99
import PanelRating from '~/components/views/panel/PanelRating.vue';
10+
import { getIconFromSource } from '~/utils/icon.utils';
1011
1112
const props = defineProps({
1213
ratings: {
@@ -17,18 +18,21 @@ const props = defineProps({
1718
1819
const { ratings } = toRefs(props);
1920
21+
const getIcon = (icon: RatingItem['icon']): DropdownOption['icon'] => {
22+
if (!icon) return undefined;
23+
return () =>
24+
h(NIcon, {
25+
style: { marginRight: '-0.25rem' },
26+
component: typeof icon === 'string' ? getIconFromSource(icon) : icon,
27+
});
28+
};
29+
2030
const options = computed<DropdownOption[]>(() => {
2131
if (!ratings?.value) return [];
2232
return ratings.value.map((rating, index) => ({
2333
label: rating.name,
2434
key: index,
25-
icon: rating.icon
26-
? () =>
27-
h(NIcon, {
28-
style: { marginRight: '-0.25rem' },
29-
component: rating.icon,
30-
})
31-
: undefined,
35+
icon: getIcon(rating.icon),
3236
}));
3337
});
3438

src/components/views/panel/PanelShowStatistics.vue

+41-33
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@ import { computed, type PropType, toRefs } from 'vue';
1111
1212
import type { RatingItem } from '~/models/rating.model';
1313
14-
import IconIMDb from '~/components/icons/IconIMDb.vue';
15-
import IconMyAnimeList from '~/components/icons/IconMyAnimeList.vue';
16-
import IconSimkl from '~/components/icons/IconSimkl.vue';
17-
import IconTrakt from '~/components/icons/IconTrakt.vue';
1814
import PanelStatistics from '~/components/views/panel/PanelStatistics.vue';
1915
import { ResolveExternalLinks } from '~/settings/external.links';
2016
import { useRatingsStore } from '~/stores/data/ratings.store';
2117
2218
import { useShowStore } from '~/stores/data/show.store';
19+
import { useSimklStore } from '~/stores/data/simkl.store';
2320
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
2421
import { useI18n } from '~/utils/i18n.utils';
22+
import { DataSource } from '~/utils/icon.utils';
2523
2624
const i18n = useI18n('panel', 'statistics');
2725
@@ -146,36 +144,46 @@ const rating = computed(() => {
146144
return show?.value?.rating;
147145
});
148146
149-
const ratings = computed<RatingItem[]>(
150-
() =>
151-
[
152-
{
153-
name: i18n('trakt', 'common', 'source', 'name'),
154-
icon: IconTrakt,
155-
rating: {
156-
votes: votes.value,
157-
rating: rating.value,
158-
url: ratingUrl.value,
159-
loading: ratingLoading.value,
160-
},
161-
},
162-
{
163-
name: i18n('simkl', 'common', 'source', 'name'),
164-
icon: IconSimkl,
165-
rating: { votes: 125846, rating: 5.2 },
166-
},
167-
{
168-
name: i18n('mal', 'common', 'source', 'name'),
169-
icon: IconMyAnimeList,
170-
rating: { votes: 250, rating: 2.45 },
171-
},
172-
{
173-
name: i18n('imdb', 'common', 'source', 'name'),
174-
icon: IconIMDb,
175-
rating: { votes: 326579, rating: 8.45 },
147+
const { getShowOrAnime, getShowOrAnimeLoading } = useSimklStore();
148+
149+
const simklShow = computed(() => {
150+
if (!show?.value?.ids?.imdb) return;
151+
return getShowOrAnime(show.value.ids.imdb).value;
152+
});
153+
154+
const simklShowLoading = computed(() => {
155+
if (!show?.value?.ids?.imdb) return;
156+
return getShowOrAnimeLoading(show.value.ids.imdb).value;
157+
});
158+
159+
const ratings = computed<RatingItem[]>(() => {
160+
const _ratings: RatingItem[] = [];
161+
_ratings.push({
162+
name: i18n('trakt', 'common', 'source', 'name'),
163+
icon: DataSource.Trakt,
164+
rating: {
165+
votes: votes.value,
166+
rating: rating.value,
167+
url: ratingUrl.value,
168+
loading: ratingLoading.value,
169+
},
170+
});
171+
if (!simklShow.value?.ratings) return _ratings;
172+
if (mode.value !== 'show') return _ratings;
173+
Object.entries(simklShow.value.ratings).forEach(([key, value]) => {
174+
_ratings.push({
175+
name: i18n(key, 'common', 'source', 'name'),
176+
icon: key,
177+
rating: {
178+
votes: value.votes,
179+
rating: value.rating,
180+
loading: simklShowLoading.value,
176181
},
177-
] satisfies RatingItem[],
178-
);
182+
});
183+
});
184+
185+
return _ratings;
186+
});
179187
180188
const onScoreEdit = async (_score: TraktSyncRatingValue) => {
181189
if (!show?.value?.ids?.trakt) return;

src/models/rating.model.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ export type RatingProp = {
3737
export type RatingItem = {
3838
name: string;
3939
rating: RatingProp;
40-
icon?: Component;
40+
icon?: Component | string;
4141
};

src/services/trakt.service.ts

+18
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,24 @@ export class TraktService {
634634
await useSimklStore().setUserSetting(undefined, account);
635635
return this.simklClient.importAuthentication({});
636636
},
637+
show: async (id: string | number, extended = true) => {
638+
const response = await this.simklClient.show.id.cached({ id, extended });
639+
const data = await response.json();
640+
if (shouldEvict(response?.cache, data?.first_aired)) response.cache?.evict?.();
641+
return data;
642+
},
643+
anime: async (id: string | number, extended = true) => {
644+
const response = await this.simklClient.anime.id.cached({ id, extended });
645+
const data = await response.json();
646+
if (shouldEvict(response?.cache, data?.first_aired)) response.cache?.evict?.();
647+
return data;
648+
},
649+
movie: async (id: string | number, extended = true) => {
650+
const response = await this.simklClient.movie.id.cached({ id, extended });
651+
const data = await response.json();
652+
if (shouldEvict(response?.cache, data?.released)) response.cache?.evict?.();
653+
return data;
654+
},
637655
};
638656

639657
static evict = {

src/stores/data/movie.store.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ErrorService } from '~/services/error.service';
99
import { Logger } from '~/services/logger.service';
1010
import { NotificationService } from '~/services/notification.service';
1111
import { TraktService } from '~/services/trakt.service';
12+
import { useSimklStore, useSimklStoreRefs } from '~/stores/data/simkl.store';
1213
import { useAuthSettingsStoreRefs } from '~/stores/settings/auth.store';
1314
import { ErrorCount, type ErrorDictionary } from '~/utils/retry.utils';
1415
import { clearProxy } from '~/utils/vue.utils';
@@ -49,6 +50,8 @@ export const useMovieStore = defineStore('data.movie', () => {
4950
clearProxy(movieErrors);
5051
};
5152

53+
const { simklEnabled } = useSimklStoreRefs();
54+
const { fetchMovie: fetchSimklMovie } = useSimklStore();
5255
const { user, isAuthenticated } = useAuthSettingsStoreRefs();
5356
const fetchMovie = async (id: string | number) => {
5457
if (!isAuthenticated.value) {
@@ -66,6 +69,7 @@ export const useMovieStore = defineStore('data.movie', () => {
6669

6770
try {
6871
movies[id] = await TraktService.movie(id);
72+
if (simklEnabled.value && movies[id]?.ids?.imdb) await fetchSimklMovie(movies[id].ids.imdb);
6973
delete movieErrors[id.toString()];
7074
} catch (error) {
7175
Logger.error('Failed to fetch movie', id);

src/stores/data/show.store.ts

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ErrorService } from '~/services/error.service';
1919
import { Logger } from '~/services/logger.service';
2020
import { NotificationService } from '~/services/notification.service';
2121
import { TraktService } from '~/services/trakt.service';
22+
import { useSimklStore, useSimklStoreRefs } from '~/stores/data/simkl.store';
2223
import { useAuthSettingsStoreRefs } from '~/stores/settings/auth.store';
2324
import { ErrorCount, type ErrorDictionary, shouldRetry } from '~/utils/retry.utils';
2425
import { clearProxy } from '~/utils/vue.utils';
@@ -154,6 +155,8 @@ export const useShowStore = defineStore('data.show', () => {
154155
clearProgressState();
155156
};
156157

158+
const { simklEnabled } = useSimklStoreRefs();
159+
const { fetchShow: fetchSimklShow, fetchAnime: fetchSimklAnime } = useSimklStore();
157160
const { user, isAuthenticated } = useAuthSettingsStoreRefs();
158161
const fetchShow = async (id: string) => {
159162
if (!isAuthenticated.value) {
@@ -170,6 +173,12 @@ export const useShowStore = defineStore('data.show', () => {
170173
showsLoading[id] = true;
171174
try {
172175
shows[id] = await TraktService.show.summary(id);
176+
177+
if (simklEnabled.value && shows[id].ids.imdb) {
178+
if (shows[id].genres.includes('anime')) await fetchSimklAnime(shows[id].ids.imdb);
179+
else await fetchSimklShow(shows[id].ids.imdb);
180+
}
181+
173182
delete showsError[id];
174183
} catch (e) {
175184
Logger.error('Failed to fetch show', id);

0 commit comments

Comments
 (0)