Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this maintained LibreTV fork are documented here.

## 1.2.13 - 2026-05-12

### Fixed

- Fixed player resource switching after search relevance helpers moved out of the player page load path.
- Player resource switching now falls back to the default selected sources when no local source selection exists.
- Added empty-state handling when no switchable resources are found.

## 1.2.12 - 2026-05-12

### Changed
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
202605120223
202605120255
2 changes: 1 addition & 1 deletion js/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const SITE_CONFIG = {
url: '',
description: '免费在线视频搜索与观看平台',
logo: 'image/logo.png',
version: '1.2.12'
version: '1.2.13'
};

const DEFAULT_SELECTED_APIS = ['ysgc', 'jszy', 'wujin', 'maoyan'];
Expand Down
19 changes: 15 additions & 4 deletions js/player.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const selectedAPIs = JSON.parse(localStorage.getItem('selectedAPIs') || '[]');
const selectedAPIs = JSON.parse(localStorage.getItem('selectedAPIs') || JSON.stringify(DEFAULT_SELECTED_APIS));
const customAPIs = JSON.parse(localStorage.getItem('customAPIs') || '[]'); // 存储自定义API列表

// 改进返回功能
Expand Down Expand Up @@ -2010,10 +2010,16 @@ async function showSwitchResourceModal() {
}
return { key: curr, name: '未知资源' };
});

if (resourceOptions.length === 0) {
modalContent.innerHTML = '<div style="text-align:center;padding:20px;color:#aaa;grid-column:1/-1;">暂无可切换的数据源,请先到设置中选择数据源。</div>';
return;
}

let allResults = {};
await Promise.all(resourceOptions.map(async (opt) => {
let queryResult = await searchByAPIAndKeyWord(opt.key, currentVideoTitle);
if (queryResult.length == 0) {
await Promise.allSettled(resourceOptions.map(async (opt) => {
const queryResult = await searchByAPIAndKeyWord(opt.key, currentVideoTitle);
if (!Array.isArray(queryResult) || queryResult.length === 0) {
return
}
// 优先取完全同名资源,否则默认取第一个
Expand All @@ -2026,6 +2032,11 @@ async function showSwitchResourceModal() {
allResults[opt.key] = result;
}));

if (Object.keys(allResults).length === 0) {
modalContent.innerHTML = '<div style="text-align:center;padding:20px;color:#aaa;grid-column:1/-1;">未找到可切换资源,请稍后重试或更换片名搜索。</div>';
return;
}

// 更新状态显示:开始速率测试
modalContent.innerHTML = '<div style="text-align:center;padding:20px;color:#aaa;grid-column:1/-1;">正在测试各资源速率...</div>';

Expand Down
50 changes: 48 additions & 2 deletions js/search.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
const SEARCH_RESULT_TITLE_FIELDS = [
'vod_name',
'vod_sub',
'vod_en',
'title',
'name'
];

function normalizeSearchResultText(value) {
return String(value || '').toLowerCase().replace(/\s+/g, '');
}

function getSearchResultTitleTexts(item) {
return SEARCH_RESULT_TITLE_FIELDS
.map(field => normalizeSearchResultText(item?.[field]))
.filter(Boolean);
}

function doesSearchResultMatchQuery(item, query) {
const compactQuery = normalizeSearchResultText(query);
if (!compactQuery) return true;

const titleTexts = getSearchResultTitleTexts(item);
if (titleTexts.some(text => text.includes(compactQuery))) {
return true;
}

const queryTerms = String(query || '')
.toLowerCase()
.split(/\s+/)
.map(normalizeSearchResultText)
.filter(Boolean);

return queryTerms.length > 1 && queryTerms.every(term =>
titleTexts.some(text => text.includes(term))
);
}

function filterSearchResultsByQuery(results, query) {
const list = Array.isArray(results) ? results : [];
return list.filter(item => doesSearchResultMatchQuery(item, query));
}

window.doesResultMatchQuery = window.doesResultMatchQuery || doesSearchResultMatchQuery;
window.filterResultsByQuery = window.filterResultsByQuery || filterSearchResultsByQuery;

function getApiSearchContext(apiId) {
if (apiId.startsWith('custom_')) {
const customIndex = apiId.replace('custom_', '');
Expand Down Expand Up @@ -75,7 +121,7 @@ async function searchByAPIAndKeyWord(apiId, query) {

// 处理第一页结果
const firstPageResults = normalizeApiList(data, apiId, context);
const results = filterResultsByQuery(firstPageResults, query);
const results = filterSearchResultsByQuery(firstPageResults, query);
if (firstPageResults.length > 0 && results.length === 0) {
return [];
}
Expand Down Expand Up @@ -103,7 +149,7 @@ async function searchByAPIAndKeyWord(apiId, query) {
if (!pageData || !pageData.list || !Array.isArray(pageData.list)) return [];

// 处理当前页结果
return filterResultsByQuery(normalizeApiList(pageData, apiId, context), query);
return filterSearchResultsByQuery(normalizeApiList(pageData, apiId, context), query);
} catch (error) {
console.warn(`API ${apiId} 第${page}页搜索失败:`, error);
return [];
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "libretv",
"type": "module",
"version": "1.2.12",
"version": "1.2.13",
"private": true,
"description": "免费在线视频搜索与观看平台",
"author": "bestZwei",
Expand Down
62 changes: 56 additions & 6 deletions test/libretv-defaults.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,56 @@ test('search results require title relevance and ignored sources do not paginate
assert.equal(requestedUrls.some(url => url.includes('pg=2')), false);
});

test('player resource switch can search without loading homepage app script', async () => {
const requestedUrls = [];
const warnings = [];
const sandbox = {
console: {
...console,
warn(...args) {
warnings.push(args.map(String).join(' '));
}
},
URL,
window: {},
fetch: async url => {
requestedUrls.push(decodeURIComponent(String(url)));
return {
ok: true,
async json() {
return {
pagecount: 1,
list: [
{ vod_id: 'match-1', vod_name: '世界的主人', vod_pic: '/poster.jpg' },
{ vod_id: 'noise-1', vod_name: '美女主播私拍' }
]
};
}
};
},
setTimeout,
clearTimeout,
AbortController
};
vm.createContext(sandbox);
vm.runInContext(await readProjectFile('js/config.js'), sandbox);
vm.runInContext(await readProjectFile('js/customer_site.js'), sandbox);
vm.runInContext(await readProjectFile('js/search.js'), sandbox);
const player = await readProjectFile('js/player.js');

assert.equal(typeof sandbox.searchByAPIAndKeyWord, 'function');
assert.equal(typeof sandbox.window.filterResultsByQuery, 'function');
assert.match(player, /JSON\.stringify\(DEFAULT_SELECTED_APIS\)/);
assert.match(player, /Promise\.allSettled\(resourceOptions\.map/);
assert.match(player, /未找到可切换资源/);

const results = await sandbox.searchByAPIAndKeyWord('ysgc', '世界的主人');

assert.deepEqual(Array.from(results).map(item => item.vod_id), ['match-1']);
assert.equal(requestedUrls.some(url => url.includes('cj.lziapi.com')), true);
assert.equal(warnings.some(message => message.includes('filterResultsByQuery')), false);
});

test('adult recommendation tag uses selected adult sources instead of Douban', async () => {
const storage = new Map([
['yellowFilterEnabled', 'false'],
Expand Down Expand Up @@ -966,12 +1016,12 @@ test('release metadata is bumped for this update', async () => {

const changelog = await readProjectFile('CHANGELOG.md');

assert.equal(packageJson.version, '1.2.12');
assert.equal(lockJson.version, '1.2.12');
assert.equal(lockJson.packages[''].version, '1.2.12');
assert.match(config, /version:\s*'1\.2\.12'/);
assert.match(changelog, /1\.2\.12/);
assert.match(changelog, /播放器|ArtPlayer|hls\.js/);
assert.equal(packageJson.version, '1.2.13');
assert.equal(lockJson.version, '1.2.13');
assert.equal(lockJson.packages[''].version, '1.2.13');
assert.match(config, /version:\s*'1\.2\.13'/);
assert.match(changelog, /1\.2\.13/);
assert.match(changelog, /resource switching|资源切换|switchable resources/);
assert.match(versionTxt, /^\d{12}$/);
assert.ok(Number(versionTxt) > 202508060117);
});
Loading