From 7df2101754141c5753247693881863d0a1192f0e Mon Sep 17 00:00:00 2001 From: elpamplina Date: Sun, 14 Dec 2025 00:50:32 +0100 Subject: [PATCH] Completed missing german translation with english replacements (help wanted!). Some type tweaks to deal with TypeScript 5 requirements. --- encrypt.ts | 10 +++++----- lang/de.json | 15 ++++++++++++++- main.ts | 36 ++++++++++++++++++++++++++---------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/encrypt.ts b/encrypt.ts index 77f3b24..7fa95a9 100644 --- a/encrypt.ts +++ b/encrypt.ts @@ -33,7 +33,7 @@ const encrypt = async (text: string, key: CryptoKey) => { } } -const pack = (buffer: ArrayBuffer) => { +const pack = (buffer: Uint8Array) => { return window.btoa( String.fromCharCode.apply(null, new Uint8Array(buffer)) ) @@ -53,16 +53,16 @@ const unpack = (packed: string) => { return string_to_buffer(window.atob(packed)); } -const decode = (bytestream: Uint8Array) => { +const decode = (bytestream: Uint8Array) => { const decoder = new TextDecoder() return decoder.decode(bytestream) } const decrypt = async (cipher: ArrayBuffer, key: CryptoKey, iv: ArrayBuffer) => { - const encoded = await window.crypto.subtle.decrypt({ + const encoded = new Uint8Array(await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv, - }, key, cipher) + }, key, cipher)) return decode(encoded) } @@ -71,7 +71,7 @@ const generateKey = (seed: string) => window.crypto.subtle.digest('SHA-256', str const encryptText = async (key: ArrayBuffer, text: string) => { const cryptoKey = await importKey(key); const {cipher, iv} = await encrypt(text, cryptoKey) - return pack(iv) + ':' + pack(cipher); + return pack(iv) + ':' + pack(new Uint8Array(cipher)); } const decryptText = async (key: ArrayBuffer, text: string) => { diff --git a/lang/de.json b/lang/de.json index 942951c..10269ce 100644 --- a/lang/de.json +++ b/lang/de.json @@ -9,6 +9,8 @@ "connect_to": "Mit Mastodon verbinden", "connect": "Verbinden", "error": "Fehler beim Verbinden mit dem Server!", + "connecting": "Connecting to {{server}}...", + "cancel": "Cancel", "max_post": "Zeichenbegrenzung", "max_post_desc": "{{server}} erlaubt Beiträge mit {{max}} Zeichen.", "visibility_first": "Sichtbarkeit des ersten Beitrags", @@ -24,7 +26,18 @@ "unlisted": "Öffentlich (still) (ungelistet und nicht im Such-Algorithmus)", "private": "Follower (nur Personen, die dir folgen)" }, - "default_language": "Standardpostsprache" + "default_language": "Standardpostsprache", + "quote_approval": "Who can quote", + "quote_approval_desc": "Decide who can quote your posts", + "quote": { + "default": "As set on your account", + "public": "Everybody", + "followers": "Your followers only", + "nobody": "Nobody" + }, + "quote_links": "Convert links into quotes", + "quote_links_desc": "Links to Mastodon posts will be converted into citations whenever possible.", + "quotes_not_supported": "The server doesn't support quotes." }, "modal": { "send_thread_count": "Versende einen Thread an Mastodon mit {{count}} Beiträgen", diff --git a/main.ts b/main.ts index b620896..508e6f0 100644 --- a/main.ts +++ b/main.ts @@ -31,11 +31,23 @@ import { import mime from "mime/lite"; import {decryptText, encryptText, generateKey} from "./encrypt"; import {QuoteApprovalPolicy} from "masto/dist/esm/mastodon/rest/v1/statuses"; -import {StatusVisibility} from "masto/dist/cjs/mastodon/entities/v1"; +import {Status, StatusVisibility} from "masto/dist/cjs/mastodon/entities/v1"; +import {HttpResponse} from "masto/dist/cjs/interfaces"; +import {Client} from "undici-types"; // @ts-ignore const t = i18next.getFixedT(null, 'plugin-mastodon-threading', null); +type SearchStatus = Status & { + quoteApproval: { + currentUser: 'automatic' | 'manual' | 'denied' | 'unknown' + } +} + +type VerifyCredentials = Client & { + scopes: string[] +} + interface MastodonThreadingSettings { server: string, clientId: string, @@ -329,14 +341,15 @@ export default class MastodonThreading extends Plugin { const found_urls = post.text.match(pattern_url); if (found_urls) { // Iterate over URLs, searching for mastodon posts - for (let url: string of found_urls) { + for (let url of found_urls) { const res = await this.getClient().v2.search.list({ q: url, type: 'statuses', resolve: true }); requests++; - if (res.statuses.length === 1 && - (res.statuses[0].quoteApproval.currentUser === 'automatic' || - res.statuses[0].quoteApproval.currentUser === 'manual')) { + const statuses: SearchStatus[] = res.statuses as SearchStatus[]; + if (statuses.length === 1 && + (statuses[0].quoteApproval.currentUser === 'automatic' || + statuses[0].quoteApproval.currentUser === 'manual')) { post.quote = res.statuses[0].id; post.text = post.text.replace(url, ''); break; // Only one quote per post @@ -371,7 +384,9 @@ export default class MastodonThreading extends Plugin { description: img.alt }); img.mediaId = m.id; - if (parseInt(headers.get('x-ratelimit-remaining'), 10) < imageList.length - i) { + const ratelimit = headers.get('x-ratelimit-remaining'); + if (ratelimit != null && + parseInt(ratelimit, 10) < imageList.length - i) { new Notice(t('error.rate_limit')); return; } @@ -388,7 +403,7 @@ export default class MastodonThreading extends Plugin { media.push(img.mediaId); } } - let {data: status, headers} = await this.getClient().v1.statuses.create.$raw({ + const resp: HttpResponse = await this.getClient().v1.statuses.create.$raw({ status: p.text, spoilerText: p.warning, visibility: (first ? visibility_first : visibility_rest), @@ -398,9 +413,10 @@ export default class MastodonThreading extends Plugin { quotedStatusId: p.quote, quoteApprovalPolicy: this.settings.quoteApproval === 'default'? null: this.settings.quoteApproval, }); - id_link = status.id; + id_link = resp.data.id; first = false; - if (parseInt(headers.get('x-ratelimit-remaining'), 10) < posts.length - i) { + const ratelimit = resp.headers.get('x-ratelimit-remaining'); + if (ratelimit != null && parseInt(ratelimit, 10) < posts.length - i) { new Notice(t('error.rate_limit')); return; } @@ -502,7 +518,7 @@ export default class MastodonThreading extends Plugin { async checkCredentials(): Promise { const client = this.getClient(); - const app = await client.v1.apps.verifyCredentials(); + const app: VerifyCredentials = await client.v1.apps.verifyCredentials() as unknown as VerifyCredentials; return (app.scopes.includes('read:search') && app.scopes.includes('write:media') && app.scopes.includes('write:statuses'));