Small cleanups and update manifest snapshot
This commit is contained in:
parent
e126970047
commit
484b59d542
4 changed files with 81 additions and 104 deletions
|
|
@ -1,116 +1,67 @@
|
|||
import { ContentType } from 'stremio-addon-sdk';
|
||||
import { Context, CountryCode } from '../types';
|
||||
import { Fetcher, Id } from '../utils';
|
||||
import { Fetcher, getTmdbId, getTmdbNameAndYear, Id } from '../utils';
|
||||
import { Source, SourceResult } from './Source';
|
||||
import { getTmdbIdFromImdbId, getTmdbNameAndYear } from '../utils/tmdb';
|
||||
import { TmdbId } from '../utils/id';
|
||||
|
||||
export class Kokoshka extends Source {
|
||||
public readonly id = 'kokoshka';
|
||||
|
||||
public readonly label = 'Kokoshka';
|
||||
|
||||
public readonly contentTypes: ContentType[] = ['movie', 'series'];
|
||||
|
||||
public readonly countryCodes: CountryCode[] = [CountryCode.al];
|
||||
|
||||
public readonly baseUrl = 'https://kokoshka.digital';
|
||||
|
||||
private readonly fetcher: Fetcher;
|
||||
|
||||
constructor(fetcher: Fetcher) {
|
||||
public constructor(fetcher: Fetcher) {
|
||||
super();
|
||||
|
||||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public async handleInternal(ctx: Context, type: string, imdbId: Id): Promise<SourceResult[]> {
|
||||
try {
|
||||
// --- 1. IMDb → TMDb ---
|
||||
let tmdbId: TmdbId;
|
||||
if (type === 'series') {
|
||||
const imdbBaseId = String(imdbId.id).split(':')[0];
|
||||
tmdbId = await getTmdbIdFromImdbId(ctx, this.fetcher, {
|
||||
id: imdbBaseId,
|
||||
season: imdbId.season,
|
||||
episode: imdbId.episode
|
||||
} as any);
|
||||
if (!tmdbId.season || !tmdbId.episode) return [];
|
||||
} else {
|
||||
tmdbId = await getTmdbIdFromImdbId(ctx, this.fetcher, { id: imdbId } as any);
|
||||
}
|
||||
public async handleInternal(ctx: Context, _type: string, id: Id): Promise<SourceResult[]> {
|
||||
const tmdbId = await getTmdbId(ctx, this.fetcher, id);
|
||||
|
||||
// --- 2. Title + Year ---
|
||||
const [title, year] = await getTmdbNameAndYear(ctx, this.fetcher, tmdbId);
|
||||
const cleanTitle = title.replace(/[:]/g, '');
|
||||
const query = encodeURIComponent(`${cleanTitle} ${year}`);
|
||||
const searchUrl = new URL(`/?s=${query}`, this.baseUrl);
|
||||
const searchHtml = await this.fetcher.text(ctx, searchUrl);
|
||||
const [title, year] = await getTmdbNameAndYear(ctx, this.fetcher, tmdbId);
|
||||
const cleanTitle = title.replace(/[:]/g, '');
|
||||
const query = encodeURIComponent(`${cleanTitle} ${year}`);
|
||||
const searchUrl = new URL(`/?s=${query}`, this.baseUrl);
|
||||
const searchHtml = await this.fetcher.text(ctx, searchUrl);
|
||||
|
||||
const linkMatch = searchHtml.match(/class=["']title["'][^>]*><a href=["']([^"']+)["']/);
|
||||
if (!linkMatch?.[1]) return [];
|
||||
const pageUrl = new URL(linkMatch[1], this.baseUrl);
|
||||
const pageHtml = await this.fetcher.text(ctx, pageUrl);
|
||||
const linkMatch = searchHtml.match(/class=["']title["'][^>]*><a href=["']([^"']+)["']/);
|
||||
if (!linkMatch?.[1]) return [];
|
||||
const pageUrl = new URL(linkMatch[1], this.baseUrl);
|
||||
const pageHtml = await this.fetcher.text(ctx, pageUrl);
|
||||
|
||||
// --- Helper: check if a URL returns valid page ---
|
||||
const testUrl = async (url: URL) => {
|
||||
try {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
return !/not\s+found/i.test(html) && html.length > 100;
|
||||
} catch { return false; }
|
||||
};
|
||||
// --- Helper: check if a URL returns valid page ---
|
||||
const testUrl = async (url: URL) => {
|
||||
try {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
return !/not\s+found/i.test(html) && html.length > 100;
|
||||
} catch { return false; }
|
||||
};
|
||||
|
||||
// --- SERIES ---
|
||||
if (type === 'series') {
|
||||
if (!/seriale/i.test(pageUrl.pathname)) return [];
|
||||
if (tmdbId.season) {
|
||||
if (!/seriale/i.test(pageUrl.pathname)) return [];
|
||||
|
||||
const serieSlugMatch = pageUrl.pathname.match(/\/seriale\/([^/]+)/);
|
||||
if (!serieSlugMatch?.[1]) return [];
|
||||
let serieSlug = serieSlugMatch[1].replace(/-me-titra-shqip\/?$/, '').replace(/\/$/, '');
|
||||
serieSlug = serieSlug.replace(/-\d{4}$/, '');
|
||||
const serieSlugMatch = pageUrl.pathname.match(/\/seriale\/([^/]+)/);
|
||||
if (!serieSlugMatch?.[1]) return [];
|
||||
let serieSlug = serieSlugMatch[1].replace(/-me-titra-shqip\/?$/, '').replace(/\/$/, '');
|
||||
serieSlug = serieSlug.replace(/-\d{4}$/, '');
|
||||
|
||||
const episodeUrl = new URL(`/episodi/${serieSlug}-${tmdbId.season}x${tmdbId.episode}-me-titra-shqip/`, this.baseUrl);
|
||||
const episodeHtml = await this.fetcher.text(ctx, episodeUrl);
|
||||
const episodeUrl = new URL(`/episodi/${serieSlug}-${tmdbId.season}x${tmdbId.episode}-me-titra-shqip/`, this.baseUrl);
|
||||
const episodeHtml = await this.fetcher.text(ctx, episodeUrl);
|
||||
|
||||
const postIdMatch = episodeHtml.match(/<li[^>]+data-post=['"](\d+)['"]/);
|
||||
if (!postIdMatch?.[1]) return [];
|
||||
const postId = postIdMatch[1];
|
||||
|
||||
const results: SourceResult[] = [];
|
||||
for (const server of [1, 2]) {
|
||||
const ajaxUrl = new URL(`/wp-json/dooplayer/v2/${postId}/tv/${server}`, this.baseUrl);
|
||||
const json = await this.fetcher.json(ctx, ajaxUrl) as any;
|
||||
|
||||
const urls: string[] = [];
|
||||
if (json.embed_url) urls.push(json.embed_url);
|
||||
if (Array.isArray(json.sources)) urls.push(...json.sources.map((s: any) => s.file));
|
||||
|
||||
for (let embed of urls) {
|
||||
embed = embed.replace(/\\/g, '').replace(/^http:/, 'https:');
|
||||
|
||||
// --- Replace Dodo domain ---
|
||||
if (embed.startsWith('https://d-s.io/')) {
|
||||
embed = embed.replace('https://d-s.io/e/', 'https://dsvplay.com/e/');
|
||||
}
|
||||
|
||||
const url = new URL(embed);
|
||||
if (await testUrl(url)) {
|
||||
results.push({
|
||||
url,
|
||||
meta: {
|
||||
countryCodes: [CountryCode.al],
|
||||
referer: this.baseUrl,
|
||||
title: `${title} S${tmdbId.season}E${tmdbId.episode}`
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// --- MOVIE ---
|
||||
const postIdMatch = pageHtml.match(/<li[^>]+data-post=['"](\d+)['"]/);
|
||||
const postIdMatch = episodeHtml.match(/<li[^>]+data-post=['"](\d+)['"]/);
|
||||
if (!postIdMatch?.[1]) return [];
|
||||
const postId = postIdMatch[1];
|
||||
|
||||
const results: SourceResult[] = [];
|
||||
for (const server of [1, 2]) {
|
||||
const ajaxUrl = new URL(`/wp-json/dooplayer/v2/${postId}/movie/${server}`, this.baseUrl);
|
||||
const ajaxUrl = new URL(`/wp-json/dooplayer/v2/${postId}/tv/${server}`, this.baseUrl);
|
||||
const json = await this.fetcher.json(ctx, ajaxUrl) as any;
|
||||
|
||||
const urls: string[] = [];
|
||||
|
|
@ -120,11 +71,6 @@ export class Kokoshka extends Source {
|
|||
for (let embed of urls) {
|
||||
embed = embed.replace(/\\/g, '').replace(/^http:/, 'https:');
|
||||
|
||||
// --- Replace Dodo domain ---
|
||||
if (embed.startsWith('https://d-s.io/')) {
|
||||
embed = embed.replace('https://d-s.io/e/', 'https://dsvplay.com/e/');
|
||||
}
|
||||
|
||||
const url = new URL(embed);
|
||||
if (await testUrl(url)) {
|
||||
results.push({
|
||||
|
|
@ -132,17 +78,44 @@ export class Kokoshka extends Source {
|
|||
meta: {
|
||||
countryCodes: [CountryCode.al],
|
||||
referer: this.baseUrl,
|
||||
title,
|
||||
}
|
||||
title: `${title} S${tmdbId.season}E${tmdbId.episode}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
|
||||
} catch (err) {
|
||||
console.error('Kokoshka handleInternal error:', err);
|
||||
return [];
|
||||
}
|
||||
|
||||
const postIdMatch = pageHtml.match(/<li[^>]+data-post=['"](\d+)['"]/);
|
||||
if (!postIdMatch?.[1]) return [];
|
||||
const postId = postIdMatch[1];
|
||||
|
||||
const results: SourceResult[] = [];
|
||||
for (const server of [1, 2]) {
|
||||
const ajaxUrl = new URL(`/wp-json/dooplayer/v2/${postId}/movie/${server}`, this.baseUrl);
|
||||
const json = await this.fetcher.json(ctx, ajaxUrl) as any;
|
||||
|
||||
const urls: string[] = [];
|
||||
if (json.embed_url) urls.push(json.embed_url);
|
||||
if (Array.isArray(json.sources)) urls.push(...json.sources.map((s: any) => s.file));
|
||||
|
||||
for (let embed of urls) {
|
||||
embed = embed.replace(/\\/g, '').replace(/^http:/, 'https:');
|
||||
|
||||
const url = new URL(embed);
|
||||
if (await testUrl(url)) {
|
||||
results.push({
|
||||
url,
|
||||
meta: {
|
||||
countryCodes: [CountryCode.al],
|
||||
referer: this.baseUrl,
|
||||
title,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Frembed } from './Frembed';
|
|||
import { FrenchCloud } from './FrenchCloud';
|
||||
import { HomeCine } from './HomeCine';
|
||||
import { KinoGer } from './KinoGer';
|
||||
import { Kokoshka } from './Kokoshka';
|
||||
import { MegaKino } from './MegaKino';
|
||||
import { MeineCloud } from './MeineCloud';
|
||||
import { MostraGuarda } from './MostraGuarda';
|
||||
|
|
@ -17,7 +18,6 @@ import { StreamKiste } from './StreamKiste';
|
|||
import { VerHdLink } from './VerHdLink';
|
||||
import { VidSrc } from './VidSrc';
|
||||
import { VixSrc } from './VixSrc';
|
||||
import { Kokoshka } from './Kokoshka';
|
||||
|
||||
export * from './Source';
|
||||
|
||||
|
|
@ -28,6 +28,8 @@ export const createSources = (fetcher: Fetcher): Source[] => {
|
|||
// multi
|
||||
new FourKHDHub(fetcher),
|
||||
new VixSrc(fetcher),
|
||||
// AL
|
||||
new Kokoshka(fetcher),
|
||||
// EN
|
||||
new VidSrc(fetcher),
|
||||
// ES / MX
|
||||
|
|
@ -48,8 +50,5 @@ export const createSources = (fetcher: Fetcher): Source[] => {
|
|||
// IT
|
||||
new Eurostreaming(fetcher),
|
||||
new MostraGuarda(fetcher),
|
||||
|
||||
// AL
|
||||
new Kokoshka(fetcher),
|
||||
].filter(source => !disabledSources.includes(source.id));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ exports[`buildManifest default manifest 1`] = `
|
|||
"title": "Multi 🌐 (4KHDHub, VixSrc)",
|
||||
"type": "checkbox",
|
||||
},
|
||||
{
|
||||
"key": "al",
|
||||
"title": "Albanian 🇦🇱 (Kokoshka)",
|
||||
"type": "checkbox",
|
||||
},
|
||||
{
|
||||
"key": "de",
|
||||
"title": "German 🇩🇪 (Einschalten, KinoGer, MegaKino, MeineCloud, StreamKiste)",
|
||||
|
|
@ -80,9 +85,9 @@ exports[`buildManifest default manifest 1`] = `
|
|||
],
|
||||
"description": "Provides HTTP URLs from streaming websites. Configure add-on for additional languages. Add MediaFlow proxy for more URLs.
|
||||
|
||||
Supported languages: German, English, Castilian Spanish, French, Italian, Latin American Spanish
|
||||
Supported languages: Albanian, German, English, Castilian Spanish, French, Italian, Latin American Spanish
|
||||
|
||||
Supported sources: 4KHDHub, VixSrc, VidSrc, CineHDPlus, Cuevana, HomeCine, VerHdLink, Einschalten, KinoGer, MegaKino, MeineCloud, StreamKiste, Frembed, FrenchCloud, Movix, Eurostreaming, MostraGuarda
|
||||
Supported sources: 4KHDHub, VixSrc, Kokoshka, VidSrc, CineHDPlus, Cuevana, HomeCine, VerHdLink, Einschalten, KinoGer, MegaKino, MeineCloud, StreamKiste, Frembed, FrenchCloud, Movix, Eurostreaming, MostraGuarda
|
||||
|
||||
Supported extractors: ",
|
||||
"id": "webstreamr",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { CountryCode } from '../types';
|
|||
|
||||
const countryCodeMap: Record<CountryCode, { language: string; flag: string; iso639: string | undefined }> = {
|
||||
multi: { language: 'Multi', flag: '🌐', iso639: undefined },
|
||||
al: { language: 'Albanian', flag: '🇦🇱', iso639: 'sq' },
|
||||
al: { language: 'Albanian', flag: '🇦🇱', iso639: 'alb' },
|
||||
ar: { language: 'Arabic', flag: '🇸🇦', iso639: 'ara' },
|
||||
bg: { language: 'Bulgarian', flag: '🇧🇬', iso639: 'bul' },
|
||||
cs: { language: 'Czech', flag: '🇨🇿', iso639: 'ces' },
|
||||
|
|
|
|||
Loading…
Reference in a new issue