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
108 changes: 108 additions & 0 deletions cli-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17428,6 +17428,53 @@
"sourceFile": "weibo/search.js",
"navigateBefore": "https://weibo.com"
},
{
"site": "weibo",
"name": "statuses",
"description": "Fetch a user's Weibo statuses/timeline",
"domain": "weibo.com",
"strategy": "cookie",
"browser": true,
"args": [
{
"name": "id",
"type": "str",
"required": true,
"positional": true,
"help": "User ID (numeric uid) or screen name"
},
{
"name": "limit",
"type": "int",
"default": 15,
"required": false,
"help": "Number of posts (max 50)"
},
{
"name": "page",
"type": "int",
"default": 1,
"required": false,
"help": "Page number"
}
],
"columns": [
"id",
"mblogid",
"text",
"isLongText",
"created_at",
"reposts",
"comments",
"likes",
"pic_num",
"url"
],
"type": "js",
"modulePath": "weibo/statuses.js",
"sourceFile": "weibo/statuses.js",
"navigateBefore": "https://weibo.com"
},
{
"site": "weibo",
"name": "user",
Expand Down Expand Up @@ -20668,6 +20715,67 @@
"sourceFile": "zhihu/search.js",
"navigateBefore": "https://www.zhihu.com"
},
{
"site": "zlibrary",
"name": "info",
"description": "Get book details and available download formats from a Z-Library book page",
"domain": "z-library.im",
"strategy": "cookie",
"browser": true,
"args": [
{
"name": "url",
"type": "str",
"required": true,
"positional": true,
"help": "Z-Library book page URL (e.g. https://z-library.im/book/...)"
}
],
"columns": [
"title",
"pdf",
"epub",
"url"
],
"type": "js",
"modulePath": "zlibrary/info.js",
"sourceFile": "zlibrary/info.js",
"navigateBefore": false
},
{
"site": "zlibrary",
"name": "search",
"description": "Search Z-Library for books by title, author, ISBN, or keyword",
"domain": "z-library.im",
"strategy": "cookie",
"browser": true,
"args": [
{
"name": "query",
"type": "str",
"required": true,
"positional": true,
"help": "Search keyword (title, author, ISBN, etc.)"
},
{
"name": "limit",
"type": "int",
"default": 10,
"required": false,
"help": "Max results (1–25)"
}
],
"columns": [
"rank",
"title",
"author",
"url"
],
"type": "js",
"modulePath": "zlibrary/search.js",
"sourceFile": "zlibrary/search.js",
"navigateBefore": false
},
{
"site": "zsxq",
"name": "dynamics",
Expand Down
95 changes: 95 additions & 0 deletions clis/weibo/statuses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Weibo statuses — fetch a user's public timeline / blog posts.
*/
import { cli, Strategy } from '@jackwener/opencli/registry';
import { CommandExecutionError } from '@jackwener/opencli/errors';

cli({
site: 'weibo',
name: 'statuses',
description: "Fetch a user's Weibo statuses/timeline",
domain: 'weibo.com',
strategy: Strategy.COOKIE,
args: [
{ name: 'id', required: true, positional: true, help: 'User ID (numeric uid) or screen name' },
{ name: 'limit', type: 'int', default: 15, help: 'Number of posts (max 50)' },
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
],
columns: ['id', 'mblogid', 'text', 'isLongText', 'created_at', 'reposts', 'comments', 'likes', 'pic_num', 'url'],
func: async (page, kwargs) => {
const count = Math.min(kwargs.limit || 15, 50);
const pageNum = kwargs.page || 1;
const id = String(kwargs.id);

await page.goto('https://weibo.com');
await page.wait(2);

// Resolve uid if screen name was provided
const isUid = /^\d+$/.test(id);
let uid = id;
if (!isUid) {
const profileResp = await page.evaluate(`
(async () => {
const resp = await fetch('/ajax/profile/info?screen_name=' + encodeURIComponent(${JSON.stringify(id)}), { credentials: 'include' });
if (!resp.ok) return { error: 'HTTP ' + resp.status };
const data = await resp.json();
if (!data.ok || !data.data?.user) return { error: 'User not found' };
return { uid: data.data.user.id };
})()
`);
if (profileResp.error) {
throw new CommandExecutionError(String(profileResp.error));
}
uid = String(profileResp.uid);
}

const data = await page.evaluate(`
(async () => {
const uid = ${JSON.stringify(uid)};
const count = ${count};
const page = ${pageNum};

const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').trim();

const resp = await fetch('/ajax/statuses/mymblog?uid=' + uid + '&page=' + page + '&feature=0', { credentials: 'include' });
if (!resp.ok) return { error: 'HTTP ' + resp.status };
const data = await resp.json();
if (!data.ok) return { error: 'API error: ' + (data.msg || 'unknown') };

return (data.data?.list || []).slice(0, count).map(s => {
const u = s.user || {};
const item = {
id: s.idstr || '',
mblogid: s.mblogid || '',
text: (s.text_raw || strip(s.text || '')).substring(0, 500),
isLongText: s.isLongText || false,
created_at: s.created_at || '',
reposts: s.reposts_count || 0,
comments: s.comments_count || 0,
likes: s.attitudes_count || 0,
pic_num: s.pic_num || 0,
url: 'https://weibo.com/' + (u.id || '') + '/' + (s.mblogid || ''),
};
if (s.retweeted_status) {
const rt = s.retweeted_status;
item.retweeted = (rt.user?.screen_name || '[deleted]') + ': ' + (rt.text_raw || strip(rt.text || '')).substring(0, 200);
}
if (s.page_info) {
item.page_title = s.page_info.title || '';
item.page_type = s.page_info.type || '';
item.page_url = s.page_info.page_url || '';
}
return item;
});
})()
`);

if (!Array.isArray(data)) {
if (data && data.error) {
throw new CommandExecutionError(String(data.error));
}
return [];
}
return data;
},
});
51 changes: 51 additions & 0 deletions clis/zlibrary/info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
import { CliError } from '@jackwener/opencli/errors';
import { ZLIBRARY_DOMAIN, extractBookTitle, extractFormats } from './utils.js';

cli({
site: 'zlibrary',
name: 'info',
description: 'Get book details and available download formats from a Z-Library book page',
domain: ZLIBRARY_DOMAIN,
strategy: Strategy.COOKIE,
browser: true,
navigateBefore: false,
args: [
{
name: 'url',
positional: true,
required: true,
help: 'Z-Library book page URL (e.g. https://z-library.im/book/...)',
},
],
columns: ['title', 'pdf', 'epub', 'url'],
func: async (page, args) => {
const url = String(args.url || '').trim();
if (!url.startsWith('http')) {
throw new CliError('INVALID_ARG', 'URL must start with http', 'Provide the full Z-Library book page URL');
}

await page.goto(url, { waitUntil: 'load', settleMs: 3000 });
await page.wait({ time: 5 });

const title = await extractBookTitle(page);
const formats = await extractFormats(page);

if (!title || title === 'Unknown') {
throw new CliError(
'NOT_FOUND',
'Could not extract book information',
'Check the URL and that you are logged into Z-Library'
);
}

return [
{
title,
pdf: formats.pdf || '',
epub: formats.epub || '',
url,
},
];
},
});
47 changes: 47 additions & 0 deletions clis/zlibrary/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
import { CliError } from '@jackwener/opencli/errors';
import { ZLIBRARY_DOMAIN, buildSearchUrl, extractSearchResults } from './utils.js';

cli({
site: 'zlibrary',
name: 'search',
description: 'Search Z-Library for books by title, author, ISBN, or keyword',
domain: ZLIBRARY_DOMAIN,
strategy: Strategy.COOKIE,
browser: true,
navigateBefore: false,
args: [
{
name: 'query',
positional: true,
required: true,
help: 'Search keyword (title, author, ISBN, etc.)',
},
{
name: 'limit',
type: 'int',
default: 10,
help: 'Max results (1–25)',
},
],
columns: ['rank', 'title', 'author', 'url'],
func: async (page, args) => {
const limit = Math.max(1, Math.min(Number(args.limit) || 10, 25));
const searchUrl = buildSearchUrl(args.query);

await page.goto(searchUrl, { waitUntil: 'load', settleMs: 3000 });
await page.wait({ time: 5 });

const results = await extractSearchResults(page, limit);

if (!results.length) {
throw new CliError(
'NOT_FOUND',
'No books found',
'Try a different keyword or check that you are logged into Z-Library'
);
}

return results;
},
});
Loading