5.4.4: moved to twitter api v2
This commit is contained in:
parent
2120cf1101
commit
8f93232e81
5 changed files with 70 additions and 25 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "cobalt",
|
||||
"description": "save what you love",
|
||||
"version": "5.4",
|
||||
"version": "5.4.4",
|
||||
"author": "wukko",
|
||||
"exports": "./src/cobalt.js",
|
||||
"type": "module",
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"SettingsAudioFullTikTok": "full audio",
|
||||
"SettingsAudioFullTikTokDescription": "downloads original sound used in the video without any additional changes by the post's author.",
|
||||
"ErrorCantGetID": "i couldn't get the full info from the shortened link. make sure it works or try a full one! if issue persists, {ContactLink}.",
|
||||
"ErrorNoVideosInTweet": "there are no videos or gifs in this tweet, try another one!",
|
||||
"ErrorNoVideosInTweet": "i couldn't find any media content in this tweet. try another one!",
|
||||
"ImagePickerTitle": "pick images to download",
|
||||
"ImagePickerDownloadAudio": "download audio",
|
||||
"ImagePickerExplanationPC": "right click an image to save it.",
|
||||
|
@ -118,6 +118,7 @@
|
|||
"SettingsDubAuto": "auto",
|
||||
"SettingsVimeoPrefer": "vimeo downloads type",
|
||||
"SettingsVimeoPreferDescription": "progressive: direct file link to vimeo's cdn. max quality is 1080p.\ndash: video and audio are merged by {appName} into one file. max quality is 4k.\n\npick \"progressive\" if you want best editor/player/social media compatibility. if progressive download isn't available, dash is used instead.",
|
||||
"ShareURL": "share"
|
||||
"ShareURL": "share",
|
||||
"ErrorTweetUnavailable": "couldn't find anything about this tweet. this could be because its visibility is limited. try another one!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"SettingsAudioFullTikTok": "полное аудио",
|
||||
"SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео. без каких-либо изменений от автора поста.",
|
||||
"ErrorCantGetID": "у меня не получилось достать инфу по этой короткой ссылке. попробуй полную ссылку, а если так и не получится, то {ContactLink}.",
|
||||
"ErrorNoVideosInTweet": "в этом твите нет ни видео, ни гифок. попробуй другой!",
|
||||
"ErrorNoVideosInTweet": "я не смог найти никакого медиа контента в этом твите. попробуй другой!",
|
||||
"ImagePickerTitle": "выбери картинки для скачивания",
|
||||
"ImagePickerDownloadAudio": "скачать звук",
|
||||
"ImagePickerExplanationPC": "нажми правой кнопкой мыши на картинку, чтобы её сохранить.",
|
||||
|
@ -118,6 +118,7 @@
|
|||
"SettingsDubAuto": "авто",
|
||||
"SettingsVimeoPrefer": "тип загрузок с vimeo",
|
||||
"SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: {appName} совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, {appName} скачает \"dash\".",
|
||||
"ShareURL": "поделиться"
|
||||
"ShareURL": "поделиться",
|
||||
"ErrorTweetUnavailable": "не смог найти что-либо об этом твите. возможно его видимость была ограничена. попробуй другой!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,73 @@
|
|||
import { genericUserAgent } from "../../config.js";
|
||||
import crypto from "crypto";
|
||||
|
||||
function bestQuality(arr) {
|
||||
return arr.filter((v) => { if (v["content_type"] === "video/mp4") return true }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"].split("?")[0]
|
||||
}
|
||||
const apiURL = "https://api.twitter.com/1.1"
|
||||
const apiURL = "https://api.twitter.com"
|
||||
|
||||
// TO-DO: move from 1.1 api to graphql
|
||||
export default async function(obj) {
|
||||
let _headers = {
|
||||
"user-agent": genericUserAgent,
|
||||
"authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
|
||||
// ^ no explicit content, but with multi media support
|
||||
"host": "api.twitter.com"
|
||||
"host": "api.twitter.com",
|
||||
"x-twitter-client-language": "en",
|
||||
"x-twitter-active-user": "yes",
|
||||
"Accept-Language": "en"
|
||||
};
|
||||
let req_act = await fetch(`${apiURL}/guest/activate.json`, {
|
||||
let conversationURL = `${apiURL}/2/timeline/conversation/${obj.id}.json?cards_platform=Web-12&tweet_mode=extended&include_cards=1&include_ext_media_availability=true&include_ext_sensitive_media_warning=true&simple_quoted_tweet=true&trim_user=1`;
|
||||
let activateURL = `${apiURL}/1.1/guest/activate.json`;
|
||||
|
||||
let req_act = await fetch(activateURL, {
|
||||
method: "POST",
|
||||
headers: _headers
|
||||
}).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false });
|
||||
if (!req_act) return { error: 'ErrorCouldntFetch' };
|
||||
|
||||
_headers["x-guest-token"] = req_act["guest_token"];
|
||||
let showURL = `${apiURL}/statuses/show/${obj.id}.json?tweet_mode=extended&include_user_entities=0&trim_user=1&include_entities=0&cards_platform=Web-12&include_cards=1`;
|
||||
_headers["cookie"] = [
|
||||
`guest_id_ads=v1%3A${req_act["guest_token"]}`,
|
||||
`guest_id_marketing=v1%3A${req_act["guest_token"]}`,
|
||||
`guest_id=v1%3A${req_act["guest_token"]}`,
|
||||
`ct0=${crypto.randomUUID().replace(/-/g, '')};`
|
||||
].join('; ');
|
||||
|
||||
if (!obj.spaceId) {
|
||||
let req_status = await fetch(showURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch((e) => { return false });
|
||||
if (!req_status) {
|
||||
let conversation = await fetch(conversationURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch((e) => { return false });
|
||||
if (!conversation || !conversation.globalObjects.tweets[obj.id]) {
|
||||
_headers.authorization = "Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw";
|
||||
// ^ explicit content, but no multi media support
|
||||
delete _headers["x-guest-token"]
|
||||
delete _headers["x-guest-token"];
|
||||
delete _headers["cookie"];
|
||||
|
||||
req_act = await fetch(`${apiURL}/guest/activate.json`, {
|
||||
req_act = await fetch(activateURL, {
|
||||
method: "POST",
|
||||
headers: _headers
|
||||
}).then((r) => { return r.status === 200 ? r.json() : false}).catch(() => { return false });
|
||||
if (!req_act) return { error: 'ErrorCouldntFetch' };
|
||||
|
||||
_headers["x-guest-token"] = req_act["guest_token"];
|
||||
req_status = await fetch(showURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false });
|
||||
}
|
||||
if (!req_status) return { error: 'ErrorCouldntFetch' };
|
||||
_headers["x-guest-token"] = req_act["guest_token"]
|
||||
_headers['cookie'] = [
|
||||
`guest_id_ads=v1%3A${req_act["guest_token"]}`,
|
||||
`guest_id_marketing=v1%3A${req_act["guest_token"]}`,
|
||||
`guest_id=v1%3A${req_act["guest_token"]}`,
|
||||
`ct0=${crypto.randomUUID().replace(/-/g, '')};`
|
||||
].join('; ');
|
||||
|
||||
let baseStatus;
|
||||
if (req_status["extended_entities"] && req_status["extended_entities"]["media"]) {
|
||||
baseStatus = req_status["extended_entities"]
|
||||
} else if (req_status["retweeted_status"] && req_status["retweeted_status"]["extended_entities"] && req_status["retweeted_status"]["extended_entities"]["media"]) {
|
||||
baseStatus = req_status["retweeted_status"]["extended_entities"]
|
||||
conversation = await fetch(conversationURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false });
|
||||
}
|
||||
if (!baseStatus) return { error: 'ErrorNoVideosInTweet' };
|
||||
if (!conversation || !conversation.globalObjects.tweets[obj.id]) return { error: 'ErrorTweetUnavailable' };
|
||||
|
||||
let single, multiple = [], media = baseStatus["media"];
|
||||
let baseMedia, baseTweet = conversation.globalObjects.tweets[obj.id];
|
||||
if (baseTweet.retweeted_status_id_str && conversation.globalObjects.tweets[baseTweet.retweeted_status_id_str].extended_entities) {
|
||||
baseMedia = conversation.globalObjects.tweets[baseTweet.retweeted_status_id_str].extended_entities
|
||||
} else if (baseTweet.extended_entities && baseTweet.extended_entities.media) {
|
||||
baseMedia = baseTweet.extended_entities
|
||||
}
|
||||
if (!baseMedia) return { error: 'ErrorNoVideosInTweet' };
|
||||
|
||||
let single, multiple = [], media = baseMedia["media"];
|
||||
media = media.filter((i) => { if (i["type"] === "video" || i["type"] === "animated_gif") return true })
|
||||
if (media.length > 1) {
|
||||
for (let i in media) { multiple.push({type: "video", thumb: media[i]["media_url_https"], url: bestQuality(media[i]["video_info"]["variants"])}) }
|
||||
|
|
|
@ -97,6 +97,30 @@
|
|||
}
|
||||
}, {
|
||||
"name": "retweeted video",
|
||||
"url": "https://twitter.com/winload_exe/status/1639005390854602758",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "redirect"
|
||||
}
|
||||
}, {
|
||||
"name": "retweeted video",
|
||||
"url": "https://twitter.com/winload_exe/status/1639005390854602758",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "redirect"
|
||||
}
|
||||
}, {
|
||||
"name": "age-restricted video",
|
||||
"url": "https://twitter.com/FckyeahCharli/status/1650987582749065220",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "redirect"
|
||||
}
|
||||
}, {
|
||||
"name": "retweeted video, isAudioOnly",
|
||||
"url": "https://twitter.com/winload_exe/status/1633091769482063874",
|
||||
"params": {
|
||||
"aFormat": "mp3",
|
||||
|
|
Loading…
Reference in a new issue