feat(route/instagram): authless tags (#13944)

* feat(route/instagram): authless tags

* fix: wwwClaimV2 condition
This commit is contained in:
Tony
2023-12-04 00:15:57 +08:00
committed by GitHub
parent c06353f580
commit d7449b3a77
4 changed files with 106 additions and 38 deletions
+7 -4
View File
@@ -11,7 +11,10 @@ const renderItems = (items) =>
let description = '';
switch (product_type) {
case 'carousel_container': {
const images = item.carousel_media.map((i) => i.image_versions2.candidates[0]);
const images = item.carousel_media.map((i) => ({
...i.image_versions2.candidates.sort((a, b) => b.width - a.width)[0],
alt: item.accessibility_caption,
}));
description = art(path.join(__dirname, 'templates/images.art'), {
summary,
images,
@@ -22,12 +25,12 @@ const renderItems = (items) =>
case 'igtv':
description = art(path.join(__dirname, 'templates/video.art'), {
summary,
image: item.image_versions2.candidates[0].url,
image: item.image_versions2.candidates.sort((a, b) => b.width - a.width)[0],
video: item.video_versions[0],
});
break;
case 'feed': {
const images = [item.image_versions2.candidates[0]];
const images = [{ ...item.image_versions2.candidates.sort((a, b) => b.width - a.width)[0], alt: item.accessibility_caption }];
description = art(path.join(__dirname, 'templates/images.art'), {
summary,
images,
@@ -40,7 +43,7 @@ const renderItems = (items) =>
// Metadata
const url = `https://www.instagram.com/p/${item.code}/`;
const pubDate = parseDate(item.taken_at, 'X');
const pubDate = parseDate(item.caption.created_at_utc || item.taken_at, 'X');
const title = summary.split('\n')[0];
return {
+1
View File
@@ -7,5 +7,6 @@
<img src="{{ i.url }}"
{{ if i.height }} height="{{ i.height }}" {{ /if }}
{{ if i.width }} width="{{ i.width }}" {{ /if }}
{{ if i.alt }} alt="{{ i.alt }}" {{ /if }}
>
{{ /each }}
+20 -9
View File
@@ -1,7 +1,7 @@
const { CookieJar } = require('tough-cookie');
const config = require('@/config').value;
const { renderItems } = require('../common-utils');
const { baseUrl, COOKIE_URL, getUserInfo, getUserFeedItems, getTagsFeedItems, renderGuestItems } = require('./utils');
const { baseUrl, COOKIE_URL, checkLogin, getUserInfo, getUserFeedItems, getTagsFeedItems, getLoggedOutTagsFeedItems, renderGuestItems } = require('./utils');
module.exports = async (ctx) => {
// if (!config.instagram || !config.instagram.cookie) {
@@ -15,19 +15,24 @@ module.exports = async (ctx) => {
}
let cookieJar = await ctx.cache.get('instagram:cookieJar');
const wwwClaimV2 = await ctx.cache.get('instagram:wwwClaimV2');
const cacheMiss = !cookieJar;
if (cookie) {
if (cacheMiss) {
cookieJar = new CookieJar();
if (cacheMiss) {
cookieJar = new CookieJar();
if (cookie) {
for await (const c of cookie.split('; ')) {
await cookieJar.setCookie(c, COOKIE_URL);
}
} else {
cookieJar = CookieJar.fromJSON(cookieJar);
}
} else {
cookieJar = new CookieJar();
cookieJar = CookieJar.fromJSON(cookieJar);
}
if (!wwwClaimV2 && cookie) {
if (!(await checkLogin(cookieJar, ctx.cache))) {
throw Error('Invalid cookie');
}
}
let feedTitle, feedLink, feedDescription, feedLogo;
@@ -45,7 +50,7 @@ module.exports = async (ctx) => {
feedLink = `${baseUrl}/${username}`;
if (cookie) {
items = await getUserFeedItems(id, username, cookieJar, ctx.cache.tryGet);
items = await getUserFeedItems(id, username, cookieJar, ctx.cache);
} else {
items = [...userInfo.edge_felix_video_timeline.edges, ...userInfo.edge_owner_to_timeline_media.edges];
}
@@ -58,7 +63,13 @@ module.exports = async (ctx) => {
feedTitle = `#${tag} - Instagram`;
feedLink = `${baseUrl}/explore/tags/${tag}`;
items = await getTagsFeedItems(tag, 'recent', cookieJar, ctx.cache.tryGet);
if (cookie) {
items = await getTagsFeedItems(tag, 'recent', cookieJar, ctx.cache.tryGet);
} else {
const hashTag = await getLoggedOutTagsFeedItems(tag, cookieJar);
items = [...hashTag.edge_hashtag_to_media.edges, ...hashTag.edge_hashtag_to_top_posts.edges];
}
break;
}
default:
+78 -25
View File
@@ -6,7 +6,6 @@ const path = require('path');
const baseUrl = 'https://www.instagram.com';
const COOKIE_URL = 'https://instagram.com';
let igWwwClaim;
const getCSRFTokenFromJar = async (cookieJar) => {
const cookieString = await cookieJar.getCookieString(COOKIE_URL);
@@ -17,9 +16,28 @@ const getHeaders = async (cookieJar) => ({
'X-ASBD-ID': 198387,
'X-CSRFToken': await getCSRFTokenFromJar(cookieJar),
'X-IG-App-ID': 936619743392459,
'X-IG-WWW-Claim': igWwwClaim,
'X-IG-WWW-Claim': undefined,
});
const checkLogin = async (cookieJar, cache) => {
const response = await got.post(`${baseUrl}/api/v1/web/fxcal/ig_sso_users/`, {
// cookieJar,
headers: {
cookie: await cookieJar.getCookieString(COOKIE_URL),
...(await getHeaders(cookieJar)),
'X-IG-WWW-Claim': '0',
},
});
const wwwClaimV2 = response.headers['x-ig-set-www-claim'];
if (wwwClaimV2) {
cache.set('instagram:wwwClaimV2', wwwClaimV2);
}
return Boolean(response.data.status === 'ok');
};
const getUserInfo = async (username, cookieJar, cache) => {
let webProfileInfo;
let id = await cache.get(`instagram:getIdByUsername:${username}`);
@@ -29,7 +47,10 @@ const getUserInfo = async (username, cookieJar, cache) => {
try {
const response = await got(`${baseUrl}/api/v1/users/web_profile_info/`, {
cookieJar,
headers: await getHeaders(cookieJar),
headers: {
...(await getHeaders(cookieJar)),
'X-IG-WWW-Claim': await cache.get('instagram:wwwClaimV2'),
},
searchParams: {
username,
},
@@ -37,7 +58,6 @@ const getUserInfo = async (username, cookieJar, cache) => {
if (response.url.includes('/accounts/login/')) {
throw Error('Invalid cookie');
}
igWwwClaim = response.headers['x-ig-set-www-claim'] || igWwwClaim;
webProfileInfo = response.data.data.user;
id = webProfileInfo.id;
@@ -57,19 +77,26 @@ const getUserInfo = async (username, cookieJar, cache) => {
return userInfoCache || webProfileInfo;
};
const getUserFeedItems = (id, username, cookieJar, tryGet) =>
tryGet(
const getUserFeedItems = (id, username, cookieJar, cache) =>
cache.tryGet(
`instagram:feed:${id}`,
async () => {
const response = await got(`${baseUrl}/api/v1/feed/user/${username}/username/`, {
cookieJar,
headers: await getHeaders(cookieJar),
// cookieJar,
headers: {
cookie: await cookieJar.getCookieString(COOKIE_URL),
...(await getHeaders(cookieJar)),
// 401 Unauthorized if cookie does not match with IP
'X-IG-WWW-Claim': await cache.get('instagram:wwwClaimV2'),
},
searchParams: {
count: 30,
},
});
// 401 Unauthorized if cookie does not match with IP
igWwwClaim = response.headers['x-ig-set-www-claim'] || igWwwClaim;
if (response.url.includes('/accounts/login/')) {
throw Error(`Invalid cookie.
Please also check if your account is being blocked by Instagram.`);
}
return response.data.items;
},
@@ -91,8 +118,6 @@ const getTagsFeedItems = (tag, tab, cookieJar, tryGet) =>
tag_name: tag,
},
});
// Looks like cookie IP check is not applied to tags
igWwwClaim = response.headers['x-ig-set-www-claim'] || igWwwClaim;
return response.data.data[tab].sections.flatMap((section) => section.layout_content.medias.map((media) => media.media));
},
@@ -100,6 +125,29 @@ const getTagsFeedItems = (tag, tab, cookieJar, tryGet) =>
false
);
const getLoggedOutTagsFeedItems = async (tag, cookieJar) => {
const response = await got(`${baseUrl}/api/v1/tags/logged_out_web_info/`, {
// cookieJar, cookieJar is behaving weirdly here, so we use cookie header instead
headers: {
cookie: await cookieJar.getCookieString(COOKIE_URL),
...(await getHeaders(cookieJar)),
'X-IG-WWW-Claim': '0',
},
searchParams: {
tag_name: tag,
},
});
const cookies = response.headers['set-cookie'];
if (cookies) {
for await (const cookie of cookies) {
await cookieJar.setCookie(cookie, COOKIE_URL);
}
}
return response.data.data.hashtag;
};
const renderGuestItems = (items) => {
const renderVideo = (node, summary) =>
art(path.join(__dirname, '../templates/video.art'), {
@@ -125,19 +173,21 @@ const renderGuestItems = (items) => {
switch (type) {
// carousel, can include GraphVideo and GraphImage
case 'GraphSidecar':
description = node.edge_sidecar_to_children.edges
.map(({ node }, i) => {
const _type = node.__typename;
switch (_type) {
case 'GraphVideo':
return renderVideo(node, i === 0 ? summary : '');
case 'GraphImage':
return renderImages(node, i === 0 ? summary : '');
default:
throw Error(`Instagram: Unhandled carousel type: ${_type}`);
}
})
.join('');
description = node.edge_sidecar_to_children
? node.edge_sidecar_to_children.edges
.map(({ node }, i) => {
const _type = node.__typename;
switch (_type) {
case 'GraphVideo':
return renderVideo(node, i === 0 ? summary : '');
case 'GraphImage':
return renderImages(node, i === 0 ? summary : '');
default:
throw Error(`Instagram: Unhandled carousel type: ${_type}`);
}
})
.join('')
: renderImages(node, summary);
break;
case 'GraphVideo':
description = renderVideo(node, summary);
@@ -151,6 +201,7 @@ const renderGuestItems = (items) => {
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}/`,
@@ -163,8 +214,10 @@ const renderGuestItems = (items) => {
module.exports = {
baseUrl,
COOKIE_URL,
checkLogin,
getUserInfo,
getUserFeedItems,
getTagsFeedItems,
getLoggedOutTagsFeedItems,
renderGuestItems,
};