2024-01-01 14:42:50 +01:00
<!DOCTYPE html>
2024-01-03 19:11:14 +01:00
< meta charset = "utf-8" / >
2024-01-02 19:23:14 +01:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
2024-01-03 19:11:14 +01:00
< title > 🃏️ [Matrix] Sticker Helper< / title >
2024-01-02 19:23:14 +01:00
<!--
< script src = "../../../SpaccDotWeb/SpaccDotWeb.js" module = "SpaccDotWeb" > < / script >
2024-01-01 14:42:50 +01:00
< script src = "//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.js" module = "SpaccDotWeb" > < / script >
2024-01-02 19:23:14 +01:00
-->
< script src = "../../../SpaccDotWeb/SpaccDotWeb.Alt.js" module = "SpaccDotWeb" > < / script >
< script src = "//SpaccInc.gitlab.io/SpaccDotWeb/SpaccDotWeb.Alt.js" module = "SpaccDotWeb" > < / script >
< link rel = "stylesheet" href = "./paper.min.css" / >
2024-01-01 14:42:50 +01:00
2024-01-03 19:11:14 +01:00
<!--
< script module = "Meta" > ( {
Name: "🃏️ [Matrix] Sticker Helper",
})< / script >
-->
<!--
TODO:
2024-01-08 22:15:40 +01:00
* more error handling and debug logging
** manage consistency between modal errors and messages in debug log (probably will need a SpaccDotWeb function for that)
2024-01-05 23:57:30 +01:00
* immediately show an image after uploading in the grid
2024-01-04 01:58:42 +01:00
* allow reordering, renaming, deleting stickers and packs
2024-01-05 23:57:30 +01:00
** maybe by default show the sticker packs in the same order in the list as the picker does, or at the very least commit them with recent stickers being at the beginning of the list
2024-01-08 22:15:40 +01:00
* import from line, import video telegram stickers, import video files, import files from URL
2024-01-04 01:58:42 +01:00
* save a linkback to a sticker pack in every single sticker optionally, allowing stickers to be copied, possibly from matrix.to URL
** set this and other options as persisting in the user account data
* fix sticker reimport protection not working?
* in a lot of places that only do HTTP(s), handle also mxc:// URLs
2024-01-05 23:57:30 +01:00
* open graph, PWA things (webmanifest, worker for offline support)
* guest/demo mode
2024-01-08 22:15:40 +01:00
* UI improvements like blocking buttons in inconsistent states, adding more feedbacks for long operations
** create and view sticker pack in gui immediately after started importing new stickers, not only after
** more loading messages and overlays
** fix race conditions between actions like user going back while app is committing or loading packs
2024-01-05 23:57:30 +01:00
* compatibility build / add polyfills
2024-01-08 22:15:40 +01:00
* multilanguage?
* gif encoding without webserver (without ES import)
2024-01-03 19:11:14 +01:00
-->
2024-01-03 00:31:15 +01:00
< div id = "Main" hidden = "true" >
< div id = "LayoutAccountSelect" > < / div >
< div id = "LayoutCollectionActions" >
< button name = "back" > 🔙️ Go Back< / button >
2024-01-05 23:57:30 +01:00
< button name = "commit" > 📝️ Commit Changes< / button >
< button name = "commitFake" hidden > 📝️ Can't commit changes in Demo Mode< / button >
2024-01-03 00:31:15 +01:00
< / div >
2024-01-08 22:15:40 +01:00
< div id = "LayoutFeedback" > < / div >
2024-01-05 23:57:30 +01:00
< div style = "overflow: auto;" >
< div id = "LayoutPacksList" class = "margin-small" style = "width: max-content;" > < / div >
< / div >
< div class = "margin-small" id = "LayoutPackActions" >
< input name = "packName" type = "text" placeholder = "Pack Name/Description" style = "display: inline-block;" disabled / >
< button name = "packDelete" disabled title = "Coming soon!" > ❌️ Delete Pack< / button >
< / div >
< div class = "margin-small" id = "LayoutPackGrid" > < / div >
2024-01-03 00:31:15 +01:00
< details class = "col border margin" id = "LayoutCollectionOptions" >
< summary >
< p > Options< / p >
< / summary >
< p >
Reinitializing sticker data for your account will simply override
2024-01-05 23:57:30 +01:00
the < code > stickerpicker< / code > subfield in its < code > m.widgets< / code > field.
2024-01-03 00:31:15 +01:00
< / p >
< button name = "reinit" > 💡️ Reinitialize sticker data as new< / button >
< details class = "col border margin" >
< summary >
< p > Advanced< / p >
< / summary >
< label >
Sticker selector app URL
< input name = "pickerUrl" type = "text" / >
< / label >
< / details >
< / details >
2024-01-08 22:15:40 +01:00
< tgs-player style = "display: none;" hidden = "true" > < / tgs-player >
2024-01-03 00:31:15 +01:00
< / div >
2024-01-05 23:57:30 +01:00
< p class = "noscript" >
2024-01-08 22:15:40 +01:00
This application requires modern JavaScript and cutting-edge web standards.
2024-01-05 23:57:30 +01:00
< / p >
2024-01-03 19:11:14 +01:00
< hr class = "margin-large" / >
2024-01-03 00:31:15 +01:00
< p >
2024-01-05 23:57:30 +01:00
🃏️ [Matrix] Sticker Helper < a name = "version" title = "Click to show changelog" href = "javascript:;" > WIP Version< / a > ,
created with 💕️ and ☕️ by < a href = "https://hub.octt.eu.org" > OctoSpacc< / a > .
2024-01-03 00:31:15 +01:00
< br / >
2024-01-03 19:11:14 +01:00
Made possible by < a href = "https://github.com/maunium/stickerpicker" target = "_blank" > Maunium sticker picker< / a > ,
brought to paper thanks to < a href = "https://www.getpapercss.com" target = "_blank" > PaperCSS< / a > .
2024-01-03 00:31:15 +01:00
< / p >
2024-01-05 23:57:30 +01:00
< p >
2024-01-08 22:15:40 +01:00
< b > Note< / b > : importing stickers from files/Telegram is currently experimental, and error handling isn't the best.
Make sure that your Internet connection is stable when uploading/importing stickers, and be patient if it seems like the app does nothing for a long time.
2024-01-05 23:57:30 +01:00
< / p >
2024-01-03 00:31:15 +01:00
< details class = "col border margin" >
< summary >
2024-01-04 01:58:42 +01:00
< p > Help and Information< / p >
2024-01-03 00:31:15 +01:00
< / summary >
2024-01-04 01:58:42 +01:00
< h3 > What is [Matrix] Sticker Helper?< / h3 >
2024-01-03 00:31:15 +01:00
< p >
2024-01-04 01:58:42 +01:00
I created this app for the pursuit of an user-friendly way
of managing sticker packs on < a href = "https://matrix.org" target = "_blank" > Matrix< / a > ,
that could also work well for other people,
after finding the < code > maunium/stickerpicker< / code > import procedure uncomfortable.
< br / >
Read more (and follow the development I guess?) at
2024-01-05 23:57:30 +01:00
< a href = "https://octospacc.altervista.org/?s=%22%23MatrixStickerHelper%22" > octospacc.altervista.org/#MatrixStickerHelper< / a > .
2024-01-04 01:58:42 +01:00
< / p >
< h3 > How can I add sticker packs to my collection?< / h3 >
< p >
2024-01-05 23:57:30 +01:00
To add new sticker packs to your collection, you can use the dedicated section, which offers 2 options. You can:
2024-01-08 22:15:40 +01:00
< / p >
< ul >
2024-01-05 23:57:30 +01:00
< li >
Either leave the optional text field blank to create a brand-new pack, to be filled with stickers by importing media files;
< / li >
< li >
2024-01-08 22:15:40 +01:00
Or, to import packs from one of a few different supported sources, input an URL. Supported URLs are:
< ul >
< li >
Those pointing to the JSON data file (in the format used by < code > maunium/stickerpicker< / code > )
of an already existing collection (< code > index.json< / code > ) or individual pack (shared by other users, or backed-up by you),
assuming the file is fetchable with CORS restrictions.
< br / >
The URL must begin with < code > http://< / code > or < code > https://< / code > .
For example, < code > https://maunium.net/stickers-demo/packs/pusheen02.json< / code > .
< / li >
< li >
URLs of Telegram sticker packs, either Static (PNG/WebP) or Animated (TGS). Video stickers will be supported in the future.
(For the technical details, see < a href = "https://core.telegram.org/stickers" > https://core.telegram.org/stickers< / a > .)
< br / >
The URL must be in the format "{optionally, < code > http://< / code > or < code > https://< / code > }<!--
-->{< code > t.me< / code > or < code > telegram.me< / code > }< code > /addstickers/< / code > {name of the pack}".
For example, < code > t.me/addstickers/FishPuffer< / code > .
< / li >
< / ul >
2024-01-05 23:57:30 +01:00
< / li >
2024-01-08 22:15:40 +01:00
< / ul >
< p >
2024-01-05 23:57:30 +01:00
Currently, you can import common picture files (JPEG, WebP, PNG, GIF) from your device's storage as stickers.
Sooner, you will be able to also import media files from URL, as well as importing video files (like MP4) for automatic conversion into GIF.
2024-01-04 01:58:42 +01:00
< br / >
2024-01-08 22:15:40 +01:00
Importing stickers and packs from Matrix messages, or the LINE store, is planned for the more distant future.
2024-01-04 01:58:42 +01:00
< / p >
< h3 > Stickerpicker information< / h3 >
< p >
2024-01-05 23:57:30 +01:00
This project is not a substitute of < code > maunium/stickerpicker< / code > ,
is not affiliated with it, and doesn't use any part of its code, but rather complements it.
< br / >
2024-01-04 01:58:42 +01:00
It sets that up as the sticker picker integration for your Matrix account if you don't have it already,
and allows you to manage your sticker collection in a way that is fully compatible with it, but without the manual setup.
< / p >
< h3 > Open-source, licensing, security, collaboration< / h3 >
< p >
This app is open-source, and licensed under the
< a href = "https://www.gnu.org/licenses/agpl-3.0.html" target = "_blank" > GNU Affero General Public License Version 3< / a > .
To modify the app, or to check its code for security reasons, you can simply "View Page Source" in your browser.
< br / >
This project additionally relies on the following third-party libraries:
2024-01-08 22:15:40 +01:00
< / p >
< ul >
< li > < a target = "_blank" href = "https://gitlab.com/SpaccInc/SpaccDotWeb" > SpaccDotWeb< / a > < / li >
< li > < a target = "_blank" href = "https://github.com/gram-js/gramjs" > GramJS< / a > < / li >
< li > < a target = "_blank" href = "https://github.com/LottieFiles/lottie-player" > Lottie-Player< / a > < / li >
< li > < a target = "_blank" href = "https://github.com/jamsinclair/gifski-wasm" > gifski-wasm< / a > < / li >
< / ul >
< p >
Additionally, if there is any issue you want to report, tip you want to give, or if you want to send a pull request,
2024-01-04 01:58:42 +01:00
feel free to do so at the OctoSpacc Hub repository:
< a href = "https://gitlab.com/octospacc/octospacc.gitlab.io" > https://gitlab.com/octospacc/octospacc.gitlab.io< / a >
(or contact me via Matrix, see below).
< / p >
< h3 > Other help< / h3 >
< p >
There is no one around to actually help here though... yet.
Maybe join my Matrix space, if you need some support:
< a href = "https://matrix.to/#/#Spacc:matrix.org" > https://matrix.to/#/#Spacc:matrix.org< / a > .
2024-01-03 00:31:15 +01:00
< / p >
2024-01-05 23:57:30 +01:00
< div id = "LayoutChangelog" >
< h3 > Changelog< / h3 >
2024-01-08 22:15:40 +01:00
< p > [2024-01-08]< / p >
< ul >
< li > Importing Static and Animated sticker packs from Telegram URLs< / li >
< li > Minor bugfixes and UX improvements< / li >
< / ul >
< p > [2024-01-05]< / p >
< ul >
2024-01-05 23:57:30 +01:00
< li > Uploading stickers from (multiple) local image files< / li >
2024-01-06 00:23:21 +01:00
< li > Deleting individual stickers from packs (+ bug hotfix)< / li >
2024-01-05 23:57:30 +01:00
< li > Various bugfixes and UX improvements< / li >
2024-01-08 22:15:40 +01:00
< / ul >
< p > [2024-01-04]< / p >
< ul >
2024-01-05 23:57:30 +01:00
< li > Importing entire sticker collections from URL< / li >
< li > Fix stickers not loading from accounts already setup with stock stickerpicker< / li >
< li > Add help and changelog< / li >
2024-01-08 22:15:40 +01:00
< / ul >
< p > [2024-01-05]< / p >
< ul >
2024-01-05 23:57:30 +01:00
< li > First release< / li >
< li > Account management (logging in, reading and writing data)< / li >
< li > Visualization of current account stickers< / li >
< li > Importing sticker packs from URL< / li >
2024-01-08 22:15:40 +01:00
< / ul >
2024-01-05 23:57:30 +01:00
< / p >
< / div >
2024-01-08 22:15:40 +01:00
< h3 > Debug Log< / h3 >
< textarea id = "LayoutDebugLog" readonly = "true" style = "width: 100%;" rows = "10" > < / textarea >
2024-01-03 00:31:15 +01:00
< / details >
2024-01-01 14:42:50 +01:00
< script module = "Main" type = "module" >
2024-01-02 19:23:14 +01:00
const Spacc = SpaccDotWeb.AppInit();
2024-01-01 14:42:50 +01:00
const State = {};
const Defaults = {
2024-01-01 19:11:18 +01:00
stickerSelectorUrl: "https://maunium.net/stickers-demo/",
2024-01-01 14:42:50 +01:00
appIdentity: "org.eu.octt.MatrixStickerHelper",
appInterface: "v1",
2024-01-08 22:15:40 +01:00
wpPastebin: "https://octospacc.altervista.org",
telegramTokens: [
"17349:344583e45741c457fe1862106095a5eb", // < https: / / github . com / telegramdesktop / tdesktop / blob / dev / Telegram / SourceFiles / config . h >
"611335:d524b414d21f4d37f08684c1df41ac9c", // < https: / / github . com / telegramdesktop / tdesktop / blob / dev / . github / workflows / win . yml >
],
2024-01-02 19:23:14 +01:00
Strings: {
2024-01-03 00:31:15 +01:00
mConfirmCommit: "Confirm committing account data?",
mMustInit: "Your account must first be initialized to handle stickers.",
mNotManaged: `
2024-01-02 19:23:14 +01:00
Your account is set-up with a sticker picker, but it's not marked as being managed by this app.
This could mean that you are currently using an incompatible sticker picker.
2024-01-03 19:11:14 +01:00
< br / >
2024-01-02 19:23:14 +01:00
You can try to continue anyway if you think it should work, otherwise you should reinitialize sticker data.
`,
2024-01-03 19:11:14 +01:00
mCreatePackHint: `
2024-01-05 23:57:30 +01:00
Optionally include the URL of a sticker pack in JSON format to import it.
Otherwise, leave the field empty to create a brand-new pack, importing media files as stickers.
2024-01-03 19:11:14 +01:00
`,
mLoginHint: `
2024-01-04 01:58:42 +01:00
Please login with your Matrix account.
2024-01-03 19:11:14 +01:00
< br / >
(Your login details are processed locally and only sent to the homeserver you specified.)
`,
2024-01-02 19:23:14 +01:00
}
2024-01-01 14:42:50 +01:00
};
let Config = localStorage.getItem('SpaccInc-Matrix-Config');
2024-01-03 19:11:14 +01:00
if (Config) {
Config = JSON.parse(Config);
} else {
Config = {
accounts: [],
2024-01-01 14:42:50 +01:00
};
}
2024-01-03 19:11:14 +01:00
Config.Save = () => localStorage.setItem('SpaccInc-Matrix-Config', JSON.stringify(Config));
2024-01-01 14:42:50 +01:00
2024-01-08 22:15:40 +01:00
const Logger = (content, type) => {
if (['f', 'feedback'].includes(type.toLowerCase())) {
$`#LayoutFeedback`.innerHTML = content;
}
$`#LayoutDebugLog`.innerHTML += `[${type}] [${new Date().toISOString().split('.').slice(0, -1).join('.')}] ${content}\n`;
}
const ArrayRandom = (array) => array[~~(Math.random() * array.length)];
2024-01-02 19:23:14 +01:00
const $ = (query, ...params) => (query
? document.querySelector(Array.isArray(query)
? (params.length > 0
? params.map((a, i) => `${query[i]}${a}`).join('')
: query.join(''))
: query)
: document);
2024-01-01 14:42:50 +01:00
2024-01-02 19:23:14 +01:00
const GetMatrixUserTag = (account) => `@${account.username}:${account.homeserver.split('://')[1]}`;
2024-01-01 19:11:18 +01:00
2024-01-03 19:11:14 +01:00
const GetMatrixMediaUrl = (mxcId, props) => (mxcId ? `${props?.homeserver || `https://${mxcId.split('mxc://')[1].split('/')[0]}`}/_matrix/media/r0/${props?.type || 'download'}/${mxcId.split('mxc://')[1]}` : undefined);
2024-01-01 19:11:18 +01:00
2024-01-08 22:15:40 +01:00
//const GetHttpOrMatrixUrl = (url) => ;
const IsUrlHttpOrS = (url) => {
url = url.toLowerCase();
if (url.startsWith('http://')) return 'http'
else if (url.startsWith('https://')) return 'https'
else return false
};
2024-01-05 23:57:30 +01:00
const SafeStripFileExtension = (name, extensions) => {
const parts = name.split('.');
return (parts.length === 1
? name
: (extensions.includes(parts.slice(-1)[0].toLowerCase())
? parts.slice(0, -1)
: parts
).join('.')
);
};
2024-01-08 22:15:40 +01:00
function ReadFileAsync (file, method='readAsDataURL') {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (event) => {
resolve(event.target.result);
};
reader[method](file);
});
}
// < https: / / stackoverflow . com / questions / 12710001 / how-to-convert-uint8-array-to-base64-encoded-string / 66046176 # 66046176 >
async function BufferToBase64 (buffer) {
return await new Promise((resolve) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(new Blob([buffer]));
});
}
// < https: / / stackoverflow . com / questions / 45365571 / convert-an-image-to-imagedata-uint8clampedarray / 68319943 # 68319943 >
async function GetImageDataFromSource (source, sizeDivisor=1) {
const image = Object.assign(new Image(), { src: source });
await new Promise((resolve) => image.addEventListener('load', () => resolve()));
const context = Object.assign(document.createElement('canvas'), {
width: (image.width / sizeDivisor),
height: (image.height / sizeDivisor),
}).getContext('2d');
context.imageSmoothingEnabled = false;
context.drawImage(image, 0, 0, (image.width / sizeDivisor), (image.height / sizeDivisor));
return context.getImageData(0, 0, (image.width / sizeDivisor), (image.height / sizeDivisor));
}
const ReadBufferOrBlob = async (data) => await (data instanceof Uint8Array ? await BufferToBase64 : ReadFileAsync)(data);
2024-01-02 19:23:14 +01:00
function ResetLayouts () {
$`#Main`.hidden = false;
2024-01-08 22:15:40 +01:00
for (const id of ['LayoutFeedback', 'LayoutAccountSelect', 'LayoutPacksList', 'LayoutPackGrid']) {
2024-01-02 19:23:14 +01:00
$`#${id}`.innerHTML = '';
}
2024-01-05 23:57:30 +01:00
for (const id of ['LayoutCollectionActions', 'LayoutPackActions', 'LayoutCollectionOptions']) {
2024-01-02 19:23:14 +01:00
$`#${id}`.hidden = true;
}
for (const id of ['LayoutCollectionOptions']) {
$`#${id}`.open = false;
}
}
2024-01-01 19:11:18 +01:00
2024-01-03 19:11:14 +01:00
function InitializeState () {
2024-01-05 23:57:30 +01:00
for (const elem of document.querySelectorAll('.noscript')) {
elem.remove();
}
2024-01-03 19:11:14 +01:00
$`#LayoutCollectionActions button[name="back"]`.onclick = () => DisplayAccountSelect();
2024-01-05 23:57:30 +01:00
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = Defaults.stickerPickerUrl;
$`#LayoutChangelog > h3`.remove();
2024-01-08 22:15:40 +01:00
const changelogHtml = `< a style = "visibility: hidden; position: absolute; top: 0;" > < / a > ${$`#LayoutChangelog`.innerHTML}`;
2024-01-05 23:57:30 +01:00
$`#LayoutChangelog`.remove();
$`a[name="version"]`.innerHTML = `v${changelogHtml.split('[')[1].split(']')[0]}`;
2024-01-08 22:15:40 +01:00
$`a[name="version"]`.onclick = async () => (await Spacc.ShowModal({ extraHTML: changelogHtml })).querySelector('a').scrollIntoView('a');
2024-01-02 19:23:14 +01:00
}
2024-01-01 19:11:18 +01:00
2024-01-03 19:11:14 +01:00
async function RequestAccountWidgetsData (postData) {
const request = await fetch(`${State.account.homeserver}/_matrix/client/v3/user/${GetMatrixUserTag(State.account)}/account_data/m.widgets`, {
2024-01-02 19:23:14 +01:00
method: (postData ? "PUT" : "GET"),
2024-01-03 19:11:14 +01:00
headers: { Authorization: `Bearer ${State.account.token}` },
2024-01-02 19:23:14 +01:00
body: JSON.stringify(postData),
});
const result = await request.json();
2024-01-05 23:57:30 +01:00
if (request.status === 200) {
2024-01-02 19:23:14 +01:00
return result;
2024-01-05 23:57:30 +01:00
} else if (result.errcode === 'M_NOT_FOUND') {
return {};
2024-01-02 19:23:14 +01:00
} else {
Spacc.ShowModal(`Error: ${JSON.stringify(result)}`);
}
2024-01-01 14:42:50 +01:00
}
2024-01-08 22:15:40 +01:00
async function RequestMatrixUploadFile (fileData, fileMime) {
2024-01-03 19:11:14 +01:00
const request = await fetch(`${State.account.homeserver}/_matrix/media/v3/upload`, {
2024-01-02 19:23:14 +01:00
method: "POST",
2024-01-03 19:11:14 +01:00
headers: { Authorization: `Bearer ${State.account.token}`, ...(fileMime & & { "Content-Type": fileMime }) },
2024-01-02 19:23:14 +01:00
body: fileData,
});
const result = await request.json();
if (request.status === 200) {
2024-01-05 23:57:30 +01:00
return result;
2024-01-08 22:15:40 +01:00
Logger(`Uploaded ${fileMime} data to ${result.content_uri}.`, 'd');
2024-01-02 19:23:14 +01:00
} else {
Spacc.ShowModal(`Error: ${JSON.stringify(result)}`);
}
}
2024-01-01 19:11:18 +01:00
2024-01-08 22:15:40 +01:00
const IsUrlTelegramSticker = (url) => {
const [domain, path] = url.toLowerCase().split('://')[IsUrlHttpOrS(url) ? 1 : 0].split('/');
return (['t.me', 'telegram.me'].includes(domain) & & path === 'addstickers');
};
async function TryGetTelegramSession () {
if (!window.telegram) {
await Spacc.RequireScript('telegram.js');
}
try {
Logger(`Initializing a Telegram session, please wait...`, 'f');
const [apiId, apiToken] = ArrayRandom(Defaults.telegramTokens).split(':');
const request = await fetch(`${Defaults.wpPastebin}/wp-content/uploads/octospacc/scripts/stuff.php?&Thing=SiteWpJsonCors&AccessToken=9ab6e20c&$Query=wp/v2/paste/958?password=${Defaults.wpPastebin}/paste/958/?ppt=4c7f6ed8f9f268e05bd7509bd22f578247567be02369dba2707383cdd20f26dd`);
const result = await request.json();
const token = atob(result.content.rendered.split(',')[1].split('"')[0]).split('\0').slice(-3)[0];
const sessionString = new telegram.sessions.StringSession(Config.lastTelegramSession);
const client = new telegram.TelegramClient(sessionString, Number(apiId), apiToken, { connectionRetries: 5 });
await client.start({ botAuthToken: token });
Config.lastTelegramSession = client.session.save();
Config.Save();
await client.connect();
State.telegramClient = client;
return client;
} catch (err) {
Spacc.ShowModal(`Error trying to initialize a Telegram session: ${err}`);
}
}
async function ConvertTgsToGif (stickerDocument, stickerBuffer) {
await Spacc.RequireScript('tgs-player.js');
if (!window.GifSkiEncoder) {
const script = document.createElement('script');
script.type = 'module';
script.innerHTML = `
import gifskiWasm from './gifskiWasm.mjs';
window.GifSkiEncoder = gifskiWasm;
`;
document.body.appendChild(script);
}
while (!window.GifSkiEncoder) {
await SpaccDotWeb.Sleep(50);
}
const renderedFrames = [];
// reducing frames and dimensions since gifski is slow (TODO: make it user-customizable, and optionally provide a faster encoder)
const sizeDivisor = 3;
const framesDivisor = 3;
const tgsPlayer = document.querySelector('tgs-player');
tgsPlayer.load(await BufferToBase64(stickerBuffer));
await new Promise((resolve) => tgsPlayer.addEventListener('ready', () => resolve()));
for (let i=0; i< tgsPlayer._lottie.totalFrames ; i + = framesDivisor ) {
tgsPlayer.seek(i);
const svgFrame = tgsPlayer.snapshot(false);
const renderedFrame = await GetImageDataFromSource(`data:image/svg+xml;base64,${btoa(svgFrame)}`, sizeDivisor);
renderedFrames.push(renderedFrame);
}
return await GifSkiEncoder({
frames: renderedFrames,
width: (512 / sizeDivisor),
height: (512 / sizeDivisor),
fps: (60 / framesDivisor),
quality: 60,
});
}
2024-01-02 19:23:14 +01:00
async function PreparePacksEditor (account) {
2024-01-08 22:15:40 +01:00
if (account !== undefined) {
2024-01-03 19:11:14 +01:00
State.account = account;
}
2024-01-02 19:23:14 +01:00
ResetLayouts();
2024-01-03 19:11:14 +01:00
State.packsData = { homeserver_url: State.account.homeserver, packs: [] };
State.stickersData = [];
2024-01-02 19:23:14 +01:00
$`#LayoutCollectionActions`.hidden = false;
$`#LayoutCollectionOptions`.hidden = false;
2024-01-05 23:57:30 +01:00
$`#LayoutCollectionActions button[name="commit"]`.disabled = true;
2024-01-03 19:11:14 +01:00
$`#LayoutCollectionOptions button[name="reinit"]`.onclick = () => Spacc.ShowModal({
label: Defaults.Strings.mConfirmCommit,
action: () => ReinitStickersAccountData(),
});
2024-01-08 22:15:40 +01:00
Logger(`Fetching Matrix account data, please wait...`, 'f');
2024-01-03 19:11:14 +01:00
State.widgetsData = await RequestAccountWidgetsData();
if (State.widgetsData) {
2024-01-05 23:57:30 +01:00
const pickerUrlFull = State.widgetsData?.stickerpicker?.content?.url;
2024-01-03 19:11:14 +01:00
const isManaged = State.widgetsData?.stickerpicker?.content?.managedBy?.includes(`${Defaults.appIdentity}/${Defaults.appInterface}`);
2024-01-05 23:57:30 +01:00
const packsUrl = (new URLSearchParams(pickerUrlFull?.split('?')[1])).get('config');
if (pickerUrlFull) {
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = pickerUrlFull.split('?')[0];
}
2024-01-03 19:11:14 +01:00
if (!isManaged || !State.widgetsData?.stickerpicker) {
2024-01-02 19:23:14 +01:00
$`#LayoutCollectionOptions`.open = true;
2024-01-03 19:11:14 +01:00
if (!State.widgetsData?.stickerpicker) {
2024-01-08 22:15:40 +01:00
Logger(Defaults.Strings.mMustInit, 'f');
2024-01-05 23:57:30 +01:00
// we always init sticker data from the default URL to avoid headaches
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = '';
$`#LayoutCollectionOptions input[name="pickerUrl"]`.disabled = true;
2024-01-02 19:23:14 +01:00
} else
if (!isManaged) {
2024-01-08 22:15:40 +01:00
$`#LayoutFeedback`.innerHTML = `
2024-01-03 00:31:15 +01:00
< p > ${Defaults.Strings.mNotManaged}< / p >
2024-01-02 19:23:14 +01:00
< button name = "continue" > ⏭️ Continue< / button >
`;
2024-01-08 22:15:40 +01:00
$`#LayoutFeedback > button[name="continue"]`.onclick = () => {
2024-01-02 19:23:14 +01:00
$`#LayoutCollectionOptions`.open = false;
2024-01-04 01:58:42 +01:00
if (State.widgetsData?.stickerpicker?.content?.url & & !packsUrl) {
// assuming the user is probably coming from stock maunium/stickerpicker,
// and we know that it loads data from a fixed URL when no config param is specified,
// we can try loading that when a generic URL is present
const packsUrlsPrefix = `${State.widgetsData?.stickerpicker?.content?.url.split('?')[0]}/packs/`;
DisplayPacksEditor(`${packsUrlsPrefix}index.json`, packsUrlsPrefix);
return;
}
2024-01-03 19:11:14 +01:00
DisplayPacksEditor(packsUrl);
2024-01-02 19:23:14 +01:00
};
}
} else {
2024-01-03 19:11:14 +01:00
DisplayPacksEditor(packsUrl);
2024-01-01 19:11:18 +01:00
}
2024-01-05 23:57:30 +01:00
} else {
State.widgetsData = {};
2024-01-01 19:11:18 +01:00
}
2024-01-02 19:23:14 +01:00
}
2024-01-03 00:31:15 +01:00
2024-01-04 01:58:42 +01:00
async function DisplayPacksEditor (packsUrl, packsUrlPrefix) {
2024-01-02 19:23:14 +01:00
ResetLayouts();
$`#LayoutCollectionActions`.hidden = false;
$`#LayoutCollectionOptions`.hidden = false;
2024-01-03 00:31:15 +01:00
$`#LayoutCollectionActions button[name="commit"]`.onclick = () => Spacc.ShowModal({
label: Defaults.Strings.mConfirmCommit,
2024-01-03 19:11:14 +01:00
action: () => CommitNewAccountStickersAndData(),
2024-01-03 00:31:15 +01:00
});
2024-01-02 19:23:14 +01:00
if (packsUrl) {
2024-01-03 00:31:15 +01:00
try {
const request = await fetch(packsUrl);
if (request.status === 200) {
2024-01-03 19:11:14 +01:00
State.packsData = await request.json();
2024-01-03 00:31:15 +01:00
}
} catch(err) {
2024-01-03 19:11:14 +01:00
Spacc.ShowModal(`${err} ${packsUrl}`);
2024-01-03 00:31:15 +01:00
}
2024-01-02 19:23:14 +01:00
}
2024-01-04 01:58:42 +01:00
if (packsUrlPrefix) {
for (const packIndex in State.packsData.packs) {
State.packsData.packs[packIndex] = `${packsUrlPrefix}${State.packsData.packs[packIndex]}`;
}
}
2024-01-02 19:23:14 +01:00
const addButton = $().createElement('button');
addButton.name = 'add';
2024-01-05 23:57:30 +01:00
addButton.innerHTML = '➕ ️ Create/Import Pack';
2024-01-03 19:11:14 +01:00
addButton.onclick = (event) => Spacc.ShowModal({ label: Defaults.Strings.mCreatePackHint, extraHTML: `
2024-01-05 23:57:30 +01:00
< label >
2024-01-08 22:15:40 +01:00
< input name = "packUrl" type = "text" placeholder = "https://example.com/example.json, t.me/addstickers/Pack, ..." / >
2024-01-05 23:57:30 +01:00
< / label >
2024-01-03 19:11:14 +01:00
`, action: (event, modalButton) => CreateNewPack(event, modalButton) });
$`#LayoutPacksList`.appendChild(addButton);
LoadStickerPacksList();
}
async function LoadStickerPacksList () {
2024-01-05 23:57:30 +01:00
for (const packIndex in State.packsData.packs) {
const packUrl = State.packsData.packs[packIndex];
2024-01-03 19:11:14 +01:00
try {
2024-01-05 23:57:30 +01:00
const request = await fetch(packUrl);
2024-01-03 19:11:14 +01:00
const packData = await request.json();
2024-01-05 23:57:30 +01:00
const packObject = { data: packData, index: packIndex, edited: false };
State.stickersData.push(packObject);
AddNewPackButton(packObject);
2024-01-03 19:11:14 +01:00
} catch(err) {
2024-01-05 23:57:30 +01:00
Spacc.ShowModal(`${err} ${packUrl}`);
2024-01-03 19:11:14 +01:00
}
}
}
2024-01-04 01:58:42 +01:00
async function CreateNewPack (event, modalButton, packUrl) {
2024-01-03 19:11:14 +01:00
let packData = { stickers: [] };
// if the user specified an URL, try downloading data from there
2024-01-04 01:58:42 +01:00
packUrl ||= modalButton.parentElement.querySelector('input[name="packUrl"]').value;
2024-01-03 19:11:14 +01:00
// TODO: warn if an existing pack is imported from that URL and let the user choose if to continue or cancel
//if (packUrl & & IsAnyPackImportedFrom(packUrl) & & await Spacc.ShowModal({ label: Defaults.Strings.mAlreadyImported, action: () => 'continue', actionCancel: () => 'cancel' }) === 'continue') {
// return;
//};
2024-01-08 22:15:40 +01:00
if (packUrl & & !IsUrlTelegramSticker(packUrl)) {
2024-01-03 19:11:14 +01:00
try {
2024-01-03 00:31:15 +01:00
const request = await fetch(packUrl);
packData = await request.json();
2024-01-04 01:58:42 +01:00
// import JSON is an index, so we try to import all its packs
if (packData.packs & & !packData.stickers) {
for (const pack of packData.packs) {
2024-01-08 22:15:40 +01:00
const packUrlPrefix = (IsUrlHttpOrS(pack) ? '' : packUrl.split('/').slice(0, -1).join('/'));
2024-01-04 01:58:42 +01:00
await CreateNewPack(null, null, `${packUrlPrefix}/${pack}`);
}
return;
}
2024-01-03 19:11:14 +01:00
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
} catch(err) {
Spacc.ShowModal(`${err} ${packUrl}`);
return;
2024-01-03 00:31:15 +01:00
}
2024-01-03 19:11:14 +01:00
}
packData[Defaults.appIdentity] = {
...(packUrl & & { importedFrom: packUrl }),
};
State.packsData.packs.push(null);
2024-01-05 23:57:30 +01:00
const packObject = { data: packData, index: State.stickersData.length, edited: true };
State.stickersData.push(packObject);
2024-01-08 22:15:40 +01:00
if (packUrl & & IsUrlTelegramSticker(packUrl)) {
const tgClient = (State.telegramClient || await TryGetTelegramSession());
try {
Logger(`Fetching Telegram sticker data...`, 'f');
const packResult = await tgClient.invoke(
new telegram.Api.messages.GetStickerSet({
stickerset: new telegram.Api.InputStickerSetShortName({
shortName: packUrl.split('/').slice(-1)[0],
}),
hash: 0,
})
);
for (const stickerDocument of packResult.documents) {
let mimeType = stickerDocument.mimeType;
let stickerImage = await tgClient.downloadMedia(stickerDocument);
// TODO: video stickers
if (mimeType === 'application/x-tgsticker') {
mimeType = 'image/gif';
stickerImage = await ConvertTgsToGif(stickerDocument, stickerImage);
}
// TODO: write provenance information in sticker meta like the manium importer does
await TryAddStickerFromDataFile({
file: stickerImage,
mime: mimeType,
size: parseInt(stickerDocument.size.value),
dimensions: stickerDocument.attributes[0].originalArgs,
text: stickerDocument.attributes[1].alt,
}, packObject);
}
} catch(err) {
Spacc.ShowModal(`Error trying to import stickers from Telegram: ${err}`);
}
$`#LayoutFeedback`.innerHTML = '';
}
2024-01-05 23:57:30 +01:00
AddNewPackButton(packObject, event, modalButton);
2024-01-03 19:11:14 +01:00
}
function IsAnyPackImportedFrom (packUrl) {
for (const pack of State.stickersData) {
if (pack.data[Defaults.appIdentity].importedFrom === packUrl) {
return true;
2024-01-02 19:23:14 +01:00
}
}
2024-01-03 19:11:14 +01:00
return false;
2024-01-02 19:23:14 +01:00
}
2024-01-05 23:57:30 +01:00
function AddNewPackButton (packObject, event, modalButton) {
2024-01-03 19:11:14 +01:00
for (const elem of $`#LayoutPacksList`.querySelectorAll('button')) {
elem.disabled = false;
}
2024-01-05 23:57:30 +01:00
const packButton = MakeStickerPackButton(packObject);
2024-01-03 19:11:14 +01:00
$`#LayoutPacksList`.insertBefore(packButton, $`#LayoutPacksList > button[name="add"]`.nextElementSibling);
2024-01-05 23:57:30 +01:00
$`#LayoutPackActions`.hidden = true;
$`#LayoutPackGrid`.innerHTML = '';
2024-01-03 19:11:14 +01:00
}
2024-01-05 23:57:30 +01:00
function MakeStickerPackButton (packObject) {
2024-01-02 19:23:14 +01:00
const packButton = $().createElement('button');
2024-01-05 23:57:30 +01:00
packButton.innerHTML = `< img src = "${GetMatrixMediaUrl(packObject.data.stickers[0]?.info?.thumbnail_url, { homeserver: State.packsData?.homeserver_url, type: 'thumbnail' }) || ''}?&height=64&width=64&method=scale" / > `;
packButton.onclick = (event) => ShowStickerPack(event, packObject);
2024-01-03 19:11:14 +01:00
return packButton;
}
2024-01-05 23:57:30 +01:00
function ShowStickerPack (event, packObject) {
2024-01-06 00:23:21 +01:00
if (event) {
const thisElem = (event.target.tagName.toLowerCase() === 'button' ? event.target : event.target.parentElement);
for (const elem of thisElem.parentElement.querySelectorAll('button')) {
elem.disabled = false;
}
thisElem.disabled = true;
2024-01-03 19:11:14 +01:00
}
2024-01-05 23:57:30 +01:00
$`#LayoutPackActions input[name="packName"]`.value = packObject.data.title;
$`#LayoutPackActions button[name="packDelete"]`.onclick = () => {
// TODO: this makes the commit button go usable even if the pack was just created and had no stickers... not important but would be a small UI improvement
Spacc.ShowModal({ label: 'Are you sure to delete this pack?', action: () => {
State.packsData.packs.splice(packObject.index, 1);
State.stickersData.splice(packObject.index, 1);
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
//$`#LayoutPackGrid`.innerHTML = '';
} });
};
$`#LayoutPackActions`.hidden = false;
2024-01-03 19:11:14 +01:00
$`#LayoutPackGrid`.innerHTML = '';
2024-01-05 23:57:30 +01:00
for (const stickerIndex in (packObject.data.stickers || [])) {
const sticker = packObject.data.stickers[stickerIndex];
2024-01-03 19:11:14 +01:00
const stickerElem = $().createElement('button');
stickerElem.innerHTML = `< img src = "${GetMatrixMediaUrl(sticker.info.thumbnail_url, { homeserver: State.packsData?.homeserver_url, type: 'thumbnail' }) || ''}?&height=128&width=128&method=scale" / > `;
2024-01-05 23:57:30 +01:00
stickerElem.onclick = () => OnClickStickerButton(event, packObject, sticker, stickerIndex);
2024-01-03 19:11:14 +01:00
$`#LayoutPackGrid`.appendChild(stickerElem);
}
const addButton = $().createElement('button');
addButton.innerHTML = `
➕ ️ Upload New Sticker(s)
< input type = file hidden = "true" multiple = "true" accept = "image/jpeg,image/gif,image/png,image/webp" / >
`;
2024-01-08 22:15:40 +01:00
addButton.querySelector('input[type="file"]').onchange = (event) => TryAddStickersFromLocalFiles(event, packObject);
2024-01-05 23:57:30 +01:00
addButton.onclick = (event) => event.target.querySelector('input[type="file"]')?.click();
// TODO:
$`#LayoutPackGrid`.appendChild(addButton);
}
async function OnClickStickerButton (showStickerPackEvent, packObject, sticker, stickerIndex) {
const stickerModal = await Spacc.ShowModal({ extraHTML: `
< img src = "${GetMatrixMediaUrl(sticker.url, { homeserver: State.packsData?.homeserver_url, type: 'download' }) || ''}?&height=512&width=512&method=scale" / >
< div class = "margin-small" >
< input name = "stickerName" type = "text" placeholder = "Sticker Name/Description" value = "${sticker.body}" style = "display: inline-block;" disabled / >
< button name = "stickerDelete" > ❌️ Delete Sticker< / button >
< / div >
` });
stickerModal.querySelector('button[name="stickerDelete"]').onclick = () => Spacc.ShowModal({ label: 'Are you sure to delete this sticker?', action: () => {
//stickerElem.remove();
stickerModal.close();
packObject.data.stickers.splice(stickerIndex, 1);
packObject.edited = true;
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
ShowStickerPack(showStickerPackEvent, packObject);
} });
}
2024-01-08 22:15:40 +01:00
async function TryUploadStickerMediaAndMakeObject (props) {
const result = await RequestMatrixUploadFile(props.file, props.mime);
if (result) {
if (!props.dimensions) {
const img = new Image();
img.src = await ReadBufferOrBlob(props.file);
await img.decode();
props.dimensions = { w: img.width, h: img.height };
}
return {
id: result.content_uri,
url: result.content_uri,
msgtype: "m.sticker",
body: props.text,
info: {
w: props.dimensions.w,
h: props.dimensions.h,
size: props.size,
mimetype: props.mime,
thumbnail_url: result.content_uri,
thumbnail_info: {
w: props.dimensions.w,
h: props.dimensions.h,
size: props.size,
mimetype: props.mime,
},
},
2024-01-05 23:57:30 +01:00
};
2024-01-08 22:15:40 +01:00
}
}
async function TryAddStickerFromDataFile (stickerProps, packObject) {
const stickerData = await TryUploadStickerMediaAndMakeObject(stickerProps);
if (stickerData) {
//stickerData[Defaults.appIdentity] = {};
const stickerElem = $().createElement('button');
stickerElem.innerHTML = `< img src = "${await ReadBufferOrBlob(stickerProps.file)}" / > `;
stickerElem.onclick = () => OnClickStickerButton(null, packObject, stickerData, packObject.data.stickers.length);
$`#LayoutPackGrid`.insertBefore(stickerElem, event.target.parentElement);
packObject.data.stickers.push(stickerData);
packObject.edited = true;
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
}
2024-01-05 23:57:30 +01:00
}
2024-01-08 22:15:40 +01:00
async function TryAddStickersFromLocalFiles (event, packObject) {
2024-01-05 23:57:30 +01:00
let newPackStickers = [];
for (const file of event.target.files) {
2024-01-08 22:15:40 +01:00
const stickerData = await TryUploadStickerMediaAndMakeObject({
file: file,
size: file.size,
mime: file.type,
text: SafeStripFileExtension(file.name, ['jpeg', 'jpg', 'gif', 'png', 'webp']),
});
if (stickerData) {
2024-01-05 23:57:30 +01:00
//stickerData[Defaults.appIdentity] = {};
2024-01-06 00:23:21 +01:00
const globalStickerIndex = (packObject.data.stickers.length + newPackStickers.length);
2024-01-05 23:57:30 +01:00
const stickerElem = $().createElement('button');
2024-01-08 22:15:40 +01:00
stickerElem.innerHTML = `< img src = "${await ReadFileAsync(file)}" / > `;
2024-01-06 00:23:21 +01:00
stickerElem.onclick = () => OnClickStickerButton(null, packObject, stickerData, globalStickerIndex);
2024-01-05 23:57:30 +01:00
$`#LayoutPackGrid`.insertBefore(stickerElem, event.target.parentElement);
newPackStickers.push(stickerData);
2024-01-08 22:15:40 +01:00
} else { // TODO fix this, I think it doesn't actually wait for user input and just continues execution
2024-01-05 23:57:30 +01:00
const answer = await Spacc.ShowModal({
label: 'File upload failed. What to do?',
action: () => 'continue',
actionCancel: () => 'cancel',
labelSecondary: '🔄️ Retry', actionSecondary: () => 'retry',
});
if (answer === 'cancel') {
newPackStickers = [];
break;
} else if (answer === 'retry') {
// ... find out how to handle this
} else if (answer === 'continue') {
continue;
2024-01-02 19:23:14 +01:00
}
2024-01-03 19:11:14 +01:00
}
2024-01-05 23:57:30 +01:00
}
if (newPackStickers.length > 0) {
packObject.data.stickers = [...packObject.data.stickers, ...newPackStickers];
packObject.edited = true;
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
}
2024-01-03 19:11:14 +01:00
}
async function ReinitStickersAccountData () {
State.packsData = { homeserver_url: State.account.homeserver, packs: [] };
State.stickersData = [];
2024-01-05 23:57:30 +01:00
State.widgetsData.stickerpicker = {
content: {
type: "m.stickerpicker",
url: `${Defaults.stickerPickerUrl}?& config=& theme=$theme`,
name: "Stickerpicker",
managedBy: [
`${Defaults.appIdentity}`,
`${Defaults.appIdentity}/${Defaults.appInterface}`,
],
2024-01-03 19:11:14 +01:00
},
2024-01-05 23:57:30 +01:00
sender: GetMatrixUserTag(State.account),
state_key: "stickerpicker",
type: "m.widget",
id: "stickerpicker",
2024-01-03 19:11:14 +01:00
};
await RequestAccountWidgetsData(State.widgetsData);
PreparePacksEditor();
}
async function CommitNewAccountStickersAndData () {
$`#LayoutCollectionActions button[name="commit"]`.disabled = true;
// upload new metadata for sticker packs which have been edited
for (const packIndex in State.stickersData) {
const pack = State.stickersData[packIndex];
if (pack.edited & & pack.data.stickers.length > 0) {
2024-01-08 22:15:40 +01:00
const uploadResult = await RequestMatrixUploadFile(JSON.stringify(pack.data), 'application/json');
2024-01-03 19:11:14 +01:00
State.packsData.packs[packIndex] = GetMatrixMediaUrl(uploadResult.content_uri);
}
2024-01-03 00:31:15 +01:00
}
2024-01-03 19:11:14 +01:00
// remove empty sticker packs before committing, they break the picker
for (const packIndex in State.stickersData) {
if (State.stickersData[packIndex].data.stickers.length === 0) {
State.stickersData[packIndex].data.stickers.splice(packIndex, 1);
State.packsData.packs.splice(packIndex, 1);
}
2024-01-03 00:31:15 +01:00
}
2024-01-03 19:11:14 +01:00
// finally upload new index file and update profile data
2024-01-08 22:15:40 +01:00
const uploadResult = await RequestMatrixUploadFile(JSON.stringify(State.packsData), 'application/json');
2024-01-03 19:11:14 +01:00
const packsUrlNew = GetMatrixMediaUrl(uploadResult.content_uri);
State.widgetsData.stickerpicker.content.url = `${$`#LayoutCollectionOptions input[name="pickerUrl"]`.value}?&config=${packsUrlNew}&theme=$theme`;
2024-01-05 23:57:30 +01:00
State.widgetsData.stickerpicker.content.managedBy = [
`${Defaults.appIdentity}`,
`${Defaults.appIdentity}/${Defaults.appInterface}`,
];
2024-01-03 19:11:14 +01:00
if (await RequestAccountWidgetsData(State.widgetsData)) {
PreparePacksEditor();
} else {
$`#LayoutCollectionActions button[name="commit"]`.disabled = false;
}
2024-01-03 00:31:15 +01:00
}
2024-01-02 19:23:14 +01:00
function DisplayAccountSelect () {
ResetLayouts();
2024-01-03 19:11:14 +01:00
for (const accountIndex in Config.accounts) {
const account = Config.accounts[accountIndex];
2024-01-02 19:23:14 +01:00
const accountButton = $().createElement('button');
accountButton.style.width = 'calc(100% - 3em)';
accountButton.innerHTML += `🐱️ ${GetMatrixUserTag(account)}`;
accountButton.onclick = () => PreparePacksEditor(account);
$`#LayoutAccountSelect`.appendChild(accountButton);
const deleteButton = $().createElement('button');
deleteButton.innerHTML += `❌️`;
deleteButton.onclick = () => Spacc.ShowModal({ label: '❌️ Confirm remove account?', action: () => {
2024-01-03 19:11:14 +01:00
Config.accounts.splice(accountIndex, 1);
2024-01-02 19:23:14 +01:00
Config.Save();
2024-01-03 19:11:14 +01:00
DisplayAccountSelect();
2024-01-02 19:23:14 +01:00
} });
$`#LayoutAccountSelect`.appendChild(deleteButton);
}
const addButton = $().createElement('button');
addButton.innerHTML += '🆕️ Add new account';
2024-01-05 23:57:30 +01:00
addButton.onclick = () => ShowLoginDialog();
$`#LayoutAccountSelect`.appendChild(addButton);
2024-01-08 22:15:40 +01:00
const demoButton = $().createElement('button');
demoButton.innerHTML += '🎭️ Guest / Demo Mode';
demoButton.onclick = () => PreparePacksEditor(null);
//$`#LayoutAccountSelect`.appendChild(demoButton);
2024-01-05 23:57:30 +01:00
}
async function ShowLoginDialog () {
const modal = await Spacc.ShowModal({ label: Defaults.Strings.mLoginHint, extraHTML: `
< label >
Homeserver
< input name = "homeserver" type = "url" placeholder = "https://matrix.example.com" value = "https://matrix.org" / >
< / label >
< label >
Username
< input name = "username" type = "text" placeholder = "AzureDiamond" / >
< / label >
< label >
Password
< input name = "password" type = "password" placeholder = "*******" / >
< / label >
< label >
Alternatively, import account via < code > access_token< / code > instead
< input name = "useToken" type = "checkbox" / >
< / label >
`, action: (event, modalButton) => TryUserLogin(event, modalButton) });
const buttonConfirm = modal.querySelector('button[name="confirm"]');
buttonConfirm.disabled = true;
for (const requiredElem of modal.querySelectorAll('input[type="url"], input[type="text"], input[type="password"]')) {
for (const event of ['change', 'input', 'paste']) {
requiredElem[`on${event}`] = () => {
let allFilled = true;
for (const requiredElem of modal.querySelectorAll('input[type="url"], input[type="text"], input[type="password"]')) {
if (!requiredElem.value) {
allFilled = false;
2024-01-03 19:11:14 +01:00
}
}
2024-01-05 23:57:30 +01:00
buttonConfirm.disabled = !allFilled;
};
2024-01-03 19:11:14 +01:00
}
2024-01-05 23:57:30 +01:00
}
}
async function TryUserLogin (event, modalButton) {
const modal = modalButton.parentElement;
const homeserver = modal.querySelector('input[name="homeserver"]').value;
const username = modal.querySelector('input[name="username"]').value;
const password = modal.querySelector('input[name="password"]').value;
const loginData = {
homeserver: homeserver,
username: username,
2024-01-03 19:11:14 +01:00
};
2024-01-05 23:57:30 +01:00
if (modal.querySelector('input[name="useToken"]').checked) {
loginData.token = password;
} else {
try {
const response = await fetch(`${homeserver}/_matrix/client/v3/login`, {
method: "POST",
body: JSON.stringify({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: username,
},
password: password,
}),
});
const result = await response.json();
if (response.status === 200) {
loginData.token = result.access_token;
} else {
throw JSON.stringify(result);
}
} catch(err) {
Spacc.ShowModal(`Error trying to get a session token: ${err}`);
return;
}
}
Config.accounts.push(loginData);
Config.Save();
DisplayAccountSelect();
2024-01-02 19:23:14 +01:00
}
2024-01-01 19:11:18 +01:00
2024-01-03 19:11:14 +01:00
function Main () {
InitializeState();
DisplayAccountSelect();
2024-01-02 19:23:14 +01:00
}
2024-01-03 19:11:14 +01:00
Main();
2024-01-01 14:42:50 +01:00
< / script >
2024-01-02 19:23:14 +01:00
< style >
:root {
--margin: 8px;
}
* {
box-sizing: border-box;
}
body, .ActionForm, .TableForm {
margin: var(--margin);
overflow-x: hidden;
}
details > summary > * {
display: inline-block;
}
2024-01-05 23:57:30 +01:00
label > * {
width: 100%;
}
2024-01-02 19:23:14 +01:00
2024-01-03 19:11:14 +01:00
#LayoutPacksList img {
max-width: 64px;
2024-01-02 19:23:14 +01:00
}
2024-01-03 19:11:14 +01:00
#LayoutPackGrid img {
max-width: 128px;
2024-01-02 19:23:14 +01:00
}
< / style >