multi media tweets support

This commit is contained in:
wukko 2022-10-09 23:44:00 +06:00
parent 97cd8c6a11
commit 2c79ae3807
17 changed files with 280 additions and 145 deletions

View file

@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
"version": "3.6.3",
"version": "3.7",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",

View file

@ -66,7 +66,7 @@ a {
::placeholder {
color: var(--accent-unhover-2);
}
::-webkit-scrollbar {
.switches::-webkit-scrollbar, #popup-content::-webkit-scrollbar {
display: none;
}
:focus-visible {
@ -128,7 +128,7 @@ button:active,
cursor: pointer;
transform: scale(0.95)
}
.imagepicker-image:active {
.picker-image:active {
cursor: pointer;
transform: scale(0.95)
}
@ -422,7 +422,7 @@ input[type="checkbox"] {
margin-top: 0.5rem;
}
.explanation {
padding-top: 1rem;
margin-top: 1rem;
width: 100%;
font-size: 0.8rem;
text-align: left;
@ -503,29 +503,54 @@ input[type="checkbox"] {
.button:active .tooltip {
display: none;
}
.imagepicker-image {
.picker-image {
object-fit: cover;
width: inherit;
height: inherit;
}
.imagepicker-image-container {
.picker-image-container {
width: 8rem;
height: 8rem;
margin-bottom: 1rem;
background-color: var(--accent-button-bg);
text-align: center;
letter-spacing: -0.2rem;
line-height: 8rem;
}
#imagepicker-holder {
.picker-various-container {
height: 20rem;
width: 25rem;
margin-bottom: 1rem;
background-color: var(--accent-button-bg);
position: relative;
}
#picker-holder {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-content: space-around;
}
#popup-imagePicker .explanation {
padding-top: 0!important;
padding-bottom: 1rem;
#picker-holder.various {
justify-content: left;
flex-wrap: unset;
overflow-x: scroll;
gap: 2rem;
}
.imageBlock {
height: 100%;
width: 100%;
position: absolute;
z-index: 9999;
}
.picker-element-name {
position: absolute;
background: var(--background);
color: var(--accent);
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
opacity: 0.7;
margin: 0.4rem;
}
#popup-picker .explanation {
margin-top: 0!important;
margin-bottom: 1rem;
}
.popup-tabs {
padding-top: 0.5rem;
@ -592,7 +617,7 @@ input[type="checkbox"] {
width: 60%;
}
}
@media screen and (max-height: 850px) {
@media screen and (max-height: 605px) {
.popup {
height: 80%
}
@ -611,7 +636,7 @@ input[type="checkbox"] {
}
@media screen and (max-width: 475px) {
.tab {
font-size: 0;
font-size: 0!important;
}
.tab .emoji {
margin-right: 0;
@ -621,17 +646,49 @@ input[type="checkbox"] {
}
}
@media screen and (max-width: 320px) {
#popup-title {
font-size: 1.3rem;
line-height: 2rem;
}
.footer-button {
font-size: 0;
font-size: 0!important;
}
#cobalt-main-box #bottom button {
font-size: 0;
.switch, .checkbox, .category-title, .subtitle, #popup-desc {
font-size: .75rem;
}
.footer-button .emoji, #cobalt-main-box #bottom button .emoji {
.explanation {
font-size: .77rem;
margin-top: 0.8rem;
}
#popup-desc {
line-height: 1.4rem;
}
.changelog-subtitle, #popup-subtitle {
font-size: 0.9rem!important;
}
.category-title {
margin-bottom: 0.8rem;
}
.footer-button .emoji {
margin-right: 0;
}
}
@media screen and (max-width: 949px) {
#picker-holder::-webkit-scrollbar {
display: none;
}
#picker-holder.various {
flex-wrap: wrap;
align-content: left;
gap: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.picker-various-container {
width: 100%;
height: 20rem;
max-width: 100%;
}
#cobalt-main-box #bottom {
flex-direction: column;
}
@ -649,7 +706,7 @@ input[type="checkbox"] {
.footer-pair .footer-button {
width: 100%!important;
}
.imagepicker-image-container {
.picker-image-container {
height: 7rem;
width: 7rem;
line-height: 7rem;
@ -687,7 +744,7 @@ input[type="checkbox"] {
.popup-title {
line-height: inherit;
}
.imagepicker-image-container {
.picker-image-container {
line-height: 6rem;
height: 6rem;
width: 6rem;

View file

@ -1,5 +1,5 @@
let isIOS = navigator.userAgent.toLowerCase().match("iphone os");
let version = 12;
let version = 13;
let regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/);
let notification = `<div class="notification-dot"></div>`
@ -141,8 +141,8 @@ function hideAllPopups() {
for (let i = 0; i < filter.length; i++) {
filter[i].style.visibility = "hidden";
}
eid("imagepicker-holder").innerHTML = '';
eid("imagepicker-download").href = '/';
eid("picker-holder").innerHTML = '';
eid("picker-download").href = '/';
eid("popup-backdrop").style.visibility = "hidden";
}
function popup(type, action, text) {
@ -163,19 +163,44 @@ function popup(type, action, text) {
eid("pd-download").href = text;
eid("pd-copy").setAttribute("onClick", `copy('pd-copy', '${text}')`);
break;
case "imagePicker":
eid("imagepicker-download").href = text.url;
for (let i in text.images) {
eid("imagepicker-holder").innerHTML += `<div class="imagepicker-image-container"><img class="imagepicker-image" src="${text.images[i]}" onerror="this.parentNode.style.display='none'"></img></div>`
case "picker":
switch (text.type) {
case "images":
eid("picker-title").innerHTML = loc.pickerImages;
eid("picker-subtitle").innerHTML = loc.pickerImagesExpl;
if (!eid("popup-picker").classList.contains("scrollable")) eid("popup-picker").classList.add("scrollable");
if (eid("picker-holder").classList.contains("various")) eid("picker-holder").classList.remove("various");
eid("picker-download").href = text.audio;
eid("picker-download").style.visibility = "visible"
for (let i in text.arr) {
eid("picker-holder").innerHTML += `<a class="picker-image-container"><img class="picker-image" src="${text.arr[i]["url"]}" onerror="this.parentNode.style.display='none'"></img></a>`
}
break;
default:
eid("picker-title").innerHTML = loc.pickerDefault;
eid("picker-subtitle").innerHTML = loc.pickerDefaultExpl;
if (eid("popup-picker").classList.contains("scrollable")) eid("popup-picker").classList.remove("scrollable");
if (!eid("picker-holder").classList.contains("various")) eid("picker-holder").classList.add("various");
for (let i in text.arr) {
let s = text.arr[i], item;
switch (s.type) {
case "video":
item = `<a class="picker-various-container" href="${text.arr[i]["url"]}" target="_blank"><div class="picker-element-name">VIDEO ${Number(i)+1}</div><div class="imageBlock"></div><img class="picker-image" src="${text.arr[i]["thumb"]}" onerror="this.style.display='none'"></img></a>`
break;
}
eid("picker-holder").innerHTML += item
}
eid("picker-download").style.visibility = "hidden";
break;
}
break;
default:
break;
}
} else {
if (type == "imagePicker") {
eid("imagepicker-download").href = '/';
eid("imagepicker-holder").innerHTML = ''
if (type == "picker") {
eid("picker-download").href = '/';
eid("picker-holder").innerHTML = ''
}
}
eid("popup-backdrop").style.visibility = vis(action);
@ -261,6 +286,21 @@ function loadSettings() {
changeSwitcher(i, sGet(i))
}
}
function changeButton(type, text) {
switch (type) {
case 0: //error
eid("url-input-area").disabled = false
eid("url-clear").style.display = "block";
changeDownloadButton(2, '!!')
popup("error", 1, text);
break;
case 1: //enable back
changeDownloadButton(1, '>>');
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
break;
}
}
async function pasteClipboard() {
let t = await navigator.clipboard.readText();
if (regex.test(t)) {
@ -291,64 +331,52 @@ async function download(url) {
if (j.url) {
switch (j.status) {
case "redirect":
changeDownloadButton(2, '>>>')
setTimeout(() => {
changeDownloadButton(1, '>>');
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
}, 3000)
if (sGet("downloadPopup") == "true") {
popup('download', 1, j.url)
changeDownloadButton(2, '>>>');
setTimeout(() => { changeButton(1); }, 3000);
sGet("downloadPopup") == "true" ? popup('download', 1, j.url) : window.open(j.url, '_blank');
break;
case "picker":
if (j.audio && j.url) {
changeDownloadButton(2, '?..')
fetch(`${j.audio}&p=1&origin=front`).then(async (res) => {
let jp = await res.json();
if (jp.status === "continue") {
changeDownloadButton(2, '>>>');
popup('picker', 1, { audio: j.audio, arr: j.url, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 5000);
} else {
changeButton(0, jp.text);
}
}).catch((error) => internetError());
} else if (j.url) {
changeDownloadButton(2, '>>>');
popup('picker', 1, { arr: j.url, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 5000);
} else {
window.open(j.url, '_blank');
changeButton(0, loc.noURLReturned);
}
break;
case "images":
case "stream":
changeDownloadButton(2, '?..')
fetch(`${j.url}&p=1&origin=front`).then(async (res) => {
let jp = await res.json();
if (jp.status == "continue") {
changeDownloadButton(2, '>>>')
if (j.status === "images") {
popup('imagePicker', 1, {
url: j.url,
images: j.images
})
} else {
window.location.href = j.url
}
setTimeout(() => {
changeDownloadButton(1, '>>');
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
}, 5000)
if (jp.status === "continue") {
changeDownloadButton(2, '>>>'); window.location.href = j.url;
setTimeout(() => { changeButton(1) }, 5000);
} else {
eid("url-input-area").disabled = false
changeDownloadButton(2, '!!');
eid("url-clear").style.display = "block";
popup("error", 1, jp.text);
changeButton(0, jp.text);
}
}).catch((error) => internetError());
break;
default:
eid("url-input-area").disabled = false
changeDownloadButton(2, '!!');
eid("url-clear").style.display = "block";
popup("error", 1, loc.unknownStatus);
changeButton(0, loc.unknownStatus);
break;
}
} else {
eid("url-input-area").disabled = false
eid("url-clear").style.display = "block";
changeDownloadButton(2, '!!')
popup("error", 1, loc.noURLReturned);
changeButton(0, loc.noURLReturned);
}
} else {
eid("url-input-area").disabled = false
eid("url-clear").style.display = "block";
changeDownloadButton(2, '!!')
popup("error", 1, j.text);
changeButton(0, j.text);
}
}).catch((error) => internetError());
}

View file

@ -1,7 +1,7 @@
{
"name": "english",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">contact the maintainer/a>"
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">contact the maintainer</a>"
},
"strings": {
"LinkInput": "paste the link here",
@ -61,7 +61,7 @@
"LinkGitHubChanges": "&gt;&gt; see previous commits and contribute on github",
"LinkDonateContact": "&gt;&gt; let me know if currency you want to donate isn't listed",
"NoScriptMessage": "{appName} uses javascript for api requests and interactive interface. you have to allow javascript to use this site. i don't have any ads or trackers, pinky promise.",
"DownloadPopupDescriptionIOS": "because you have an ios device, you have to press and hold the download button and then select \"download video\" in appeared popup to save the video. this will be required for as long as apple forces safari webview upon all browser developers on ios.",
"DownloadPopupDescriptionIOS": "on ios devices, you have to press and hold the download button, hide the video preview, and then select \"download linked file\" in appeared popup to save the video. this will be required for as long as apple forces safari webview upon all browser developers on ios.",
"DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.",
"DownloadPopupWayToSave": "pick a way to save",
"ClickToCopy": "press to copy",
@ -101,6 +101,10 @@
"Miscellaneous": "miscellaneous",
"ModeToggleAuto": "auto mode",
"ModeToggleAudio": "audio mode",
"SettingsDisableNotifications": "hide notification dots"
"SettingsDisableNotifications": "hide notification dots",
"MediaPickerTitle": "pick what to save",
"MediaPickerExplanationPC": "click or right click to download what you want.",
"MediaPickerExplanationPhone": "press or press and hold to download what you want.",
"MediaPickerExplanationPhoneIOS": "press and hold, hide the preview, and then select \"download linked file\" to save."
}
}

View file

@ -62,7 +62,7 @@
"LinkGitHubChanges": "&gt;&gt; смотри предыдущие изменения на гитхабе",
"LinkDonateContact": "&gt;&gt; напиши мне, если в этом списке нет подходящей валюты",
"NoScriptMessage": "{appName} использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких трекеров или рекламы, обещаю.",
"DownloadPopupDescriptionIOS": "так как у тебя устройство на ios, тебе нужно зажать кнопку \"скачать\" и выбрать что-то похожее на \"сохранить в галерею\" в появившемся окне.",
"DownloadPopupDescriptionIOS": "так как у тебя устройство на ios, тебе нужно зажать кнопку \"скачать\", затем скрыть превью видео и выбрать что-то похожее на \"сохранить файл\" в появившемся окне.",
"DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода сохранения файла в настройках.",
"DownloadPopupWayToSave": "выбери, как сохранить",
"ClickToCopy": "нажми, чтобы скопировать",
@ -104,6 +104,10 @@
"Miscellaneous": "разное",
"ModeToggleAuto": "авто режим",
"ModeToggleAudio": "аудио режим",
"SettingsDisableNotifications": "скрыть значки уведомлений"
"SettingsDisableNotifications": "скрыть значки уведомлений",
"MediaPickerTitle": "выбери, что сохранить",
"MediaPickerExplanationPC": "кликни, чтобы скачать. также можно скачать через контекстное меню правой кнопки мыши.",
"MediaPickerExplanationPhone": "нажми, или нажми и удерживай, чтобы скачать.",
"MediaPickerExplanationPhoneIOS": "нажми и удерживай, затем скрой превью, и наконец выбери что-то похожее на \"сохранить файл\", чтобы скачать."
}
}

View file

@ -5,7 +5,7 @@ import { services as patterns } from "./config.js";
import { cleanURL, apiJSON } from "./sub/utils.js";
import { errorUnsupported } from "./sub/errors.js";
import loc from "../localization/manager.js";
import match from "./match.js";
import match from "./processing/match.js";
export async function getJSON(originalURL, lang, obj) {
try {

View file

@ -1,9 +1,12 @@
{
"current": {
"title": "less disturbance (3.6.2 + 3.6.3)",
"content": "changelog popup no longer annoys you after a major update! this action has been replaced with a notification dot. if you see a red dot, then there's something new.\n\nyour old setting that disabled the changelog popup now applies to notifications.\n\nnew users will see a notification dot instead of an about popup, too. this was mostly done to prevent complications if your browser is set up to clean local storage when you close it.\n\nother changes:\n- popups are now a bit wider, just so more content fits at once.\n- better interface scaling.\n- code is a bit cleaner now.\n- changed twitter api endpoint. there should no longer be any rate limits."
"title": "support for multi media tweets is here! (3.7)",
"content": "{appName} now lets you save any of the videos or gifs in a tweet. even if there are many of them.\n\nsimply paste a link like you'd usually do and {appName} will ask what exactly you want to save.\n\nFIREFOX USERS: if you have strict tracking protection on, you might wanna turn it off for {appName}, or else twitter video previews won't load. firefox filters out twitter image cdn as if it was a tracker, which it's not. it's a false-positive.\n\nhowever, you can leave it on if you're fine with blank squares and video numbers. i have thought of that in prior, you're welcome.\n\nother changes:\n- repurposed ex tiktok-only image picker to be dynamic and adapt depending on content to pick. that's exactly how twitter multi media downloads work.\n- cobalt is now properly viewable on phones with tiny screens, such as first gen iphone se.\n- scrollbars now should be visible only where they're needed.\n- brought back proper twitter api, because other one doesn't have multi media stuff (at least yet).\n- cleaned up some internal files, including main frontend js file.\n- reorganized some files in project directory, now you won't get lost when contributing or just looking through cobalt's code."
},
"history": [{
"title": "less disturbance (3.6.2 + 3.6.3)",
"content": "changelog popup no longer annoys you after a major update! this action has been replaced with a notification dot. if you see a red dot, then there's something new.\n\nyour old setting that disabled the changelog popup now applies to notifications.\n\nnew users will see a notification dot instead of an about popup, too. this was mostly done to prevent complications if your browser is set up to clean local storage when you close it.\n\nother changes:\n- popups are now a bit wider, just so more content fits at once.\n- better interface scaling.\n- code is a bit cleaner now.\n- changed twitter api endpoint. there should no longer be any rate limits."
}, {
"title": "improvements all around! (3.6)",
"content": "- download mode switcher is moving places, it's now right next to link input area.\n- smart mode has been renamed to auto mode, because this name is easier to understand.\n- all spacings in ui have been evened out. no more eye strain.\n- added support for twitter /video/1 links\n- clipboard button exception has been redone to prepare for adoption of readtext clipboard api in firefox.\n- cobalt is now using different tiktok api endpoint, because previous one got killed, just like the one before.\n- \"other\" settings tab has been cleaned up."
}, {

View file

@ -1,7 +1,7 @@
import loadJson from "./sub/loadJSON.js";
const config = loadJson("./src/config.json");
const packageJson = loadJson("./package.json");
const servicesConfigJson = loadJson("./src/modules/servicesConfig.json");
const servicesConfigJson = loadJson("./src/modules/processing/servicesConfig.json");
export const
services = servicesConfigJson.config,

View file

@ -44,11 +44,13 @@ export function popup(obj) {
if (Array.isArray(obj.body)) {
body = ``
for (let i = 0; i < obj.body.length; i++) {
classes = obj.body[i]["classes"] ? obj.body[i]["classes"] : []
if (i != obj.body.length - 1 && !obj.body[i]["nopadding"]) {
classes.push("desc-padding")
if (obj.body[i]["text"].length > 0) {
classes = obj.body[i]["classes"] ? obj.body[i]["classes"] : []
if (i != obj.body.length - 1 && !obj.body[i]["nopadding"]) {
classes.push("desc-padding")
}
body += obj.body[i]["raw"] ? obj.body[i]["text"] : `<div id="popup-desc" class="${classes.length > 0 ? classes.join(' ') : ''}">${obj.body[i]["text"]}</div>`
}
body += obj.body[i]["raw"] ? obj.body[i]["text"] : `<div id="popup-desc" class="${classes.length > 0 ? classes.join(' ') : ''}">${obj.body[i]["text"]}</div>`
}
}
return `

View file

@ -84,8 +84,7 @@ export default function(obj) {
}, {
text: `${loc(obj.lang, 'AboutSupportedServices')} ${enabledServices}.`
}, {
text: obj.lang != "ru" ? `<div id="popup-desc" class="desc-padding">${loc(obj.lang, 'FollowTwitter')}</div>` : "",
raw: true
text: obj.lang != "ru" ? loc(obj.lang, 'FollowTwitter') : ""
}, {
text: backdropLink(repo, loc(obj.lang, 'LinkGitHubIssues')),
classes: ["bottom-link"]
@ -271,14 +270,14 @@ export default function(obj) {
})
})}
${popupWithBottomButtons({
name: "imagePicker",
name: "picker",
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
header: {
title: loc(obj.lang, 'ImagePickerTitle'),
explanation: isMobile ? loc(obj.lang, 'ImagePickerExplanationPhone') : loc(obj.lang, 'ImagePickerExplanationPC')
title: `<div id="picker-title"></div>`,
explanation: `<div id="picker-subtitle"></div>`,
},
buttons: [`<a id="imagepicker-download" class="switch" target="_blank" href="/">${loc(obj.lang, 'ImagePickerDownloadAudio')}</a>`],
content: '<div id="imagepicker-holder"></div>'
buttons: [`<a id="picker-download" class="switch" target="_blank" href="/">${loc(obj.lang, 'ImagePickerDownloadAudio')}</a>`],
content: '<div id="picker-holder"></div>'
})}
${popup({
name: "error",
@ -293,19 +292,6 @@ export default function(obj) {
},
body: `<div id="desc-error" class="desc-padding subtext"></div>`
})}
${popup({
name: "info",
standalone: true,
buttonOnly: true,
emoji: emoji("✨", 48, 1),
classes: ["small"],
buttonText: loc(obj.lang, 'ErrorPopupCloseButton'),
header: {
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
title: "popup title"
},
body: `<div id="popup-info-desc" class="desc-padding subtext"></div>`
})}
<div id="popup-backdrop" style="visibility: hidden;" onclick="hideAllPopups()"></div>
<div id="cobalt-main-box" class="center" style="visibility: hidden;">
<div id="logo-area">${appName}</div>
@ -342,7 +328,11 @@ export default function(obj) {
noURLReturned: ` + "`" + loc(obj.lang, 'ErrorNoUrlReturned') + "`" + `,
unknownStatus: ` + "`" + loc(obj.lang, 'ErrorUnknownStatus') + "`" + `,
toggleDefault: '${emoji("✨")} ${loc(obj.lang, "ModeToggleAuto")}',
toggleAudio: '${emoji("🎶")} ${loc(obj.lang, "ModeToggleAudio")}'
toggleAudio: '${emoji("🎶")} ${loc(obj.lang, "ModeToggleAudio")}',
pickerDefault: ` + "`" + loc(obj.lang, 'MediaPickerTitle') + "`" + `,
pickerImages: ` + "`" + loc(obj.lang, 'ImagePickerTitle') + "`" + `,
pickerImagesExpl: ` + "`" + loc(obj.lang, `ImagePickerExplanation${isMobile ? "Phone" : "PC"}`) + "`" + `,
pickerDefaultExpl: ` + "`" + loc(obj.lang, `MediaPickerExplanation${isMobile ? `Phone${isIOS ? "IOS" : ""}` : "PC"}`) + "`" + `,
};</script>
<script type="text/javascript" src="cobalt.js"></script>
</html>`;

View file

@ -1,18 +1,18 @@
import { apiJSON } from "./sub/utils.js";
import { errorUnsupported, genericError } from "./sub/errors.js";
import { apiJSON } from "../sub/utils.js";
import { errorUnsupported, genericError } from "../sub/errors.js";
import { testers } from "./servicesPatternTesters.js";
import bilibili from "./services/bilibili.js";
import reddit from "./services/reddit.js";
import twitter from "./services/twitter.js";
import youtube from "./services/youtube.js";
import vk from "./services/vk.js";
import tiktok from "./services/tiktok.js";
import tumblr from "./services/tumblr.js";
import matchActionDecider from "./sub/matchActionDecider.js";
import vimeo from "./services/vimeo.js";
import soundcloud from "./services/soundcloud.js";
import bilibili from "../services/bilibili.js";
import reddit from "../services/reddit.js";
import twitter from "../services/twitter.js";
import youtube from "../services/youtube.js";
import vk from "../services/vk.js";
import tiktok from "../services/tiktok.js";
import tumblr from "../services/tumblr.js";
import matchActionDecider from "./matchActionDecider.js";
import vimeo from "../services/vimeo.js";
import soundcloud from "../services/soundcloud.js";
export default async function (host, patternMatch, url, lang, obj) {
try {

View file

@ -1,9 +1,9 @@
import { audioIgnore, services, supportedAudio } from "../config.js"
import { apiJSON } from "./utils.js"
import { apiJSON } from "../sub/utils.js"
export default function(r, host, ip, audioFormat, isAudioOnly) {
if (!r.error) {
if (!isAudioOnly) {
if (!isAudioOnly && !r.picker) {
switch (host) {
case "twitter":
return apiJSON(1, { u: r.urls });
@ -44,13 +44,26 @@ export default function(r, host, ip, audioFormat, isAudioOnly) {
case "vimeo":
return apiJSON(1, { u: r.urls });
}
} else if (r.picker) {
switch (host) {
case "douyin":
case "tiktok":
return apiJSON(5, {
picker: r.picker,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls, service: host, ip: ip,
filename: r.audioFilename, salt: process.env.streamSalt, isAudioOnly: true, audioFormat: audioFormat, copy: audioFormat == "best" ? true : false
})
case "twitter":
return apiJSON(5, {
picker: r.picker, service: host
})
}
} else {
if (host == "reddit" && r.typeId == 1 || audioIgnore.includes(host)) return apiJSON(0, { t: r.audioFilename });
let type = "render";
let copy = false;
if (!supportedAudio.includes(audioFormat)) audioFormat = "best";
if (!supportedAudio.includes(audioFormat)) audioFormat = "best";
if ((host == "tiktok" || host == "douyin") && isAudioOnly && services.tiktok.audioFormats.includes(audioFormat)) {
if (r.isMp3) {
if (audioFormat == "mp3" || audioFormat == "best") {
@ -62,7 +75,6 @@ export default function(r, host, ip, audioFormat, isAudioOnly) {
type = "bridge"
}
}
if ((audioFormat == "best" && services[host]["bestAudio"]) || services[host]["bestAudio"] && (audioFormat == services[host]["bestAudio"])) {
audioFormat = services[host]["bestAudio"]
type = "bridge"
@ -70,20 +82,11 @@ export default function(r, host, ip, audioFormat, isAudioOnly) {
audioFormat = "m4a"
copy = true
}
if ((host == "tiktok" || host == "douyin") && r.images) {
return apiJSON(5, {
type: type,
images: r.images,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls, service: host, ip: ip,
filename: r.audioFilename, salt: process.env.streamSalt, isAudioOnly: true, audioFormat: audioFormat, copy: copy
})
} else {
return apiJSON(2, {
type: type,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls, service: host, ip: ip,
filename: r.audioFilename, salt: process.env.streamSalt, isAudioOnly: true, audioFormat: audioFormat, copy: copy
})
}
return apiJSON(2, {
type: type,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls, service: host, ip: ip,
filename: r.audioFilename, salt: process.env.streamSalt, isAudioOnly: true, audioFormat: audioFormat, copy: copy
})
}
} else {
return apiJSON(0, { t: r.error });

View file

@ -17,6 +17,9 @@
},
"vk": {
"alias": "vk clips, vk video",
"localizedAlias": {
"ru": "vk видео, vk клипы"
},
"patterns": ["video-:userId_:videoId", "clip-:userId_:videoId", "clips-:userId?z=clip-:userId_:videoId"],
"quality_match": {
"2160": 7,

View file

@ -93,10 +93,10 @@ export default async function(obj) {
if (images) {
let imageLinks = [];
for (let i in images) {
imageLinks.push(images[i]["display_image"]["url_list"][0])
imageLinks.push({url: images[i]["display_image"]["url_list"][0]})
}
return {
images: imageLinks,
picker: imageLinks,
urls: audio,
audioFilename: audioFilename,
isAudioOnly: true,

View file

@ -2,18 +2,51 @@ import got from "got";
import loc from "../../localization/manager.js";
import { genericUserAgent } from "../config.js";
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"
export default async function(obj) {
try {
let req_status = await got.get(`https://cdn.syndication.twimg.com/tweet?id=${obj.id}&tweet_mode=extended`, {
headers: { "User-Agent": genericUserAgent }
let _headers = {
"User-Agent": genericUserAgent,
"Authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
"Host": "api.twitter.com",
"Content-Type": "application/json",
"Content-Length": 0
};
let req_act = await got.post(`${apiURL}/guest/activate.json`, {
headers: _headers
});
req_act.on('error', (err) => {
return { error: loc(obj.lang, 'ErrorCantConnectToServiceAPI', 'twitter') }
})
_headers["x-guest-token"] = req_act.body["guest_token"];
let req_status = await got.get(`${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: _headers
});
req_status.on('error', (err) => {
return { error: loc(obj.lang, 'ErrorCantConnectToServiceAPI', 'twitter') }
})
let parsbod = JSON.parse(req_status.body);
if (parsbod["video"] && parsbod["video"]["variants"]) {
let media = parsbod["video"]["variants"];
return { urls: media.filter((v) => { if (v["type"] == "video/mp4") return true; }).sort((a, b) => Number(b["src"].split("vid/")[1].split("x")[0]) - Number(a["src"].split("vid/")[1].split("x")[0]))[0]["src"].split('?')[0], audioFilename: `twitter_${obj.id}_audio` }
if (parsbod["extended_entities"] && parsbod["extended_entities"]["media"]) {
let single, multiple = [], media = parsbod["extended_entities"]["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"])})
}
} else {
single = bestQuality(media[0]["video_info"]["variants"])
}
if (single) {
return { urls: single, audioFilename: `twitter_${obj.id}_audio` }
} else if (multiple) {
return { picker: multiple }
} else {
return { error: loc(obj.lang, 'ErrorNoVideosInTweet') }
}
} else {
return { error: loc(obj.lang, 'ErrorNoVideosInTweet') }
}

View file

@ -14,7 +14,15 @@ export function apiJSON(type, obj) {
case 4:
return { status: 429, body: { status: "rate-limit", text: obj.t } };
case 5:
return { status: 200, body: { status: "images", images: obj.images, url: createStream(obj) } };
let pickerType = "various", audio = false
switch (obj.service) {
case "douyin":
case "tiktok":
audio = createStream(obj)
pickerType = "images"
break;
}
return { status: 200, body: { status: "picker", pickerType: pickerType, url: obj.picker, audio: audio } };
default:
return { status: 400, body: { status: "error", text: "Bad Request" } };
}