mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
c63bad34b0
* feat(twitter): add lists command to retrieve user lists Add twitter/lists command that fetches Twitter/X lists for a user. Supports: - Lists with member and follower counts - Private/public mode detection - Default to current user if no user specified - Works for any Twitter user * docs: add lists command to twitter commands in README Add twitter lists command to Built-in Commands table in both English and Chinese README files * fix(twitter): parse lists from card DOM instead of locale-specific page text --------- Co-authored-by: isanwenyu <isanwenyu@users.noreply.github.com> Co-authored-by: jackwener <jakevingoo@gmail.com>
78 lines
2.4 KiB
JavaScript
78 lines
2.4 KiB
JavaScript
const MEMBER_PATTERNS = [
|
|
/([\d.,]+(?:\s?[KMB千萬万亿])?)\s*members?/i,
|
|
/([\d.,]+(?:\s?[KMB千萬万亿])?)\s*位成员/,
|
|
];
|
|
const FOLLOWER_PATTERNS = [
|
|
/([\d.,]+(?:\s?[KMB千萬万亿])?)\s*followers?/i,
|
|
/([\d.,]+(?:\s?[KMB千萬万亿])?)\s*位关注者/,
|
|
];
|
|
const PRIVATE_PATTERNS = [/\bprivate\b/i, /锁定列表/];
|
|
const EMPTY_STATE_PATTERNS = [
|
|
/hasn't created any lists/i,
|
|
/has not created any lists/i,
|
|
/no lists yet/i,
|
|
/没有创建任何列表/,
|
|
/还没有创建任何列表/,
|
|
];
|
|
function normalizeText(text) {
|
|
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
}
|
|
function matchMetric(text, patterns) {
|
|
for (const pattern of patterns) {
|
|
const match = text.match(pattern);
|
|
if (match)
|
|
return normalizeText(match[1]);
|
|
}
|
|
return '0';
|
|
}
|
|
function looksLikeMetadata(line) {
|
|
const text = normalizeText(line);
|
|
if (!text)
|
|
return true;
|
|
if (text.startsWith('@'))
|
|
return true;
|
|
if (MEMBER_PATTERNS.some((pattern) => pattern.test(text)))
|
|
return true;
|
|
if (FOLLOWER_PATTERNS.some((pattern) => pattern.test(text)))
|
|
return true;
|
|
if (PRIVATE_PATTERNS.some((pattern) => pattern.test(text)))
|
|
return true;
|
|
if (/^(public|pinned)$/i.test(text))
|
|
return true;
|
|
if (/^(lists?|你的列表)$/i.test(text))
|
|
return true;
|
|
return false;
|
|
}
|
|
export function parseListCards(cards) {
|
|
const seen = new Set();
|
|
const results = [];
|
|
for (const card of cards || []) {
|
|
const href = normalizeText(card?.href);
|
|
const rawText = String(card?.text || '');
|
|
if (!href || seen.has(href))
|
|
continue;
|
|
seen.add(href);
|
|
const text = normalizeText(rawText);
|
|
if (!text)
|
|
continue;
|
|
const lines = rawText
|
|
.split('\n')
|
|
.map((line) => normalizeText(line))
|
|
.filter(Boolean);
|
|
const name = lines.find((line) => !looksLikeMetadata(line));
|
|
if (!name)
|
|
continue;
|
|
results.push({
|
|
name,
|
|
members: matchMetric(text, MEMBER_PATTERNS),
|
|
followers: matchMetric(text, FOLLOWER_PATTERNS),
|
|
mode: PRIVATE_PATTERNS.some((pattern) => pattern.test(text)) ? 'private' : 'public',
|
|
});
|
|
}
|
|
return results;
|
|
}
|
|
export function isEmptyListsState(text) {
|
|
const normalized = normalizeText(text);
|
|
return EMPTY_STATE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
}
|