Skip to content

Commit

Permalink
feature: add artstation
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyhalight committed Dec 20, 2024
1 parent 3691591 commit 9c2390d
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 0 deletions.
7 changes: 7 additions & 0 deletions packages/ext/src/data/sites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,13 @@ export default [
selector: ".VideoLayersContainer",
needExtraData: true,
},
{
host: ExtVideoService.artstation,
url: "https://www.artstation.com/learning/",
match: /^(www.)?artstation.com$/,
selector: ".vjs-v7",
needExtraData: true,
},
{
host: CoreVideoService.custom,
url: "stub",
Expand Down
89 changes: 89 additions & 0 deletions packages/ext/src/helpers/artstation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { BaseHelper } from "./base";
import type { MinimalVideoData } from "../types/client";
import * as Artstation from "../types/helpers/artstation";

import type { VideoDataSubtitle } from "@vot.js/core/types/client";
import { normalizeLang } from "@vot.js/shared/utils/utils";
import Logger from "@vot.js/shared/utils/logger";

export default class ArtstationHelper extends BaseHelper {
API_ORIGIN = "https://www.artstation.com/api/v2/learning";

async getCourseInfo(courseId: string) {
try {
const res = await this.fetch(
`${this.API_ORIGIN}/courses/${courseId}/autoplay.json`,
);

return (await res.json()) as Artstation.Course;
} catch (err) {
Logger.error(
`Failed to get artstation course info by courseId: ${courseId}.`,
(err as Error).message,
);
return false;
}
}

async getVideoUrl(chapterId: number) {
try {
const res = await this.fetch(
`${this.API_ORIGIN}/quicksilver/video_url.json?chapter_id=${chapterId}`,
);

const data = (await res.json()) as Artstation.VideoUrlData;
return data.url.replace("qsep://", "https://");
} catch (err) {
Logger.error(
`Failed to get artstation video url by chapterId: ${chapterId}.`,
(err as Error).message,
);
return false;
}
}

async getVideoData(videoId: string): Promise<MinimalVideoData | undefined> {
const [, courseId, , , chapterId] = videoId.split("/")?.[1];
const courseInfo = await this.getCourseInfo(courseId);
if (!courseInfo) {
return undefined;
}

const chapter = courseInfo.chapters.find(
(chapter) => chapter.hash_id === chapterId,
);
if (!chapter) {
return undefined;
}

const videoUrl = await this.getVideoUrl(chapter.id);
if (!videoUrl) {
return undefined;
}

const { title, duration, subtitles: videoSubtitles } = chapter;
const subtitles: VideoDataSubtitle[] = videoSubtitles
.filter((subtitle) => subtitle.format === "vtt")
.map((subtitle) => ({
language: normalizeLang(subtitle.locale),
source: "artstation",
format: "vtt",
url: subtitle.file_url,
}));

// url returns a json containing a dash playlist (in base64) in the playlist field
return {
url: videoUrl,
title,
duration,
subtitles,
};
}

// eslint-disable-next-line @typescript-eslint/require-await
async getVideoId(url: URL): Promise<string | undefined> {
return /courses\/(\w{3,5})\/([^/]+)\/chapters\/(\w{3,5})/.exec(
url.pathname,
)?.[0];
}
}
3 changes: 3 additions & 0 deletions packages/ext/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import CourseraHelper from "./coursera";
import CloudflareStreamHelper from "./cloudflarestream";
import DouyinHelper from "./douyin";
import LoomHelper from "./loom";
import ArtstationHelper from "./artstation";

export * as MailRuHelper from "./mailru";
export * as WeverseHelper from "./weverse";
Expand Down Expand Up @@ -100,6 +101,7 @@ export * as CourseraHelper from "./coursera";
export * as CloudflareStreamHelper from "./cloudflarestream";
export * as DouyinHelper from "./douyin";
export * as LoomHelper from "./loom";
export * as ArtstationHelper from "./artstation";

export const availableHelpers = {
[CoreVideoService.mailru]: MailRuHelper,
Expand Down Expand Up @@ -155,6 +157,7 @@ export const availableHelpers = {
[ExtVideoService.udemy]: UdemyHelper,
[ExtVideoService.coursera]: CourseraHelper,
[ExtVideoService.douyin]: DouyinHelper,
[ExtVideoService.artstation]: ArtstationHelper,
};

export type AvailableVideoHelpers = typeof availableHelpers;
Expand Down
55 changes: 55 additions & 0 deletions packages/ext/src/types/helpers/artstation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ISODate } from "@vot.js/shared/types/utils";

export type Subtitle = {
id: number;
locale: string;
file_url: string;
format: "str" | "vtt";
};

export type Chapter = {
id: number;
hash_id: string;
slug: string;
title: string;
position: number;
free: boolean;
viewed: boolean;
duration: number;
subtitles: Subtitle[];
};

export type Level = "beginner" | "intermediate";
export type AutoPlayData = {
chapter_id: number;
playback_time: number;
drm_token: null;
};

export type Course = {
id: number;
hash_id: string;
slug: string;
title: string;
description: string; // with html
medium_cover_url: string;
larget_cover_url: string;
published_at: ISODate;
level: Level;
duration: number;
series_hash_id: null | string;
likes_count: number;
learners_count: number;
liked: boolean;
playlist: unknown[];
instructors: unknown[];
chapters: Chapter[];
topics: unknown[];
attached_files: unknown[];
autoplay_data: AutoPlayData;
};

export type VideoUrlData = {
url: string;
type: "quicksilver";
};
1 change: 1 addition & 0 deletions packages/ext/src/types/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum ExtVideoService {
udemy = "udemy",
coursera = "coursera",
douyin = "douyin",
artstation = "artstation",
}

export type VideoService = CoreVideoService | ExtVideoService;
Expand Down

0 comments on commit 9c2390d

Please sign in to comment.