-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathbase_page.ts
162 lines (131 loc) · 4.42 KB
/
base_page.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import stringSimilarity from 'string-similarity';
import { browser } from 'webextension-polyfill-ts';
import AnilistHttpClient from '../api/anilist_http_client';
import AniskipHttpClient from '../api/aniskip_http_client';
import MalsyncHttpClient from '../api/malsync_http_client';
import Page from '../types/page_type';
import { capitalizeFirstLetter, getDomainName } from '../utils/string_utils';
abstract class BasePage implements Page {
hostname: string;
pathname: string;
document: Document;
providerName: string;
malId: number;
episodeNumber: number;
constructor(hostname: string, pathname: string, document: Document) {
this.hostname = hostname;
this.pathname = pathname;
this.document = document;
const domainName = getDomainName(this.hostname);
this.providerName = capitalizeFirstLetter(domainName);
this.malId = 0;
this.episodeNumber = 0;
}
abstract getIdentifier(): string;
abstract getRawEpisodeNumber(): number;
async applyRules() {
const aniskipHttpClient = new AniskipHttpClient();
const malId = await this.getMalId();
const { rules } = await aniskipHttpClient.getRules(malId);
let rawEpisodeNumber = this.getRawEpisodeNumber();
this.episodeNumber = rawEpisodeNumber;
rules.forEach((rule) => {
const { start, end: endOrUndefined } = rule.from;
const end = endOrUndefined || Infinity;
const { malId: toMalId } = rule.to;
// Handle seasons with multiple parts and continuous counting
if (malId === toMalId && rawEpisodeNumber > end) {
const seasonLength = end - (start - 1);
const episodeOverflow = rawEpisodeNumber - end;
rawEpisodeNumber = episodeOverflow + seasonLength;
}
if (rawEpisodeNumber >= start && rawEpisodeNumber <= end) {
this.malId = toMalId;
this.episodeNumber = rawEpisodeNumber - (start - 1);
}
});
}
getEpisodeNumber() {
return this.episodeNumber;
}
getTitle() {
return this.getIdentifier();
}
getProviderName() {
return this.providerName;
}
async getMalId() {
// Episode redirection rules applied
if (this.malId > 0) {
return this.malId;
}
const identifier = this.getIdentifier();
if (!identifier) {
return 0;
}
this.malId = await BasePage.getMalIdCached(identifier);
if (this.malId > 0) {
return this.malId;
}
try {
const providerName = this.getProviderName();
const malsyncHttpClient = new MalsyncHttpClient();
this.malId = await malsyncHttpClient.getMalId(providerName, identifier);
} catch {
// MALSync was not able to find the id
const title = this.getTitle();
if (!title) {
return 0;
}
this.malId = await BasePage.findClosestMalId(title);
}
// Cache MAL id
const { malIdCache } = await browser.storage.local.get('malIdCache');
malIdCache[identifier] = this.malId;
browser.storage.local.set({ malIdCache });
return this.malId;
}
/**
* Search MAL and find the closest MAL id to the identifier
* @param titleVariant Title from the provider
*/
static async findClosestMalId(title: string) {
const anilistHttpClient = new AnilistHttpClient();
const sanitisedTitle = title.replace(/\(.*\)/, '').trim();
const searchResponse = await anilistHttpClient.search(sanitisedTitle);
const searchResults = searchResponse.data.Page.media;
let closest = 0;
let bestSimilarity = 0;
searchResults.forEach(({ title: titleVariants, idMal, synonyms }) => {
const titles = [...synonyms];
Object.values(titleVariants).forEach((titleVariant) => {
if (titleVariant) {
titles.push(titleVariant);
}
});
titles.forEach((titleVariant) => {
const similarity = stringSimilarity.compareTwoStrings(
titleVariant.toLocaleLowerCase(),
title.toLocaleLowerCase()
);
if (similarity > bestSimilarity) {
bestSimilarity = similarity;
closest = idMal;
}
});
});
if (bestSimilarity > 0.6) {
return closest;
}
throw new Error('Closest MAL id not found');
}
/**
* Returns a MAL id from the cache
* @param identifier Provider anime identifier
*/
static async getMalIdCached(identifier: string): Promise<number> {
const { malIdCache } = await browser.storage.local.get('malIdCache');
return malIdCache[identifier] || 0;
}
}
export default BasePage;