Skip to content

Commit

Permalink
Add AMC Plus service (trakt-tools#198)
Browse files Browse the repository at this point in the history
* Allow specifying separate IDs for episodes and movies when using watchingUrlRegex

* Add AMC Plus service

* Update @trakt-tools/cli to 0.3.3

* Update README.md
  • Loading branch information
rafaelgomesxyz authored Oct 2, 2022
1 parent 6b92672 commit 4af0926
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 29 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,22 @@ If you want to scrobble / sync from Netflix, this is the only Trakt.tv [plugin](
| Streaming Service | Scrobble | Sync | Limitations |
| :---------------: | :------: | :--: | :------------------------------ |
| Amazon Prime | ✔️ | ✔️ | - |
| Crunchyroll Beta || ✔️ | can't identify movies as movies |
| AMC Plus | ✔️ || - |
| Crunchyroll Beta || ✔️ | Can't identify movies as movies |
| DisneyPlus | ✔️ || - |
| GoPlay BE | ✔️ || - |
| HBO Go | ✔️ || - |
| HBO Max | ✔️ | ✔️ | - |
| Kijk.nl | ✔️ || - |
| Netflix | ✔️ | ✔️ | - |
| NRK | ✔️ | ✔️ | - |
| Player.pl | ✔️ || - |
| Polsatboxgo.pl | ✔️ || - |
| Streamz BE | ✔️ || - |
| Viaplay | ✔️ | ✔️ | - |
| VRTNu BE | ✔️ || - |
| VTMGo BE | ✔️ || - |
| Wakanim.tv | ✔️ || - |

<!-- services-end -->

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"@babel/preset-typescript": "^7.15.0",
"@babel/runtime": "^7.15.4",
"@octokit/rest": "^18.10.0",
"@trakt-tools/cli": "^0.3.2",
"@trakt-tools/cli": "^0.3.3",
"@types/archiver": "^5.1.1",
"@types/circular-dependency-plugin": "^5.0.5",
"@types/dotenv-webpack": "^7.0.3",
Expand Down
74 changes: 49 additions & 25 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions src/common/ScrobbleParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,12 @@ export abstract class ScrobbleParser {
}

protected parseItemIdFromUrl(): string | null {
const { id = null } = this.options.watchingUrlRegex?.exec(this.getLocation())?.groups ?? {};
return id;
const {
id = null,
episodeId = null,
movieId = null,
} = this.options.watchingUrlRegex?.exec(this.getLocation())?.groups ?? {};
return episodeId || movieId || id;
}

protected async parseItemIdFromInjectedScript(): Promise<string | null> {
Expand Down
161 changes: 161 additions & 0 deletions src/services/amc-plus/AmcPlusApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { AmcPlusService } from '@/amc-plus/AmcPlusService';
import { ServiceApi, ServiceApiSession } from '@apis/ServiceApi';
import { Requests, withHeaders } from '@common/Requests';
import { ScriptInjector } from '@common/ScriptInjector';
import { Shared } from '@common/Shared';
import { EpisodeItem, MovieItem, ScrobbleItem } from '@models/Item';

export interface AmcPlusSession extends ServiceApiSession {
auth: {
accessToken: string;
};
}

export interface AmcPlusItemResponse {
data: {
properties: AmcPlusEpisodeResponse | AmcPlusMovieResponse;
};
}

export interface AmcPlusEpisodeResponse {
pageType: 'episode';
pageTitle: string;
id: string;

// In slug-ish format (e.g. "the-walking-dead")
showName: string;

seasonNumber: number;
episodeNumber: number;
}

export interface AmcPlusMovieResponse {
pageType: 'movie';
pageTitle: string;
id: string;
}

class _AmcPlusApi extends ServiceApi {
HOST_URL = 'https://www.amcplus.com/foryou';
API_URL = 'https://gw.cds.amcn.com/content-compiler-cr/api/v1';

authRequests = Requests;

isActivated = false;
session?: AmcPlusSession | null;

constructor() {
super(AmcPlusService.id);
}

async activate() {
if (this.session === null) {
return;
}

try {
const partialSession = await this.getSession();
if (!partialSession || !partialSession.auth || !partialSession.auth.accessToken) {
throw new Error();
}

this.authRequests = withHeaders({
Authorization: `Bearer ${partialSession.auth.accessToken}`,
});

this.session = {
auth: {
accessToken: partialSession.auth.accessToken,
},
profileName: null,
};

this.isActivated = true;
} catch (err) {
this.session = null;
}
}

async getItem(id: string): Promise<ScrobbleItem | null> {
let item: ScrobbleItem | null = null;
if (!this.isActivated) {
await this.activate();
}
if (!this.session) {
throw new Error('Invalid session');
}
try {
const responseText = await this.authRequests.send({
url: `${this.API_URL}/content/amcn/amcplus/path/${id}?`,
method: 'GET',
});
const response = JSON.parse(responseText) as AmcPlusItemResponse;

item = this.parseMetadata(response.data.properties);
} catch (err) {
if (Shared.errors.validate(err)) {
Shared.errors.error('Failed to get item.', err);
}
}
return item;
}

parseMetadata(metadata: AmcPlusEpisodeResponse | AmcPlusMovieResponse): ScrobbleItem {
let item: ScrobbleItem;
const serviceId = this.id;
const { pageType: type, pageTitle: title, id } = metadata;
if (type === 'episode') {
const { showName: showSlug, seasonNumber: season, episodeNumber: number } = metadata;
const showTitle = this.formatSlug(showSlug);

item = new EpisodeItem({
serviceId,
id,
title,
season,
number,
show: {
serviceId,
title: showTitle,
},
});
} else {
item = new MovieItem({
serviceId,
id,
title,
});
}
return item;
}

formatSlug(slug: string): string {
return slug
.split('-')
.map((word) => `${word[0].toUpperCase()}${word.slice(1).toLowerCase()}`)
.join(' ');
}

async getSession(): Promise<Partial<AmcPlusSession> | null> {
const result = await ScriptInjector.inject<Partial<AmcPlusSession>>(
this.id,
'session',
this.HOST_URL,
() => {
const session: Partial<AmcPlusSession> = {};

const accessToken = window.localStorage.getItem('access_token');
if (accessToken) {
session.auth = {
accessToken,
};
}

return session;
}
);
return result;
}
}

export const AmcPlusApi = new _AmcPlusApi();
Loading

0 comments on commit 4af0926

Please sign in to comment.