mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 14:53:24 +00:00
Compare commits
1 Commits
9694630cdf
...
bd8a4394ea
Author | SHA1 | Date | |
---|---|---|---|
|
bd8a4394ea |
@@ -1,6 +1,17 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type { LoginResponse } from "./types";
|
||||
export interface SessionResponse {
|
||||
id: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
accessedAt: string;
|
||||
device: string;
|
||||
userAgent: string;
|
||||
}
|
||||
export interface LoginResponse {
|
||||
session: SessionResponse;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export function getAuthHeaders(token: string): Record<string, string> {
|
||||
return {
|
||||
|
@@ -1,55 +0,0 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type {
|
||||
AccountWithToken,
|
||||
BookmarkInput,
|
||||
BookmarkMediaItem,
|
||||
BookmarkResponse,
|
||||
} from "./types";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
|
||||
export function bookmarkMediaToInput(
|
||||
tmdbId: string,
|
||||
item: BookmarkMediaItem,
|
||||
): BookmarkInput {
|
||||
return {
|
||||
meta: {
|
||||
title: item.title,
|
||||
type: item.type,
|
||||
poster: item.poster,
|
||||
year: item.year ?? 0,
|
||||
},
|
||||
tmdbId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function addBookmark(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
input: BookmarkInput,
|
||||
) {
|
||||
return ofetch<BookmarkResponse>(
|
||||
`/users/${account.userId}/bookmarks/${input.tmdbId}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
body: input,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeBookmark(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
id: string,
|
||||
) {
|
||||
return ofetch<{ tmdbId: string }>(
|
||||
`/users/${account.userId}/bookmarks/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
},
|
||||
);
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type { AccountWithToken, BookmarkInput, ProgressInput } from "./types";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
|
||||
export function importProgress(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
progressItems: ProgressInput[],
|
||||
) {
|
||||
return ofetch<void>(`/users/${account.userId}/progress/import`, {
|
||||
method: "PUT",
|
||||
body: progressItems,
|
||||
baseURL: url,
|
||||
headers: getAuthHeaders(account.token),
|
||||
});
|
||||
}
|
||||
|
||||
export function importBookmarks(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
bookmarks: BookmarkInput[],
|
||||
) {
|
||||
return ofetch<void>(`/users/${account.userId}/bookmarks`, {
|
||||
method: "PUT",
|
||||
body: bookmarks,
|
||||
baseURL: url,
|
||||
headers: getAuthHeaders(account.token),
|
||||
});
|
||||
}
|
@@ -1,13 +1,3 @@
|
||||
export const name = "api";
|
||||
export * from "./auth";
|
||||
export * from "./bookmarks";
|
||||
export * from "./crypto";
|
||||
export * from "./import";
|
||||
export * from "./login";
|
||||
export * from "./meta";
|
||||
export * from "./progress";
|
||||
export * from "./register";
|
||||
export * from "./sessions";
|
||||
export * from "./settings";
|
||||
export * from "./types";
|
||||
export * from "./user";
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type {
|
||||
ChallengeTokenResponse,
|
||||
LoginInput,
|
||||
LoginResponse,
|
||||
} from "./types";
|
||||
import type { SessionResponse } from "./auth";
|
||||
|
||||
export interface ChallengeTokenResponse {
|
||||
challenge: string;
|
||||
}
|
||||
|
||||
export async function getLoginChallengeToken(
|
||||
url: string,
|
||||
@@ -19,6 +19,20 @@ export async function getLoginChallengeToken(
|
||||
});
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
session: SessionResponse;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface LoginInput {
|
||||
publicKey: string;
|
||||
challenge: {
|
||||
code: string;
|
||||
signature: string;
|
||||
};
|
||||
device: string;
|
||||
}
|
||||
|
||||
export async function loginAccount(
|
||||
url: string,
|
||||
data: LoginInput,
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type { MetaResponse } from "./types";
|
||||
export interface MetaResponse {
|
||||
version: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
hasCaptcha: boolean;
|
||||
captchaClientKey?: string;
|
||||
}
|
||||
|
||||
export async function getBackendMeta(url: string): Promise<MetaResponse> {
|
||||
return ofetch<MetaResponse>("/meta", {
|
||||
|
@@ -1,102 +0,0 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type {
|
||||
AccountWithToken,
|
||||
ProgressInput,
|
||||
ProgressMediaItem,
|
||||
ProgressResponse,
|
||||
ProgressUpdateItem,
|
||||
} from "./types";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
|
||||
export function progressUpdateItemToInput(
|
||||
item: ProgressUpdateItem,
|
||||
): ProgressInput {
|
||||
return {
|
||||
duration: item.progress?.duration ?? 0,
|
||||
watched: item.progress?.watched ?? 0,
|
||||
tmdbId: item.tmdbId,
|
||||
meta: {
|
||||
title: item.title ?? "",
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
poster: item.poster,
|
||||
},
|
||||
episodeId: item.episodeId,
|
||||
seasonId: item.seasonId,
|
||||
episodeNumber: item.episodeNumber,
|
||||
seasonNumber: item.seasonNumber,
|
||||
};
|
||||
}
|
||||
|
||||
export function progressMediaItemToInputs(
|
||||
tmdbId: string,
|
||||
item: ProgressMediaItem,
|
||||
): ProgressInput[] {
|
||||
if (item.type === "show") {
|
||||
return Object.entries(item.episodes).flatMap(([_, episode]) => ({
|
||||
duration: item.progress?.duration ?? episode.progress.duration,
|
||||
watched: item.progress?.watched ?? episode.progress.watched,
|
||||
tmdbId,
|
||||
meta: {
|
||||
title: item.title ?? "",
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
poster: item.poster,
|
||||
},
|
||||
episodeId: episode.id,
|
||||
seasonId: episode.seasonId,
|
||||
episodeNumber: episode.number,
|
||||
seasonNumber: item.seasons[episode.seasonId]?.number,
|
||||
updatedAt: new Date(episode.updatedAt).toISOString(),
|
||||
}));
|
||||
}
|
||||
return [
|
||||
{
|
||||
duration: item.progress?.duration ?? 0,
|
||||
watched: item.progress?.watched ?? 0,
|
||||
tmdbId,
|
||||
updatedAt: new Date(item.updatedAt).toISOString(),
|
||||
meta: {
|
||||
title: item.title ?? "",
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
poster: item.poster,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function setProgress(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
input: ProgressInput,
|
||||
) {
|
||||
return ofetch<ProgressResponse>(
|
||||
`/users/${account.userId}/progress/${input.tmdbId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
body: input,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeProgress(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
id: string,
|
||||
episodeId?: string,
|
||||
seasonId?: string,
|
||||
) {
|
||||
await ofetch(`/users/${account.userId}/progress/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
body: {
|
||||
episodeId,
|
||||
seasonId,
|
||||
},
|
||||
});
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type {
|
||||
ChallengeTokenResponse,
|
||||
RegisterInput,
|
||||
SessionResponse,
|
||||
UserResponse,
|
||||
} from "./types";
|
||||
|
||||
export async function getRegisterChallengeToken(
|
||||
url: string,
|
||||
captchaToken?: string,
|
||||
): Promise<ChallengeTokenResponse> {
|
||||
return ofetch<ChallengeTokenResponse>("/auth/register/start", {
|
||||
method: "POST",
|
||||
body: {
|
||||
captchaToken,
|
||||
},
|
||||
baseURL: url,
|
||||
});
|
||||
}
|
||||
|
||||
export interface RegisterResponse {
|
||||
user: UserResponse;
|
||||
session: SessionResponse;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export async function registerAccount(
|
||||
url: string,
|
||||
data: RegisterInput,
|
||||
): Promise<RegisterResponse> {
|
||||
return ofetch<RegisterResponse>("/auth/register/complete", {
|
||||
method: "POST",
|
||||
body: {
|
||||
namespace: "movie-web",
|
||||
...data,
|
||||
},
|
||||
baseURL: url,
|
||||
});
|
||||
}
|
@@ -1,8 +1,36 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type { AccountWithToken, SessionResponse, SessionUpdate } from "./types";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
|
||||
export interface SessionResponse {
|
||||
id: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
accessedAt: string;
|
||||
device: string;
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
export interface SessionUpdate {
|
||||
deviceName: string;
|
||||
}
|
||||
|
||||
interface Account {
|
||||
profile: {
|
||||
colorA: string;
|
||||
colorB: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type AccountWithToken = Account & {
|
||||
sessionId: string;
|
||||
userId: string;
|
||||
token: string;
|
||||
seed: string;
|
||||
deviceName: string;
|
||||
};
|
||||
|
||||
export async function getSessions(url: string, account: AccountWithToken) {
|
||||
return ofetch<SessionResponse[]>(`/users/${account.userId}/sessions`, {
|
||||
headers: getAuthHeaders(account.token),
|
||||
|
@@ -1,12 +1,22 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type {
|
||||
AccountWithToken,
|
||||
SettingsInput,
|
||||
SettingsResponse,
|
||||
} from "./types";
|
||||
import type { AccountWithToken } from "./sessions";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
|
||||
export interface SettingsInput {
|
||||
applicationLanguage?: string;
|
||||
applicationTheme?: string | null;
|
||||
defaultSubtitleLanguage?: string;
|
||||
proxyUrls?: string[] | null;
|
||||
}
|
||||
|
||||
export interface SettingsResponse {
|
||||
applicationTheme?: string | null;
|
||||
applicationLanguage?: string | null;
|
||||
defaultSubtitleLanguage?: string | null;
|
||||
proxyUrls?: string[] | null;
|
||||
}
|
||||
|
||||
export function updateSettings(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
|
@@ -1,231 +0,0 @@
|
||||
export interface SessionResponse {
|
||||
id: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
accessedAt: string;
|
||||
device: string;
|
||||
userAgent: string;
|
||||
}
|
||||
export interface LoginResponse {
|
||||
session: SessionResponse;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface BookmarkMetaInput {
|
||||
title: string;
|
||||
year: number;
|
||||
poster?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface BookmarkInput {
|
||||
tmdbId: string;
|
||||
meta: BookmarkMetaInput;
|
||||
}
|
||||
|
||||
export interface ChallengeTokenResponse {
|
||||
challenge: string;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
session: SessionResponse;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface LoginInput {
|
||||
publicKey: string;
|
||||
challenge: {
|
||||
code: string;
|
||||
signature: string;
|
||||
};
|
||||
device: string;
|
||||
}
|
||||
|
||||
export interface MetaResponse {
|
||||
version: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
hasCaptcha: boolean;
|
||||
captchaClientKey?: string;
|
||||
}
|
||||
|
||||
export interface RegisterInput {
|
||||
publicKey: string;
|
||||
challenge: {
|
||||
code: string;
|
||||
signature: string;
|
||||
};
|
||||
device: string;
|
||||
profile: {
|
||||
colorA: string;
|
||||
colorB: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProgressInput {
|
||||
meta?: {
|
||||
title: string;
|
||||
year: number;
|
||||
poster?: string;
|
||||
type: string;
|
||||
};
|
||||
tmdbId: string;
|
||||
watched: number;
|
||||
duration: number;
|
||||
seasonId?: string;
|
||||
episodeId?: string;
|
||||
seasonNumber?: number;
|
||||
episodeNumber?: number;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface SessionResponse {
|
||||
id: string;
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
accessedAt: string;
|
||||
device: string;
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
export interface SessionUpdate {
|
||||
deviceName: string;
|
||||
}
|
||||
|
||||
interface Account {
|
||||
profile: {
|
||||
colorA: string;
|
||||
colorB: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type AccountWithToken = Account & {
|
||||
sessionId: string;
|
||||
userId: string;
|
||||
token: string;
|
||||
seed: string;
|
||||
deviceName: string;
|
||||
};
|
||||
|
||||
export interface SettingsInput {
|
||||
applicationLanguage?: string;
|
||||
applicationTheme?: string | null;
|
||||
defaultSubtitleLanguage?: string;
|
||||
proxyUrls?: string[] | null;
|
||||
}
|
||||
|
||||
export interface SettingsResponse {
|
||||
applicationTheme?: string | null;
|
||||
applicationLanguage?: string | null;
|
||||
defaultSubtitleLanguage?: string | null;
|
||||
proxyUrls?: string[] | null;
|
||||
}
|
||||
|
||||
export interface UserResponse {
|
||||
id: string;
|
||||
namespace: string;
|
||||
name: string;
|
||||
roles: string[];
|
||||
createdAt: string;
|
||||
profile: {
|
||||
colorA: string;
|
||||
colorB: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserEdit {
|
||||
profile?: {
|
||||
colorA: string;
|
||||
colorB: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BookmarkMediaItem {
|
||||
title: string;
|
||||
year?: number;
|
||||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface BookmarkResponse {
|
||||
tmdbId: string;
|
||||
meta: {
|
||||
title: string;
|
||||
year: number;
|
||||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
};
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ProgressItem {
|
||||
watched: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
export interface ProgressSeasonItem {
|
||||
title: string;
|
||||
number: number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ProgressEpisodeItem {
|
||||
title: string;
|
||||
number: number;
|
||||
id: string;
|
||||
seasonId: string;
|
||||
updatedAt: number;
|
||||
progress: ProgressItem;
|
||||
}
|
||||
|
||||
export interface ProgressMediaItem {
|
||||
title: string;
|
||||
year?: number;
|
||||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
progress?: ProgressItem;
|
||||
updatedAt: number;
|
||||
seasons: Record<string, ProgressSeasonItem>;
|
||||
episodes: Record<string, ProgressEpisodeItem>;
|
||||
}
|
||||
|
||||
export interface ProgressUpdateItem {
|
||||
title?: string;
|
||||
year?: number;
|
||||
poster?: string;
|
||||
type?: "show" | "movie";
|
||||
progress?: ProgressItem;
|
||||
tmdbId: string;
|
||||
id: string;
|
||||
episodeId?: string;
|
||||
seasonId?: string;
|
||||
episodeNumber?: number;
|
||||
seasonNumber?: number;
|
||||
action: "upsert" | "delete";
|
||||
}
|
||||
|
||||
export interface ProgressResponse {
|
||||
tmdbId: string;
|
||||
season: {
|
||||
id?: string;
|
||||
number?: number;
|
||||
};
|
||||
episode: {
|
||||
id?: string;
|
||||
number?: number;
|
||||
};
|
||||
meta: {
|
||||
title: string;
|
||||
year: number;
|
||||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
};
|
||||
duration: string;
|
||||
watched: string;
|
||||
updatedAt: string;
|
||||
}
|
@@ -1,133 +0,0 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
import type {
|
||||
AccountWithToken,
|
||||
BookmarkMediaItem,
|
||||
BookmarkResponse,
|
||||
ProgressMediaItem,
|
||||
ProgressResponse,
|
||||
SessionResponse,
|
||||
UserEdit,
|
||||
UserResponse,
|
||||
} from "./types";
|
||||
import { getAuthHeaders } from "./auth";
|
||||
|
||||
export function bookmarkResponsesToEntries(responses: BookmarkResponse[]) {
|
||||
const entries = responses.map((bookmark) => {
|
||||
const item: BookmarkMediaItem = {
|
||||
...bookmark.meta,
|
||||
updatedAt: new Date(bookmark.updatedAt).getTime(),
|
||||
};
|
||||
return [bookmark.tmdbId, item] as const;
|
||||
});
|
||||
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
export function progressResponsesToEntries(responses: ProgressResponse[]) {
|
||||
const items: Record<string, ProgressMediaItem> = {};
|
||||
|
||||
responses.forEach((v) => {
|
||||
if (!items[v.tmdbId]) {
|
||||
items[v.tmdbId] = {
|
||||
title: v.meta.title,
|
||||
poster: v.meta.poster,
|
||||
type: v.meta.type,
|
||||
updatedAt: new Date(v.updatedAt).getTime(),
|
||||
episodes: {},
|
||||
seasons: {},
|
||||
year: v.meta.year,
|
||||
};
|
||||
}
|
||||
|
||||
const item = items[v.tmdbId];
|
||||
if (!item) return;
|
||||
|
||||
// Since each watched episode is a single array entry but with the same tmdbId, the root item updatedAt will only have the first episode's timestamp (which is not the newest).
|
||||
// Here, we are setting it explicitly so the updatedAt always has the highest updatedAt from the episodes.
|
||||
if (new Date(v.updatedAt).getTime() > item.updatedAt) {
|
||||
item.updatedAt = new Date(v.updatedAt).getTime();
|
||||
}
|
||||
|
||||
if (item.type === "movie") {
|
||||
item.progress = {
|
||||
duration: Number(v.duration),
|
||||
watched: Number(v.watched),
|
||||
};
|
||||
}
|
||||
|
||||
if (item.type === "show" && v.season.id && v.episode.id) {
|
||||
item.seasons[v.season.id] = {
|
||||
id: v.season.id,
|
||||
number: v.season.number ?? 0,
|
||||
title: "",
|
||||
};
|
||||
item.episodes[v.episode.id] = {
|
||||
id: v.episode.id,
|
||||
number: v.episode.number ?? 0,
|
||||
title: "",
|
||||
progress: {
|
||||
duration: Number(v.duration),
|
||||
watched: Number(v.watched),
|
||||
},
|
||||
seasonId: v.season.id,
|
||||
updatedAt: new Date(v.updatedAt).getTime(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
export async function getUser(
|
||||
url: string,
|
||||
token: string,
|
||||
): Promise<{ user: UserResponse; session: SessionResponse }> {
|
||||
return ofetch<{ user: UserResponse; session: SessionResponse }>(
|
||||
"/users/@me",
|
||||
{
|
||||
headers: getAuthHeaders(token),
|
||||
baseURL: url,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function editUser(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
object: UserEdit,
|
||||
): Promise<{ user: UserResponse; session: SessionResponse }> {
|
||||
return ofetch<{ user: UserResponse; session: SessionResponse }>(
|
||||
`/users/${account.userId}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: getAuthHeaders(account.token),
|
||||
body: object,
|
||||
baseURL: url,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteUser(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
): Promise<UserResponse> {
|
||||
return ofetch<UserResponse>(`/users/${account.userId}`, {
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getBookmarks(url: string, account: AccountWithToken) {
|
||||
return ofetch<BookmarkResponse[]>(`/users/${account.userId}/bookmarks`, {
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getProgress(url: string, account: AccountWithToken) {
|
||||
return ofetch<ProgressResponse[]>(`/users/${account.userId}/progress`, {
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user