fix: allow no-cookie weibo context and adjust insight copy

This commit is contained in:
Jason
2026-04-13 23:58:02 +08:00
parent 2e77d9468a
commit 5da4454af9
3 changed files with 70 additions and 17 deletions

View File

@@ -809,7 +809,7 @@ ${topMentionText}
if (!allowSocialContext) return '' if (!allowSocialContext) return ''
const rawCookie = String(this.config.get('aiInsightWeiboCookie') || '').trim() const rawCookie = String(this.config.get('aiInsightWeiboCookie') || '').trim()
if (!rawCookie) return '' const hasCookie = rawCookie.length > 0
const bindings = const bindings =
(this.config.get('aiInsightWeiboBindings') as Record<string, { uid?: string; screenName?: string }> | undefined) || {} (this.config.get('aiInsightWeiboBindings') as Record<string, { uid?: string; screenName?: string }> | undefined) || {}
@@ -830,7 +830,10 @@ ${topMentionText}
return `[微博 ${time}] ${text}` return `[微博 ${time}] ${text}`
}) })
insightLog('INFO', `已加载 ${lines.length} 条微博公开内容 (uid=${uid})`) insightLog('INFO', `已加载 ${lines.length} 条微博公开内容 (uid=${uid})`)
return `近期公开社交平台内容(实验性,来源:微博,最近 ${lines.length} 条):\n${lines.join('\n')}` const riskHint = hasCookie
? ''
: '\n提示未配置微博 Cookie使用移动端公开接口抓取可能因平台风控导致获取失败或内容较少。'
return `近期公开社交平台内容(来源:微博,最近 ${lines.length} 条):\n${lines.join('\n')}${riskHint}`
} catch (error) { } catch (error) {
insightLog('WARN', `拉取微博公开内容失败 (uid=${uid}): ${(error as Error).message}`) insightLog('WARN', `拉取微博公开内容失败 (uid=${uid}): ${(error as Error).message}`)
return '' return ''

View File

@@ -7,6 +7,8 @@ const WEIBO_MAX_POSTS = 5
const WEIBO_CACHE_TTL_MS = 30 * 60 * 1000 const WEIBO_CACHE_TTL_MS = 30 * 60 * 1000
const WEIBO_USER_AGENT = const WEIBO_USER_AGENT =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
const WEIBO_MOBILE_USER_AGENT =
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1'
interface BrowserCookieEntry { interface BrowserCookieEntry {
domain?: string domain?: string
@@ -48,6 +50,18 @@ interface WeiboStatusShowResponse {
retweeted_status?: WeiboWaterFallItem retweeted_status?: WeiboWaterFallItem
} }
interface MWeiboCard {
mblog?: WeiboWaterFallItem
card_group?: MWeiboCard[]
}
interface MWeiboContainerResponse {
ok?: number
data?: {
cards?: MWeiboCard[]
}
}
export interface WeiboRecentPost { export interface WeiboRecentPost {
id: string id: string
createdAt: string createdAt: string
@@ -61,7 +75,7 @@ interface CachedRecentPosts {
posts: WeiboRecentPost[] posts: WeiboRecentPost[]
} }
function requestJson<T>(url: string, options: { cookie: string; referer?: string }): Promise<T> { function requestJson<T>(url: string, options: { cookie?: string; referer?: string; userAgent?: string }): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let urlObj: URL let urlObj: URL
try { try {
@@ -71,19 +85,23 @@ function requestJson<T>(url: string, options: { cookie: string; referer?: string
return return
} }
const headers: Record<string, string> = {
Accept: 'application/json, text/plain, */*',
Referer: options.referer || 'https://weibo.com',
'User-Agent': options.userAgent || WEIBO_USER_AGENT,
'X-Requested-With': 'XMLHttpRequest'
}
if (options.cookie) {
headers.Cookie = options.cookie
}
const req = https.request( const req = https.request(
{ {
hostname: urlObj.hostname, hostname: urlObj.hostname,
port: urlObj.port || 443, port: urlObj.port || 443,
path: urlObj.pathname + urlObj.search, path: urlObj.pathname + urlObj.search,
method: 'GET', method: 'GET',
headers: { headers
Accept: 'application/json, text/plain, */*',
Referer: options.referer || 'https://weibo.com',
'User-Agent': WEIBO_USER_AGENT,
'X-Requested-With': 'XMLHttpRequest',
Cookie: options.cookie
}
}, },
(res) => { (res) => {
let raw = '' let raw = ''
@@ -232,10 +250,10 @@ class WeiboService {
): Promise<WeiboRecentPost[]> { ): Promise<WeiboRecentPost[]> {
const uid = normalizeWeiboUid(uidInput) const uid = normalizeWeiboUid(uidInput)
const cookie = normalizeWeiboCookieInput(cookieInput) const cookie = normalizeWeiboCookieInput(cookieInput)
if (!cookie) return [] const hasCookie = Boolean(cookie)
const count = Math.max(1, Math.min(WEIBO_MAX_POSTS, Math.floor(Number(requestedCount) || 0))) const count = Math.max(1, Math.min(WEIBO_MAX_POSTS, Math.floor(Number(requestedCount) || 0)))
const cacheKey = buildCacheKey(uid, count, cookie) const cacheKey = buildCacheKey(uid, count, hasCookie ? cookie : '__no_cookie_mobile__')
const cached = this.recentPostsCache.get(cacheKey) const cached = this.recentPostsCache.get(cacheKey)
const now = Date.now() const now = Date.now()
@@ -243,8 +261,9 @@ class WeiboService {
return cached.posts return cached.posts
} }
const timeline = await this.fetchTimeline(uid, cookie) const rawItems = hasCookie
const rawItems = Array.isArray(timeline.data?.list) ? timeline.data.list : [] ? (await this.fetchTimeline(uid, cookie)).data?.list || []
: await this.fetchMobileTimeline(uid)
const posts: WeiboRecentPost[] = [] const posts: WeiboRecentPost[] = []
for (const item of rawItems) { for (const item of rawItems) {
@@ -254,7 +273,7 @@ class WeiboService {
if (!id) continue if (!id) continue
let text = mergeRetweetText(item) let text = mergeRetweetText(item)
if (item.isLongText) { if (item.isLongText && hasCookie) {
try { try {
const detail = await this.fetchDetail(id, cookie) const detail = await this.fetchDetail(id, cookie)
text = mergeRetweetText(detail) text = mergeRetweetText(detail)
@@ -298,6 +317,37 @@ class WeiboService {
}) })
} }
private fetchMobileTimeline(uid: string): Promise<WeiboWaterFallItem[]> {
const containerid = `107603${uid}`
return requestJson<MWeiboContainerResponse>(
`https://m.weibo.cn/api/container/getIndex?type=uid&value=${encodeURIComponent(uid)}&containerid=${encodeURIComponent(containerid)}`,
{
referer: `https://m.weibo.cn/u/${encodeURIComponent(uid)}`,
userAgent: WEIBO_MOBILE_USER_AGENT
}
).then((response) => {
if (response.ok !== 1 || !Array.isArray(response.data?.cards)) {
throw new Error('微博时间线获取失败,请稍后重试')
}
const rows: WeiboWaterFallItem[] = []
for (const card of response.data.cards) {
if (card?.mblog) rows.push(card.mblog)
if (Array.isArray(card?.card_group)) {
for (const subCard of card.card_group) {
if (subCard?.mblog) rows.push(subCard.mblog)
}
}
}
if (rows.length === 0) {
throw new Error('该微博账号暂无可读取的近期公开内容')
}
return rows
})
}
private fetchDetail(id: string, cookie: string): Promise<WeiboStatusShowResponse> { private fetchDetail(id: string, cookie: string): Promise<WeiboStatusShowResponse> {
return requestJson<WeiboStatusShowResponse>( return requestJson<WeiboStatusShowResponse>(
`https://weibo.com/ajax/statuses/show?id=${encodeURIComponent(id)}&isGetLongText=true`, `https://weibo.com/ajax/statuses/show?id=${encodeURIComponent(id)}&isGetLongText=true`,

View File

@@ -3220,7 +3220,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
</div> </div>
{!hasWeiboCookieConfigured && ( {!hasWeiboCookieConfigured && (
<span className="form-hint" style={{ marginTop: 8, display: 'block' }}> <span className="form-hint" style={{ marginTop: 8, display: 'block' }}>
Cookie Cookie
</span> </span>
)} )}
</div> </div>
@@ -4625,7 +4625,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
</button> </button>
<button className="btn btn-primary" onClick={() => { void handleCloseWeiboCookieModal() }} disabled={isSavingWeiboCookie}> <button className="btn btn-primary" onClick={() => { void handleCloseWeiboCookieModal() }} disabled={isSavingWeiboCookie}>
{isSavingWeiboCookie ? '保存中...' : '关闭'} {isSavingWeiboCookie ? '保存中...' : '关闭并保存'}
</button> </button>
</div> </div>
</div> </div>