mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-10-02 08:56:38 +08:00
修复预览视频播放 更新文档
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import {GM_xmlhttpRequest} from 'vite-plugin-monkey/dist/client';
|
||||
|
||||
const DMM_GRAPHQL_URL = 'https://api.video.dmm.co.jp/graphql';
|
||||
|
||||
const dmmGraphQLKeyword = (code) => {
|
||||
const match = String(code || '').trim().toUpperCase().match(/^([A-Z0-9]{2,10})-(\d{1,6})$/);
|
||||
if (!match) return '';
|
||||
return `${match[1].toLowerCase()}${match[2].padStart(5, '0')}`;
|
||||
};
|
||||
|
||||
const dmmGraphQLRequest = (query) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
GM_xmlhttpRequest({
|
||||
method: 'POST',
|
||||
url: DMM_GRAPHQL_URL,
|
||||
data: JSON.stringify({query}),
|
||||
timeout: 12000,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Referer: 'https://video.dmm.co.jp/',
|
||||
'Fanza-Device': 'BROWSER',
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
onload: (response) => {
|
||||
if (response.status < 200 || response.status >= 400) {
|
||||
reject(new Error(`DMM GraphQL HTTP ${response.status || 0}`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(response.responseText || '{}');
|
||||
if (Array.isArray(json.errors) && json.errors.length) {
|
||||
reject(new Error(json.errors[0].message || 'DMM GraphQL error'));
|
||||
return;
|
||||
}
|
||||
resolve(json.data || {});
|
||||
} catch (e) {
|
||||
reject(new Error(`DMM GraphQL JSON parse error: ${e.message}`));
|
||||
}
|
||||
},
|
||||
onerror: () => reject(new Error('DMM GraphQL network error')),
|
||||
ontimeout: () => reject(new Error('DMM GraphQL timeout')),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const loadDmmCover = async (code) => {
|
||||
const keyword = dmmGraphQLKeyword(code);
|
||||
if (!keyword) throw new Error('DMM cover keyword missing');
|
||||
|
||||
const searchKeywords = [...new Set([keyword, `${keyword}#`])];
|
||||
for (const searchKeyword of searchKeywords) {
|
||||
const query = `{ legacySearchPPV(limit: 5, offset: 0, sort: SALES_RANK_SCORE, floor: AV, queryWord: ${JSON.stringify(searchKeyword)}) { result { contents { id } } } }`;
|
||||
const data = await dmmGraphQLRequest(query);
|
||||
|
||||
const ids = Array.isArray(data && data.legacySearchPPV && data.legacySearchPPV.result && data.legacySearchPPV.result.contents)
|
||||
? data.legacySearchPPV.result.contents.map((item) => String(item && item.id || '').trim()).filter(Boolean)
|
||||
: [];
|
||||
|
||||
const compactKeyword = keyword.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
||||
const contentID =
|
||||
ids.find((id) => id.replace(/[^a-z0-9]/gi, '').toLowerCase().includes(compactKeyword)) ||
|
||||
(ids.length === 1 ? ids[0] : '');
|
||||
|
||||
if (contentID) {
|
||||
const encoded = encodeURIComponent(contentID);
|
||||
return `https://pics.dmm.co.jp/digital/video/${encoded}/${encoded}pl.jpg`;
|
||||
}
|
||||
}
|
||||
throw new Error('DMM cover missing');
|
||||
};
|
||||
|
||||
const searchDmmContentIdsByGraphQL = async (code) => {
|
||||
const keyword = dmmGraphQLKeyword(code);
|
||||
if (!keyword) return [];
|
||||
const carNumNoHyphen = String(code || '').replace(/-/g, '').toLowerCase();
|
||||
|
||||
const results = [];
|
||||
const searchKeywords = [...new Set([keyword, `${keyword}#`])];
|
||||
for (const searchKeyword of searchKeywords) {
|
||||
const query = `{ legacySearchPPV(limit: 10, offset: 0, sort: SALES_RANK_SCORE, floor: AV, queryWord: ${JSON.stringify(searchKeyword)}) { result { contents { contentId serviceCode floorCode } } } }`;
|
||||
const data = await dmmGraphQLRequest(query);
|
||||
|
||||
const contents = Array.isArray(data && data.legacySearchPPV && data.legacySearchPPV.result && data.legacySearchPPV.result.contents)
|
||||
? data.legacySearchPPV.result.contents
|
||||
: [];
|
||||
|
||||
for (const item of contents) {
|
||||
const contentId = String(item.contentId || '');
|
||||
if (contentId.replace(/[^a-z0-9]/gi, '').toLowerCase().includes(carNumNoHyphen)) {
|
||||
results.push({
|
||||
serviceCode: item.serviceCode || 'digital',
|
||||
floorCode: item.floorCode || 'AV',
|
||||
contentId: contentId,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (results.length > 0) break;
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const getDmmGraphQLVideoUrls = async (code) => {
|
||||
const contentItems = await searchDmmContentIdsByGraphQL(code);
|
||||
if (!contentItems.length) return null;
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
contentItems.map((item) => extractTrailerLinks(item))
|
||||
);
|
||||
|
||||
for (const result of results) {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const extractTrailerLinks = (item) => {
|
||||
const {contentId, serviceCode, floorCode} = item;
|
||||
return new Promise((resolve, reject) => {
|
||||
const trailerPageUrl = `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${contentId}/mtype=AhRVShI_/service=${serviceCode}/floor=${floorCode}/mode=/`;
|
||||
|
||||
GM_xmlhttpRequest({
|
||||
method: 'GET',
|
||||
url: trailerPageUrl,
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'accept-language': 'ja-JP,ja;q=0.9',
|
||||
Cookie: 'age_check_done=1',
|
||||
},
|
||||
onload: (response) => {
|
||||
const htmlContent = response.responseText || '';
|
||||
if (htmlContent.includes('このサービスはお住まいの地域からは')) {
|
||||
reject(new Error('节点不可用,请将DMM域名分流到日本ip'));
|
||||
return;
|
||||
}
|
||||
const match = htmlContent.match(/const\s+args\s+=\s+(.*);/);
|
||||
if (!match) {
|
||||
reject(new Error('未在脚本中找到 const args = ... 变量'));
|
||||
return;
|
||||
}
|
||||
let bitrates;
|
||||
try {
|
||||
bitrates = JSON.parse(match[1]).bitrates;
|
||||
} catch (e) {
|
||||
reject(new Error(`解析播放器脚本 JSON 失败: ${e.message}`));
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(bitrates)) {
|
||||
reject(new Error('解析画质链接失败: bitrates 字段不是一个数组或不存在'));
|
||||
return;
|
||||
}
|
||||
const qualityKeys = [
|
||||
'4ks', '4k', 'hhbs', 'hhb', 'hmb', 'mhb', 'mmb',
|
||||
'dmb_w', 'dmb_s', 'dm_s', 'sm_s', 'mhb_w',
|
||||
];
|
||||
const qualityNameRegex = new RegExp(`(${qualityKeys.join('|')})\\.mp4$`);
|
||||
const finalQualityMap = {};
|
||||
for (const bitrate of bitrates) {
|
||||
const url = bitrate && bitrate.src;
|
||||
if (!url || typeof url !== 'string' || !url.endsWith('.mp4')) continue;
|
||||
const qualityMatch = url.match(qualityNameRegex);
|
||||
let qualityKey = '';
|
||||
if (qualityMatch && qualityMatch[1]) qualityKey = qualityMatch[1];
|
||||
if (qualityKey && !finalQualityMap[qualityKey]) {
|
||||
finalQualityMap[qualityKey] = url;
|
||||
}
|
||||
}
|
||||
if (Object.keys(finalQualityMap).length === 0) {
|
||||
reject(new Error('未找到匹配要求的预览画质视频'));
|
||||
return;
|
||||
}
|
||||
resolve(finalQualityMap);
|
||||
},
|
||||
onerror: () => reject(new Error('DMM 播放页请求网络错误')),
|
||||
ontimeout: () => reject(new Error('DMM 播放页请求超时')),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
DMM_GRAPHQL_URL,
|
||||
dmmGraphQLKeyword,
|
||||
dmmGraphQLRequest,
|
||||
loadDmmCover,
|
||||
searchDmmContentIdsByGraphQL,
|
||||
getDmmGraphQLVideoUrls,
|
||||
extractTrailerLinks,
|
||||
};
|
||||
+31
-3
@@ -1,4 +1,6 @@
|
||||
import { qualityOptions } from '../core/constants.js';
|
||||
import {getDmmGraphQLVideoUrls} from './dmm-graphql.js';
|
||||
|
||||
const selectDefaultQuality = (dmmVideoQualityList, intendedDefault) => {
|
||||
if (!dmmVideoQualityList || 0 === dmmVideoQualityList.length) return null;
|
||||
const availableSet = new Set(dmmVideoQualityList);
|
||||
@@ -138,6 +140,25 @@ class DmmVideoFetcher {
|
||||
if (0 === Object.keys(finalQualityMap).length) throw new Error("未找到匹配要求的预览画质视频");
|
||||
return finalQualityMap;
|
||||
}
|
||||
|
||||
async _searchViaGraphQL() {
|
||||
clog.debug("尝试通过 GraphQL API 搜索视频...");
|
||||
const videoMap = await getDmmGraphQLVideoUrls(this.carNum);
|
||||
if (videoMap && Object.keys(videoMap).length > 0) {
|
||||
this._updateCache(videoMap);
|
||||
clog.debug("GraphQL 成功解析出预览视频:", videoMap);
|
||||
const dmmCacheKey = "jhs_other_site_dmm";
|
||||
const dmmCacheData = localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {};
|
||||
dmmCacheData[this.carNum] = {
|
||||
type: "graphql",
|
||||
url: `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`,
|
||||
};
|
||||
localStorage.setItem(dmmCacheKey, JSON.stringify(dmmCacheData));
|
||||
return videoMap;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async fetchVideo() {
|
||||
const cachedResult = this._checkCache();
|
||||
if (cachedResult) return cachedResult;
|
||||
@@ -149,22 +170,29 @@ class DmmVideoFetcher {
|
||||
contentItems = await this._searchContentIds();
|
||||
} catch (e) {
|
||||
clog.error("DMM API 搜索失败:", e);
|
||||
}
|
||||
if (!contentItems || 0 === contentItems.length) {
|
||||
clog.warn("Affiliate API 未找到视频,尝试 GraphQL API...");
|
||||
const graphQLResult = await this._searchViaGraphQL();
|
||||
if (graphQLResult) return graphQLResult;
|
||||
const $btn = $("#fanzaBtn");
|
||||
$btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`);
|
||||
$btn.attr("title", "未查询到, 点击前往搜索页");
|
||||
$btn.css("backgroundColor", "#de3333");
|
||||
return null;
|
||||
}
|
||||
if (!contentItems || 0 === contentItems.length) return null;
|
||||
try {
|
||||
const finalVideoMap = await Promise.any(contentItems.map((item => this._extractTrailerLinks(item))));
|
||||
this._updateCache(finalVideoMap);
|
||||
return finalVideoMap;
|
||||
} catch (error) {
|
||||
const errors = error.errors || [ error ];
|
||||
clog.error(`Affiliate 解析失败: ${errors[0] && errors[0].message || errors[0]}`, errors);
|
||||
clog.warn("尝试 GraphQL API 作为 fallback...");
|
||||
const graphQLResult = await this._searchViaGraphQL();
|
||||
if (graphQLResult) return graphQLResult;
|
||||
if (errors.some((err => err.message.includes("节点不可用")))) this.showErrorMessages && show.error("节点不可用,请将DMM域名分流到日本ip"); else {
|
||||
const displayError = errors[0].message || errors[0];
|
||||
clog.error(`解析失败: ${displayError}`, errors);
|
||||
this.showErrorMessages && show.error(`解析失败: ${displayError}`);
|
||||
}
|
||||
const $btn = $("#fanzaBtn");
|
||||
@@ -178,4 +206,4 @@ class DmmVideoFetcher {
|
||||
|
||||
const getDmmVideo = async (carNum2, showErrorMessages = !0) => new DmmVideoFetcher(carNum2, showErrorMessages).fetchVideo();
|
||||
|
||||
export { DmmVideoFetcher, getDmmVideo, selectDefaultQuality };
|
||||
export { DmmVideoFetcher, getDmmVideo, selectDefaultQuality };
|
||||
@@ -0,0 +1,223 @@
|
||||
import {qualityOptions} from '../core/constants.js';
|
||||
|
||||
const JAVXY_TOKEN = [118, 119, 112, 71, 97, 110, 28, 84, 124, 65, 76, 102, 65, 16, 77, 109, 64, 82, 85, 83, 67, 92, 125, 108, 83, 65, 124, 107, 84, 104, 71, 84, 17, 124, 118, 125, 104, 8, 125, 96, 112, 103, 29, 18, 82, 83, 87, 84]
|
||||
.map(v => String.fromCharCode(v ^ 0x25)).join('');
|
||||
|
||||
const JAVXY_ENDPOINTS = [
|
||||
{host: String.fromCharCode(106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), label: 'Javxy'},
|
||||
{
|
||||
host: String.fromCharCode(119, 111, 114, 107, 101, 114, 46, 106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100),
|
||||
label: 'Javxy Worker'
|
||||
},
|
||||
];
|
||||
|
||||
const JAVXY_SOURCE_LABELS = {
|
||||
'Tokyo-Hot': 'Javxy | Tokyo-Hot',
|
||||
FC2: 'Javxy | FC2',
|
||||
Direct: 'Javxy | Direct',
|
||||
DMM: 'Javxy | dmm',
|
||||
MGStage: 'Javxy | MGStage',
|
||||
DUGA: 'Javxy | DUGA',
|
||||
MYWIFE: 'Javxy | MyWife',
|
||||
JavTrailers: 'Javxy | JavTrailers',
|
||||
JavDB: 'Javxy | Javdb',
|
||||
AVWikiDB: 'Javxy | AVWikiDB',
|
||||
JAVDatabase: 'Javxy | JAVDatabase',
|
||||
HEYZO: 'Javxy | Heyzo',
|
||||
HeyDouga: 'Javxy | HeyDouga',
|
||||
PACO: 'Javxy | Paco',
|
||||
'10MU': 'Javxy | 10mu',
|
||||
Caribbean: 'Javxy | 加勒比',
|
||||
'1Pondo': 'Javxy | 一本道',
|
||||
};
|
||||
|
||||
const JAVXY_QUALITY_OPTIONS = [
|
||||
{quality: '4k', text: '4K'},
|
||||
{quality: 'hhb', text: '1080p'},
|
||||
{quality: '1080p', text: '1080p'},
|
||||
{quality: 'hmb', text: '720p'},
|
||||
{quality: '720p', text: '720p'},
|
||||
{quality: 'mhb', text: '576p'},
|
||||
{quality: '540p', text: '540p'},
|
||||
{quality: 'mmb', text: '432p'},
|
||||
{quality: '480p', text: '480p'},
|
||||
{quality: '396p', text: '396p'},
|
||||
{quality: '360p', text: '360p'},
|
||||
{quality: '240p', text: '240p'},
|
||||
];
|
||||
|
||||
const jpSourceFailedKey = (source = 'DMM') => `javxy_jp_source_failed_until_${String(source || '').toUpperCase()}`;
|
||||
|
||||
const isJpSourceTemporarilyFailed = (source = 'DMM') => {
|
||||
const until = Number(sessionStorage.getItem(jpSourceFailedKey(source)) || 0);
|
||||
if (!Number.isFinite(until) || until <= Date.now()) {
|
||||
sessionStorage.removeItem(jpSourceFailedKey(source));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const markJpSourceTemporarilyFailed = (source = 'DMM') => {
|
||||
sessionStorage.setItem(jpSourceFailedKey(source), String(Date.now() + 30 * 60 * 1000));
|
||||
};
|
||||
|
||||
const selectHighestQuality = (qualityMap) => {
|
||||
const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index]));
|
||||
return Object.keys(qualityMap || {})
|
||||
.filter(key => qualityMap[key])
|
||||
.sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1))[0] || null;
|
||||
};
|
||||
|
||||
const sortQualityKeys = (qualityMap) => {
|
||||
const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index]));
|
||||
return Object.keys(qualityMap || {})
|
||||
.filter(key => qualityMap[key])
|
||||
.sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1));
|
||||
};
|
||||
|
||||
const normalizeJavxySource = (value) => {
|
||||
const raw = String(value || '').trim().toLowerCase();
|
||||
if (raw.includes('fc2')) return 'FC2';
|
||||
if (raw.includes('mgstage')) return 'MGStage';
|
||||
if (raw.includes('heydouga')) return 'Direct';
|
||||
if (raw.includes('mywife')) return 'MYWIFE';
|
||||
if (raw.includes('duga')) return 'DUGA';
|
||||
if (raw.includes('javtrailers')) return 'JavTrailers';
|
||||
if (raw.includes('javdb')) return 'JavDB';
|
||||
if (raw.includes('avwikidb')) return 'AVWikiDB';
|
||||
if (raw.includes('javdatabase')) return 'JAVDatabase';
|
||||
if (raw.includes('dmm')) return 'DMM';
|
||||
if (raw === 'direct' || raw.includes('heyzo') || raw.includes('heydouga') || raw.includes('paco') || raw.includes('10musume') || raw.includes('10mu') || raw.includes('1pondo') || raw.includes('caribbean') || raw.includes('tokyo-hot') || raw.includes('tokyohot')) return 'Direct';
|
||||
return String(value || '').trim();
|
||||
};
|
||||
|
||||
const javxyQualityMapToDmmFormat = (javxyQualityMap) => {
|
||||
const result = {};
|
||||
const qualityKeys = qualityOptions.map(o => o.quality);
|
||||
|
||||
for (const [key, url] of Object.entries(javxyQualityMap || {})) {
|
||||
if (!url) continue;
|
||||
const lowerKey = key.toLowerCase();
|
||||
|
||||
if (qualityKeys.includes(key)) {
|
||||
result[key] = url;
|
||||
} else if (qualityKeys.includes(lowerKey)) {
|
||||
result[lowerKey] = url;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const fromJavxyCcCd = async (id, rawCode = '', options = {}) => {
|
||||
const query = String(id || rawCode || '').trim();
|
||||
if (!query) {
|
||||
clog.debug('Javxy 跳过:查询词为空');
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const endpoint of JAVXY_ENDPOINTS) {
|
||||
const params = new URLSearchParams({client: 'laosiji-new'});
|
||||
if (Array.isArray(options.skip) && options.skip.length) params.set('skip', options.skip.join(','));
|
||||
if (Array.isArray(options.prefer) && options.prefer.length) params.set('prefer', options.prefer.join(','));
|
||||
if (Array.isArray(options.source) && options.source.length) params.set('source', options.source.join(','));
|
||||
if (options.playbackFallback) params.set('purpose', 'playback-fallback');
|
||||
|
||||
const apiUrl = `https://${endpoint.host}/trailers/${encodeURIComponent(query)}?${params}`;
|
||||
clog.debug('Javxy 请求 API', {query, apiUrl, endpoint: endpoint.label});
|
||||
|
||||
let r;
|
||||
try {
|
||||
r = await gmHttp.get(apiUrl, null, {
|
||||
'Accept': 'application/json,text/plain,*/*',
|
||||
'X-Javxy-Token': JAVXY_TOKEN,
|
||||
}, {timeout: 8000});
|
||||
} catch (e) {
|
||||
clog.debug('Javxy API 网络失败,尝试下一个节点', {endpoint: endpoint.label, error: e.message});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r) {
|
||||
clog.debug('Javxy API 无响应,尝试下一个节点', {endpoint: endpoint.label});
|
||||
continue;
|
||||
}
|
||||
|
||||
const trailerUrl = String(r?.trailer || '').trim();
|
||||
if (!trailerUrl) {
|
||||
clog.debug('Javxy 无 trailer 字段', {endpoint: endpoint.label, keys: Object.keys(r || {})});
|
||||
return null;
|
||||
}
|
||||
|
||||
const qualityMap = r?.qualities && typeof r.qualities === 'object' ? r.qualities : {};
|
||||
const quality = r?.quality && qualityMap[r.quality] ? r.quality : selectHighestQuality(qualityMap);
|
||||
const sourceBase = JAVXY_SOURCE_LABELS[r?.source] || `Javxy | ${r?.source || 'dmm'}`;
|
||||
const directUrl = qualityMap[quality] || trailerUrl;
|
||||
|
||||
clog.debug('Javxy 返回结果', {
|
||||
endpoint: endpoint.label,
|
||||
source: r?.source,
|
||||
quality,
|
||||
qualities: Object.keys(qualityMap),
|
||||
url: directUrl,
|
||||
});
|
||||
|
||||
const dmmQualityMap = javxyQualityMapToDmmFormat(qualityMap);
|
||||
|
||||
return {
|
||||
url: directUrl,
|
||||
source: sourceBase,
|
||||
type: String(r?.type || '').trim() || 'video',
|
||||
qualities: qualityMap,
|
||||
quality,
|
||||
directUrl,
|
||||
code: id,
|
||||
rawCode,
|
||||
javxySource: String(r?.source || '').trim(),
|
||||
requiresJP: Boolean(r?.requiresJP),
|
||||
urls: Array.isArray(r?.urls) && r.urls.length ? r.urls : sortQualityKeys(qualityMap).map(key => qualityMap[key]),
|
||||
dmmQualityMap,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const fallbackJavxyResult = async (code, rawCode = '', failedSources = [], options = {}) => {
|
||||
const skip = [...new Set((failedSources || []).map(source => normalizeJavxySource(source)).filter(Boolean))];
|
||||
const source = [...new Set((options.source || []).map(s => String(s || '').trim()).filter(Boolean))];
|
||||
|
||||
if (!skip.length && !source.length) return null;
|
||||
|
||||
clog.debug('Javxy 播放失败回落查询', {code, skip, source});
|
||||
|
||||
return fromJavxyCcCd(code, rawCode, {skip, source, playbackFallback: true});
|
||||
};
|
||||
|
||||
const getJavxyVideoUrls = async (code, failedSources = []) => {
|
||||
const result = await fallbackJavxyResult(code, code, failedSources, {
|
||||
source: ['JavTrailers', 'JavDB'],
|
||||
});
|
||||
|
||||
if (result?.dmmQualityMap && Object.keys(result.dmmQualityMap).length > 0) {
|
||||
return result.dmmQualityMap;
|
||||
}
|
||||
|
||||
if (result?.url) {
|
||||
const qualityMap = {};
|
||||
qualityMap['720p'] = result.url;
|
||||
return qualityMap;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export {
|
||||
fromJavxyCcCd,
|
||||
fallbackJavxyResult,
|
||||
getJavxyVideoUrls,
|
||||
normalizeJavxySource,
|
||||
isJpSourceTemporarilyFailed,
|
||||
markJpSourceTemporarilyFailed,
|
||||
JAVXY_TOKEN,
|
||||
JAVXY_ENDPOINTS,
|
||||
};
|
||||
Reference in New Issue
Block a user