feat(route): authless instagram (#13920)
* feat(route): authless instagram * docs: prefer instagram gen2 route
This commit is contained in:
parent
52fbf7cdb2
commit
8f236d2759
|
|
@ -1,5 +1,11 @@
|
|||
{{@ summary.replace(/\n/g, '<br>') }}
|
||||
{{ if summary }}
|
||||
{{@ summary.replace(/\n/g, '<br>') }}
|
||||
<br>
|
||||
{{ /if }}
|
||||
|
||||
{{ each images i }}
|
||||
<img src="{{ i.url }}" height="{{ i.height }}" width="{{ i.width }}">
|
||||
<img src="{{ i.url }}"
|
||||
{{ if i.height }} height="{{ i.height }}" {{ /if }}
|
||||
{{ if i.width }} width="{{ i.width }}" {{ /if }}
|
||||
>
|
||||
{{ /each }}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{{@ summary.replace(/\n/g, '<br>') }}
|
||||
{{ if summary }}
|
||||
{{@ summary.replace(/\n/g, '<br>') }}
|
||||
<br>
|
||||
{{ /if }}
|
||||
|
||||
<video controls preload="none" poster="{{ image }}" width="{{ video.width }}">
|
||||
<source src="{{ video.url }}" type="video/mp4">
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
const { CookieJar } = require('tough-cookie');
|
||||
const config = require('@/config').value;
|
||||
const { renderItems } = require('../common-utils');
|
||||
const { baseUrl, COOKIE_URL, getUserInfo, getUserFeedItems, getTagsFeedItems } = require('./utils');
|
||||
const { baseUrl, COOKIE_URL, getUserInfo, getUserFeedItems, getTagsFeedItems, renderGuestItems } = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
if (!config.instagram || !config.instagram.cookie) {
|
||||
throw Error('Instagram RSS is disabled due to the lack of <a href="https://docs.rsshub.app/install/#pei-zhi-bu-fen-rss-mo-kuai-pei-zhi">relevant config</a>');
|
||||
}
|
||||
// if (!config.instagram || !config.instagram.cookie) {
|
||||
// throw Error('Instagram RSS is disabled due to the lack of <a href="https://docs.rsshub.app/install/#pei-zhi-bu-fen-rss-mo-kuai-pei-zhi">relevant config</a>');
|
||||
// }
|
||||
const availableCategories = ['user', 'tags'];
|
||||
const { category, key } = ctx.params;
|
||||
const { cookie } = config.instagram;
|
||||
|
|
@ -16,13 +16,18 @@ module.exports = async (ctx) => {
|
|||
|
||||
let cookieJar = await ctx.cache.get('instagram:cookieJar');
|
||||
const cacheMiss = !cookieJar;
|
||||
if (cacheMiss) {
|
||||
cookieJar = new CookieJar();
|
||||
for await (const c of cookie.split('; ')) {
|
||||
await cookieJar.setCookie(c, COOKIE_URL);
|
||||
|
||||
if (cookie) {
|
||||
if (cacheMiss) {
|
||||
cookieJar = new CookieJar();
|
||||
for await (const c of cookie.split('; ')) {
|
||||
await cookieJar.setCookie(c, COOKIE_URL);
|
||||
}
|
||||
} else {
|
||||
cookieJar = CookieJar.fromJSON(cookieJar);
|
||||
}
|
||||
} else {
|
||||
cookieJar = CookieJar.fromJSON(cookieJar);
|
||||
cookieJar = new CookieJar();
|
||||
}
|
||||
|
||||
let feedTitle, feedLink, feedDescription, feedLogo;
|
||||
|
|
@ -39,7 +44,12 @@ module.exports = async (ctx) => {
|
|||
feedLogo = userInfo.profile_pic_url_hd ?? userInfo.hd_profile_pic_url_info?.url ?? userInfo.profile_pic_url;
|
||||
feedLink = `${baseUrl}/${username}`;
|
||||
|
||||
items = await getUserFeedItems(id, username, cookieJar, ctx.cache.tryGet);
|
||||
if (cookie) {
|
||||
items = await getUserFeedItems(id, username, cookieJar, ctx.cache.tryGet);
|
||||
} else {
|
||||
items = [...userInfo.edge_felix_video_timeline.edges, ...userInfo.edge_owner_to_timeline_media.edges];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'tags': {
|
||||
|
|
@ -61,7 +71,7 @@ module.exports = async (ctx) => {
|
|||
title: feedTitle,
|
||||
link: feedLink,
|
||||
description: feedDescription,
|
||||
item: renderItems(items),
|
||||
item: cookie ? renderItems(items) : renderGuestItems(items),
|
||||
icon: `${baseUrl}/static/images/ico/xxhdpi_launcher.png/99cf3909d459.png`,
|
||||
logo: feedLogo,
|
||||
image: feedLogo,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const config = require('@/config').value;
|
||||
const { art } = require('@/utils/render');
|
||||
const path = require('path');
|
||||
|
||||
const baseUrl = 'https://www.instagram.com';
|
||||
const COOKIE_URL = 'https://instagram.com';
|
||||
|
|
@ -23,23 +26,30 @@ const getUserInfo = async (username, cookieJar, cache) => {
|
|||
let userInfoCache = await cache.get(`instagram:userInfo:${id}`);
|
||||
|
||||
if (!userInfoCache) {
|
||||
const response = await got(`${baseUrl}/api/v1/users/web_profile_info/`, {
|
||||
cookieJar,
|
||||
headers: await getHeaders(cookieJar),
|
||||
searchParams: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
if (response.url.includes('/accounts/login/')) {
|
||||
throw Error('Invalid cookie');
|
||||
try {
|
||||
const response = await got(`${baseUrl}/api/v1/users/web_profile_info/`, {
|
||||
cookieJar,
|
||||
headers: await getHeaders(cookieJar),
|
||||
searchParams: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
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;
|
||||
|
||||
await cache.set(`instagram:getIdByUsername:${username}`, id, 31536000); // 1 year since it will never change
|
||||
await cache.set(`instagram:userInfo:${id}`, webProfileInfo);
|
||||
} catch (e) {
|
||||
if (e.message.includes("Cookie not in this host's domain")) {
|
||||
throw Error('Invalid cookie');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
igWwwClaim = response.headers['x-ig-set-www-claim'] || igWwwClaim;
|
||||
|
||||
webProfileInfo = response.data.data.user;
|
||||
id = webProfileInfo.id;
|
||||
|
||||
await cache.set(`instagram:getIdByUsername:${username}`, id, 31536000); // 1 year since it will never change
|
||||
await cache.set(`instagram:userInfo:${id}`, webProfileInfo);
|
||||
}
|
||||
|
||||
userInfoCache = typeof userInfoCache === 'string' ? JSON.parse(userInfoCache) : userInfoCache;
|
||||
|
|
@ -90,10 +100,71 @@ const getTagsFeedItems = (tag, tab, cookieJar, tryGet) =>
|
|||
false
|
||||
);
|
||||
|
||||
const renderGuestItems = (items) => {
|
||||
const renderVideo = (node, summary) =>
|
||||
art(path.join(__dirname, '../templates/video.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.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) {
|
||||
// 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('');
|
||||
break;
|
||||
case 'GraphVideo':
|
||||
description = renderVideo(node, summary);
|
||||
break;
|
||||
case 'GraphImage':
|
||||
description = renderImages(node, summary);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Instagram: Unhandled feed type: ${type}`);
|
||||
}
|
||||
|
||||
return {
|
||||
title: summary.split('\n')[0],
|
||||
pubDate: parseDate(node.taken_at_timestamp, 'X'),
|
||||
author: node.owner.username,
|
||||
link: `${baseUrl}/p/${node.shortcode}/`,
|
||||
summary,
|
||||
description,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
baseUrl,
|
||||
COOKIE_URL,
|
||||
getUserInfo,
|
||||
getUserFeedItems,
|
||||
getTagsFeedItems,
|
||||
renderGuestItems,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -579,33 +579,35 @@ Type
|
|||
|
||||
## Instagram {#instagram}
|
||||
|
||||
:::warning
|
||||
|
||||
Due to Instagram Private API restrictions, you have to setup your credentials on the server. 2FA is not supported. See [deployment guide](https://docs.rsshub.app/install/) for more.
|
||||
|
||||
If you don't want to setup credentials, you can use [Picnob](#picnob) or [Picuki](#picuki).
|
||||
|
||||
:::
|
||||
|
||||
### User Profile / Hashtag - Private API {#instagram-user-profile-hashtag-private-api}
|
||||
|
||||
<Route author="oppilate DIYgod" example="/instagram/user/stefaniejoosten" path="/instagram/:category/:key" paramsDesc={['Feed category, see table below','Username / Hashtag name']} radar="1" anticrawler="1" selfhost="1">
|
||||
|
||||
| User timeline | Hashtag |
|
||||
| ------------- | ------- |
|
||||
| user | tags |
|
||||
|
||||
:::tip
|
||||
|
||||
It's highly recommended to deploy with Redis cache enabled.
|
||||
|
||||
:::
|
||||
|
||||
</Route>
|
||||
### User Profile / Hashtag {#instagram-user-profile-hashtag-cookie}
|
||||
|
||||
### User Profile / Hashtag - Cookie {#instagram-user-profile-hashtag-cookie}
|
||||
:::tip
|
||||
|
||||
<Route author="TonyRL" example="/instagram/2/user/stefaniejoosten" path="/instagram/2/:category/:key" paramsDesc={['Feed category, see table above','Username / Hashtag name']} radar="1" anticrawler="1" selfhost="1" />
|
||||
You may need to setup cookie for a less restrictive rate limit and private profiles.
|
||||
|
||||
:::
|
||||
|
||||
<Route author="TonyRL" example="/instagram/2/user/stefaniejoosten" path="/instagram/2/:category/:key" paramsDesc={['Feed category, see table below','Username / Hashtag name']} radar="1" anticrawler="1" />
|
||||
|
||||
| User timeline | Hashtag |
|
||||
| ------------- | ------- |
|
||||
| user | tags |
|
||||
|
||||
### User Profile / Hashtag - Private API {#instagram-user-profile-hashtag-private-api}
|
||||
|
||||
:::warning
|
||||
|
||||
Due to [Instagram Private API](https://github.com/dilame/instagram-private-api) restrictions, you have to setup your credentials on the server. 2FA is not supported. See [deployment guide](https://docs.rsshub.app/install/) for more.
|
||||
|
||||
:::
|
||||
|
||||
<Route author="oppilate DIYgod" example="/instagram/user/stefaniejoosten" path="/instagram/:category/:key" paramsDesc={['Feed category, see table above','Username / Hashtag name']} radar="1" anticrawler="1" selfhost="1" />
|
||||
|
||||
## Keep {#keep}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue