Update MatrixStickerHelper

This commit is contained in:
octospacc 2024-01-08 22:15:40 +01:00
parent 4e414f4d26
commit ac1bf74964
5 changed files with 835 additions and 77 deletions

View File

@ -0,0 +1,8 @@
/**
* Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
* Original file: /npm/gifski-wasm@1.0.1/dist/encode.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
let e,n=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});n.decode();let t=null;function r(){return null!==t&&t.buffer===e.memory.buffer||(t=new Uint8Array(e.memory.buffer)),t}let a=0;function i(e){return null==e}let o,s=null;function u(){return null!==s&&s.buffer===e.memory.buffer||(s=new Int32Array(e.memory.buffer)),s}function c(n,t,o,s,c,f,l,w,y){try{const A=e.__wbindgen_add_to_stack_pointer(-16);var b=function(e,n){const t=n(1*e.length);return r().set(e,t/1),a=e.length,t}(n,e.__wbindgen_malloc),m=a;e.encode(A,b,m,t,o,s,c,i(f)?16777215:f,!i(l),i(l)?0:l,!i(w),i(w)?0:w,!i(y),i(y)?0:y);var d=u()[A/4+0],_=u()[A/4+1],g=(p=d,h=_,r().subarray(p/1,p/1+h)).slice();return e.__wbindgen_free(d,1*_),g}finally{e.__wbindgen_add_to_stack_pointer(16)}var p,h}async function f(t){void 0===t&&(t=new URL("gifski_wasm_bg.wasm",import.meta.url));const a={wbg:{}};a.wbg.__wbindgen_throw=function(e,t){throw new Error((a=e,i=t,n.decode(r().subarray(a,a+i))));var a,i},("string"==typeof t||"function"==typeof Request&&t instanceof Request||"function"==typeof URL&&t instanceof URL)&&(t=fetch(t));const{instance:i,module:o}=await async function(e,n){if("function"==typeof Response&&e instanceof Response){if("function"==typeof WebAssembly.instantiateStreaming)try{return await WebAssembly.instantiateStreaming(e,n)}catch(n){if("application/wasm"==e.headers.get("Content-Type"))throw n;console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",n)}const t=await e.arrayBuffer();return await WebAssembly.instantiate(t,n)}{const t=await WebAssembly.instantiate(e,n);return t instanceof WebAssembly.Instance?{instance:t,module:e}:t}}(await t,a);return e=i.exports,f.__wbindgen_wasm_module=o,e}async function l(e){return o||(o=f(e)),o}async function w({frames:e,width:n,height:t,fps:r,quality:a,repeat:i,resizeWidth:o,resizeHeight:s}){await l();const u=e.length,f=function(e){return e.reduce(((e,n)=>{let t=n instanceof ImageData||"data"in n?n.data:n;return 0===e.length?new Uint8Array([...t]):new Uint8Array([...e,...t])}),new Uint8Array)}(e),w=await c(f,u,n,t,r,a,i,o,s);if(!w)throw new Error("Encoding error.");return w}export{w as default,l as init};
//# sourceMappingURL=/sm/0ab991bbadedff9172e10cdbaf8863ccfe6ed2722a690eb1ed55d8a76f12d2ee.map

Binary file not shown.

View File

@ -19,22 +19,25 @@
<!-- <!--
TODO: TODO:
* more error handling * more error handling and debug logging
** manage consistency between modal errors and messages in debug log (probably will need a SpaccDotWeb function for that)
* immediately show an image after uploading in the grid * immediately show an image after uploading in the grid
* allow reordering, renaming, deleting stickers and packs * allow reordering, renaming, deleting stickers and packs
** update grid when adding a new sticker!
** 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 ** 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
* import from telegram, line * import from line, import video telegram stickers, import video files, import files from URL
* save a linkback to a sticker pack in every single sticker optionally, allowing stickers to be copied, possibly from matrix.to URL * 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 ** set this and other options as persisting in the user account data
* fix sticker reimport protection not working? * fix sticker reimport protection not working?
* operations/status log
* importing animated stickers, and handling automatic media conversion from video to GIF
* in a lot of places that only do HTTP(s), handle also mxc:// URLs * in a lot of places that only do HTTP(s), handle also mxc:// URLs
* open graph, PWA things (webmanifest, worker for offline support) * open graph, PWA things (webmanifest, worker for offline support)
* guest/demo mode * guest/demo mode
* fix race conditions between actions like user going back while app is committing or loading packs * 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
* compatibility build / add polyfills * compatibility build / add polyfills
* multilanguage?
* gif encoding without webserver (without ES import)
--> -->
<div id="Main" hidden="true"> <div id="Main" hidden="true">
@ -46,6 +49,8 @@ TODO:
<button name="commitFake" hidden>📝️ Can't commit changes in Demo Mode</button> <button name="commitFake" hidden>📝️ Can't commit changes in Demo Mode</button>
</div> </div>
<div id="LayoutFeedback"></div>
<div style="overflow: auto;"> <div style="overflow: auto;">
<div id="LayoutPacksList" class="margin-small" style="width: max-content;"></div> <div id="LayoutPacksList" class="margin-small" style="width: max-content;"></div>
</div> </div>
@ -56,7 +61,6 @@ TODO:
</div> </div>
<div class="margin-small" id="LayoutPackGrid"></div> <div class="margin-small" id="LayoutPackGrid"></div>
<div id="LayoutInfo"></div>
<details class="col border margin" id="LayoutCollectionOptions"> <details class="col border margin" id="LayoutCollectionOptions">
<summary> <summary>
@ -77,10 +81,12 @@ TODO:
</label> </label>
</details> </details>
</details> </details>
<tgs-player style="display: none;" hidden="true"></tgs-player>
</div> </div>
<p class="noscript"> <p class="noscript">
This application requires modern JavaScript. This application requires modern JavaScript and cutting-edge web standards.
</p> </p>
<hr class="margin-large"/> <hr class="margin-large"/>
@ -94,8 +100,8 @@ TODO:
</p> </p>
<p> <p>
<b>Note</b>: importing stickers from files is currently experimental, and error handling isn't the best. <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 files. 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.
</p> </p>
<details class="col border margin"> <details class="col border margin">
@ -115,18 +121,38 @@ TODO:
<h3>How can I add sticker packs to my collection?</h3> <h3>How can I add sticker packs to my collection?</h3>
<p> <p>
To add new sticker packs to your collection, you can use the dedicated section, which offers 2 options. You can: To add new sticker packs to your collection, you can use the dedicated section, which offers 2 options. You can:
</p>
<ul>
<li> <li>
Either leave the optional text field blank to create a brand-new pack, to be filled with stickers by importing media files; Either leave the optional text field blank to create a brand-new pack, to be filled with stickers by importing media files;
</li> </li>
<li> <li>
Or, input an URL pointing to the JSON data file (in the format used by <code>maunium/stickerpicker</code>) Or, to import packs from one of a few different supported sources, input an URL. Supported URLs are:
of an already existing collection (<code>index.json</code>) or individual pack (created by other users, or from your backups). <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>
</li> </li>
<br/> </ul>
<p>
Currently, you can import common picture files (JPEG, WebP, PNG, GIF) from your device's storage as stickers. 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. 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.
<br/> <br/>
Importing stickers and packs from Matrix messages, or the LINE or Telegram library, is planned for the more distant future. Importing stickers and packs from Matrix messages, or the LINE store, is planned for the more distant future.
</p> </p>
<h3>Stickerpicker information</h3> <h3>Stickerpicker information</h3>
<p> <p>
@ -143,9 +169,15 @@ TODO:
To modify the app, or to check its code for security reasons, you can simply "View Page Source" in your browser. To modify the app, or to check its code for security reasons, you can simply "View Page Source" in your browser.
<br/> <br/>
This project additionally relies on the following third-party libraries: This project additionally relies on the following third-party libraries:
<li><a href="https://gitlab.com/SpaccInc/SpaccDotWeb" target="_blank">SpaccDotWeb</a></li> </p>
<br/> <ul>
Additionally, if there is any issue you want to report, or if you want to send a pull request, <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,
feel free to do so at the OctoSpacc Hub repository: 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> <a href="https://gitlab.com/octospacc/octospacc.gitlab.io">https://gitlab.com/octospacc/octospacc.gitlab.io</a>
(or contact me via Matrix, see below). (or contact me via Matrix, see below).
@ -158,26 +190,34 @@ TODO:
</p> </p>
<div id="LayoutChangelog"> <div id="LayoutChangelog">
<h3>Changelog</h3> <h3>Changelog</h3>
<p> <p>[2024-01-08]</p>
[2024-01-05] <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>
<li>Uploading stickers from (multiple) local image files</li> <li>Uploading stickers from (multiple) local image files</li>
<li>Deleting individual stickers from packs (+ bug hotfix)</li> <li>Deleting individual stickers from packs (+ bug hotfix)</li>
<li>Various bugfixes and UX improvements</li> <li>Various bugfixes and UX improvements</li>
</p> </ul>
<p> <p>[2024-01-04]</p>
[2024-01-04] <ul>
<li>Importing entire sticker collections from URL</li> <li>Importing entire sticker collections from URL</li>
<li>Fix stickers not loading from accounts already setup with stock stickerpicker</li> <li>Fix stickers not loading from accounts already setup with stock stickerpicker</li>
<li>Add help and changelog</li> <li>Add help and changelog</li>
</p> </ul>
<p> <p>[2024-01-05]</p>
[2024-01-03] <ul>
<li>First release</li> <li>First release</li>
<li>Account management (logging in, reading and writing data)</li> <li>Account management (logging in, reading and writing data)</li>
<li>Visualization of current account stickers</li> <li>Visualization of current account stickers</li>
<li>Importing sticker packs from URL</li> <li>Importing sticker packs from URL</li>
</ul>
</p> </p>
</div> </div>
<h3>Debug Log</h3>
<textarea id="LayoutDebugLog" readonly="true" style="width: 100%;" rows="10"></textarea>
</details> </details>
<script module="Main" type="module"> <script module="Main" type="module">
@ -188,6 +228,11 @@ TODO:
stickerSelectorUrl: "https://maunium.net/stickers-demo/", stickerSelectorUrl: "https://maunium.net/stickers-demo/",
appIdentity: "org.eu.octt.MatrixStickerHelper", appIdentity: "org.eu.octt.MatrixStickerHelper",
appInterface: "v1", appInterface: "v1",
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>
],
Strings: { Strings: {
mConfirmCommit: "Confirm committing account data?", mConfirmCommit: "Confirm committing account data?",
mMustInit: "Your account must first be initialized to handle stickers.", mMustInit: "Your account must first be initialized to handle stickers.",
@ -219,6 +264,15 @@ TODO:
} }
Config.Save = () => localStorage.setItem('SpaccInc-Matrix-Config', JSON.stringify(Config)); Config.Save = () => localStorage.setItem('SpaccInc-Matrix-Config', JSON.stringify(Config));
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)];
const $ = (query, ...params) => (query const $ = (query, ...params) => (query
? document.querySelector(Array.isArray(query) ? document.querySelector(Array.isArray(query)
? (params.length > 0 ? (params.length > 0
@ -231,6 +285,15 @@ TODO:
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); 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);
//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
};
const SafeStripFileExtension = (name, extensions) => { const SafeStripFileExtension = (name, extensions) => {
const parts = name.split('.'); const parts = name.split('.');
return (parts.length === 1 return (parts.length === 1
@ -242,9 +305,43 @@ TODO:
); );
}; };
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);
function ResetLayouts () { function ResetLayouts () {
$`#Main`.hidden = false; $`#Main`.hidden = false;
for (const id of ['LayoutInfo', 'LayoutAccountSelect', 'LayoutPacksList', 'LayoutPackGrid']) { for (const id of ['LayoutFeedback', 'LayoutAccountSelect', 'LayoutPacksList', 'LayoutPackGrid']) {
$`#${id}`.innerHTML = ''; $`#${id}`.innerHTML = '';
} }
for (const id of ['LayoutCollectionActions', 'LayoutPackActions', 'LayoutCollectionOptions']) { for (const id of ['LayoutCollectionActions', 'LayoutPackActions', 'LayoutCollectionOptions']) {
@ -262,10 +359,10 @@ TODO:
$`#LayoutCollectionActions button[name="back"]`.onclick = () => DisplayAccountSelect(); $`#LayoutCollectionActions button[name="back"]`.onclick = () => DisplayAccountSelect();
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = Defaults.stickerPickerUrl; $`#LayoutCollectionOptions input[name="pickerUrl"]`.value = Defaults.stickerPickerUrl;
$`#LayoutChangelog > h3`.remove(); $`#LayoutChangelog > h3`.remove();
const changelogHtml = $`#LayoutChangelog`.innerHTML; const changelogHtml = `<a style="visibility: hidden; position: absolute; top: 0;"></a>${$`#LayoutChangelog`.innerHTML}`;
$`#LayoutChangelog`.remove(); $`#LayoutChangelog`.remove();
$`a[name="version"]`.innerHTML = `v${changelogHtml.split('[')[1].split(']')[0]}`; $`a[name="version"]`.innerHTML = `v${changelogHtml.split('[')[1].split(']')[0]}`;
$`a[name="version"]`.onclick = () => Spacc.ShowModal({ extraHTML: changelogHtml }); $`a[name="version"]`.onclick = async () => (await Spacc.ShowModal({ extraHTML: changelogHtml })).querySelector('a').scrollIntoView('a');
} }
async function RequestAccountWidgetsData (postData) { async function RequestAccountWidgetsData (postData) {
@ -284,7 +381,7 @@ TODO:
} }
} }
async function RequestUploadFile (fileData, fileMime) { async function RequestMatrixUploadFile (fileData, fileMime) {
const request = await fetch(`${State.account.homeserver}/_matrix/media/v3/upload`, { const request = await fetch(`${State.account.homeserver}/_matrix/media/v3/upload`, {
method: "POST", method: "POST",
headers: { Authorization: `Bearer ${State.account.token}`, ...(fileMime && { "Content-Type": fileMime }) }, headers: { Authorization: `Bearer ${State.account.token}`, ...(fileMime && { "Content-Type": fileMime }) },
@ -293,13 +390,78 @@ TODO:
const result = await request.json(); const result = await request.json();
if (request.status === 200) { if (request.status === 200) {
return result; return result;
Logger(`Uploaded ${fileMime} data to ${result.content_uri}.`, 'd');
} else { } else {
Spacc.ShowModal(`Error: ${JSON.stringify(result)}`); Spacc.ShowModal(`Error: ${JSON.stringify(result)}`);
} }
} }
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,
});
}
async function PreparePacksEditor (account) { async function PreparePacksEditor (account) {
if (account) { if (account !== undefined) {
State.account = account; State.account = account;
} }
ResetLayouts(); ResetLayouts();
@ -312,9 +474,7 @@ TODO:
label: Defaults.Strings.mConfirmCommit, label: Defaults.Strings.mConfirmCommit,
action: () => ReinitStickersAccountData(), action: () => ReinitStickersAccountData(),
}); });
$`#LayoutInfo`.innerHTML = `<p> Logger(`Fetching Matrix account data, please wait...`, 'f');
Fetching account data...
</p>`;
State.widgetsData = await RequestAccountWidgetsData(); State.widgetsData = await RequestAccountWidgetsData();
if (State.widgetsData) { if (State.widgetsData) {
const pickerUrlFull = State.widgetsData?.stickerpicker?.content?.url; const pickerUrlFull = State.widgetsData?.stickerpicker?.content?.url;
@ -326,17 +486,17 @@ TODO:
if (!isManaged || !State.widgetsData?.stickerpicker) { if (!isManaged || !State.widgetsData?.stickerpicker) {
$`#LayoutCollectionOptions`.open = true; $`#LayoutCollectionOptions`.open = true;
if (!State.widgetsData?.stickerpicker) { if (!State.widgetsData?.stickerpicker) {
$`#LayoutInfo`.innerHTML = `<p>${Defaults.Strings.mMustInit}</p>`; Logger(Defaults.Strings.mMustInit, 'f');
// we always init sticker data from the default URL to avoid headaches // we always init sticker data from the default URL to avoid headaches
$`#LayoutCollectionOptions input[name="pickerUrl"]`.value = ''; $`#LayoutCollectionOptions input[name="pickerUrl"]`.value = '';
$`#LayoutCollectionOptions input[name="pickerUrl"]`.disabled = true; $`#LayoutCollectionOptions input[name="pickerUrl"]`.disabled = true;
} else } else
if (!isManaged) { if (!isManaged) {
$`#LayoutInfo`.innerHTML = ` $`#LayoutFeedback`.innerHTML = `
<p>${Defaults.Strings.mNotManaged}</p> <p>${Defaults.Strings.mNotManaged}</p>
<button name="continue">⏭️ Continue</button> <button name="continue">⏭️ Continue</button>
`; `;
$`#LayoutInfo > button[name="continue"]`.onclick = () => { $`#LayoutFeedback > button[name="continue"]`.onclick = () => {
$`#LayoutCollectionOptions`.open = false; $`#LayoutCollectionOptions`.open = false;
if (State.widgetsData?.stickerpicker?.content?.url && !packsUrl) { if (State.widgetsData?.stickerpicker?.content?.url && !packsUrl) {
// assuming the user is probably coming from stock maunium/stickerpicker, // assuming the user is probably coming from stock maunium/stickerpicker,
@ -385,7 +545,7 @@ TODO:
addButton.innerHTML = ' Create/Import Pack'; addButton.innerHTML = ' Create/Import Pack';
addButton.onclick = (event) => Spacc.ShowModal({ label: Defaults.Strings.mCreatePackHint, extraHTML: ` addButton.onclick = (event) => Spacc.ShowModal({ label: Defaults.Strings.mCreatePackHint, extraHTML: `
<label> <label>
<input name="packUrl" type="text" placeholder="https://example.com/packs/example.json"/> <input name="packUrl" type="text" placeholder="https://example.com/example.json, t.me/addstickers/Pack, ..."/>
</label> </label>
`, action: (event, modalButton) => CreateNewPack(event, modalButton) }); `, action: (event, modalButton) => CreateNewPack(event, modalButton) });
$`#LayoutPacksList`.appendChild(addButton); $`#LayoutPacksList`.appendChild(addButton);
@ -415,15 +575,14 @@ TODO:
//if (packUrl && IsAnyPackImportedFrom(packUrl) && await Spacc.ShowModal({ label: Defaults.Strings.mAlreadyImported, action: () => 'continue', actionCancel: () => 'cancel' }) === 'continue') { //if (packUrl && IsAnyPackImportedFrom(packUrl) && await Spacc.ShowModal({ label: Defaults.Strings.mAlreadyImported, action: () => 'continue', actionCancel: () => 'cancel' }) === 'continue') {
// return; // return;
//}; //};
if (packUrl) { if (packUrl && !IsUrlTelegramSticker(packUrl)) {
try { try {
const request = await fetch(packUrl); const request = await fetch(packUrl);
packData = await request.json(); packData = await request.json();
// import JSON is an index, so we try to import all its packs // import JSON is an index, so we try to import all its packs
if (packData.packs && !packData.stickers) { if (packData.packs && !packData.stickers) {
for (const pack of packData.packs) { for (const pack of packData.packs) {
const packLower = pack.toLowerCase(); const packUrlPrefix = (IsUrlHttpOrS(pack) ? '' : packUrl.split('/').slice(0, -1).join('/'));
const packUrlPrefix = (packLower.startsWith('http://') || packLower.startsWith('http://') ? '' : packUrl.split('/').slice(0, -1).join('/'));
await CreateNewPack(null, null, `${packUrlPrefix}/${pack}`); await CreateNewPack(null, null, `${packUrlPrefix}/${pack}`);
} }
return; return;
@ -440,6 +599,40 @@ TODO:
State.packsData.packs.push(null); State.packsData.packs.push(null);
const packObject = { data: packData, index: State.stickersData.length, edited: true }; const packObject = { data: packData, index: State.stickersData.length, edited: true };
State.stickersData.push(packObject); State.stickersData.push(packObject);
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 = '';
}
AddNewPackButton(packObject, event, modalButton); AddNewPackButton(packObject, event, modalButton);
} }
@ -501,7 +694,7 @@ TODO:
Upload New Sticker(s) Upload New Sticker(s)
<input type=file hidden="true" multiple="true" accept="image/jpeg,image/gif,image/png,image/webp"/> <input type=file hidden="true" multiple="true" accept="image/jpeg,image/gif,image/png,image/webp"/>
`; `;
addButton.querySelector('input[type="file"]').onchange = (event) => AddStickerFromFile(event, packObject); addButton.querySelector('input[type="file"]').onchange = (event) => TryAddStickersFromLocalFiles(event, packObject);
addButton.onclick = (event) => event.target.querySelector('input[type="file"]')?.click(); addButton.onclick = (event) => event.target.querySelector('input[type="file"]')?.click();
// TODO: // TODO:
$`#LayoutPackGrid`.appendChild(addButton); $`#LayoutPackGrid`.appendChild(addButton);
@ -525,51 +718,69 @@ TODO:
} }); } });
} }
function readFileAsync(file, method='readAsDataURL') { async function TryUploadStickerMediaAndMakeObject (props) {
return new Promise((resolve) => { const result = await RequestMatrixUploadFile(props.file, props.mime);
const reader = new FileReader(); if (result) {
reader.onload = (event) => { if (!props.dimensions) {
resolve(event.target.result); 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,
},
},
}; };
reader[method](file); }
});
} }
async function AddStickerFromFile (event, packObject) { 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;
}
}
async function TryAddStickersFromLocalFiles (event, packObject) {
let newPackStickers = []; let newPackStickers = [];
for (const file of event.target.files) { for (const file of event.target.files) {
const result = await RequestUploadFile(file); const stickerData = await TryUploadStickerMediaAndMakeObject({
if (result) { file: file,
const img = new Image(); size: file.size,
img.src = await readFileAsync(file); mime: file.type,
await img.decode(); text: SafeStripFileExtension(file.name, ['jpeg', 'jpg', 'gif', 'png', 'webp']),
const stickerData = { });
id: result.content_uri, if (stickerData) {
url: result.content_uri,
msgtype: "m.sticker",
body: SafeStripFileExtension(file.name, ['jpeg', 'jpg', 'gif', 'png', 'webp']),
info: {
w: img.width,
h: img.height,
size: file.size,
mimetype: file.type,
thumbnail_url: result.content_uri,
thumbnail_info: {
w: img.width,
h: img.height,
size: file.size,
mimetype: file.type,
},
},
};
//stickerData[Defaults.appIdentity] = {}; //stickerData[Defaults.appIdentity] = {};
const globalStickerIndex = (packObject.data.stickers.length + newPackStickers.length); const globalStickerIndex = (packObject.data.stickers.length + newPackStickers.length);
const stickerElem = $().createElement('button'); const stickerElem = $().createElement('button');
stickerElem.innerHTML = `<img src="${img.src}"/>`; stickerElem.innerHTML = `<img src="${await ReadFileAsync(file)}"/>`;
stickerElem.onclick = () => OnClickStickerButton(null, packObject, stickerData, globalStickerIndex); stickerElem.onclick = () => OnClickStickerButton(null, packObject, stickerData, globalStickerIndex);
$`#LayoutPackGrid`.insertBefore(stickerElem, event.target.parentElement); $`#LayoutPackGrid`.insertBefore(stickerElem, event.target.parentElement);
newPackStickers.push(stickerData); newPackStickers.push(stickerData);
} else { } else { // TODO fix this, I think it doesn't actually wait for user input and just continues execution
const answer = await Spacc.ShowModal({ const answer = await Spacc.ShowModal({
label: 'File upload failed. What to do?', label: 'File upload failed. What to do?',
action: () => 'continue', action: () => 'continue',
@ -621,7 +832,7 @@ TODO:
for (const packIndex in State.stickersData) { for (const packIndex in State.stickersData) {
const pack = State.stickersData[packIndex]; const pack = State.stickersData[packIndex];
if (pack.edited && pack.data.stickers.length > 0) { if (pack.edited && pack.data.stickers.length > 0) {
const uploadResult = await RequestUploadFile(JSON.stringify(pack.data), 'application/json'); const uploadResult = await RequestMatrixUploadFile(JSON.stringify(pack.data), 'application/json');
State.packsData.packs[packIndex] = GetMatrixMediaUrl(uploadResult.content_uri); State.packsData.packs[packIndex] = GetMatrixMediaUrl(uploadResult.content_uri);
} }
} }
@ -633,7 +844,7 @@ TODO:
} }
} }
// finally upload new index file and update profile data // finally upload new index file and update profile data
const uploadResult = await RequestUploadFile(JSON.stringify(State.packsData), 'application/json'); const uploadResult = await RequestMatrixUploadFile(JSON.stringify(State.packsData), 'application/json');
const packsUrlNew = GetMatrixMediaUrl(uploadResult.content_uri); const packsUrlNew = GetMatrixMediaUrl(uploadResult.content_uri);
State.widgetsData.stickerpicker.content.url = `${$`#LayoutCollectionOptions input[name="pickerUrl"]`.value}?&config=${packsUrlNew}&theme=$theme`; State.widgetsData.stickerpicker.content.url = `${$`#LayoutCollectionOptions input[name="pickerUrl"]`.value}?&config=${packsUrlNew}&theme=$theme`;
State.widgetsData.stickerpicker.content.managedBy = [ State.widgetsData.stickerpicker.content.managedBy = [
@ -670,6 +881,10 @@ TODO:
addButton.innerHTML += '🆕️ Add new account'; addButton.innerHTML += '🆕️ Add new account';
addButton.onclick = () => ShowLoginDialog(); addButton.onclick = () => ShowLoginDialog();
$`#LayoutAccountSelect`.appendChild(addButton); $`#LayoutAccountSelect`.appendChild(addButton);
const demoButton = $().createElement('button');
demoButton.innerHTML += '🎭️ Guest / Demo Mode';
demoButton.onclick = () => PreparePacksEditor(null);
//$`#LayoutAccountSelect`.appendChild(demoButton);
} }
async function ShowLoginDialog () { async function ShowLoginDialog () {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long