7.8 update

merge pull request #286 from wukko/7.8
This commit is contained in:
wukko 2023-12-26 01:40:06 +06:00 committed by GitHub
commit a19cb5513f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 450 additions and 310 deletions

View file

@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
"version": "7.7.5",
"version": "7.8",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",
@ -36,6 +36,7 @@
"hls-parser": "^0.10.7",
"nanoid": "^4.0.2",
"node-cache": "^5.1.2",
"psl": "1.9.0",
"set-cookie-parser": "2.6.0",
"undici": "^5.19.1",
"url-pattern": "1.0.3",

View file

@ -8,37 +8,37 @@
"contact": "https://wukko.me/contacts",
"support": {
"default": {
"email": {
"emoji": "📧",
"url": "mailto:support@cobalt.tools",
"name": "support@cobalt.tools"
},
"twitter": {
"emoji": "🐦",
"url": "https://twitter.com/justusecobalt",
"handle": "@justusecobalt"
"name": "@justusecobalt"
},
"discord": {
"emoji": "👾",
"url": "https://discord.gg/pQPt8HBUPu",
"handle": "cobalt community server"
"name": "cobalt discord server"
},
"mastodon": {
"emoji": "🐘",
"url": "https://wetdry.world/@cobalt",
"handle": "@cobalt@wetdry.world"
},
"support email": {
"emoji": "📧",
"url": "mailto:support@cobalt.tools",
"handle": "support@cobalt.tools"
"name": "@cobalt@wetdry.world"
}
},
"ru": {
"канал в telegram": {
"telegram": {
"emoji": "📬",
"url": "https://t.me/justusecobalt_ru",
"handle": "@justusecobalt_ru"
"name": "канал в telegram"
},
"поддержка по почте": {
"email": {
"emoji": "📧",
"url": "mailto:support@cobalt.tools",
"handle": "support@cobalt.tools"
"name": "support@cobalt.tools"
}
}
}
@ -58,7 +58,9 @@
}
},
"links": {
"saveToGalleryShortcut": "https://www.icloud.com/shortcuts/b401917928fd407daf1db0fd07eb7e78"
"saveToGalleryShortcut": "https://www.icloud.com/shortcuts/b401917928fd407daf1db0fd07eb7e78",
"statusPage": "https://status.cobalt.tools/",
"troubleshootingGuide": "https://github.com/wukko/cobalt/blob/current/docs/troubleshooting.md"
},
"celebrations": {
"01-01": "🎄",

View file

@ -97,7 +97,7 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
let chck = checkJSONPost(request);
if (!chck) throw new Error();
j = await getJSON(chck["url"], lang, chck);
j = await getJSON(chck.url, lang, chck);
} else {
j = apiJSON(0, {
t: !contentCon ? "invalid content type header" : loc(lang, 'ErrorNoLink')

View file

@ -254,16 +254,17 @@ button:active,
}
#cobalt-main-box {
position: fixed;
width: 60%;
width: 40rem;
height: auto;
display: flex;
flex-direction: row;
flex-direction: column;
align-content: center;
align-items: center;
}
#logo {
text-align: left;
font-size: 1rem;
white-space: nowrap;
width: 7rem;
height: 2.5rem;
align-items: center;
display: flex;
@ -295,7 +296,7 @@ button:active,
}
#url-input-area {
background: none;
padding: 0 1rem;
padding-left: calc(20px + 1.4rem);
width: 100%;
color: var(--accent);
border: 0;
@ -316,6 +317,15 @@ button:active,
outline: none;
border-bottom: var(--border-10);
}
#link-icon {
display: flex;
position: absolute;
width: 20px;
padding-top: 0.2rem;
left: 0.7rem;
flex-wrap: nowrap;
color: var(--accent-subtext);
}
#download-button {
height: 2.5rem;
color: var(--accent);
@ -331,6 +341,10 @@ button:active,
color: var(--accent-subtext);
cursor: not-allowed;
}
#cobalt-main-box .switch,
#footer .switch {
box-shadow: 0 0 0 0.1rem var(--accent-highlight) inset;
}
#footer {
bottom: 0;
width: 100%;
@ -458,9 +472,6 @@ button:active,
.popup.scrollable {
height: 95%;
}
.scrollable .bottom-link {
padding-bottom: 2rem;
}
.changelog-subtitle {
font-size: 1.3rem;
padding-bottom: var(--gap-no-icon);
@ -797,6 +808,7 @@ button:active,
.collapse-body {
display: none;
padding: var(--padding-1);
padding-bottom: 1rem;
user-select: text;
-webkit-user-select: text;
}
@ -947,7 +959,7 @@ button:active,
.text-to-copy,
.text-to-copy.text-backdrop,
#filename-preview {
border-radius: 5px / 6px;
border-radius: 6px / 7px;
}
[type=checkbox] {
border-radius: 3px / 4px;
@ -968,28 +980,28 @@ button:active,
border-top: var(--accent-highlight) solid 0.1rem;
bottom: -1px;
}
.switches .first {
border-top-left-radius: 5px 6px;
border-bottom-left-radius: 5px 6px;
.switches :first-child {
border-top-left-radius: 6px 7px;
border-bottom-left-radius: 6px 7px;
}
.switches .last {
border-top-right-radius: 5px 6px;
border-bottom-right-radius: 5px 6px;
.switches :last-child {
border-top-right-radius: 6px 7px;
border-bottom-right-radius: 6px 7px;
}
.text-backdrop {
border-radius: 3px / 4px;
}
.collapse-list.first,
.collapse-list.first .collapse-header {
border-top-left-radius: 6px 7px;
border-top-right-radius: 6px 7px;
.collapse-list:first-child,
.collapse-list:first-child .collapse-header {
border-top-left-radius: 7px 8px;
border-top-right-radius: 7px 8px;
}
.collapse-list.last,
.collapse-list.last .collapse-header {
border-bottom-left-radius: 6px 7px;
border-bottom-right-radius: 6px 7px;
.collapse-list:last-child,
.collapse-list:last-child .collapse-header {
border-bottom-left-radius: 7px 8px;
border-bottom-right-radius: 7px 8px;
}
.collapse-list.last.expanded .collapse-header {
.collapse-list:last-child.expanded .collapse-header {
border-radius: 0;
}
/* prevent resizing fliecker on ios if web app is installed as standalone */
@ -1008,9 +1020,6 @@ button:active,
}
}
@media screen and (max-width: 1440px) {
#cobalt-main-box {
width: 65%;
}
.popup.small {
width: 30%
}
@ -1024,9 +1033,6 @@ button:active,
}
}
@media screen and (max-width: 1200px) {
#cobalt-main-box {
width: 70%;
}
.popup.small {
width: 35%
}
@ -1035,9 +1041,6 @@ button:active,
}
}
@media screen and (max-width: 1025px) {
#cobalt-main-box {
width: 75%;
}
.popup.small {
width: 40%
}
@ -1062,14 +1065,14 @@ button:active,
width: calc(100% - 1.3rem);
}
}
@media screen and (max-width: 720px) {
@media screen and (max-width: 660px) {
#cobalt-main-box {
width: calc(100% - (0.7rem * 2));
}
#cobalt-main-box #bottom {
flex-direction: column-reverse;
flex-direction: row-reverse;
}
#cobalt-main-box #bottom button {
#cobalt-main-box #bottom #audioMode button, #audioMode {
width: 100%;
}
#footer {
@ -1166,9 +1169,6 @@ button:active,
#popup-tabs {
padding-bottom: calc(env(safe-area-inset-bottom)/2 + 1.5rem);
}
.bottom-link {
padding-bottom: 2rem;
}
.popup-content-inner,
.tab-content-settings,
.popup-tabs-child,

View file

@ -1,4 +1,4 @@
const version = 39;
const version = 40;
const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.match("iphone os");
@ -358,7 +358,7 @@ async function download(url) {
eid("url-clear").style.display = "none";
eid("url-input-area").disabled = true;
let req = {
url: encodeURIComponent(url.split("&")[0].split('%')[0]),
url,
aFormat: sGet("aFormat").slice(0, 4),
filenamePattern: sGet("filenamePattern"),
dubLang: false

View file

@ -0,0 +1,30 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5 18C25.6421 18 29 14.6421 29 10.5C29 6.35786 25.6421 3 21.5 3C17.3579 3 14 6.35786 14 10.5C14 14.6421 17.3579 18 21.5 18Z" fill="#E0AEF8"/>
<path d="M10.75 14.375C10.75 16.6532 8.90317 18.5 6.625 18.5C4.34683 18.5 2.5 16.6532 2.5 14.375C2.5 12.0968 4.34683 10.25 6.625 10.25C8.90317 10.25 10.75 12.0968 10.75 14.375Z" fill="#E0AEF8"/>
<path d="M19.5989 24.5C19.5989 27.316 17.316 29.5989 14.5 29.5989C11.684 29.5989 9.40112 27.316 9.40112 24.5C9.40112 21.684 11.684 19.4011 14.5 19.4011C17.316 19.4011 19.5989 21.684 19.5989 24.5Z" fill="#E0AEF8"/>
<path d="M29 10.5C29 14.0281 26.5639 16.9872 23.2821 17.787C24.9278 16.6077 26 14.6791 26 12.5C26 8.91015 23.0899 6 19.5 6C17.321 6 15.3923 7.07225 14.213 8.71792C15.0128 5.43607 17.9719 3 21.5 3C25.6421 3 29 6.35786 29 10.5Z" fill="#C4BBF9"/>
<path d="M19.5 24.5C19.5 26.0548 18.7903 27.4439 17.6772 28.3609C17.8861 27.7797 18 27.1532 18 26.5C18 23.4624 15.5376 21 12.5 21C11.8468 21 11.2203 21.1139 10.639 21.3228C11.5561 20.2097 12.9452 19.5 14.5 19.5C17.2614 19.5 19.5 21.7386 19.5 24.5Z" fill="#C4BBF9"/>
<path d="M29.5 10.5C29.5 13.9844 27.2723 16.9485 24.1635 18.0459C26.4529 16.7619 28 14.3116 28 11.5C28 7.35786 24.6421 4 20.5 4C17.6883 4 15.2381 5.54715 13.954 7.83654C15.0514 4.72769 18.0155 2.5 21.5 2.5C25.9182 2.5 29.5 6.08172 29.5 10.5Z" fill="#AEDDFF"/>
<path d="M19.7969 24.2162C19.7969 25.3601 19.3936 26.4109 18.7201 27.237C18.9017 26.691 19 26.107 19 25.5C19 22.4624 16.5376 20 13.5 20C13.116 20 12.7412 20.0394 12.3794 20.1142C13.121 19.662 13.9946 19.4011 14.9297 19.4011C17.6178 19.4011 19.7969 21.5569 19.7969 24.2162Z" fill="#AEDDFF"/>
<path d="M29.4999 8C29.7959 8.78448 29.9347 10.1243 29.9855 10.9996C29.8755 12.897 29.1432 14.6275 27.9895 15.9899C26.5085 17.2439 24.5925 18.5 22.4999 18.5C22.3229 18.5 22.147 18.3576 21.9726 18.2164C21.8138 18.0878 21.6562 17.9602 21.4999 17.9418C25.7231 17.4469 28.9999 13.8562 28.9999 9.50003C28.9999 8.53244 28.8383 7.60261 28.5404 6.73608C28.9999 7 29.2635 7.3734 29.4999 8Z" fill="#FCD53F"/>
<path d="M10.9931 14.7496C10.9875 13.826 10.6534 12.8627 10.2458 12.5056C10.4104 12.9732 10.4999 13.4762 10.4999 14.0001C10.4999 16.3164 8.74995 18.2239 6.5 18.4726C6.5773 18.4812 6.6458 18.5222 6.71428 18.5633C6.79122 18.6094 6.86816 18.6556 6.95756 18.6556C8.07293 18.6556 9.06514 18.2854 9.92215 17.4223C10.7144 16.6244 11 15.874 10.9931 14.7496Z" fill="#FCD53F"/>
<path d="M19.1466 22.0559C19.1949 22.132 19.2416 22.203 19.2866 22.2714C19.4726 22.5544 19.6291 22.7924 19.745 23.1541C19.8905 23.608 19.9721 24.2532 19.9944 24.7499C19.9365 26.0435 19.4317 27.2208 18.6307 28.1312C17.662 28.9832 16.3395 29.6941 14.948 29.6941C14.8603 29.6941 14.7871 29.6397 14.7138 29.5853C14.646 29.5349 14.5781 29.4845 14.4987 29.4774C17.3026 29.2253 19.4999 26.8691 19.4999 23.9997C19.4999 23.3154 19.375 22.6603 19.1466 22.0559Z" fill="#FCD53F"/>
<path d="M29.9856 11C29.9952 10.8346 30 10.6679 30 10.5C30 8.04319 28.9577 5.82978 27.291 4.27795C25.9269 3.34543 24.2772 2.80005 22.5 2.80005C21.749 2.80005 21.0207 2.89745 20.3271 3.08031C20.7105 3.02739 21.1021 3.00005 21.5 3.00005C26.0266 3.00005 29.7267 6.53836 29.9856 11Z" fill="#FF6DC6"/>
<path d="M11 14.5001C11 14.5838 10.9977 14.667 10.9932 14.7495C10.8677 12.4636 9.03606 10.6321 6.74999 10.5069C6.79014 10.5047 6.82851 10.4836 6.86696 10.4624C6.90775 10.44 6.94863 10.4175 6.99181 10.4175C8.66187 10.5069 9.13592 10.9059 9.92216 11.5779C10.5942 12.3641 11 13.3847 11 14.5001Z" fill="#FF6DC6"/>
<path d="M19.9944 24.7502C19.9981 24.6673 20 24.5839 20 24.5001C20 23.1085 19.4832 21.8377 18.6311 20.869C17.6773 19.9687 16.5385 19.3779 15.1229 19.3181C15.0807 19.3181 15.0065 19.366 14.9331 19.4133C14.8626 19.4588 14.7929 19.5038 14.7527 19.5055C17.5902 19.6339 19.8676 21.9123 19.9944 24.7502Z" fill="#FF6DC6"/>
<path d="M15 29.5C16.3916 29.5 17.6625 28.9832 18.6312 28.131C17.6233 29.2769 16.1461 30 14.5 30C13.0491 30 11.7294 29.4382 10.7467 28.5203C9.49997 26.8685 9.26615 25.8102 9.49997 24C9.49997 27.0376 11.9624 29.5 15 29.5Z" fill="#FF6DC6"/>
<path d="M6.99998 18.5C8.11535 18.5 9.13594 18.0942 9.92218 17.4222C9.09682 18.3879 7.86988 19 6.49998 19C5.18777 19 4.00675 18.4383 3.18419 17.5423C2.60279 16.559 2.36462 15.6482 2.36462 14.4751C2.38643 14.3988 2.39759 14.3397 2.40768 14.2863C2.42512 14.1941 2.43937 14.1187 2.49998 14C2.49998 16.4853 4.51469 18.5 6.99998 18.5Z" fill="#FF6DC6"/>
<path d="M16.0102 16.9896C17.4912 18.2438 19.4073 19.0001 21.5 19.0001C24.1018 19.0001 26.4305 17.8311 27.9897 15.9898C26.5087 17.2438 24.5926 18.0001 22.5 18.0001C18.1439 18.0001 14.5531 14.7232 14.0582 10.5001C14.0399 10.6564 13.9122 10.814 13.7836 10.9728C13.6424 11.1471 13.5 11.3229 13.5 11.5C13.5 13.5926 14.7562 15.5086 16.0102 16.9896Z" fill="#FF6DC6"/>
<path d="M24.9686 10.9687C25.2209 10.9348 25.4701 10.8735 25.7115 10.7846C25.7859 10.7572 25.8596 10.7272 25.9324 10.6946C26.46 9.42491 26.2075 7.9076 25.1749 6.87498C24.1184 5.81849 22.5545 5.57861 21.2676 6.15534C21.1502 6.43779 21.0715 6.7325 21.0313 7.0314C22.076 6.89103 23.1719 7.2223 23.9748 8.02519C24.7777 8.82809 25.109 9.92403 24.9686 10.9687Z" fill="#EBCAFF"/>
<path d="M8.82323 14.8232C8.70434 14.877 8.57925 14.9195 8.44933 14.9493C8.48249 14.8049 8.5 14.6545 8.5 14.5C8.5 13.3954 7.60457 12.5 6.5 12.5C6.3455 12.5 6.21094 12.5195 6.05066 12.5507C6.09766 12.3281 6.17676 12.1767 6.17676 12.1767C6.42782 12.0632 6.70653 12 6.99999 12C8.10456 12 8.99999 12.8954 8.99999 14C8.99999 14.2935 8.93678 14.5722 8.82323 14.8232Z" fill="#EBCAFF"/>
<path d="M16.0598 24.9944C16.34 24.9236 16.5909 24.8093 16.8104 24.6618C16.9804 23.6993 16.4826 22.8173 15.7467 22.3141C15.0109 21.8109 13.7989 21.5999 13.0531 22.1033C13.0184 22.2655 13 22.4356 13 22.6125C13 22.7045 13.0051 22.7952 13.0149 22.8846C13.1498 22.8637 13.2879 22.8529 13.4286 22.8529C14.7256 22.8529 15.8079 23.772 16.0598 24.9944Z" fill="#EBCAFF"/>
<path d="M26.1249 5.87498C24.903 4.6531 23.0025 4.52352 21.6367 5.48622C21.4704 5.7221 21.3366 5.97393 21.2355 6.23547C22.4884 5.75093 23.9639 6.01414 24.9749 7.02511C25.9859 8.03608 26.2491 9.51165 25.7645 10.7645C26.0259 10.6635 26.2776 10.5298 26.5133 10.3637C27.4764 8.9978 27.3469 7.09699 26.1249 5.87498Z" fill="#EFD5FF"/>
<path d="M9.14202 14.6421C9.04203 14.7118 8.93539 14.7726 8.82323 14.8233C8.93678 14.5722 8.99999 14.2935 8.99999 14C8.99999 12.8955 8.10456 12 6.99999 12C6.70653 12 6.42782 12.0633 6.17676 12.1768C6.24219 12.0469 6.29688 11.9493 6.35797 11.8579C6.68176 11.6323 7.06816 11.4113 7.49272 11.4113C8.0001 11.4113 8.61197 11.5466 8.96458 11.8579C9.37959 12.2244 9.6122 12.8748 9.6122 13.472C9.6122 13.8966 9.36766 14.3183 9.14202 14.6421Z" fill="#EFD5FF"/>
<path d="M16.7415 24.7069C17.0113 24.5411 17.2466 24.3247 17.4341 24.0708C17.9701 23.3453 17.371 21.9121 16.7423 21.3271C15.9331 20.5743 14.2913 20.4843 13.4795 21.155C13.2655 21.4449 13.1137 21.7835 13.0439 22.1511C13.3562 22.024 13.6978 21.954 14.0558 21.954C15.5395 21.954 16.7423 23.1567 16.7423 24.6404C16.7423 24.6627 16.742 24.6848 16.7415 24.7069Z" fill="#EFD5FF"/>
<path d="M26.9748 9.97487C26.8036 10.1462 26.6189 10.296 26.4243 10.4243C27.3202 9.06595 27.1704 7.22067 25.9748 6.02513C24.7793 4.82958 22.934 4.67976 21.5756 5.57566C21.704 5.38104 21.8538 5.19641 22.0251 5.02513C23.3919 3.65829 25.608 3.65829 26.9748 5.02513C28.3417 6.39196 28.3417 8.60804 26.9748 9.97487Z" fill="white"/>
<path d="M9.14195 14.6421C9.66057 14.2808 9.99995 13.68 9.99995 13C9.99995 11.8954 9.10452 11 7.99995 11C7.31998 11 6.71927 11.3393 6.35791 11.8579C6.6817 11.6323 7.07536 11.5 7.49991 11.5C8.60448 11.5 9.49991 12.3954 9.49991 13.5C9.49991 13.9246 9.3676 14.3183 9.14195 14.6421Z" fill="white"/>
<path d="M17.3629 24.1618C17.7068 23.7391 17.913 23.1999 17.913 22.6125C17.913 21.2558 16.8131 20.156 15.4564 20.156C14.6246 20.156 13.8893 20.5695 13.4449 21.2022C13.8303 20.9885 14.2737 20.8668 14.7456 20.8668C16.2293 20.8668 17.4321 22.0696 17.4321 23.5533C17.4321 23.7626 17.4082 23.9663 17.3629 24.1618Z" fill="white"/>
<path d="M22.5 3C24.5926 3 26.5087 3.75622 27.9897 5.0103C26.4305 3.16895 24.1018 2 21.5 2C16.8056 2 13 5.80558 13 10.5C13 13.1018 14.1689 15.4305 16.0103 16.9897C14.7562 15.5087 14 13.5926 14 11.5C14 6.80558 17.8056 3 22.5 3Z" fill="#26C9FC"/>
<path d="M9.9222 11.5778C9.13597 10.9058 8.11537 10.5 7 10.5C4.51472 10.5 2.5 12.5147 2.5 15C2.5 16.1154 2.90579 17.136 3.5778 17.9222C2.61213 17.0968 2 15.8699 2 14.5C2 12.0147 4.01472 10 6.5 10C7.86991 10 9.09685 10.6121 9.9222 11.5778Z" fill="#26C9FC"/>
<path d="M18.6311 20.8689C17.6624 20.0168 16.3916 19.5 15 19.5C11.9624 19.5 9.5 21.9624 9.5 25C9.5 26.3916 10.0168 27.6624 10.8689 28.6311C9.72307 27.6231 9 26.146 9 24.5C9 21.4624 11.4624 19 14.5 19C16.146 19 17.6231 19.7231 18.6311 20.8689Z" fill="#26C9FC"/>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.18 19.61C28.2345 19.61 29.9 17.9445 29.9 15.89C29.9 13.8355 28.2345 12.17 26.18 12.17C24.1255 12.17 22.46 13.8355 22.46 15.89C22.46 17.9445 24.1255 19.61 26.18 19.61Z" fill="#212121"/>
<path d="M10.9999 11L11.6799 9.99997C12.9299 9.99997 14.1699 9.70997 15.2899 9.16997L21.5499 6.08997V25.71L15.2899 22.63C14.1699 22.08 12.9299 21.79 11.6799 21.79L10.9999 20V11ZM6.21586 29.0083H8.78989C9.45989 29.0083 9.99989 28.4683 9.99989 27.7983V19.89H5.00586V27.7983C5.00586 28.4683 5.54586 29.0083 6.21586 29.0083Z" fill="#D3D3D3"/>
<path d="M24.07 3C22.38 3 21 4.37 21 6.07V25.72C21 27.41 22.37 28.79 24.07 28.79C25.76 28.79 27.14 27.42 27.14 25.72V6.07C27.13 4.37 25.76 3 24.07 3Z" fill="#F8312F"/>
<path d="M3.72662 10H12V21.78H3.72662C2.77081 21.78 2 21.03 2 20.11V11.68C2 10.75 2.77081 10 3.72662 10Z" fill="#CA0B4A"/>
</svg>

After

Width:  |  Height:  |  Size: 931 B

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.2359 5.89697L6.00835 8.66735C6.26317 8.92198 6.66069 8.92198 6.91551 8.66735L8.66867 6.91549C8.92349 6.66086 8.92349 6.26364 8.66867 6.00901L5.89623 3.23862C5.53948 2.88214 5.71276 2.28121 6.19182 2.15899C7.96537 1.72102 9.92239 2.18954 11.329 3.54418C12.8928 5.05575 13.3617 7.28456 12.7243 9.22843L22.7212 19.2909C24.6418 18.6406 26.8528 19.0802 28.387 20.6132C29.7936 22.0188 30.2828 24.0049 29.8445 25.8178C29.7222 26.2864 29.1208 26.4595 28.7641 26.103L25.9917 23.3326C25.7368 23.078 25.3393 23.078 25.0845 23.3326L23.3313 25.0845C23.0765 25.3391 23.0765 25.7364 23.3313 25.991L26.1038 28.7614C26.4605 29.1179 26.2872 29.7188 25.8082 29.841C24.0346 30.279 22.0776 29.8105 20.671 28.4558C19.1243 26.9608 18.6487 24.7642 19.2552 22.8355L9.2093 12.7321C7.30512 13.3486 5.12872 12.9014 3.61304 11.3868C2.20643 9.98124 1.71717 7.99512 2.15546 6.18215C2.27778 5.71363 2.87915 5.54048 3.2359 5.89697Z" fill="#B4ACBC"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View file

@ -1,7 +1,7 @@
{
"name": "english",
"substrings": {
"ContactLink": "<a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>"
"ContactLink": "check the <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">status page</a> or <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>."
},
"strings": {
"AppTitleCobalt": "cobalt",
@ -45,7 +45,6 @@
"SettingsEnableDownloadPopup": "ask how to save",
"AccessibilityEnableDownloadPopup": "ask what to do with downloads",
"SettingsQualityDescription": "if selected quality isn't available, closest one is used instead.",
"LinkGitHubChanges": "&gt;&gt; see previous commits and contribute on github",
"NoScriptMessage": "cobalt uses javascript for api requests and interactive interface. you have to allow javascript to use this site. there are no pesty scripts, pinky promise.",
"DownloadPopupDescriptionIOS": "easiest way to save videos on ios:\n1. add <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">this siri shortcut</a>.\n2. press \"share\" above and select \"save to photos\" in appeared share sheet.\nif asked, review the permission request, and press \"always allow\".\n\nalternative method:\npress and hold the download button, hide the video preview, and select \"download linked file\" to download.\nthen, open safari downloads, select the file you downloaded, open share menu, and finally press \"save video\".",
"DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.",
@ -77,12 +76,12 @@
"ImagePickerExplanationPhone": "press and hold an image to save it.",
"ErrorNoUrlReturned": "i didn't get a download link from the server. this should never happen. try again, but if it still doesn't work, {ContactLink}.",
"ErrorUnknownStatus": "i received a response i can't process. this should never happen. try again, but if it still doesn't work, {ContactLink}.",
"PasteFromClipboard": "paste and download",
"PasteFromClipboard": "paste",
"ChangelogOlder": "previous versions",
"ChangelogPressToExpand": "expand",
"Miscellaneous": "miscellaneous",
"ModeToggleAuto": "auto mode",
"ModeToggleAudio": "audio mode",
"ModeToggleAuto": "auto",
"ModeToggleAudio": "audio",
"SettingsDisableNotifications": "hide notifications",
"MediaPickerTitle": "pick what to save",
"MediaPickerExplanationPC": "click or right click to download what you want.",
@ -127,7 +126,7 @@
"FeatureErrorGeneric": "your browser doesn't allow or support this feature. check if there are any updates available and try again!",
"ClipboardErrorFirefox": "you're using firefox where all clipboard reading functionality is disabled.\n\nyou can fix this by following steps listed <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">here!</a>\n\n...or you can paste the link manually instead.",
"ClipboardErrorNoPermission": "cobalt can't access the most recent item in your clipboard without your permission.\n\nif you don't want to give access, just paste the link manually instead.\n\nif you do, go to site settings and enable the clipboard permission.",
"SupportSelfTroubleshooting": "experiencing issues? try <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md\" target=\"_blank\">self-troubleshooting guide</a> first!",
"SupportSelfTroubleshooting": "experiencing issues? try one of these first:",
"AccessibilityGoBack": "go back and close the popup",
"CollapseKeyboard": "keyboard shortcuts",
"KeyboardShortcutsIntro": "use cobalt even faster with keyboard shortcuts:",
@ -157,6 +156,9 @@
"FilenamePreviewAudioTitle": "Audio Title",
"FilenamePreviewAudioAuthor": "Audio Author",
"UrgentFilenameUpdate": "customizable file names!",
"UrgentTwitterPatch": "fixes and easier downloads"
"UrgentTwitterPatch": "fixes and easier downloads",
"StatusPage": "service status page",
"TroubleshootingGuide": "self-troubleshooting guide",
"UpdateNewYears": "new years clean up"
}
}

View file

@ -1,14 +1,14 @@
{
"name": "русский",
"substrings": {
"ContactLink": "<a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">напиши об этом на github (можно на русском)</a>"
"ContactLink": "глянь <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">статус серверов</a> или <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">напиши о проблеме на github (можно на русском)</a>"
},
"strings": {
"AppTitleCobalt": "кобальт",
"LinkInput": "вставь ссылку сюда",
"AboutSummary": "кобальт - твой друг при скачивании контента из соцсетей и других сервисов. никакой рекламы, трекеров и прочего мусора. вставляешь ссылку и получаешь файл. всё. ничего лишнего.",
"EmbedBriefDescription": "сохраняй то, что любишь. без рекламы, трекеров и лишней мороки.",
"MadeWithLove": "сделано wukko, с <3",
"MadeWithLove": "сделано с любовью <3",
"AccessibilityInputArea": "зона вставки ссылки",
"AccessibilityOpenAbout": "открыть окно с инфой",
"AccessibilityDownloadButton": "кнопка скачивания",
@ -45,7 +45,6 @@
"SettingsEnableDownloadPopup": "выбор метода скачивания",
"AccessibilityEnableDownloadPopup": "спрашивать, что делать с загрузками",
"SettingsQualityDescription": "если выбранное качество недоступно, то выбирается ближайшее к нему.",
"LinkGitHubChanges": "&gt;&gt; смотри предыдущие изменения на github",
"NoScriptMessage": "кобальт использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких зловредных скриптов, обещаю.",
"DownloadPopupDescriptionIOS": "наиболее простой метод скачивания видео на ios:\n1. добавь <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">этот сценарий siri</a>.\n2. нажми \"поделиться\" выше и выбери \"save to photos\" в открывшемся окне.\nесли появляется окно с запросом разрешения, то прочитай его, потом нажми \"всегда разрешать\".\n\nальтернативный метод:\nзажми кнопку \"скачать\", затем скрой превью и выбери \"загрузить файл по ссылке\" в появившемся окне.\nпотом открой загрузки в safari, выбери скачанный файл, нажми иконку \"поделиться\", и, наконец, нажми \"сохранить видео\".",
"DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода скачивания файла в настройках.",
@ -77,12 +76,12 @@
"ImagePickerExplanationPhone": "зажми и удерживай картинку, чтобы её сохранить.",
"ErrorNoUrlReturned": "я не получил ссылку для скачивания от сервера. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.",
"ErrorUnknownStatus": "сервер ответил мне чем-то непонятным. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.",
"PasteFromClipboard": "вставить и скачать",
"PasteFromClipboard": "вставить",
"ChangelogOlder": "предыдущие версии (тоже на английском)",
"ChangelogPressToExpand": "раскрыть",
"Miscellaneous": "разное",
"ModeToggleAuto": "авто режим",
"ModeToggleAudio": "аудио режим",
"ModeToggleAuto": "авто",
"ModeToggleAudio": "аудио",
"SettingsDisableNotifications": "cкрыть уведомления",
"MediaPickerTitle": "выбери, что сохранить",
"MediaPickerExplanationPC": "кликни то, что хочешь скачать. также можно скачать правой кнопки мыши.",
@ -128,7 +127,7 @@
"FeatureErrorGeneric": "твой браузер не разрешает или не поддерживает эту функцию. проверь наличие обновлений и попробуй ещё раз!",
"ClipboardErrorFirefox": "ты используешь firefox в котором все функции чтения из буфера обмена отключены по умолчанию.\n\nно это можно исправить следуя шагам, описанным <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">здесь</a>\n\n...или же ты можешь просто вставить ссылку вручную.",
"ClipboardErrorNoPermission": "кобальт не может прочитать последний элемент в буфере обмена без твоего разрешения.\n\nесли ты не хочешь давать доступ, просто вставь ссылку вручную.\n\nну а если хочешь, то открой настройки сайта и разреши доступ на чтение буфера обмена.",
"SupportSelfTroubleshooting": "возникли проблемы? попробуй сначала исправить всё сам <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md\" target=\"_blank\">по этому гиду!</a>",
"SupportSelfTroubleshooting": "возникли проблемы? попробуй сначала что-то из этого:",
"AccessibilityGoBack": "вернуться назад и закрыть окно",
"CollapseKeyboard": "горячие клавиши",
"KeyboardShortcutsIntro": "пользуйся кобальтом ещё быстрее с горячими клавишами:",
@ -159,6 +158,9 @@
"FilenamePreviewAudioTitle": "Название Аудио",
"FilenamePreviewAudioAuthor": "Автор Аудио",
"UrgentFilenameUpdate": "изменяемые названия файлов!",
"UrgentTwitterPatch": "фиксы и удобное скачивание"
"UrgentTwitterPatch": "фиксы и удобное скачивание",
"StatusPage": "статус серверов",
"TroubleshootingGuide": "гайд по устранению проблем",
"UpdateNewYears": "новогодняя уборка"
}
}

View file

@ -16,7 +16,12 @@ export async function loadLoc() {
}
export function replaceBase(s) {
return s.replace(/\n/g, '<br/>').replace(/{saveToGalleryShortcut}/g, links.saveToGalleryShortcut).replace(/{repo}/g, repo).replace(/\*;/g, "&bull;");
return s
.replace(/\n/g, '<br/>')
.replace(/{saveToGalleryShortcut}/g, links.saveToGalleryShortcut)
.replace(/{repo}/g, repo)
.replace(/{statusPage}/g, links.statusPage)
.replace(/\*;/g, "&bull;");
}
export function replaceAll(lang, str, string, replacement) {
let s = replaceBase(str[string])

View file

@ -1,33 +1,30 @@
import UrlPattern from "url-pattern";
import { services } from "./config.js";
import { services as patterns } from "./config.js";
import { cleanURL, apiJSON } from "./sub/utils.js";
import { apiJSON } from "./sub/utils.js";
import { errorUnsupported } from "./sub/errors.js";
import loc from "../localization/manager.js";
import match from "./processing/match.js";
import hostOverrides from "./processing/hostOverrides.js";
import { getHostIfValid } from "./processing/url.js";
export async function getJSON(originalURL, lang, obj) {
export async function getJSON(url, lang, obj) {
try {
let patternMatch, url = encodeURI(decodeURIComponent(originalURL)),
hostname = new URL(url).hostname.split('.'),
host = hostname[hostname.length - 2];
const host = getHostIfValid(url);
if (!url.startsWith('https://')) return apiJSON(0, { t: errorUnsupported(lang) });
let overrides = hostOverrides(host, url);
host = overrides.host;
url = overrides.url;
if (!(host && host.length < 20 && host in patterns && patterns[host]["enabled"])) return apiJSON(0, { t: errorUnsupported(lang) });
let pathToMatch = cleanURL(url, host).split(`.${patterns[host]['tld'] ? patterns[host]['tld'] : "com"}/`)[1].replace('.', '');
for (let i in patterns[host]["patterns"]) {
patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(pathToMatch);
if (patternMatch) break
if (!host || !services[host].enabled) {
return apiJSON(0, { t: errorUnsupported(lang) });
}
let patternMatch;
for (const pattern of services[host].patterns) {
patternMatch = pattern.match(
url.pathname.substring(1) + url.search
);
if (patternMatch) break;
}
if (!patternMatch) {
return apiJSON(0, { t: errorUnsupported(lang) });
}
if (!patternMatch) return apiJSON(0, { t: errorUnsupported(lang) });
return await match(host, patternMatch, url, lang, obj)
} catch (e) {

View file

@ -1,16 +1,26 @@
{
"current": {
"version": "7.8",
"date": "December 25, 2023",
"title": "new years clean up! bug fixes and fresh look for the home page",
"banner": {
"file": "catroomba.webp",
"width": 300,
"height": 168
},
"content": "merry christmas and happy new year! this update fixes several (very annoying) bugs to help you enjoy your holidays better.\n\nyou might have already noticed, but we've refreshed the home page on desktop and mobile! less space wasted, more pleasant to look at. let us know if you like it or not :D\n\nservice improvements:\n*; <a class=\"text-backdrop link\" href=\"{repo}/issues/264\" target=\"_blank\">#264</a> anything that includes a period in the url should be possible to download (including instagram stories).\n*; <a class=\"text-backdrop link\" href=\"{repo}/issues/273\" target=\"_blank\">#73</a> soundcloud: falling back to mp3 instead of refusing to download the song at all.\n*; <a class=\"text-backdrop link\" href=\"{repo}/issues/275\" target=\"_blank\">#275</a> youtube: query parameters are parsed and handled correctly, all links should be supported, no matter where v query is located.\n*; tlds are parsed and validated correctly (e.g. \"pinterest.co.uk\" works now).\n\ninterface improvements:\n*; cleaner and more consistent home page layout.\n*; cleaned up support section in \"about\". also includes a link to the status page.\n\ninternal improvements:\n*; urls, subdomains, and tlds are properly validated.\n*; minor clean up.\n\nchanges since 7.7:\n*; made terms and ethics more descriptive.\n*; fix only affected twitter videos.\n*; fixed quick ⌘+V pasting on mac.\n*; now catching even more youtube-related errors.\n\nthis might not seem like a lot, but even smaller changes make a difference!\n\nenjoy this update and the rest of your day :D"
},
"history": [{
"version": "7.7",
"date": "December 2, 2023",
"title": "bugfixes and better downloads!",
"banner": {
"file": "meowthpolishegg.webp",
"width": 851,
"height": 640
"width": 640,
"height": 480
},
"content": "this update fixes various issues with supported services. no new features yet, but twitter fix is surely something good to have in the meantime!\n\nservice improvements:\n*; broken twitter videos are now automatically fixed by cobalt.\n*; all vimeo videos and audios should now be possible to download.\n*; vimeo: fixed short resolution displayed in \"basic\" and \"pretty\" filename styles.\n\ninterface improvements:\n*; streamables are now easier to save on ios.\n\ninternal improvements:\n*; port env variable is now not strictly necessary for cobalt to run.\n*; minor clean up.\n\nchanges since 7.6:\n*; fix for an issue related to youtube dubs.\n*; fixed a memory leak related to live renders.\n*; handling all errors related to twitter downloads.\n*; fixed support for reddit links in various languages.\n*; added rich filenames support for twitch clips.\n*; updated support and donation lists.\n\nstay tuned for future updates and have a great day :D"
},
"history": [{
}, {
"version": "7.6",
"date": "October 15, 2023",
"title": "customizable file names, instagram stories, and first cobalt sponsor!",
@ -19,7 +29,7 @@
"width": 851,
"height": 640
},
"content": "as many have (very) often requested, cobalt now lets you pick between several file name format styles!\ngo to <span class=\"text-backdrop\">settings > other</span> and change it to whichever you like! there's a preview of each style, so you know how exactly files are gonna look like.\n\nif you liked file names the way they were before, don't worry: classic style is still the default :)\n\non a different but not any less important note: cobalt is now sponsored by <a class=\"text-backdrop link\" href=\"https://royalehosting.net/\" target=\"_blank\">royalehosting.net</a>!\noverall service performance and stability is gonna be better, but also more content will be possible to download thanks to geniuine server locations. and yes, still no ads or trackers.\n\nthis update also includes a bunch of other changes, check them out:\n\nservice improvements:\n*; added support for instagram stories thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/194\" target=\"_blank\">#194</a>.\n*; fixed reddit support thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/221\" target=\"_blank\">#221</a>.\n*; added support for rich file names for youtube, vimeo, soundcloud, rutube, and vk.\n*; numbers and emoji no longer disappear from file name and metadata.\n*; mute and audio dub file name tags don't appear together anymore.\n*; youtube: dub file name tag doesn't appear anymore if audio track is default.\n\ninterface improvements:\n*; added a list of sponsors to about tab. if you host an instance, it's disabled by default, but can be enabled with showSponsors env variable.\n*; about button now opens about tab when no new changelog is available.\n*; fixed download button thickness on ios.\n\nyou now can reach out to cobalt via email for support! it's located in the about tab along with other socials, such as discord.\n\ni hope you enjoy this long-awaited update and have a blissful day :D"
"content": "as many have (very) often requested, cobalt now lets you pick between several file name format styles!\ngo to <span class=\"text-backdrop\">settings > other</span> and change it to whichever you like! there's a preview of each style, so you know how exactly files are gonna look like.\n\nif you liked file names the way they were before, don't worry: classic style is still the default :)\n\non a different but not any less important note: cobalt is now sponsored by <a class=\"text-backdrop link\" href=\"https://royalehosting.net/\" target=\"_blank\">royalehosting.net</a>!\noverall service performance and stability is gonna be better, but also more content will be possible to download thanks to geniuine server locations. and yes, still no ads or trackers.\n\nthis update also includes a bunch of other changes, check them out:\n\nservice improvements:\n*; added support for instagram stories thanks to <a class=\"text-backdrop link\" href=\"{repo}/pull/194\" target=\"_blank\">#194</a>.\n*; fixed reddit support thanks to <a class=\"text-backdrop link\" href=\"{repo}/pull/221\" target=\"_blank\">#221</a>.\n*; added support for rich file names for youtube, vimeo, soundcloud, rutube, and vk.\n*; numbers and emoji no longer disappear from file name and metadata.\n*; mute and audio dub file name tags don't appear together anymore.\n*; youtube: dub file name tag doesn't appear anymore if audio track is default.\n\ninterface improvements:\n*; added a list of sponsors to about tab. if you host an instance, it's disabled by default, but can be enabled with showSponsors env variable.\n*; about button now opens about tab when no new changelog is available.\n*; fixed download button thickness on ios.\n\nyou now can reach out to cobalt via email for support! it's located in the about tab along with other socials, such as discord.\n\ni hope you enjoy this long-awaited update and have a blissful day :D"
}, {
"version": "7.5",
"date": "September 16, 2023",
@ -49,7 +59,7 @@
"width": 500,
"height": 280
},
"content": "this update gives cobalt a sharp look in chromium browsers and makes it even more useful than before. check out the full changelog below!\n\nservice improvements:\n*; increased video length limit from 3 hours to 5 hours. feel free to download lectures you need :)\n*; you can now disable file metadata in settings.\n*; fixed a bug which previously caused some downloads to end up being 0 bytes.\n\nui improvements:\n*; fixed clickable area for urgent notice (text on top).\n*; fixed blurry header in chrome.\n*; fixed blurry tab bar in chrome.\n*; fixed blurry switches in chrome.\n*; fixed weirdly rounded corners in popups.\n*; fixed 1px gap on edges of various elements in popup in chrome.\n*; fixed overscrolling in other settings tab on ios.\n*; fixed unexpected button highlight effect on phones.\n*; removed outdated fixes for tiny screens.\n\nother improvements:\n*; cobalt web & api start faster than before, additional preparation functions aren't unexpectedly run anymore.\n*; cobalt is now available as a docker package. check it out on <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pkgs/container/cobalt\" target=\"_blank\">github</a>.\n\nthank you for being here. i hope you have a great day :D"
"content": "this update gives cobalt a sharp look in chromium browsers and makes it even more useful than before. check out the full changelog below!\n\nservice improvements:\n*; increased video length limit from 3 hours to 5 hours. feel free to download lectures you need :)\n*; you can now disable file metadata in settings.\n*; fixed a bug which previously caused some downloads to end up being 0 bytes.\n\nui improvements:\n*; fixed clickable area for urgent notice (text on top).\n*; fixed blurry header in chrome.\n*; fixed blurry tab bar in chrome.\n*; fixed blurry switches in chrome.\n*; fixed weirdly rounded corners in popups.\n*; fixed 1px gap on edges of various elements in popup in chrome.\n*; fixed overscrolling in other settings tab on ios.\n*; fixed unexpected button highlight effect on phones.\n*; removed outdated fixes for tiny screens.\n\nother improvements:\n*; cobalt web & api start faster than before, additional preparation functions aren't unexpectedly run anymore.\n*; cobalt is now available as a docker package. check it out on <a class=\"text-backdrop link\" href=\"{repo}/pkgs/container/cobalt\" target=\"_blank\">github</a>.\n\nthank you for being here. i hope you have a great day :D"
}, {
"version": "7.1",
"date": "August 20, 2023",
@ -59,7 +69,7 @@
"width": 640,
"height": 358
},
"content": "service improvements:\n*; extended instagram support: high quality photos, videos, reels. everything should work without any issues, enjoy! :)\n*; added support for streamable.com (thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/179\" target=\"_blank\">#179</a>)\n*; added video metadata to youtube videos.\n*; fixed vk video downloads.\n*; vxtwitter links are now supported.\n*; fixed support for youtube audio dubs.\n\nui improvements:\n*; fixed picker popup: it's now scrollable in all cases and clickable areas don't overlap each other.\n\nbackend improvements:\n*; cobalt will now let you know if something goes wrong during video download instead of nuking the stream.\n*; added support for cookies (thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/177\" target=\"_blank\">#177</a>)\n*; replaced got with undici (thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/182\" target=\"_blank\">#182</a>). downloads should be slightly faster and clean of garbage in headers.\n\ninternal improvements:\n*; moved host overrides into its own module.\n*; minor clean ups.\n\neven more cool stuff is coming in future updates! thank you for using cobalt :D"
"content": "service improvements:\n*; extended instagram support: high quality photos, videos, reels. everything should work without any issues, enjoy! :)\n*; added support for streamable.com (thanks to <a class=\"text-backdrop link\" href=\"{repo}/pull/179\" target=\"_blank\">#179</a>)\n*; added video metadata to youtube videos.\n*; fixed vk video downloads.\n*; vxtwitter links are now supported.\n*; fixed support for youtube audio dubs.\n\nui improvements:\n*; fixed picker popup: it's now scrollable in all cases and clickable areas don't overlap each other.\n\nbackend improvements:\n*; cobalt will now let you know if something goes wrong during video download instead of nuking the stream.\n*; added support for cookies (thanks to <a class=\"text-backdrop link\" href=\"{repo}/pull/177\" target=\"_blank\">#177</a>)\n*; replaced got with undici (thanks to <a class=\"text-backdrop link\" href=\"{repo}/pull/182\" target=\"_blank\">#182</a>). downloads should be slightly faster and clean of garbage in headers.\n\ninternal improvements:\n*; moved host overrides into its own module.\n*; minor clean ups.\n\neven more cool stuff is coming in future updates! thank you for using cobalt :D"
}, {
"version": "7.0",
"date": "August 15, 2023",
@ -89,7 +99,7 @@
"width": 600,
"height": 314
},
"content": "hey! long time no see, hopefully over 40 changes will make up for it :)\n\ncobalt now has an official community discord server. you can go there for news, support, or just to chat. <a class=\"text-backdrop link\" href=\"https://discord.gg/pQPt8HBUPu\" target=\"_blank\">go check it out!</a>\n\n<span class='text-backdrop'>tl;dr</span>\n*; new infra, new hosting structure, new main instance api url. developers, <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/blob/current/docs/API.md\" target=\"_blank\">get it here.</a>\n*; added support for pinterest, vine archive, tumblr audio, youtube vr videos.\n*; better web app performance and look.\n*; better stability thanks to load balancing.\n*; (hopefully) no more random video/audio download drops.\n\nservice improvements:\n*; added support for pinterest videos and stories (pr by <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/commit/40291c4d24cb5f441cdddfd26104f149bc4ee27c\" target=\"_blank\">@Snazzah</a>).\n*; added support for tumblr audio. sorry, tumblr.\n*; added support for youtube vr videos. please note that they're in youtube's proprietary ratio.\n*; added support for vine archive.\n*; added support for ancient vk videos in 240p.\n*; fixed an issue related to muted video downloads from tumblr.\n*; moved to twitter v2 api.\n*; soundcloud share links are now processed without errors.\n\nui improvements:\n*; lazy image loading. should significantly speed up the page load.\n*; fixed checkbox width on mobile devices.\n*; addition of a temporary urgent notice.\n*; added hover border to all buttons.\n*; less annoying donation button highlight.\n*; more consistent color scheme.\n*; added link to a discord server into about popup.\n*; remember celebratory emoji changes? they've been fixed, and are now dynamically loaded!\n*; changelog history now lets you try to load it again if first attempt failed for whatever reason.\n*; padding (everywhere) has been slightly reduced to fit in more content and be consistent across ui.\n*; added more info to the \"how to save\" popup for ios devices.\n*; crypto wallet press-to-copy buttons now look like buttons.\n*; improved ui layout for smallest screens (iphone 5, 5s, se, etc).\n*; removed partial translations for sake of clarity and consistency.\n\ninternal improvements:\n*; separated web and api servers. they're now completely independent and therefore more stress-resistant.\n*; added a dedicated script for building the web app if you don't want to reload the frontend server.\n*; web app building improvements.\n*; async localization preloading.\n*; consistent server start time reporting.\n*; dynamic stream and ip hashing salt generation.\n\ninfrastructure improvements:\n*; load balancing: your api requests are now sent to the least busy server. yes, there are now several of them with more to come in the future.\n*; when possible, server in closest region is used instead of a far-away one. this should help with download speeds.\n*; currently there are multiple servers in europe. i will let you know when (and if) i manage to get an american one.\n\nupdates for developers and instance hosters:\n*; server info api endpoint: you can now check up on the api server of choice. it reports all the basic info you may need. <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/blob/current/docs/API.md#get-apiserverinfo\" target=\"_blank\">check the api docs</a> for more info.\n*; api names: each and every api instance should have a distinctive name. this will be useful in the future :)\n*; added docker compose sample config.\n*; updated and more granular setup script.\n*; better api scalability and faster server start up thanks to web and api separation.\n*; added ability to specify ffmpeg threads. simply add ffmpegThreads to your environment variables!\n\ni'm still in awe from how popular cobalt has become. there are now over 200k of unique users monthly, and that number only keeps growing. i even had to come up with something to accommodate for larger traffic, it's absolutely insane.\n\nlove you all, have a great day :D"
"content": "hey! long time no see, hopefully over 40 changes will make up for it :)\n\ncobalt now has an official community discord server. you can go there for news, support, or just to chat. <a class=\"text-backdrop link\" href=\"https://discord.gg/pQPt8HBUPu\" target=\"_blank\">go check it out!</a>\n\n<span class='text-backdrop'>tl;dr</span>\n*; new infra, new hosting structure, new main instance api url. developers, <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/API.md\" target=\"_blank\">get it here.</a>\n*; added support for pinterest, vine archive, tumblr audio, youtube vr videos.\n*; better web app performance and look.\n*; better stability thanks to load balancing.\n*; (hopefully) no more random video/audio download drops.\n\nservice improvements:\n*; added support for pinterest videos and stories (pr by <a class=\"text-backdrop link\" href=\"{repo}/commit/40291c4d24cb5f441cdddfd26104f149bc4ee27c\" target=\"_blank\">@Snazzah</a>).\n*; added support for tumblr audio. sorry, tumblr.\n*; added support for youtube vr videos. please note that they're in youtube's proprietary ratio.\n*; added support for vine archive.\n*; added support for ancient vk videos in 240p.\n*; fixed an issue related to muted video downloads from tumblr.\n*; moved to twitter v2 api.\n*; soundcloud share links are now processed without errors.\n\nui improvements:\n*; lazy image loading. should significantly speed up the page load.\n*; fixed checkbox width on mobile devices.\n*; addition of a temporary urgent notice.\n*; added hover border to all buttons.\n*; less annoying donation button highlight.\n*; more consistent color scheme.\n*; added link to a discord server into about popup.\n*; remember celebratory emoji changes? they've been fixed, and are now dynamically loaded!\n*; changelog history now lets you try to load it again if first attempt failed for whatever reason.\n*; padding (everywhere) has been slightly reduced to fit in more content and be consistent across ui.\n*; added more info to the \"how to save\" popup for ios devices.\n*; crypto wallet press-to-copy buttons now look like buttons.\n*; improved ui layout for smallest screens (iphone 5, 5s, se, etc).\n*; removed partial translations for sake of clarity and consistency.\n\ninternal improvements:\n*; separated web and api servers. they're now completely independent and therefore more stress-resistant.\n*; added a dedicated script for building the web app if you don't want to reload the frontend server.\n*; web app building improvements.\n*; async localization preloading.\n*; consistent server start time reporting.\n*; dynamic stream and ip hashing salt generation.\n\ninfrastructure improvements:\n*; load balancing: your api requests are now sent to the least busy server. yes, there are now several of them with more to come in the future.\n*; when possible, server in closest region is used instead of a far-away one. this should help with download speeds.\n*; currently there are multiple servers in europe. i will let you know when (and if) i manage to get an american one.\n\nupdates for developers and instance hosters:\n*; server info api endpoint: you can now check up on the api server of choice. it reports all the basic info you may need. <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/API.md#get-apiserverinfo\" target=\"_blank\">check the api docs</a> for more info.\n*; api names: each and every api instance should have a distinctive name. this will be useful in the future :)\n*; added docker compose sample config.\n*; updated and more granular setup script.\n*; better api scalability and faster server start up thanks to web and api separation.\n*; added ability to specify ffmpeg threads. simply add ffmpegThreads to your environment variables!\n\ni'm still in awe from how popular cobalt has become. there are now over 200k of unique users monthly, and that number only keeps growing. i even had to come up with something to accommodate for larger traffic, it's absolutely insane.\n\nlove you all, have a great day :D"
}, {
"version": "5.4",
"title": "instagram support, docker, and more!",
@ -170,7 +180,7 @@
"width": 500,
"height": 280
},
"content": "your favorite social media downloader just got even better! this update includes a ton of improvements and fixes.\n\nin fact, there are so many changes, i had to split them in sections.\n\nservice-related improvements:\n*; vimeo module has been revamped, all sorts of videos should now be supported.\n*; vimeo audio downloads! you now can download audios from more recent videos.\n*; cobalt now supports all sorts of tumblr links. (even those scary ones from the mobile app)\n*; vk clips support has been fixed. they rolled back the separation of videos and clips, so i had to do the same.\n*; youtube videos with community warnings should now be possible to download.\nuser interface improvements:\n*; list of supported services is now MUCH easier to read.\n*; banners in changelog history should no longer overlap each other.\n*; bullet points! they have a bit of extra padding, so it makes them stand out of the rest of text.\ninternal improvements:\n*; cobalt will now match the link to regex when using ?u= query for autopasting it into input area.\n*; better rate limiting: limiting now is done per minute, not per 20 minutes. this ensures less waiting and less attack area for request spammers.\n*; moved to my own fork of ytdl-core, cause main project seems to have been abandoned. go check it out on <a class=\"text-backdrop link\" href=\"https://github.com/wukko/better-ytdl-core\" target=\"_blank\">github</a> or <a class=\"text-backdrop link\" href=\"https://www.npmjs.com/package/better-ytdl-core\" target=\"_blank\">npm</a>!\n*; ALL user inputs are now properly sanitized on the server. that includes variables for POST api method, too.\n*; \"got\" package has been (mostly) replaced by native fetch api. this should greatly reduce ram usage.\n*; all unnecessary duplications of module imports have been gotten rid of. no more error passing strings from inside of service modules. you don't make mistakes only if you don't do anything, right?\n*; other code optimizations. there's less clutter overall.\nhuge update, right? seems like everything's fixed now?\n\nnope, one issue still persists: sometimes youtube server drops packets for an audio file while cobalt's rendering the video for you. this results in abrupt cuts of audio. if you want to help solving this issue, <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/issues/62\" target=\"_blank\">please feel free to do it on github!</a>\n\nthank you for reading this, and thank you for sticking with cobalt and me."
"content": "your favorite social media downloader just got even better! this update includes a ton of improvements and fixes.\n\nin fact, there are so many changes, i had to split them in sections.\n\nservice-related improvements:\n*; vimeo module has been revamped, all sorts of videos should now be supported.\n*; vimeo audio downloads! you now can download audios from more recent videos.\n*; cobalt now supports all sorts of tumblr links. (even those scary ones from the mobile app)\n*; vk clips support has been fixed. they rolled back the separation of videos and clips, so i had to do the same.\n*; youtube videos with community warnings should now be possible to download.\nuser interface improvements:\n*; list of supported services is now MUCH easier to read.\n*; banners in changelog history should no longer overlap each other.\n*; bullet points! they have a bit of extra padding, so it makes them stand out of the rest of text.\ninternal improvements:\n*; cobalt will now match the link to regex when using ?u= query for autopasting it into input area.\n*; better rate limiting: limiting now is done per minute, not per 20 minutes. this ensures less waiting and less attack area for request spammers.\n*; moved to my own fork of ytdl-core, cause main project seems to have been abandoned. go check it out on <a class=\"text-backdrop link\" href=\"https://github.com/wukko/better-ytdl-core\" target=\"_blank\">github</a> or <a class=\"text-backdrop link\" href=\"https://www.npmjs.com/package/better-ytdl-core\" target=\"_blank\">npm</a>!\n*; ALL user inputs are now properly sanitized on the server. that includes variables for POST api method, too.\n*; \"got\" package has been (mostly) replaced by native fetch api. this should greatly reduce ram usage.\n*; all unnecessary duplications of module imports have been gotten rid of. no more error passing strings from inside of service modules. you don't make mistakes only if you don't do anything, right?\n*; other code optimizations. there's less clutter overall.\nhuge update, right? seems like everything's fixed now?\n\nnope, one issue still persists: sometimes youtube server drops packets for an audio file while cobalt's rendering the video for you. this results in abrupt cuts of audio. if you want to help solving this issue, <a class=\"text-backdrop link\" href=\"{repo}/issues/62\" target=\"_blank\">please feel free to do it on github!</a>\n\nthank you for reading this, and thank you for sticking with cobalt and me."
}, {
"version": "4.4",
"title": "over 1 million monthly requests. thank you.",
@ -192,7 +202,7 @@
"width": 640,
"height": 360
},
"content": "this update features a TON of improvements.\n\n<a class=\"text-backdrop link\" href=\"https://www.youtube.com/watch?v=SaVTHG-Ev4k\" target=\"_blank\">developers</a>, you now can rely on cobalt for getting content from social media. the api has been revamped and <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/tree/current/docs/API.md\" target=\"_blank\">documentation</a> is now available. you can read more about API changes down below. go crazy, and have fun :D\n\nif you're not a developer, here's a list of changes that you probably care about:\n- rate limit is now approximately 8 times bigger. no more waiting, even if you want to download entirety of your tiktok \"for you\" page.\n- some updates will now have expressive banners, just like this one.\n- fixed what was causing an error when a youtube video had no description.\n- mp4 format button text should now be displayed properly, no matter if you touched the switcher or not.\n\nnext, the star of this update — improved api!\n- main endpoint now uses POST method instead of GET.\n- internal variables for preferences have been updated to be consistent and easier to understand.\n- ip address is now hashed right upon request, not somewhere deep inside the code.\n- global stream salt variable is no longer unnecessarily passed over a billion functions.\n- url and picker keys are now separate in the json response.\n- cobalt web app now correctly processes responses with \"success\" status.\n\nif you currently have a siri shortcut or some other script that uses the GET method, make sure to update it soon. this method is deprecated, limited, and will be removed entirely in coming updates.\n\nif you ever make something using cobalt's api, make sure to mention <a class=\"text-backdrop link\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a> on twitter, i would absolutely love to see what you made."
"content": "this update features a TON of improvements.\n\n<a class=\"text-backdrop link\" href=\"https://www.youtube.com/watch?v=SaVTHG-Ev4k\" target=\"_blank\">developers</a>, you now can rely on cobalt for getting content from social media. the api has been revamped and <a class=\"text-backdrop link\" href=\"{repo}/tree/current/docs/API.md\" target=\"_blank\">documentation</a> is now available. you can read more about API changes down below. go crazy, and have fun :D\n\nif you're not a developer, here's a list of changes that you probably care about:\n- rate limit is now approximately 8 times bigger. no more waiting, even if you want to download entirety of your tiktok \"for you\" page.\n- some updates will now have expressive banners, just like this one.\n- fixed what was causing an error when a youtube video had no description.\n- mp4 format button text should now be displayed properly, no matter if you touched the switcher or not.\n\nnext, the star of this update — improved api!\n- main endpoint now uses POST method instead of GET.\n- internal variables for preferences have been updated to be consistent and easier to understand.\n- ip address is now hashed right upon request, not somewhere deep inside the code.\n- global stream salt variable is no longer unnecessarily passed over a billion functions.\n- url and picker keys are now separate in the json response.\n- cobalt web app now correctly processes responses with \"success\" status.\n\nif you currently have a siri shortcut or some other script that uses the GET method, make sure to update it soon. this method is deprecated, limited, and will be removed entirely in coming updates.\n\nif you ever make something using cobalt's api, make sure to mention <a class=\"text-backdrop link\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a> on twitter, i would absolutely love to see what you made."
}, {
"version": "4.2",
"title": "optimized quality picking and 8k video support",
@ -204,7 +214,7 @@
}, {
"version": "4.0",
"title": "better and faster than ever",
"content": "this update has a ton of improvements and new features.\n\nchanges you probably care about:\n- cobalt now has support for recorded twitter spaces! download the previous conversation no matter how long it was.\n- download speeds from youtube are at least 10 times better now. you're welcome.\n- both video and audio length limits have been extended to 2 hours.\n- audio downloads from youtube, youtube music, twitter spaces, and soundcloud now have metadata! most often it's just title and artist, but when cobalt is able to get more info, it adds that metadata too.\n- tiktok downloads have been fixed, yet again, and if they ever break in the future, cobalt will fall back to downloading a less annoyingly watermarked video.\n- soundcloud downloads have been fixed, too.\n\nless notable changes:\n- currently experimenting with using mp3 as default audio format. if you set something other than mp3 before, it'll be set to mp3. you can always change it back in settings. let me know what you think about this.\n- \"download audio\" button from image picker no longer stays on the screen after popup was closed.\n- clipboard button now shows up depending on your browser's support for it.\n- you can no longer manually hide the clipboard button, 'cause it's unnecessary.\n- small internal improvements such as separation of changelog version and title.\n- fair bit of internal clean up.\n\nif you want to help me implement covers for downloaded audios, <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt\" target=\"_blank\">you can do it on github</a>."
"content": "this update has a ton of improvements and new features.\n\nchanges you probably care about:\n- cobalt now has support for recorded twitter spaces! download the previous conversation no matter how long it was.\n- download speeds from youtube are at least 10 times better now. you're welcome.\n- both video and audio length limits have been extended to 2 hours.\n- audio downloads from youtube, youtube music, twitter spaces, and soundcloud now have metadata! most often it's just title and artist, but when cobalt is able to get more info, it adds that metadata too.\n- tiktok downloads have been fixed, yet again, and if they ever break in the future, cobalt will fall back to downloading a less annoyingly watermarked video.\n- soundcloud downloads have been fixed, too.\n\nless notable changes:\n- currently experimenting with using mp3 as default audio format. if you set something other than mp3 before, it'll be set to mp3. you can always change it back in settings. let me know what you think about this.\n- \"download audio\" button from image picker no longer stays on the screen after popup was closed.\n- clipboard button now shows up depending on your browser's support for it.\n- you can no longer manually hide the clipboard button, 'cause it's unnecessary.\n- small internal improvements such as separation of changelog version and title.\n- fair bit of internal clean up.\n\nif you want to help me implement covers for downloaded audios, <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">you can do it on github</a>."
}, {
"version": "3.7",
"title": "support for multi media tweets is here!",

View file

@ -1,8 +1,17 @@
import UrlPattern from "url-pattern";
import { loadJSON } from "./sub/loadFromFs.js";
const config = loadJSON("./src/config.json");
const packageJson = loadJSON("./package.json");
const servicesConfigJson = loadJSON("./src/modules/processing/servicesConfig.json");
Object.values(servicesConfigJson.config).forEach(service => {
service.patterns = service.patterns.map(
pattern => new UrlPattern(pattern, {
segmentValueCharset: UrlPattern.defaultOptions.segmentValueCharset + '@\\.'
})
)
})
export const
services = servicesConfigJson.config,
audioIgnore = servicesConfigJson.audioIgnore,

View file

@ -39,7 +39,10 @@ const names = {
"🎞️": "film_frames",
"🎧": "headphone",
"📧": "email",
"📬": "mailbox"
"📬": "mailbox",
"📢": "loudspeaker",
"🔧": "wrench",
"🫧": "bubbles"
}
let sizing = {
18: 0.8,

View file

@ -10,6 +10,8 @@ export const dropdownSVG = `<svg width="18" height="18" viewBox="0 0 32 32" fill
<path d="M28 12.0533L16 24L4 12.0533L6.03571 10L14.7188 18.4104L16.25 19.9348L17.7813 18.4104L25.9375 10L28 12.0533Z" fill="#E1E1E1"/>
</svg>`
export const linkSVG = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M137.54 186.36a8 8 0 0 1 0 11.31l-9.94 10a56 56 0 0 1-79.22-79.27l24.12-24.12a56 56 0 0 1 76.81-2.28a8 8 0 1 1-10.64 12a40 40 0 0 0-54.85 1.63L59.7 139.72a40 40 0 0 0 56.58 56.58l9.94-9.94a8 8 0 0 1 11.32 0Zm70.08-138a56.08 56.08 0 0 0-79.22 0l-9.94 9.95a8 8 0 0 0 11.32 11.31l9.94-9.94a40 40 0 0 1 56.58 56.58l-24.12 24.14a40 40 0 0 1-54.85 1.6a8 8 0 1 0-10.64 12a56 56 0 0 0 76.81-2.26l24.12-24.12a56.08 56.08 0 0 0 0-79.24Z"/></svg>'
export function switcher(obj) {
let items = ``;
if (obj.name === "download") {
@ -17,8 +19,6 @@ export function switcher(obj) {
} else {
for (let i = 0; i < obj.items.length; i++) {
let classes = obj.items[i]["classes"] ? obj.items[i]["classes"] : [];
if (i === 0) classes.push("first");
if (i === (obj.items.length - 1)) classes.push("last");
items += `<button id="${obj.name}-${obj.items[i]["action"]}" class="switch${classes.length > 0 ? ' ' + classes.join(' ') : ''}" onclick="changeSwitcher('${obj.name}', '${obj.items[i]["action"]}')">${obj.items[i]["text"] ? obj.items[i]["text"] : obj.items[i]["action"]}</button>`
}
}
@ -68,7 +68,7 @@ export function popup(obj) {
}
}
return `
${obj.standalone ? `<div id="popup-${obj.name}" class="popup center${!obj.buttonOnly ? " box": ''}${classes.length > 0 ? ' ' + classes.join(' ') : ''}">` : ''}
${obj.standalone ? `<div id="popup-${obj.name}" class="popup center${!obj.buttonOnly ? " box" : ''}${classes.length > 0 ? ' ' + classes.join(' ') : ''}">` : ''}
<div id="popup-header" class="popup-header">
<div id="popup-header-contents">
${obj.buttonOnly ? obj.header.emoji : ``}
@ -76,12 +76,12 @@ export function popup(obj) {
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}
</div>
${!obj.buttonOnly ? `<div class="glass-bkg alone"></div>`: ''}
${!obj.buttonOnly ? `<div class="glass-bkg alone"></div>` : ''}
</div>
<div id="popup-content" class="popup-content-inner">
${body}${obj.buttonOnly ? `<button id="close-error" class="switch" onclick="popup('${obj.name}', 0)">${obj.buttonText}</button>` : ''}
</div>
${classes.includes("small") ? `<div class="glass-bkg small"></div>`: ''}
${classes.includes("small") ? `<div class="glass-bkg small"></div>` : ''}
${obj.standalone ? `</div>` : ''}`
}
@ -119,8 +119,6 @@ export function collapsibleList(arr) {
for (let i = 0; i < arr.length; i++) {
let classes = arr[i]["classes"] ? arr[i]["classes"] : [];
if (i === 0) classes.push("first");
if (i === (arr.length - 1)) classes.push("last");
items += `<div id="${arr[i]["name"]}-collapse" class="collapse-list${classes.length > 0 ? ' ' + classes.join(' ') : ''}">
<div class="collapse-header" onclick="expandCollapsible(event)">
<div class="collapse-title">${arr[i]["title"]}</div>
@ -158,15 +156,15 @@ export function popupWithBottomButtons(obj) {
</div>
</div>`
}
export function socialLink(emji, name, handle, url) {
return `<div class="cobalt-support-link">${emji} ${name}: <a class="text-backdrop link" href="${url}" target="_blank">${handle}</a></div>`
export function socialLink(emji, name, url) {
return `<div class="cobalt-support-link">${emji} <a class="text-backdrop link" href="${url}" target="_blank">${name}</a></div>`
}
export function socialLinks(lang) {
let links = authorInfo.support[lang] ? authorInfo.support[lang] : authorInfo.support.default;
let r = ``;
for (let i in links) {
r += socialLink(
emoji(links[i].emoji), i, links[i].handle, links[i].url
emoji(links[i].emoji), links[i].name, links[i].url
)
}
return r
@ -181,32 +179,22 @@ export function settingsCategory(obj) {
export function footerButtons(obj) {
let items = ``
for (let i = 0; i < obj.length; i++) {
switch (obj[i]["type"]) {
case "toggle":
items += `<button id="${obj[i]["name"]}-footer" class="switch footer-button" onclick="toggle('${obj[i]["name"]}')" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>`;
break;
case "action":
items += `<button id="${obj[i]["name"]}-footer" class="switch footer-button" onclick="${obj[i]["action"]}()" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>`;
break;
case "popup":
let buttonName = obj[i]["context"] ? `${obj[i]["name"]}-${obj[i]["context"]}` : obj[i]["name"],
context = obj[i]["context"] ? `, '${obj[i]["context"]}'` : '',
buttonName2,
context2;
let buttonName = obj[i]["context"] ? `${obj[i]["name"]}-${obj[i]["context"]}` : obj[i]["name"],
context = obj[i]["context"] ? `, '${obj[i]["context"]}'` : '',
buttonName2,
context2;
if (obj[i+1]) {
buttonName2 = obj[i+1]["context"] ? `${obj[i+1]["name"]}-${obj[i+1]["context"]}` : obj[i+1]["name"];
context2 = obj[i+1]["context"] ? `, '${obj[i+1]["context"]}'` : '';
}
items += `
<div class="footer-pair">
<button id="${buttonName}-footer" class="switch footer-button" onclick="popup('${obj[i]["name"]}', 1${context})" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>
${obj[i+1] ? `<button id="${buttonName2}-footer" class="switch footer-button" onclick="popup('${obj[i+1]["name"]}', 1${context2})" aria-label="${obj[i+1]["aria"]}">${obj[i+1]["text"]}</button>`: ''}
</div>`;
i++;
break;
if (obj[i + 1]) {
buttonName2 = obj[i + 1]["context"] ? `${obj[i + 1]["name"]}-${obj[i + 1]["context"]}` : obj[i + 1]["name"];
context2 = obj[i + 1]["context"] ? `, '${obj[i + 1]["context"]}'` : '';
}
items +=
`<div class="footer-pair">
<button id="${buttonName}-footer" class="switch footer-button" onclick="popup('${obj[i]["name"]}', 1${context})" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>
${obj[i + 1] ? `<button id="${buttonName2}-footer" class="switch footer-button" onclick="popup('${obj[i + 1]["name"]}', 1${context2})" aria-label="${obj[i + 1]["aria"]}">${obj[i + 1]["text"]}</button>` : ''}
</div>`;
i++;
}
return `
<div id="footer-buttons">${items}</div>`

View file

@ -1,5 +1,5 @@
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, socialLinks, urgentNotice, keyboardShortcuts, webLoc, sponsoredList, betaTag } from "./elements.js";
import { services as s, authorInfo, version, repo, donations, supportedAudio } from "../config.js";
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, socialLinks, urgentNotice, keyboardShortcuts, webLoc, sponsoredList, betaTag, linkSVG } from "./elements.js";
import { services as s, authorInfo, version, repo, donations, supportedAudio, links } from "../config.js";
import { getCommitInfo } from "../sub/currentCommit.js";
import loc from "../../localization/manager.js";
import emoji from "../emoji.js";
@ -146,15 +146,15 @@ export default function(obj) {
}, {
name: "support",
title: `${emoji("❤️‍🩹")} ${t("CollapseSupport")}`,
body:
`${t("SupportSelfTroubleshooting")}<br/><br/>`
+ `${t("FollowSupport")}<br/>`
+ `${socialLinks(obj.lang)}<br/>`
+ `${t("SourceCode")}<br/>`
+ `${socialLink(
emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo
)}<br/>
${t("SupportNote")}`
body: `${t("SupportSelfTroubleshooting")}`
+ `${socialLink(emoji("📢"), t("StatusPage"), links.statusPage)}`
+ `${socialLink(emoji("🔧"), t("TroubleshootingGuide"), links.troubleshootingGuide)}`
+ `<br/>`
+ `${t("FollowSupport")}`
+ `${socialLinks(obj.lang)}`
+ `<br/>`
+ `${t("SourceCode")}`
+ `${socialLink(emoji("🐙"), repo.replace("https://github.com/", ''), repo)}`
}, {
name: "privacy",
title: `${emoji("🔒")} ${t("CollapsePrivacy")}`,
@ -562,8 +562,8 @@ export default function(obj) {
<div id="popup-backdrop" onclick="hideAllPopups()"></div>
<div id="home" style="visibility:hidden">
${urgentNotice({
emoji: "🧮",
text: t("UrgentTwitterPatch"),
emoji: "🫧",
text: t("UpdateNewYears"),
visible: true,
action: "popup('about', 1, 'changelog')"
})}
@ -571,7 +571,8 @@ export default function(obj) {
<div id="logo">${t("AppTitleCobalt")}${betaTag()}</div>
<div id="download-area">
<div id="top">
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${t('LinkInput')}" aria-label="${t('AccessibilityInputArea')}" oninput="button()"></input>
<div id="link-icon">${linkSVG}</div>
<input id="url-input-area" class="mono" type="text" autofocus autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${t('LinkInput')}" aria-label="${t('AccessibilityInputArea')}" oninput="button()"></input>
<button id="url-clear" onclick="clearInput()" style="display:none;">x</button>
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${t('AccessibilityDownloadButton')}">
</div>

View file

@ -1,48 +0,0 @@
export default function (inHost, inURL) {
let host = String(inHost);
let url = String(inURL);
switch(host) {
case "youtube":
if (url.startsWith("https://youtube.com/live/") || url.startsWith("https://www.youtube.com/live/")) {
url = url.split("?")[0].replace("www.", "");
url = `https://youtube.com/watch?v=${url.replace("https://youtube.com/live/", "")}`
}
if (url.includes('youtube.com/shorts/')) {
url = url.split('?')[0].replace('shorts/', 'watch?v=');
}
break;
case "youtu":
if (url.startsWith("https://youtu.be/")) {
host = "youtube";
url = `https://youtube.com/watch?v=${url.replace("https://youtu.be/", "")}`
}
break;
case "vxtwitter":
case "x":
if (url.startsWith("https://x.com/")) {
host = "twitter";
url = url.replace("https://x.com/", "https://twitter.com/")
}
if (url.startsWith("https://vxtwitter.com/")) {
host = "twitter";
url = url.replace("https://vxtwitter.com/", "https://twitter.com/")
}
break;
case "tumblr":
if (!url.includes("blog/view")) {
if (url.slice(-1) === '/') url = url.slice(0, -1);
url = url.replace(url.split('/')[5], '')
}
break;
case "twitch":
if (url.includes('clips.twitch.tv')) {
url = url.split('?')[0].replace('clips.twitch.tv/', 'twitch.tv/_/clip/');
}
break;
}
return {
host: host,
url: url
}
}

View file

@ -1,3 +1,5 @@
import { strict as assert } from "node:assert";
import { apiJSON } from "../sub/utils.js";
import { errorUnsupported, genericError, brokenLink } from "../sub/errors.js";
@ -23,6 +25,8 @@ import twitch from "./services/twitch.js";
import rutube from "./services/rutube.js";
export default async function(host, patternMatch, url, lang, obj) {
assert(url instanceof URL);
try {
let r, isAudioOnly = !!obj.isAudioOnly, disableMetadata = !!obj.disableMetadata;
@ -37,7 +41,6 @@ export default async function(host, patternMatch, url, lang, obj) {
break;
case "vk":
r = await vk({
url: url,
userId: patternMatch["userId"],
videoId: patternMatch["videoId"],
quality: obj.vQuality
@ -57,11 +60,13 @@ export default async function(host, patternMatch, url, lang, obj) {
isAudioMuted: obj.isAudioMuted,
dubLang: obj.dubLang
}
if (url.match('music.youtube.com') || isAudioOnly === true) {
if (url.hostname === 'music.youtube.com' || isAudioOnly === true) {
fetchInfo.quality = "max";
fetchInfo.format = "vp9";
fetchInfo.isAudioOnly = true
}
r = await youtube(fetchInfo);
break;
case "reddit":
@ -83,9 +88,9 @@ export default async function(host, patternMatch, url, lang, obj) {
break;
case "tumblr":
r = await tumblr({
id: patternMatch["id"],
url: url,
user: patternMatch["user"] || false
id: patternMatch.id,
user: patternMatch.user,
url
});
break;
case "vimeo":
@ -99,12 +104,11 @@ export default async function(host, patternMatch, url, lang, obj) {
case "soundcloud":
isAudioOnly = true;
r = await soundcloud({
url: url,
url,
author: patternMatch["author"],
song: patternMatch["song"],
shortLink: patternMatch["shortLink"] || false,
accessKey: patternMatch["accessKey"] || false,
format: obj.aFormat
accessKey: patternMatch["accessKey"] || false
});
break;
case "instagram":

View file

@ -3,7 +3,7 @@ import { apiJSON } from "../sub/utils.js";
import loc from "../../localization/manager.js";
import createFilename from "./createFilename.js";
export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, filenamePattern) {
export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, filenamePattern) {
let action,
responseType = 2,
defaultParams = {
@ -13,7 +13,8 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, d
createFilename(r.filenameAttributes, filenamePattern, isAudioOnly, isAudioMuted) : r.filename,
fileMetadata: !disableMetadata ? r.fileMetadata : false
},
params = {}
params = {},
audioFormat = String(userFormat)
if (r.isPhoto) action = "photo";
else if (r.picker) action = "picker"
@ -32,9 +33,49 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, d
}
switch (action) {
default:
return apiJSON(0, { t: loc(lang, 'ErrorEmptyDownload') });
case "photo":
responseType = 1;
break;
case "singleM3U8":
params = { type: "remux" }
break;
case "muteVideo":
params = {
type: Array.isArray(r.urls) ? "bridge" : "mute",
u: Array.isArray(r.urls) ? r.urls[0] : r.urls,
mute: true
}
if (host === "reddit" && r.typeId === 1) responseType = 1;
break;
case "picker":
responseType = 5;
switch (host) {
case "instagram":
case "twitter":
params = { picker: r.picker };
break;
case "douyin":
case "tiktok":
let pickerType = "render";
if (audioFormat === "mp3" || audioFormat === "best") {
audioFormat = "mp3";
pickerType = "bridge"
}
params = {
type: pickerType,
picker: r.picker,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls,
copy: audioFormat === "best" ? true : false
}
}
break;
case "video":
switch (host) {
case "bilibili":
@ -78,81 +119,63 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, d
break;
}
break;
case "singleM3U8":
params = { type: "remux" }
break;
case "muteVideo":
params = {
type: Array.isArray(r.urls) ? "bridge" : "mute",
u: Array.isArray(r.urls) ? r.urls[0] : r.urls,
mute: true
}
if (host === "reddit" && r.typeId === 1) responseType = 1;
break;
case "picker":
responseType = 5;
switch (host) {
case "instagram":
case "twitter":
params = { picker: r.picker };
break;
case "douyin":
case "tiktok":
let pickerType = "render";
if (audioFormat === "mp3" || audioFormat === "best") {
audioFormat = "mp3";
pickerType = "bridge"
}
params = {
type: pickerType,
picker: r.picker,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls,
copy: audioFormat === "best" ? true : false
}
}
break;
case "audio":
if ((host === "reddit" && r.typeId === 1) || audioIgnore.includes(host)) {
return apiJSON(0, { t: loc(lang, 'ErrorEmptyDownload') })
}
let processType = "render";
let copy = false;
let processType = "render",
copy = false;
if (!supportedAudio.includes(audioFormat)) audioFormat = "best";
if (!supportedAudio.includes(audioFormat)) {
audioFormat = "best"
}
if ((host === "tiktok" || host === "douyin")
&& services.tiktok.audioFormats.includes(audioFormat)) {
if (r.isMp3) {
if (audioFormat === "mp3" || audioFormat === "best") {
audioFormat = "mp3";
processType = "bridge"
}
} else if (audioFormat === "best") {
const isBestAudio = audioFormat === "best";
const isBestOrMp3 = audioFormat === "mp3" || isBestAudio;
const isBestAudioDefined = isBestAudio && services[host]["bestAudio"];
const isBestHostAudio = services[host]["bestAudio"] && (audioFormat === services[host]["bestAudio"]);
const isTikTok = host === "tiktok" || host === "douyin";
const isTumblr = host === "tumblr" && !r.filename;
const isSoundCloud = host === "soundcloud";
if (isTikTok && services.tiktok.audioFormats.includes(audioFormat)) {
if (r.isMp3 && isBestOrMp3) {
audioFormat = "mp3";
processType = "bridge"
} else if (isBestAudio) {
audioFormat = "m4a";
processType = "bridge"
}
}
if (host === "tumblr" && !r.filename
&& (audioFormat === "best" || audioFormat === "mp3")) {
if (isSoundCloud && services.soundcloud.audioFormats.includes(audioFormat)) {
if (r.isMp3 && isBestOrMp3) {
audioFormat = "mp3";
processType = "render"
copy = true
} else if (isBestAudio || audioFormat === "opus") {
audioFormat = "opus";
processType = "render"
copy = true
}
}
if (isTumblr && isBestOrMp3) {
audioFormat = "mp3";
processType = "bridge"
}
if ((audioFormat === "best" && services[host]["bestAudio"])
|| (services[host]["bestAudio"] && (audioFormat === services[host]["bestAudio"]))) {
if (isBestAudioDefined || isBestHostAudio) {
audioFormat = services[host]["bestAudio"];
if (host === "soundcloud") {
processType = "render"
copy = true
} else {
processType = "bridge"
}
} else if (audioFormat === "best") {
processType = "bridge";
} else if (isBestAudio && !isSoundCloud) {
audioFormat = "m4a";
copy = true;
copy = true
}
if (r.isM3U8 || host === "vimeo") {
copy = false;
processType = "render"
@ -165,8 +188,6 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, d
copy: copy
}
break;
default:
return apiJSON(0, { t: loc(lang, 'ErrorEmptyDownload') });
}
return apiJSON(responseType, {...defaultParams, ...params})

View file

@ -39,17 +39,18 @@ export default async function(obj) {
if (!clientId) return { error: 'ErrorSoundCloudNoClientId' };
let link;
if (obj.shortLink && !obj.author && !obj.song) {
if (obj.url.hostname === 'on.soundcloud.com' && obj.shortLink) {
link = await fetch(`https://on.soundcloud.com/${obj.shortLink}/`, { redirect: "manual" }).then((r) => {
if (r.status === 302 && r.headers.get("location").startsWith("https://soundcloud.com/")) {
return r.headers.get("location").split('?', 1)[0]
}
return false
}).catch(() => { return false });
}).catch(() => {});
}
if (!link && obj.author && obj.song) {
link = `https://soundcloud.com/${obj.author}/${obj.song}${obj.accessKey ? `/s-${obj.accessKey}` : ''}`
}
if (!link) return { error: 'ErrorCouldntFetch' };
let json = await fetch(`https://api-v2.soundcloud.com/resolve?url=${link}&client_id=${clientId}`).then((r) => {
@ -59,8 +60,16 @@ export default async function(obj) {
if (!json["media"]["transcodings"]) return { error: 'ErrorEmptyDownload' };
let fileUrlBase = json.media.transcodings.filter(v => v.preset === "opus_0_0")[0]["url"],
fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
let isMp3,
selectedStream = json.media.transcodings.filter(v => v.preset === "opus_0_0")
// fall back to mp3 if no opus is available
if (selectedStream.length === 0) {
selectedStream = json.media.transcodings.filter(v => v.preset === "mp3_0_0")
isMp3 = true
}
let fileUrlBase = selectedStream[0]["url"];
let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
if (fileUrl.substring(0, 54) !== "https://api-v2.soundcloud.com/media/soundcloud:tracks:") return { error: 'ErrorEmptyDownload' };
@ -82,6 +91,7 @@ export default async function(obj) {
title: fileMetadata.title,
author: fileMetadata.artist
},
fileMetadata: fileMetadata
isMp3,
fileMetadata
}
}

View file

@ -1,9 +1,16 @@
import psl from "psl";
import { genericUserAgent } from "../../config.js";
export default async function(obj) {
let html = await fetch(`https://${
obj.user ? obj.user : obj.url.split('.')[0].replace('https://', '')
}.tumblr.com/post/${obj.id}`, {
let { subdomain } = psl.parse(obj.url.hostname);
if (subdomain?.includes('.')) {
return { error: ['ErrorBrokenLink', 'tumblr'] }
} else if (subdomain === 'www' || subdomain === 'at') {
subdomain = undefined
}
let html = await fetch(`https://${subdomain ?? obj.user}.tumblr.com/post/${obj.id}`, {
headers: { "user-agent": genericUserAgent }
}).then((r) => { return r.text() }).catch(() => { return false });
@ -24,5 +31,5 @@ export default async function(obj) {
}
} else r = { error: 'ErrorEmptyDownload' };
return r;
return r
}

View file

@ -13,6 +13,8 @@
},
"twitter": {
"alias": "twitter videos & voice",
"altDomains": ["x.com", "vxtwitter.com", "fixvx.com"],
"subdomains": ["mobile"],
"patterns": [":user/status/:id", ":user/status/:id/video/:v"],
"enabled": true
},
@ -24,22 +26,26 @@
"youtube": {
"alias": "youtube videos, shorts & music",
"patterns": ["watch?v=:id", "embed/:id", "watch/:id"],
"subdomains": ["music", "m"],
"bestAudio": "opus",
"enabled": true
},
"tumblr": {
"patterns": ["post/:id", "blog/view/:user/:id", ":user/:id", ":user/:id/:trackingId"],
"subdomains": "*",
"enabled": true
},
"tiktok": {
"alias": "tiktok videos, photos & audio",
"patterns": [":user/video/:postId", ":id", "t/:id"],
"subdomains": ["vt", "vm"],
"audioFormats": ["best", "m4a", "mp3"],
"enabled": true
},
"douyin": {
"alias": "douyin videos & audio",
"patterns": ["video/:postId", ":id"],
"subdomains": ["v"],
"enabled": false
},
"vimeo": {
@ -49,7 +55,8 @@
},
"soundcloud": {
"patterns": [":author/:song/s-:accessKey", ":author/:song", ":shortLink"],
"bestAudio": "opus",
"subdomains": ["on"],
"audioFormats": ["best", "opus", "mp3"],
"enabled": true
},
"instagram": {

View file

@ -0,0 +1,96 @@
import { services } from "../config.js";
import { strict as assert } from "node:assert";
import psl from "psl";
export function aliasURL(url) {
assert(url instanceof URL);
const host = psl.parse(url.hostname);
const parts = url.pathname.split('/');
switch (host.sld) {
case "youtube":
if (url.pathname.startsWith('/live/') || url.pathname.startsWith('/shorts/')) {
url.pathname = '/watch';
// parts := ['', 'live' || 'shorts', id, ...rest]
url.search = `?v=${encodeURIComponent(parts[2])}`
}
break;
case "youtu":
if (url.hostname === 'youtu.be' && parts.length >= 2) {
/* youtu.be urls can be weird, e.g. https://youtu.be/<id>//asdasd// still works
** but we only care about the 1st segment of the path */
url = new URL(`https://youtube.com/watch?v=${
encodeURIComponent(parts[1])
}`)
}
break;
case "vxtwitter":
case "fixvx":
case "x":
if (services.twitter.altDomains.includes(url.hostname)) {
url.hostname = 'twitter.com'
}
break;
case "twitch":
if (url.hostname === 'clips.twitch.tv' && parts.length >= 2) {
url = new URL(`https://twitch.tv/_/clip/${parts[1]}`);
}
break;
}
return url
}
export function cleanURL(url) {
assert(url instanceof URL);
const host = psl.parse(url.hostname).sld;
let stripQuery = true;
if (host === 'pinterest') {
url.hostname = 'pinterest.com'
} else if (host === 'vk' && url.pathname.includes('/clip')) {
if (url.searchParams.get('z'))
url.search = '?z=' + encodeURIComponent(url.searchParams.get('z'));
stripQuery = false;
} else if (host === 'youtube' && url.searchParams.get('v')) {
url.search = '?v=' + encodeURIComponent(url.searchParams.get('v'));
stripQuery = false;
}
if (stripQuery) {
url.search = ''
}
url.username = url.password = url.port = url.hash = ''
if (url.pathname.endsWith('/'))
url.pathname = url.pathname.slice(0, -1);
return url
}
export function normalizeURL(url) {
return cleanURL(
aliasURL(
new URL(url.replace(/^https\/\//, 'https://'))
)
);
}
export function getHostIfValid(url) {
const host = psl.parse(url.hostname);
if (host.error) return;
const service = services[host.sld];
if (!service) return;
if ((service.tld ?? 'com') !== host.tld) return;
const anySubdomainAllowed = service.subdomains === '*';
const validSubdomain = [null, 'www', ...(service.subdomains ?? [])].includes(host.subdomain);
if (!validSubdomain && !anySubdomainAllowed) return;
return host.sld;
}

View file

@ -1,3 +1,4 @@
import { normalizeURL } from "../processing/url.js";
import { createStream } from "../stream/manage.js";
const apiVar = {
@ -52,29 +53,7 @@ export function metadataManager(obj) {
for (let i in keys) { if (tags.includes(keys[i])) commands.push('-metadata', `${keys[i]}=${obj[keys[i]]}`) }
return commands;
}
export function cleanURL(url, host) {
switch (host) {
case "vk":
url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0];
break;
case "youtube":
url = url.split('&')[0];
break;
case "tiktok":
url = url.replace(/@([a-zA-Z]+(\.[a-zA-Z]+)+)/, "@a")
case "pinterest":
url = url.replace(/:\/\/(?:www.)pinterest(?:\.[a-z.]+)/, "://pinterest.com")
default:
url = url.split('?')[0];
if (url.substring(url.length - 1) === "/") url = url.substring(0, url.length - 1);
break;
}
for (let i in forbiddenChars) {
url = url.replaceAll(forbiddenChars[i], '')
}
url = url.replace('https//', 'https://')
return url.slice(0, 128)
}
export function cleanString(string) {
for (let i in forbiddenCharsString) {
string = string.replaceAll("/", "_").replaceAll(forbiddenCharsString[i], '')
@ -94,6 +73,7 @@ export function unicodeDecode(str) {
}
export function checkJSONPost(obj) {
let def = {
url: normalizeURL(decodeURIComponent(obj.url)),
vCodec: "h264",
vQuality: "720",
aFormat: "mp3",
@ -121,12 +101,8 @@ export function checkJSONPost(obj) {
}
}
if (def.dubLang) def.dubLang = verifyLanguageCode(obj.dubLang);
obj["url"] = decodeURIComponent(String(obj["url"]));
let hostname = obj["url"].replace("https://", "").replace(' ', '').split('&')[0].split("/")[0].split("."),
host = hostname[hostname.length - 2];
def["url"] = encodeURIComponent(cleanURL(obj["url"], host));
if (def.dubLang)
def.dubLang = verifyLanguageCode(obj.dubLang);
return def
} catch (e) {

View file

@ -304,6 +304,14 @@
"code": 200,
"status": "stream"
}
}, {
"name": "no opus audio, fallback to mp3",
"url": "https://soundcloud.com/frums/credits",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}],
"youtube": [{
"name": "4k video (h264, 1440)",