rsshub
Version:
Make RSS Great Again!
223 lines (218 loc) • 8.58 kB
JavaScript
import { n as init_esm_shims, t as __dirname } from "./esm-shims-CzJ_djXG.mjs";
import { t as config } from "./config-C37vj7VH.mjs";
import "./dist-BInvbO1W.mjs";
import "./logger-Czu8UMNd.mjs";
import { t as ofetch_default } from "./ofetch-BIyrKU3Y.mjs";
import { t as parseDate } from "./parse-date-BrP7mxXf.mjs";
import { t as cache_default } from "./cache-Bo__VnGm.mjs";
import { t as art } from "./render-BQo6B4tL.mjs";
import { t as invalid_parameter_default } from "./invalid-parameter-rr4AgGpp.mjs";
import { t as config_not_found_default } from "./config-not-found-Dyp3RlZZ.mjs";
import { t as renderItems } from "./common-utils-Cjv924V2.mjs";
import path from "node:path";
import { CookieJar } from "tough-cookie";
//#region lib/routes/instagram/web-api/utils.ts
init_esm_shims();
const baseUrl = "https://www.instagram.com";
const COOKIE_URL = baseUrl;
const getCSRFTokenFromJar = async (cookieJar) => {
return (await cookieJar.getCookieString(COOKIE_URL)).match(/csrftoken=([^;]+)/)?.[1];
};
const getHeaders = async (cookieJar) => ({
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-asbd-id": 359341,
"x-csrftoken": await getCSRFTokenFromJar(cookieJar),
"x-ig-app-id": 936619743392459,
"x-ig-www-claim": "0"
});
const checkLogin = async (cookieJar) => {
const response = await ofetch_default(`${baseUrl}/api/v1/web/fxcal/ig_sso_users/`, {
headers: {
"content-type": "application/x-www-form-urlencoded",
cookie: await cookieJar.getCookieString(COOKIE_URL),
...await getHeaders(cookieJar)
},
method: "POST"
});
return Boolean(response.status === "ok");
};
const getUserInfo = async (username, cookieJar) => {
let webProfileInfo;
let id = await cache_default.get(`instagram:getIdByUsername:${username}`);
let userInfoCache = await cache_default.get(`instagram:userInfo:${id}`);
userInfoCache = userInfoCache && typeof userInfoCache === "string" ? JSON.parse(userInfoCache) : userInfoCache;
if (!userInfoCache) try {
const response = await ofetch_default.raw(`${baseUrl}/api/v1/users/web_profile_info/`, {
headers: {
cookie: await cookieJar.getCookieString(COOKIE_URL),
...await getHeaders(cookieJar)
},
query: { username }
});
if (response.url.includes("/accounts/login/")) throw new config_not_found_default("Invalid cookie");
webProfileInfo = response._data.data.user;
id = webProfileInfo.id;
await cache_default.set(`instagram:getIdByUsername:${username}`, id, 31536e3);
await cache_default.set(`instagram:userInfo:${id}`, webProfileInfo);
} catch (error) {
if (error.message.includes("Cookie not in this host's domain")) throw new config_not_found_default("Invalid cookie");
throw error;
}
return userInfoCache || webProfileInfo;
};
const getUserFeedItems = (id, username, cookieJar) => cache_default.tryGet(`instagram:feed:${id}`, async () => {
const response = await ofetch_default.raw(`${baseUrl}/api/v1/feed/user/${username}/username/`, {
headers: {
cookie: await cookieJar.getCookieString(COOKIE_URL),
...await getHeaders(cookieJar)
},
query: { count: 30 }
});
if (response.url.includes("/accounts/login/")) throw new config_not_found_default(`Invalid cookie.
Please also check if your account is being blocked by Instagram.`);
return response._data.items;
}, config.cache.routeExpire, false);
const getTagsFeed = (tag, cookieJar) => cache_default.tryGet(`instagram:tags:${tag}`, async () => {
return (await ofetch_default(`${baseUrl}/api/v1/tags/web_info/`, {
headers: {
cookie: await cookieJar.getCookieString(COOKIE_URL),
...await getHeaders(cookieJar)
},
query: { tag_name: tag }
})).data;
}, config.cache.routeExpire, false);
const renderGuestItems = (items) => {
const renderVideo = (node, summary) => art(path.join(__dirname, "templates/video-fb8df18d.art"), {
summary,
image: node.display_url,
video: {
url: node.video_url,
height: node.dimensions.height,
width: node.dimensions.width
}
});
const renderImages = (node, summary) => art(path.join(__dirname, "templates/images-105c7ff0.art"), {
summary,
images: [{
url: node.display_url,
height: node.dimensions.height,
width: node.dimensions.width
}]
});
return items.map(({ node }) => {
const type = node.__typename;
const summary = node.edge_media_to_caption.edges[0]?.node.text ?? "";
let description = "";
switch (type) {
case "GraphSidecar":
description = node.edge_sidecar_to_children ? node.edge_sidecar_to_children.edges.map(({ node: node$1 }, i) => {
const _type = node$1.__typename;
switch (_type) {
case "GraphVideo": return renderVideo(node$1, i === 0 ? summary : "");
case "GraphImage": return renderImages(node$1, i === 0 ? summary : "");
default: throw new Error(`Instagram: Unhandled carousel type: ${_type}`);
}
}).join("") : renderImages(node, summary);
break;
case "GraphVideo":
description = renderVideo(node, summary);
break;
case "GraphImage":
description = renderImages(node, summary);
break;
default: throw new Error(`Instagram: Unhandled feed type: ${type}`);
}
return {
title: summary.split("\n")[0],
id: node.id,
pubDate: parseDate(node.taken_at_timestamp, "X"),
author: node.owner.username,
link: `${baseUrl}/p/${node.shortcode}/`,
summary,
description
};
});
};
//#endregion
//#region lib/routes/instagram/web-api/index.ts
const route = {
path: "/2/:category/:key",
categories: ["social-media"],
example: "/instagram/2/user/stefaniejoosten",
parameters: {
category: "Feed category, see table below",
key: "Username / Hashtag name"
},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: true,
supportBT: false,
supportPodcast: false,
supportScihub: false
},
name: "User Profile / Hashtag",
maintainers: ["TonyRL"],
handler,
description: `::: tip
You may need to setup cookie for a less restrictive rate limit and private profiles.
:::
| User timeline | Hashtag |
| ------------- | ------- |
| user | tags |`
};
async function handler(ctx) {
const availableCategories = ["user", "tags"];
const { category, key } = ctx.req.param();
const { cookie } = config.instagram;
if (!availableCategories.includes(category)) throw new invalid_parameter_default("Such feed is not supported.");
let cookieJar = await cache_default.get("instagram:cookieJar");
if (!cookieJar) {
cookieJar = new CookieJar();
if (cookie) for await (const c of cookie.split("; ")) await cookieJar.setCookie(c, COOKIE_URL);
} else cookieJar = CookieJar.fromJSON(cookieJar);
if (cookie && !await checkLogin(cookieJar)) throw new config_not_found_default("Invalid cookie");
let feedTitle, feedLink, feedDescription, feedLogo;
let items;
switch (category) {
case "user": {
const userInfo = await getUserInfo(key, cookieJar);
const biography = userInfo.biography;
const fullName = userInfo.full_name;
const id = userInfo.id;
const username = userInfo.username;
feedTitle = `${fullName} (@${username}) - Instagram`;
feedDescription = biography;
feedLogo = userInfo.profile_pic_url_hd ?? userInfo.hd_profile_pic_url_info?.url ?? userInfo.profile_pic_url;
feedLink = `${baseUrl}/${username}`;
items = cookie ? await getUserFeedItems(id, username, cookieJar) : [...userInfo.edge_felix_video_timeline.edges, ...userInfo.edge_owner_to_timeline_media.edges];
break;
}
case "tags": {
if (!config.instagram || !config.instagram.cookie) throw new config_not_found_default("Instagram RSS is disabled due to the lack of <a href=\"https://docs.rsshub.app/deploy/config#route-specific-configurations\">relevant config</a>");
const tag = key;
feedTitle = `#${tag} - Instagram`;
feedLink = `${baseUrl}/explore/search/keyword/?q=%23${tag}`;
const feedData = await getTagsFeed(tag, cookieJar);
feedLogo = feedData.profile_pic_url;
items = feedData.top.sections.flatMap((section) => (section.feed_type === "media" ? section.layout_content.medias : [...section.layout_content.one_by_two_item.clips.items, ...section.layout_content.fill_items]).map((item) => item.media));
break;
}
default: break;
}
await cache_default.set("instagram:cookieJar", cookieJar.toJSON(), 31536e3);
return {
title: feedTitle,
link: feedLink,
description: feedDescription,
item: cookie ? renderItems(items) : renderGuestItems(items),
icon: `${baseUrl}/static/images/ico/xxhdpi_launcher.png/99cf3909d459.png`,
logo: feedLogo,
image: feedLogo,
allowEmpty: true
};
}
//#endregion
export { route };