weebcli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/releases.yml +39 -0
- package/LICENSE +400 -0
- package/README-EN.md +134 -0
- package/README.md +134 -0
- package/aur/.SRCINFO +16 -0
- package/aur/PKGBUILD +43 -0
- package/eslint.config.js +49 -0
- package/jsconfig.json +9 -0
- package/package.json +45 -0
- package/src/constants.js +13 -0
- package/src/functions/episodes.js +38 -0
- package/src/functions/time.js +27 -0
- package/src/functions/variables.js +3 -0
- package/src/i18n/en.json +351 -0
- package/src/i18n/index.js +80 -0
- package/src/i18n/tr.json +348 -0
- package/src/index.js +307 -0
- package/src/jsdoc.js +72 -0
- package/src/sources/allanime.js +195 -0
- package/src/sources/animecix.js +223 -0
- package/src/sources/animely.js +100 -0
- package/src/sources/handlers/allanime.js +318 -0
- package/src/sources/handlers/animecix.js +316 -0
- package/src/sources/handlers/animely.js +338 -0
- package/src/sources/handlers/base.js +391 -0
- package/src/sources/handlers/index.js +4 -0
- package/src/sources/index.js +80 -0
- package/src/utils/anilist.js +193 -0
- package/src/utils/data_manager.js +27 -0
- package/src/utils/discord.js +86 -0
- package/src/utils/download/concurrency.js +27 -0
- package/src/utils/download/download.js +485 -0
- package/src/utils/download/progress.js +84 -0
- package/src/utils/players/mpv.js +251 -0
- package/src/utils/players/vlc.js +120 -0
- package/src/utils/process_queue.js +121 -0
- package/src/utils/resume_watch.js +137 -0
- package/src/utils/search.js +39 -0
- package/src/utils/search_download.js +21 -0
- package/src/utils/speedtest.js +30 -0
- package/src/utils/spinner.js +7 -0
- package/src/utils/storage/cache.js +42 -0
- package/src/utils/storage/config.js +71 -0
- package/src/utils/storage/history.js +69 -0
- package/src/utils/storage/queue.js +43 -0
- package/src/utils/storage/watch_progress.js +104 -0
- package/src/utils/system.js +176 -0
- package/src/utils/ui/box.js +140 -0
- package/src/utils/ui/settings_ui.js +322 -0
- package/src/utils/ui/show_history.js +92 -0
- package/src/utils/ui/stats.js +67 -0
- package/start.js +21 -0
- package/tanitim-en.md +66 -0
- package/tanitim-tr.md +66 -0
package/src/jsdoc.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* _id: string;
|
|
4
|
+
* NAME: string;
|
|
5
|
+
* TOTAL_EPISODES: number;
|
|
6
|
+
* FIRST_IMAGE: string;
|
|
7
|
+
* CATEGORIES: string[];
|
|
8
|
+
* DESCRIPTION: string;
|
|
9
|
+
* SLUG: string;
|
|
10
|
+
* SEASON_NUMBER: number;
|
|
11
|
+
* OTHER_NAMES: string[];
|
|
12
|
+
* }} Anime
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {{
|
|
17
|
+
* id: string;
|
|
18
|
+
* episode_number: number;
|
|
19
|
+
* backblaze_link: string;
|
|
20
|
+
* watch_link_1: string;
|
|
21
|
+
* watch_link_2: string;
|
|
22
|
+
* watch_link_3: string;
|
|
23
|
+
* type: string;
|
|
24
|
+
* fansub: string;
|
|
25
|
+
* }} Episode
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {{
|
|
30
|
+
* id: string;
|
|
31
|
+
* episode_number: number|string;
|
|
32
|
+
* link: string;
|
|
33
|
+
* }} DownloadEpisode
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {{
|
|
38
|
+
* id: string;
|
|
39
|
+
* name: string;
|
|
40
|
+
* poster?: string;
|
|
41
|
+
* type?: string;
|
|
42
|
+
* totalEpisodes?: number;
|
|
43
|
+
* otherNames?: string[];
|
|
44
|
+
* _raw?: any;
|
|
45
|
+
* _isMovie?: boolean;
|
|
46
|
+
* }} SearchResult
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {{
|
|
51
|
+
* id: string;
|
|
52
|
+
* episode_number: number|string;
|
|
53
|
+
* name?: string;
|
|
54
|
+
* season?: number;
|
|
55
|
+
* type?: string;
|
|
56
|
+
* fansub?: string;
|
|
57
|
+
* _links?: string[];
|
|
58
|
+
* _url?: string;
|
|
59
|
+
* _animeId?: string;
|
|
60
|
+
* }} SourceEpisode
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {{
|
|
65
|
+
* url: string;
|
|
66
|
+
* quality?: string;
|
|
67
|
+
* label?: string;
|
|
68
|
+
* }} StreamLink
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export { };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
const ALLANIME_API = "https://api.allanime.day";
|
|
5
|
+
const ALLANIME_REFR = "https://allmanga.to";
|
|
6
|
+
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0";
|
|
7
|
+
|
|
8
|
+
function decodeProviderUrl(encoded) {
|
|
9
|
+
const hexMap = {
|
|
10
|
+
"79": "A", "7a": "B", "7b": "C", "7c": "D", "7d": "E", "7e": "F", "7f": "G",
|
|
11
|
+
"70": "H", "71": "I", "72": "J", "73": "K", "74": "L", "75": "M", "76": "N", "77": "O",
|
|
12
|
+
"68": "P", "69": "Q", "6a": "R", "6b": "S", "6c": "T", "6d": "U", "6e": "V", "6f": "W",
|
|
13
|
+
"60": "X", "61": "Y", "62": "Z",
|
|
14
|
+
"59": "a", "5a": "b", "5b": "c", "5c": "d", "5d": "e", "5e": "f", "5f": "g",
|
|
15
|
+
"50": "h", "51": "i", "52": "j", "53": "k", "54": "l", "55": "m", "56": "n", "57": "o",
|
|
16
|
+
"48": "p", "49": "q", "4a": "r", "4b": "s", "4c": "t", "4d": "u", "4e": "v", "4f": "w",
|
|
17
|
+
"40": "x", "41": "y", "42": "z",
|
|
18
|
+
"08": "0", "09": "1", "0a": "2", "0b": "3", "0c": "4", "0d": "5", "0e": "6", "0f": "7",
|
|
19
|
+
"00": "8", "01": "9",
|
|
20
|
+
"15": "-", "16": ".", "67": "_", "46": "~", "02": ":", "17": "/", "07": "?",
|
|
21
|
+
"1b": "#", "63": "[", "65": "]", "78": "@", "19": "!", "1c": "$", "1e": "&",
|
|
22
|
+
"10": "(", "11": ")", "12": "*", "13": "+", "14": ",", "03": ";", "05": "=", "1d": "%"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let result = "";
|
|
26
|
+
for (let i = 0; i < encoded.length; i += 2) {
|
|
27
|
+
const hex = encoded.substring(i, i + 2).toLowerCase();
|
|
28
|
+
result += hexMap[hex] || "";
|
|
29
|
+
}
|
|
30
|
+
return result.replace("/clock", "/clock.json");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AllAnimeSource {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.name = "AllAnime";
|
|
36
|
+
this.id = "allanime";
|
|
37
|
+
this.language = "en";
|
|
38
|
+
this.supportsLocalSearch = false;
|
|
39
|
+
this.mode = "sub";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async _gqlRequest(params) {
|
|
43
|
+
try {
|
|
44
|
+
const response = await axios.get(`${ALLANIME_API}/api`, {
|
|
45
|
+
params,
|
|
46
|
+
headers: {
|
|
47
|
+
"User-Agent": USER_AGENT,
|
|
48
|
+
"Referer": ALLANIME_REFR
|
|
49
|
+
},
|
|
50
|
+
timeout: 15000
|
|
51
|
+
});
|
|
52
|
+
return response.data;
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async search(query) {
|
|
59
|
+
const searchGql = `query($search: SearchInput $limit: Int $page: Int $translationType: VaildTranslationTypeEnumType $countryOrigin: VaildCountryOriginEnumType) {
|
|
60
|
+
shows(search: $search limit: $limit page: $page translationType: $translationType countryOrigin: $countryOrigin) {
|
|
61
|
+
edges { _id name availableEpisodes __typename }
|
|
62
|
+
}
|
|
63
|
+
}`;
|
|
64
|
+
|
|
65
|
+
const variables = JSON.stringify({
|
|
66
|
+
search: { allowAdult: false, allowUnknown: false, query },
|
|
67
|
+
limit: 40,
|
|
68
|
+
page: 1,
|
|
69
|
+
translationType: this.mode,
|
|
70
|
+
countryOrigin: "ALL"
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const data = await this._gqlRequest({ variables, query: searchGql });
|
|
74
|
+
if (!data?.data?.shows?.edges) return [];
|
|
75
|
+
|
|
76
|
+
return data.data.shows.edges
|
|
77
|
+
.filter(show => show.availableEpisodes?.[this.mode] > 0)
|
|
78
|
+
.map(show => ({
|
|
79
|
+
id: show._id,
|
|
80
|
+
name: show.name,
|
|
81
|
+
totalEpisodes: show.availableEpisodes?.[this.mode] || 0,
|
|
82
|
+
_mode: this.mode
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getEpisodes(showId) {
|
|
87
|
+
const episodesGql = `query($showId: String!) {
|
|
88
|
+
show(_id: $showId) { _id availableEpisodesDetail }
|
|
89
|
+
}`;
|
|
90
|
+
|
|
91
|
+
const variables = JSON.stringify({ showId });
|
|
92
|
+
const data = await this._gqlRequest({ variables, query: episodesGql });
|
|
93
|
+
|
|
94
|
+
const episodeList = data?.data?.show?.availableEpisodesDetail?.[this.mode];
|
|
95
|
+
if (!episodeList || !Array.isArray(episodeList)) return [];
|
|
96
|
+
|
|
97
|
+
const sorted = [...episodeList].sort((a, b) => parseFloat(a) - parseFloat(b));
|
|
98
|
+
|
|
99
|
+
return sorted.map((epNum, idx) => ({
|
|
100
|
+
id: `${showId}_${epNum}`,
|
|
101
|
+
episode_number: parseFloat(epNum) || idx + 1,
|
|
102
|
+
name: `Episode ${epNum}`,
|
|
103
|
+
_showId: showId,
|
|
104
|
+
_epString: epNum
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async _getLinksFromProvider(providerId) {
|
|
109
|
+
try {
|
|
110
|
+
const response = await axios.get(`https://allanime.day${providerId}`, {
|
|
111
|
+
headers: {
|
|
112
|
+
"User-Agent": USER_AGENT,
|
|
113
|
+
"Referer": ALLANIME_REFR
|
|
114
|
+
},
|
|
115
|
+
timeout: 10000
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const data = response.data;
|
|
119
|
+
const links = [];
|
|
120
|
+
|
|
121
|
+
if (data.links) {
|
|
122
|
+
for (const link of data.links) {
|
|
123
|
+
if (link.hls && link.link) {
|
|
124
|
+
links.push({ quality: link.resolutionStr || "auto", url: link.link, _type: "hls" });
|
|
125
|
+
} else if (link.mp4 && link.link) {
|
|
126
|
+
links.push({ quality: link.resolutionStr || "auto", url: link.link, _type: "mp4" });
|
|
127
|
+
} else if (link.link) {
|
|
128
|
+
links.push({ quality: link.resolutionStr || "auto", url: link.link });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return links;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async getStreamLinks(episodeData) {
|
|
140
|
+
const showId = episodeData._showId;
|
|
141
|
+
const epString = episodeData._epString;
|
|
142
|
+
|
|
143
|
+
if (!showId || !epString) return [];
|
|
144
|
+
|
|
145
|
+
const episodeGql = `query($showId: String!, $translationType: VaildTranslationTypeEnumType!, $episodeString: String!) {
|
|
146
|
+
episode(showId: $showId translationType: $translationType episodeString: $episodeString) {
|
|
147
|
+
episodeString sourceUrls
|
|
148
|
+
}
|
|
149
|
+
}`;
|
|
150
|
+
|
|
151
|
+
const variables = JSON.stringify({
|
|
152
|
+
showId,
|
|
153
|
+
translationType: this.mode,
|
|
154
|
+
episodeString: epString
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const data = await this._gqlRequest({ variables, query: episodeGql });
|
|
158
|
+
|
|
159
|
+
const sourceUrls = data?.data?.episode?.sourceUrls;
|
|
160
|
+
if (!sourceUrls || !Array.isArray(sourceUrls)) return [];
|
|
161
|
+
|
|
162
|
+
const providers = [];
|
|
163
|
+
for (const source of sourceUrls) {
|
|
164
|
+
if (source.sourceUrl && source.sourceName) {
|
|
165
|
+
const encodedUrl = source.sourceUrl.replace("--", "");
|
|
166
|
+
const decodedUrl = decodeProviderUrl(encodedUrl);
|
|
167
|
+
providers.push({ name: source.sourceName, url: decodedUrl });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const preferredOrder = ["Luf-Mp4", "Default", "S-mp4", "Yt-mp4"];
|
|
172
|
+
const sortedProviders = providers.sort((a, b) => {
|
|
173
|
+
const aIdx = preferredOrder.indexOf(a.name);
|
|
174
|
+
const bIdx = preferredOrder.indexOf(b.name);
|
|
175
|
+
return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
for (const provider of sortedProviders) {
|
|
179
|
+
const links = await this._getLinksFromProvider(provider.url);
|
|
180
|
+
if (links.length > 0) {
|
|
181
|
+
return links.map(l => ({
|
|
182
|
+
url: l.url,
|
|
183
|
+
quality: l.quality,
|
|
184
|
+
label: `${l.quality} (${provider.name})`
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
setMode(mode) {
|
|
193
|
+
this.mode = mode;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
const BASE_URL = "https://animecix.tv/";
|
|
5
|
+
const MANGACIX_URL = "https://mangacix.net";
|
|
6
|
+
const VIDEO_PLAYER = "tau-video.xyz";
|
|
7
|
+
|
|
8
|
+
const HEADERS = {
|
|
9
|
+
"Accept": "application/json",
|
|
10
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Animecix.tv kaynağı - API üzerinden arama yapar
|
|
15
|
+
*/
|
|
16
|
+
export class AnimecixSource {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.name = "Animecix";
|
|
19
|
+
this.id = "animecix";
|
|
20
|
+
this.language = "tr";
|
|
21
|
+
this.supportsLocalSearch = false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} url
|
|
26
|
+
* @returns {Promise<any>}
|
|
27
|
+
*/
|
|
28
|
+
async _getJson(url) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await axios.get(url, { headers: HEADERS, timeout: 10000 });
|
|
31
|
+
return response.data;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Anime arama (API üzerinden)
|
|
39
|
+
* @param {string} query
|
|
40
|
+
* @returns {Promise<import("./index.js").SearchResult[]>}
|
|
41
|
+
*/
|
|
42
|
+
async search(query) {
|
|
43
|
+
const url = `${BASE_URL}secure/search/${encodeURIComponent(query)}?type=&limit=20`;
|
|
44
|
+
const data = await this._getJson(url);
|
|
45
|
+
|
|
46
|
+
if (!data || !data.results) return [];
|
|
47
|
+
|
|
48
|
+
return data.results.map(item => ({
|
|
49
|
+
id: String(item.id),
|
|
50
|
+
name: item.name,
|
|
51
|
+
poster: item.poster,
|
|
52
|
+
type: item.title_type || item.type,
|
|
53
|
+
_isMovie: item.title_type === "movie" || item.type === "movie",
|
|
54
|
+
_originalTitle: item.original_title
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Sezon listesi
|
|
60
|
+
* @param {string} animeId
|
|
61
|
+
* @returns {Promise<number[]>}
|
|
62
|
+
*/
|
|
63
|
+
async _getSeasons(animeId) {
|
|
64
|
+
const url = `${MANGACIX_URL}/secure/related-videos?episode=1&season=1&titleId=${animeId}&videoId=637113`;
|
|
65
|
+
const data = await this._getJson(url);
|
|
66
|
+
|
|
67
|
+
if (!data || !data.videos || !data.videos[0]) return [];
|
|
68
|
+
|
|
69
|
+
const seasons = data.videos[0]?.title?.seasons;
|
|
70
|
+
if (Array.isArray(seasons)) {
|
|
71
|
+
return seasons.map((_, i) => i);
|
|
72
|
+
}
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Bölüm listesi
|
|
78
|
+
* @param {string} animeId
|
|
79
|
+
* @returns {Promise<import("./index.js").Episode[]>}
|
|
80
|
+
*/
|
|
81
|
+
async getEpisodes(animeId) {
|
|
82
|
+
const seasons = await this._getSeasons(animeId);
|
|
83
|
+
|
|
84
|
+
// Eğer sezon bulunamazsa, tek sezon varsay
|
|
85
|
+
const seasonList = seasons.length > 0 ? seasons : [0];
|
|
86
|
+
|
|
87
|
+
const episodes = [];
|
|
88
|
+
const seenNames = new Set();
|
|
89
|
+
|
|
90
|
+
for (const seasonIndex of seasonList) {
|
|
91
|
+
const url = `${MANGACIX_URL}/secure/related-videos?episode=1&season=${seasonIndex + 1}&titleId=${animeId}&videoId=637113`;
|
|
92
|
+
const data = await this._getJson(url);
|
|
93
|
+
|
|
94
|
+
if (!data || !data.videos) continue;
|
|
95
|
+
|
|
96
|
+
for (const item of data.videos) {
|
|
97
|
+
const name = item.name || "Bilinmeyen";
|
|
98
|
+
if (seenNames.has(name)) continue;
|
|
99
|
+
|
|
100
|
+
seenNames.add(name);
|
|
101
|
+
episodes.push({
|
|
102
|
+
id: `${animeId}_${seasonIndex + 1}_${episodes.length + 1}`,
|
|
103
|
+
episode_number: episodes.length + 1,
|
|
104
|
+
name: name,
|
|
105
|
+
season: item.season_num || seasonIndex + 1,
|
|
106
|
+
_url: item.url // Bu URL'i izleme için kullanacağız
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return episodes;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Episode URL'den stream linklerini çıkar (Python'daki fetch_anime_watch_api_url)
|
|
116
|
+
* @param {string} episodeUrl - Episode URL (örn: "izle/anime-adi/bolum-1")
|
|
117
|
+
* @returns {Promise<import("./index.js").StreamLink[]>}
|
|
118
|
+
*/
|
|
119
|
+
async _getStreamLinksFromUrl(episodeUrl) {
|
|
120
|
+
try {
|
|
121
|
+
// URL'i tam hale getir
|
|
122
|
+
const fullUrl = episodeUrl.startsWith("http") ? episodeUrl : `${BASE_URL}${episodeUrl}`;
|
|
123
|
+
|
|
124
|
+
// Redirect'i takip et
|
|
125
|
+
const response = await axios.get(fullUrl, {
|
|
126
|
+
headers: HEADERS,
|
|
127
|
+
maxRedirects: 10,
|
|
128
|
+
timeout: 15000,
|
|
129
|
+
validateStatus: () => true
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const finalUrl = response.request?.res?.responseUrl || response.request?.responseURL || fullUrl;
|
|
133
|
+
|
|
134
|
+
const urlObj = new URL(finalUrl);
|
|
135
|
+
const pathParts = urlObj.pathname.split("/").filter(p => p);
|
|
136
|
+
|
|
137
|
+
let embedId = null;
|
|
138
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
139
|
+
if (pathParts[i] === "e" || pathParts[i] === "embed") {
|
|
140
|
+
embedId = pathParts[i + 1];
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!embedId && pathParts.length >= 2) {
|
|
146
|
+
embedId = pathParts[pathParts.length - 1];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!embedId) {
|
|
150
|
+
console.log("Embed ID not found:", finalUrl);
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const vid = urlObj.searchParams.get("vid");
|
|
155
|
+
|
|
156
|
+
const apiUrl = `https://${VIDEO_PLAYER}/api/video/${embedId}${vid ? `?vid=${vid}` : ""}`;
|
|
157
|
+
|
|
158
|
+
const apiResponse = await axios.get(apiUrl, {
|
|
159
|
+
timeout: 10000,
|
|
160
|
+
headers: {
|
|
161
|
+
"User-Agent": HEADERS["User-Agent"],
|
|
162
|
+
"Referer": finalUrl
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const urls = apiResponse.data?.urls || [];
|
|
167
|
+
|
|
168
|
+
return urls.map(item => ({
|
|
169
|
+
url: item.url,
|
|
170
|
+
quality: item.label || "default",
|
|
171
|
+
label: item.label
|
|
172
|
+
}));
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error("Stream link error:", e.message);
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {string} titleId
|
|
181
|
+
* @returns {Promise<import("./index.js").StreamLink[]>}
|
|
182
|
+
*/
|
|
183
|
+
async _getMovieStreamLinks(titleId) {
|
|
184
|
+
const url = `${BASE_URL}secure/titles/${titleId}?titleId=${titleId}`;
|
|
185
|
+
const headers = { ...HEADERS, "x-e-h": "=.a" };
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const response = await axios.get(url, { headers, timeout: 10000 });
|
|
189
|
+
const data = response.data;
|
|
190
|
+
const videos = data?.title?.videos || [];
|
|
191
|
+
|
|
192
|
+
for (const video of videos) {
|
|
193
|
+
const videoUrl = video.url;
|
|
194
|
+
if (!videoUrl) continue;
|
|
195
|
+
|
|
196
|
+
const streamLinks = await this._getStreamLinksFromUrl(videoUrl);
|
|
197
|
+
if (streamLinks.length > 0) return streamLinks;
|
|
198
|
+
}
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.error("Movie stream error:", e.message);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {any} episodeData
|
|
208
|
+
* @returns {Promise<import("./index.js").StreamLink[]>}
|
|
209
|
+
*/
|
|
210
|
+
async getStreamLinks(episodeData) {
|
|
211
|
+
if (episodeData._isMovie) {
|
|
212
|
+
return this._getMovieStreamLinks(episodeData._animeId);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const videoUrl = episodeData._url;
|
|
216
|
+
if (!videoUrl) {
|
|
217
|
+
console.log("Episode URL not found:", episodeData);
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return this._getStreamLinksFromUrl(videoUrl);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { API_URL } from "../constants.js";
|
|
4
|
+
import { getCachedAnimeList, saveAnimeListToCache } from "../utils/storage/cache.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Animely.net
|
|
8
|
+
*/
|
|
9
|
+
export class AnimelySource {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.name = "Animely";
|
|
12
|
+
this.id = "animely";
|
|
13
|
+
this.language = "tr";
|
|
14
|
+
this.supportsLocalSearch = true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @returns {Promise<import("../jsdoc.js").Anime[]>}
|
|
19
|
+
*/
|
|
20
|
+
async getAnimeList() {
|
|
21
|
+
const cached = getCachedAnimeList();
|
|
22
|
+
if (cached) return cached;
|
|
23
|
+
|
|
24
|
+
const response = await axios.get(`${API_URL}/animes`);
|
|
25
|
+
const animes = response.data;
|
|
26
|
+
saveAnimeListToCache(animes);
|
|
27
|
+
return animes;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} query
|
|
32
|
+
* @returns {Promise<import("./index.js").SearchResult[]>}
|
|
33
|
+
*/
|
|
34
|
+
async search(query) {
|
|
35
|
+
const animes = await this.getAnimeList();
|
|
36
|
+
const lowerQuery = query.toLowerCase().trim();
|
|
37
|
+
|
|
38
|
+
let results = animes.filter(({ NAME, OTHER_NAMES }) => {
|
|
39
|
+
const lowerName = NAME.toLowerCase();
|
|
40
|
+
const lowerOthers = OTHER_NAMES.map(n => n.toLowerCase());
|
|
41
|
+
return lowerName === lowerQuery || lowerOthers.includes(lowerQuery);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (results.length === 0) {
|
|
45
|
+
results = animes.filter(({ NAME, OTHER_NAMES }) => {
|
|
46
|
+
const lowerName = NAME.toLowerCase();
|
|
47
|
+
const lowerOthers = OTHER_NAMES.map(n => n.toLowerCase());
|
|
48
|
+
return lowerName.includes(lowerQuery) || lowerOthers.some(n => n.includes(lowerQuery));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (results.length === 0) {
|
|
53
|
+
results = animes.filter(({ NAME, OTHER_NAMES }) => {
|
|
54
|
+
const allNames = [NAME, ...OTHER_NAMES].map(n => n.toLowerCase());
|
|
55
|
+
const words = lowerQuery.split(" ");
|
|
56
|
+
return allNames.some(name => words.every(word => name.includes(word)));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return results.map(anime => ({
|
|
61
|
+
id: anime.SLUG,
|
|
62
|
+
name: anime.NAME,
|
|
63
|
+
poster: anime.FIRST_IMAGE,
|
|
64
|
+
totalEpisodes: anime.TOTAL_EPISODES,
|
|
65
|
+
otherNames: anime.OTHER_NAMES,
|
|
66
|
+
_raw: anime
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} animeSlug
|
|
72
|
+
* @returns {Promise<import("./index.js").Episode[]>}
|
|
73
|
+
*/
|
|
74
|
+
async getEpisodes(animeSlug) {
|
|
75
|
+
const response = await axios.post(`${API_URL}/searchAnime`, { payload: animeSlug });
|
|
76
|
+
const episodes = response.data.episodes || [];
|
|
77
|
+
|
|
78
|
+
return episodes.map(ep => ({
|
|
79
|
+
id: ep.id,
|
|
80
|
+
episode_number: ep.episode_number,
|
|
81
|
+
name: `${ep.episode_number}. Bölüm`,
|
|
82
|
+
type: ep.type,
|
|
83
|
+
fansub: ep.fansub,
|
|
84
|
+
_links: [ep.backblaze_link, ep.watch_link_1, ep.watch_link_2, ep.watch_link_3]
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {any} episodeData
|
|
90
|
+
* @returns {Promise<import("./index.js").StreamLink[]>}
|
|
91
|
+
*/
|
|
92
|
+
async getStreamLinks(episodeData) {
|
|
93
|
+
const links = episodeData._links || [];
|
|
94
|
+
const validLink = links.find(l => l && typeof l === "string" && l.trim() !== "");
|
|
95
|
+
|
|
96
|
+
if (!validLink) return [];
|
|
97
|
+
|
|
98
|
+
return [{ url: validLink, quality: "default" }];
|
|
99
|
+
}
|
|
100
|
+
}
|