5.3.2: link sharing and nanoid
- you can now share video links directly from cobalt! - cobalt is now using nanoid for stream ids instead of giant sha256 hashes - one more fix to address the copy animation, this time on pc
This commit is contained in:
parent
307da3dce4
commit
68703ae300
8 changed files with 38 additions and 18 deletions
|
@ -43,8 +43,8 @@ Content live render streaming endpoint.<br>
|
||||||
### Request Query Variables
|
### Request Query Variables
|
||||||
| key | variables | description |
|
| key | variables | description |
|
||||||
|:----|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------|
|
|:----|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| p | ``1`` | Used for checking the rate limit. |
|
| p | ``1`` | Used for probing the rate limit. |
|
||||||
| t | Stream token | Unique stream identificator which is used for retrieving cached stream info data. |
|
| t | Stream token | Unique stream ID. Used for retrieving cached stream info data. |
|
||||||
| h | HMAC | Hashed combination of: (hashed) ip address, stream token, expiry timestamp, and service name. Used for verification of stream. |
|
| h | HMAC | Hashed combination of: (hashed) ip address, stream token, expiry timestamp, and service name. Used for verification of stream. |
|
||||||
| e | Expiry timestamp | |
|
| e | Expiry timestamp | |
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "cobalt",
|
"name": "cobalt",
|
||||||
"description": "save what you love",
|
"description": "save what you love",
|
||||||
"version": "5.3.1",
|
"version": "5.3.2",
|
||||||
"author": "wukko",
|
"author": "wukko",
|
||||||
"exports": "./src/cobalt.js",
|
"exports": "./src/cobalt.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
"express-rate-limit": "^6.3.0",
|
"express-rate-limit": "^6.3.0",
|
||||||
"ffmpeg-static": "^5.1.0",
|
"ffmpeg-static": "^5.1.0",
|
||||||
"got": "^12.1.0",
|
"got": "^12.1.0",
|
||||||
|
"nanoid": "^4.0.2",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
"url-pattern": "1.0.3",
|
"url-pattern": "1.0.3",
|
||||||
"xml-js": "^1.6.11",
|
"xml-js": "^1.6.11",
|
||||||
|
|
|
@ -147,8 +147,10 @@ button:active,
|
||||||
background: var(--accent-press);
|
background: var(--accent-press);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.switch.text-backdrop,
|
||||||
.switch.text-backdrop:hover,
|
.switch.text-backdrop:hover,
|
||||||
.switch.text-backdrop:active,
|
.switch.text-backdrop:active,
|
||||||
|
.text-to-copy.text-backdrop,
|
||||||
.text-to-copy.text-backdrop:hover,
|
.text-to-copy.text-backdrop:hover,
|
||||||
.text-to-copy.text-backdrop:active {
|
.text-to-copy.text-backdrop:active {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
|
@ -648,7 +650,13 @@ input[type="checkbox"] {
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
}
|
}
|
||||||
.expanded .collapse-body {
|
.expanded .collapse-body {
|
||||||
display: block
|
display: block;
|
||||||
|
}
|
||||||
|
#download-switcher .switches {
|
||||||
|
gap: var(--gap);
|
||||||
|
}
|
||||||
|
#pd-share {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
/* adapt the page according to screen size */
|
/* adapt the page according to screen size */
|
||||||
@media screen and (min-width: 2300px) {
|
@media screen and (min-width: 2300px) {
|
||||||
|
|
|
@ -91,6 +91,9 @@ function copy(id, data) {
|
||||||
setTimeout(() => { e.classList.remove("text-backdrop") }, 600);
|
setTimeout(() => { e.classList.remove("text-backdrop") }, 600);
|
||||||
data ? navigator.clipboard.writeText(data) : navigator.clipboard.writeText(e.innerText);
|
data ? navigator.clipboard.writeText(data) : navigator.clipboard.writeText(e.innerText);
|
||||||
}
|
}
|
||||||
|
async function share(url) {
|
||||||
|
try { await navigator.share({url: url}) } catch (e) {}
|
||||||
|
}
|
||||||
function detectColorScheme() {
|
function detectColorScheme() {
|
||||||
let theme = "auto";
|
let theme = "auto";
|
||||||
let localTheme = sGet("theme");
|
let localTheme = sGet("theme");
|
||||||
|
@ -174,6 +177,8 @@ function popup(type, action, text) {
|
||||||
case "download":
|
case "download":
|
||||||
eid("pd-download").href = text;
|
eid("pd-download").href = text;
|
||||||
eid("pd-copy").setAttribute("onClick", `copy('pd-copy', '${text}')`);
|
eid("pd-copy").setAttribute("onClick", `copy('pd-copy', '${text}')`);
|
||||||
|
eid("pd-share").setAttribute("onClick", `share('${text}')`);
|
||||||
|
if (navigator.canShare) eid("pd-share").style.display = "flex";
|
||||||
break;
|
break;
|
||||||
case "picker":
|
case "picker":
|
||||||
switch (text.type) {
|
switch (text.type) {
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
"DownloadPopupWayToSave": "pick a way to save",
|
"DownloadPopupWayToSave": "pick a way to save",
|
||||||
"ClickToCopy": "press to copy",
|
"ClickToCopy": "press to copy",
|
||||||
"Download": "download",
|
"Download": "download",
|
||||||
"CopyURL": "copy url",
|
"CopyURL": "copy",
|
||||||
"AboutTab": "about",
|
"AboutTab": "about",
|
||||||
"ChangelogTab": "changelog",
|
"ChangelogTab": "changelog",
|
||||||
"DonationsTab": "donations",
|
"DonationsTab": "donations",
|
||||||
|
@ -117,6 +117,7 @@
|
||||||
"SettingsDubDefault": "original",
|
"SettingsDubDefault": "original",
|
||||||
"SettingsDubAuto": "auto",
|
"SettingsDubAuto": "auto",
|
||||||
"SettingsVimeoPrefer": "vimeo downloads type",
|
"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."
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
"DownloadPopupWayToSave": "выбери, как сохранить",
|
"DownloadPopupWayToSave": "выбери, как сохранить",
|
||||||
"ClickToCopy": "нажми, чтобы скопировать",
|
"ClickToCopy": "нажми, чтобы скопировать",
|
||||||
"Download": "скачать",
|
"Download": "скачать",
|
||||||
"CopyURL": "скопировать ссылку",
|
"CopyURL": "скопировать",
|
||||||
"AboutTab": "о {appName}",
|
"AboutTab": "о {appName}",
|
||||||
"ChangelogTab": "изменения",
|
"ChangelogTab": "изменения",
|
||||||
"DonationsTab": "донаты",
|
"DonationsTab": "донаты",
|
||||||
|
@ -117,6 +117,7 @@
|
||||||
"SettingsDubDefault": "оригинал",
|
"SettingsDubDefault": "оригинал",
|
||||||
"SettingsDubAuto": "авто",
|
"SettingsDubAuto": "авто",
|
||||||
"SettingsVimeoPrefer": "тип загрузок с vimeo",
|
"SettingsVimeoPrefer": "тип загрузок с vimeo",
|
||||||
"SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: {appName} совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, {appName} скачает \"dash\"."
|
"SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: {appName} совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, {appName} скачает \"dash\".",
|
||||||
|
"ShareURL": "поделиться"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,7 +333,8 @@ export default function(obj) {
|
||||||
name: "download",
|
name: "download",
|
||||||
subtitle: t('DownloadPopupWayToSave'),
|
subtitle: t('DownloadPopupWayToSave'),
|
||||||
explanation: `${!isIOS ? t('DownloadPopupDescription') : t('DownloadPopupDescriptionIOS')}`,
|
explanation: `${!isIOS ? t('DownloadPopupDescription') : t('DownloadPopupDescriptionIOS')}`,
|
||||||
items: `<a id="pd-download" class="switch full space-right" target="_blank" href="/">${t('Download')}</a>
|
items: `<a id="pd-download" class="switch full" target="_blank" href="/">${t('Download')}</a>
|
||||||
|
<div id="pd-share" class="switch full">${t('ShareURL')}</div>
|
||||||
<div id="pd-copy" class="switch full">${t('CopyURL')}</div>`
|
<div id="pd-copy" class="switch full">${t('CopyURL')}</div>`
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import NodeCache from "node-cache";
|
import NodeCache from "node-cache";
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
import { sha256 } from "../sub/crypto.js";
|
import { sha256 } from "../sub/crypto.js";
|
||||||
import { streamLifespan } from "../config.js";
|
import { streamLifespan } from "../config.js";
|
||||||
|
@ -11,9 +12,9 @@ streamCache.on("expired", (key) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createStream(obj) {
|
export function createStream(obj) {
|
||||||
let streamID = sha256(`${obj.ip},${obj.service},${obj.filename},${obj.audioFormat},${obj.mute}`, salt),
|
let streamID = nanoid(),
|
||||||
exp = Math.floor(new Date().getTime()) + streamLifespan,
|
exp = Math.floor(new Date().getTime()) + streamLifespan,
|
||||||
ghmac = sha256(`${streamID},${obj.service},${obj.ip},${exp}`, salt);
|
ghmac = sha256(`${streamID},${obj.ip},${obj.service},${exp}`, salt);
|
||||||
|
|
||||||
if (!streamCache.has(streamID)) {
|
if (!streamCache.has(streamID)) {
|
||||||
streamCache.set(streamID, {
|
streamCache.set(streamID, {
|
||||||
|
@ -42,14 +43,16 @@ export function createStream(obj) {
|
||||||
|
|
||||||
export function verifyStream(ip, id, hmac, exp) {
|
export function verifyStream(ip, id, hmac, exp) {
|
||||||
try {
|
try {
|
||||||
|
if (id.length === 21) {
|
||||||
let streamInfo = streamCache.get(id);
|
let streamInfo = streamCache.get(id);
|
||||||
if (!streamInfo) return { error: 'this stream token does not exist', status: 400 };
|
if (!streamInfo) return { error: 'this stream token does not exist', status: 400 };
|
||||||
|
|
||||||
let ghmac = sha256(`${id},${streamInfo.service},${ip},${exp}`, salt);
|
let ghmac = sha256(`${id},${ip},${streamInfo.service},${exp}`, salt);
|
||||||
if (String(hmac) === ghmac && String(exp) === String(streamInfo.exp) && ghmac === String(streamInfo.hmac)
|
if (String(hmac) === ghmac && String(exp) === String(streamInfo.exp) && ghmac === String(streamInfo.hmac)
|
||||||
&& String(ip) === streamInfo.ip && Number(exp) > Math.floor(new Date().getTime())) {
|
&& String(ip) === streamInfo.ip && Number(exp) > Math.floor(new Date().getTime())) {
|
||||||
return streamInfo;
|
return streamInfo;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return { error: 'Unauthorized', status: 401 };
|
return { error: 'Unauthorized', status: 401 };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { status: 500, body: { status: "error", text: "Internal Server Error" } };
|
return { status: 500, body: { status: "error", text: "Internal Server Error" } };
|
||||||
|
|
Loading…
Reference in a new issue