Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ git clone git@github.com:jackwener/opencli.git && cd opencli && npm install && n
| **twitter** | `trending` `search` `timeline` `bookmarks` `post` `download` `profile` `article` `like` `likes` `notifications` `reply` `reply-dm` `thread` `follow` `unfollow` `followers` `following` `block` `unblock` `bookmark` `unbookmark` `delete` `hide-reply` `accept` |
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `user` `user-posts` `user-comments` `read` `save` `saved` `subscribe` `upvote` `upvoted` `comment` |
| **spotify** | `auth` `status` `play` `pause` `next` `prev` `volume` `search` `queue` `shuffle` `repeat` |
| **xiaoe** | `courses` `detail` `catalog` `play-url` `content` |

66+ adapters in total — **[→ see all supported sites & commands](./docs/adapters/index.md)**

Expand Down
129 changes: 129 additions & 0 deletions src/clis/xiaoe/catalog.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
site: xiaoe
name: catalog
description: 小鹅通课程目录(支持普通课程、专栏、大专栏)
domain: h5.xet.citv.cn
strategy: cookie

args:
url:
type: str
required: true
positional: true
description: 课程页面 URL

pipeline:
- navigate: ${{ args.url }}

- wait: 8

- evaluate: |
(async () => {
var el = document.querySelector('#app');
var store = (el && el.__vue__) ? el.__vue__.$store : null;
if (!store) return [];
var coreInfo = store.state.coreInfo || {};
var resourceType = coreInfo.resource_type || 0;
var origin = window.location.origin;
var courseName = coreInfo.resource_name || '';

function typeLabel(t) {
return {1:'图文',2:'直播',3:'音频',4:'视频',6:'专栏',8:'大专栏'}[Number(t)] || String(t||'');
}
function buildUrl(item) {
var u = item.jump_url || item.h5_url || item.url || '';
return (u && !u.startsWith('http')) ? origin + u : u;
}
function clickTab(name) {
var tabs = document.querySelectorAll('span, div');
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].children.length === 0 && tabs[i].textContent.trim() === name) {
tabs[i].click(); return;
}
}
}

clickTab('目录');
await new Promise(function(r) { setTimeout(r, 2000); });

// ===== 专栏 / 大专栏 =====
if (resourceType === 6 || resourceType === 8) {
await new Promise(function(r) { setTimeout(r, 1000); });
var listData = [];
var walkList = function(vm, depth) {
if (!vm || depth > 6 || listData.length > 0) return;
var d = vm.$data || {};
var keys = ['columnList', 'SingleItemList', 'chapterChildren'];
for (var ki = 0; ki < keys.length; ki++) {
var arr = d[keys[ki]];
if (arr && Array.isArray(arr) && arr.length > 0 && arr[0].resource_id) {
for (var j = 0; j < arr.length; j++) {
var item = arr[j];
if (!item.resource_id || !/^[pvlai]_/.test(item.resource_id)) continue;
listData.push({
ch: 1, chapter: courseName, no: j + 1,
title: item.resource_title || item.title || item.chapter_title || '',
type: typeLabel(item.resource_type || item.chapter_type),
resource_id: item.resource_id,
url: buildUrl(item),
status: item.finished_state === 1 ? '已完成' : (item.resource_count ? item.resource_count + '节' : ''),
});
}
return;
}
}
if (vm.$children) {
for (var c = 0; c < vm.$children.length; c++) walkList(vm.$children[c], depth + 1);
}
};
walkList(el.__vue__, 0);
return listData;
}

// ===== 普通课程 =====
var chapters = document.querySelectorAll('.chapter_box');
for (var ci = 0; ci < chapters.length; ci++) {
var vue = chapters[ci].__vue__;
if (vue && typeof vue.getSecitonList === 'function' && (!vue.isShowSecitonsList || !vue.chapterChildren.length)) {
if (vue.isShowSecitonsList) vue.isShowSecitonsList = false;
try { vue.getSecitonList(); } catch(e) {}
await new Promise(function(r) { setTimeout(r, 1500); });
}
}
await new Promise(function(r) { setTimeout(r, 3000); });

var result = [];
chapters = document.querySelectorAll('.chapter_box');
for (var cj = 0; cj < chapters.length; cj++) {
var v = chapters[cj].__vue__;
if (!v) continue;
var chTitle = (v.chapterItem && v.chapterItem.chapter_title) || '';
var children = v.chapterChildren || [];
for (var ck = 0; ck < children.length; ck++) {
var child = children[ck];
var resId = child.resource_id || child.chapter_id || '';
var chType = child.chapter_type || child.resource_type || 0;
var urlPath = {1:'/v1/course/text/',2:'/v2/course/alive/',3:'/v1/course/audio/',4:'/v1/course/video/'}[Number(chType)];
result.push({
ch: cj + 1, chapter: chTitle, no: ck + 1,
title: child.chapter_title || child.resource_title || '',
type: typeLabel(chType),
resource_id: resId,
url: urlPath ? origin + urlPath + resId + '?type=2' : '',
status: child.is_finish === 1 ? '已完成' : (child.learn_progress > 0 ? child.learn_progress + '%' : '未学'),
});
}
}
return result;
})()

- map:
ch: ${{ item.ch }}
chapter: ${{ item.chapter }}
no: ${{ item.no }}
title: ${{ item.title }}
type: ${{ item.type }}
resource_id: ${{ item.resource_id }}
url: ${{ item.url }}
status: ${{ item.status }}

columns: [ch, chapter, no, title, type, resource_id, status]
43 changes: 43 additions & 0 deletions src/clis/xiaoe/content.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
site: xiaoe
name: content
description: 提取小鹅通图文页面内容为文本
domain: h5.xet.citv.cn
strategy: cookie

args:
url:
type: str
required: true
positional: true
description: 页面 URL

pipeline:
- navigate: ${{ args.url }}

- wait: 6

- evaluate: |
(() => {
var selectors = ['.rich-text-wrap','.content-wrap','.article-content','.text-content',
'.course-detail','.detail-content','[class*="richtext"]','[class*="rich-text"]','.ql-editor'];
var content = '';
for (var i = 0; i < selectors.length; i++) {
var el = document.querySelector(selectors[i]);
if (el && el.innerText.trim().length > 50) { content = el.innerText.trim(); break; }
}
if (!content) content = (document.querySelector('main') || document.querySelector('#app') || document.body).innerText.trim();

var images = [];
document.querySelectorAll('img').forEach(function(img) {
if (img.src && !img.src.startsWith('data:') && img.src.includes('xiaoe')) images.push(img.src);
});
return [{
title: document.title,
content: content.substring(0, 10000),
content_length: content.length,
image_count: images.length,
images: JSON.stringify(images.slice(0, 20)),
}];
})()

columns: [title, content_length, image_count]
73 changes: 73 additions & 0 deletions src/clis/xiaoe/courses.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
site: xiaoe
name: courses
description: 列出已购小鹅通课程(含 URL 和店铺名)
domain: study.xiaoe-tech.com
strategy: cookie

pipeline:
- navigate: https://study.xiaoe-tech.com/

- wait: 8

- evaluate: |
(async () => {
// 切换到「内容」tab
var tabs = document.querySelectorAll('span, div');
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].children.length === 0 && tabs[i].textContent.trim() === '内容') {
tabs[i].click();
break;
}
}
await new Promise(function(r) { setTimeout(r, 2000); });

// 匹配课程卡片标题与 Vue 数据
function matchEntry(title, vm, depth) {
if (!vm || depth > 5) return null;
var d = vm.$data || {};
for (var k in d) {
if (!Array.isArray(d[k])) continue;
for (var j = 0; j < d[k].length; j++) {
var e = d[k][j];
if (!e || typeof e !== 'object') continue;
var t = e.title || e.resource_name || '';
if (t && title.includes(t.substring(0, 10))) return e;
}
}
return vm.$parent ? matchEntry(title, vm.$parent, depth + 1) : null;
}

// 构造课程 URL
function buildUrl(entry) {
if (entry.h5_url) return entry.h5_url;
if (entry.url) return entry.url;
if (entry.app_id && entry.resource_id) {
var base = 'https://' + entry.app_id + '.h5.xet.citv.cn';
if (entry.resource_type === 6) return base + '/v1/course/column/' + entry.resource_id + '?type=3';
return base + '/p/course/ecourse/' + entry.resource_id;
}
return '';
}

var cards = document.querySelectorAll('.course-card-list');
var results = [];
for (var c = 0; c < cards.length; c++) {
var titleEl = cards[c].querySelector('.card-title-box');
var title = titleEl ? titleEl.textContent.trim() : '';
if (!title) continue;
var entry = matchEntry(title, cards[c].__vue__, 0);
results.push({
title: title,
shop: entry ? (entry.shop_name || entry.app_name || '') : '',
url: entry ? buildUrl(entry) : '',
});
}
return results;
})()

- map:
title: ${{ item.title }}
shop: ${{ item.shop }}
url: ${{ item.url }}

columns: [title, shop, url]
39 changes: 39 additions & 0 deletions src/clis/xiaoe/detail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
site: xiaoe
name: detail
description: 小鹅通课程详情(名称、价格、学员数、店铺)
domain: h5.xet.citv.cn
strategy: cookie

args:
url:
type: str
required: true
positional: true
description: 课程页面 URL

pipeline:
- navigate: ${{ args.url }}

- wait: 5

- evaluate: |
(() => {
var vm = (document.querySelector('#app') || {}).__vue__;
if (!vm || !vm.$store) return [];
var core = vm.$store.state.coreInfo || {};
var goods = vm.$store.state.goodsInfo || {};
var shop = ((vm.$store.state.compositeInfo || {}).shop_conf) || {};
return [{
name: core.resource_name || '',
resource_id: core.resource_id || '',
resource_type: core.resource_type || '',
cover: core.resource_img || '',
user_count: core.user_count || 0,
price: goods.price ? (goods.price / 100).toFixed(2) : '0',
original_price: goods.line_price ? (goods.line_price / 100).toFixed(2) : '0',
is_free: goods.is_free || 0,
shop_name: shop.shop_name || '',
}];
})()

columns: [name, price, original_price, user_count, shop_name]
Loading