@hkai-ai/weibo-api
Version:
香港人工智能协会封装的新浪微博API
212 lines (199 loc) • 8.76 kB
JavaScript
import { parseTabInfoFromInitResponseBody, extractUserWeiboContainerId, extractCardInfosFromWeiboResponseBody, parseCardInfosToWeiboInfos } from "./parser.js";
import { Effect, Option } from "effect";
import { ofetch } from "ofetch";
import { fetchBlogFullContentEffect } from "./tweet/blogContent.js";
/**
* 匹配 fetch 错误
* @param e
* @returns
*/
export const matchFetchError = e => {
if (e instanceof TypeError) {
return new Error(`fetch错误 - TypeError -网络故障或CORS配置错误: ${e.message}`);
}
if (e instanceof DOMException && e.name === 'AbortError') {
return new Error('fetch 错误- AbortError - 请求被中止;可见 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch#aborterror');
}
if (e instanceof DOMException && e.name === 'NotAllowedError') {
return new Error('fetch 错误- NotAllowedError - 请求被拒绝;可见 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch#notallowederror');
}
return new Error(`fetch 时发生错误 - 未知的错误: ${String(e)}`);
};
export const isWeiboFetchSuccess = res => res.ok === 1;
/**
* 返回用于获取用户的 containerId 的 fetch
* @param uid
* @returns
*/
const weiboUserContainerIdFetchEffect = (uid, containerId) => Effect.tryPromise({
try: async () => ofetch(`https://m.weibo.cn/api/container/getIndex`, {
query: {
type: 'uid',
value: uid,
containerid: containerId
},
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Referer': `https://m.weibo.cn/u/${uid}`
}
}),
catch: matchFetchError
});
export const weiboUserContainerIdFetch = (uid, containerId) => Effect.runPromise(weiboUserContainerIdFetchEffect(uid, containerId));
/**
* 返回用于获取用户的最新微博的 fetch effect
* @param uid
* @returns
*/
export const weiboUserLatestWeiboFetchEffect = uid => containerId => Effect.tryPromise({
try: () => ofetch(`https://m.weibo.cn/api/container/getIndex`, {
query: {
type: 'uid',
value: uid,
containerid: containerId
},
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Referer': `https://m.weibo.cn/u/${uid}`
}
}),
catch: matchFetchError
});
/**
* 获取用户的最新微博
* @param uid
* @returns
*/
export const weiboUserLatestWeiboFetch = uid => containerId => Effect.runPromise(weiboUserLatestWeiboFetchEffect(uid)(containerId));
export const sampleFetchWeiboUserContainerData = () => weiboUserContainerIdFetchEffect('5576168164', '1078035576168164');
export const fetchUserLatestWeiboByUIDEffect = uid => Effect.gen(function* () {
// 获取原始的卡片数据
const containerIdOne = yield* fetchWeiboUserLfidEffect(uid);
if (!containerIdOne) {
return yield* Effect.fail(new Error('获取 lfid 失败'));
}
const cardInfosOption = yield* weiboUserContainerIdFetchEffect(uid, containerIdOne);
const isSuccess = isWeiboFetchSuccess(cardInfosOption);
if (!isSuccess) {
return yield* Effect.fail(new Error('获取用户最新微博失败,因为新浪微博接口返回的 ok 状态码不为 1'));
}
const parsedTabInfo = parseTabInfoFromInitResponseBody(cardInfosOption);
const extractedResult = extractUserWeiboContainerId(parsedTabInfo);
if (Option.isNone(extractedResult)) {
return yield* Effect.fail(new Error('获取用户最新微博失败,因为新浪微博接口返回的 ok 状态码不为 1'));
}
const containerId = extractedResult.value;
const weiboResponse = yield* weiboUserLatestWeiboFetchEffect(uid)(containerId);
const cardInfos = extractCardInfosFromWeiboResponseBody(weiboResponse);
const result = parseCardInfosToWeiboInfos(Option.getOrElse(cardInfos, () => []));
return result;
});
/**
* 根据用户的 UUID 获取用户的最新十条解析后的博文(一般是10条,但不保证,会随着接口变动而更改)
* 请注意,该函数获得的博文 text 字段不一定是博文的原文。过长的原文会被截断。
* @param uid
* @returns {Promise<WeiboInfo[]>} 返回解析的 WeiboInfo
*/
export const fetchUserLatestWeiboByUID = uid => Effect.runPromise(fetchUserLatestWeiboByUIDEffect(uid));
const fetchUserLatestWeiboByUIDWithoutParsedEffect = uid => Effect.gen(function* () {
const containerIdOne = yield* fetchWeiboUserLfidEffect(uid);
if (!containerIdOne) {
return yield* Effect.fail(new Error('获取 lfid 失败'));
}
const initResponse = yield* weiboUserContainerIdFetchEffect(uid, containerIdOne);
if (!isWeiboFetchSuccess(initResponse)) {
return yield* Effect.fail(new Error('获取用户最新微博失败,因为新浪微博接口返回的 ok 状态码不为 1,这个状态码是正常获取下应该具备的状态码'));
}
const tabInfo = parseTabInfoFromInitResponseBody(initResponse);
const containerIdOption = extractUserWeiboContainerId(tabInfo);
const containerId = Option.getOrNull(containerIdOption);
if (containerId === null) {
return yield* Effect.fail(new Error('获取 containerId 失败'));
}
const weiboResponse = yield* weiboUserLatestWeiboFetchEffect(uid)(containerId);
const optionResult = extractCardInfosFromWeiboResponseBody(weiboResponse);
return Option.getOrNull(optionResult);
});
/**
* 获取用户的最新十条博文的卡片数据(一般是10条,但不保证,会随着接口变动而更改),但是不进行解析
* @param uid
* @returns
*/
export const fetchUserLatestWeiboByUIDWithoutParsed = uid => Effect.runPromise(fetchUserLatestWeiboByUIDWithoutParsedEffect(uid));
/**
* 获取用户的最新十条博文,并确保博文内容是完整的
* @param uid
* @returns
*/
export const fetchUserLatestWeiboByUIDEnsureFullTexted = uid => Effect.runPromise(Effect.gen(function* () {
const weiboInfos = yield* fetchUserLatestWeiboByUIDEffect(uid);
const updatedWeibos = yield* Effect.all(weiboInfos.map(signleWeiboInfo => Effect.gen(function* () {
const blogId = signleWeiboInfo.weiboId;
const fullContent = yield* fetchBlogFullContentEffect(blogId);
if (fullContent === null) {
return yield* Effect.fail(new Error('获取博文全文失败'));
}
signleWeiboInfo.text = fullContent.thePostText;
if (fullContent.theRetweetedText !== undefined) {
if ('retweetedWeibo' in signleWeiboInfo && signleWeiboInfo.retweetedWeibo) {
signleWeiboInfo.retweetedWeibo.text = fullContent.theRetweetedText;
}
}
return signleWeiboInfo;
})));
return updatedWeibos;
}));
// const d = await fetchUserLatestWeiboByUIDEnsureFullTexted('7797535872')
/**
* 获取微博用户页面的 lfid cookie
* @param uid 用户ID
* @returns Promise<string | null> 返回 lfid 值,如果获取失败则返回 null
*/
export const fetchWeiboUserLfidEffect = uid => Effect.tryPromise({
try: async () => {
console.log(`正在获取用户 ${uid} 的 lfid...`);
const response = await fetch(`https://m.weibo.cn/u/${uid}`, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
});
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
}
const cookies = response.headers.get('set-cookie');
console.log('获取到的 cookies:', cookies);
if (!cookies) {
throw new Error('未获取到 cookies');
}
// 查找 M_WEIBOCN_PARAMS cookie
const paramsMatch = cookies.match(/M_WEIBOCN_PARAMS=([^;]+)/);
if (!paramsMatch) {
throw new Error('未找到 M_WEIBOCN_PARAMS cookie');
}
// 解码参数
const params = decodeURIComponent(paramsMatch[1]);
console.log('解码后的参数:', params);
// 从参数中提取 fid
const fidMatch = params.match(/fid=(\d+)/);
if (!fidMatch) {
throw new Error('未找到 fid 参数');
}
const lfid = fidMatch[1];
console.log('成功获取 lfid:', lfid);
return lfid;
},
catch: error => {
console.error('获取 lfid 失败:', error);
return matchFetchError(error);
}
});
/**
* 获取微博用户页面的 lfid cookie(Promise版本)
* @param uid 用户ID
* @returns Promise<string | null>
*/
export const fetchWeiboUserLfid = uid => Effect.runPromise(fetchWeiboUserLfidEffect(uid));