diff --git a/auth.js b/auth.js index 16063de..ea5769c 100644 --- a/auth.js +++ b/auth.js @@ -1,46 +1,45 @@ const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store'); -module.exports.getMatrixToken = async () => { - matrixClient = sdk.createClient(config.matrix.domain); - matrixClient.loginWithPassword(config.matrix.user, config.matrix.password) - .then((response) => { - matrix_auth = { - user_id: response.user_id, - access_token: response.access_token, - device_id: response.device_id, - }; - localStorage.setItem('matrix_auth', JSON.stringify(response, null, 2)); - }).then(() => { - matrixTokenLogin(); - }) - .catch((e) => { - console.log(e); - }); -}; - -matrixTokenLogin = async () => { +const matrixTokenLogin = async () => { matrixClient = sdk.createClient({ baseUrl: config.matrix.domain, - accessToken: matrix_auth.access_token, - userId: matrix_auth.user_id, - deviceId: matrix_auth.device_id, + accessToken: matrix.auth.access_token, + userId: matrix.auth.user_id, + deviceId: matrix.auth.device_id, sessionStore: new sdk.WebStorageSessionStore(localStorage), cryptoStore: new LocalStorageCryptoStore(localStorage), }); matrixClient.initCrypto() - .then(() => { - if(!localStorage.getItem('crypto.device_data')) + .then(() => { + if (!localStorage.getItem('crypto.device_data')) { return console.log( - '====================================================\n'+ - 'New OLM Encryption Keys created, please restart ligh7hau5.\n'+ - '====================================================' + '====================================================\n' + + 'New OLM Encryption Keys created, please restart ligh7hau5.\n' + + '====================================================', ); + } matrixClient.startClient(); }); }; module.exports.matrixTokenLogin = matrixTokenLogin; +module.exports.getMatrixToken = async () => { + matrixClient = sdk.createClient(config.matrix.domain); + matrixClient.loginWithPassword(config.matrix.user, config.matrix.password) + .then((response) => { + matrix.auth = { + user_id: response.user_id, + access_token: response.access_token, + device_id: response.device_id, + }; + localStorage.setItem('matrix_auth', JSON.stringify(response, null, 2)); + }).then(() => matrixTokenLogin()) + .catch((e) => { + console.log(e); + }); +}; + module.exports.registerFediverseApp = async () => { axios.post(`${config.fediverse.domain}/api/v1/apps`, { diff --git a/commands/archive.js b/commands/archive.js index 6ca99dd..a8802c3 100644 --- a/commands/archive.js +++ b/commands/archive.js @@ -3,23 +3,6 @@ const qs = require('qs'); const sleep = ms => new Promise(r => setTimeout(r, ms)); -const editNoticeHTML = (client, roomId, event, html, plain) => client.sendMessage(roomId, { - body: ` * ${plain || html.replace(/<[^<]+?>/g, '')}`, - formatted_body: ` * ${html}`, - format: 'org.matrix.custom.html', - msgtype: 'm.notice', - 'm.new_content': { - body: plain || html.replace(/<[^<]+?>/g, ''), - formatted_body: html, - format: 'org.matrix.custom.html', - msgtype: 'm.notice' - }, - 'm.relates_to': { - rel_type: 'm.replace', - event_id: event.event_id - } -}); - const headers = ({ domain, userAgent }) => ({ 'Host': `${domain}`, 'User-Agent': `${userAgent}` @@ -49,7 +32,7 @@ const arc1Str = str => `Archiving page ${str}`; const arc2Str = (str, title, date) => `Archived page ${str} [${date}]
${title}`; const arc3Str = str => `Timed out ${str}`; -const run = async (matrixClient, { roomId }, userInput, rearchive) => { +const run = async (roomId, userInput, rearchive) => { const instance = axios.create({ baseURL: `https://${config.archive.domain}`, headers: headers(config.archive), @@ -62,29 +45,29 @@ const run = async (matrixClient, { roomId }, userInput, rearchive) => { reply = await matrixClient.sendHtmlNotice(roomId, '', reqStr(userInput)); const { refresh, id, title, date } = await archive(instance, userInput, rearchive); if (id) - return await editNoticeHTML(matrixClient, roomId, reply, arc2Str(`${config.archive.domain}${id}`, title, date)); + return await matrix.utils.editNoticeHTML(roomId, reply, arc2Str(`${config.archive.domain}${id}`, title, date)); if (refresh) { const path = refresh.split(`https://${config.archive.domain}`); if (!path[1]) throw refresh; - await editNoticeHTML(matrixClient, roomId, reply, arc1Str(refresh)); + await matrix.utils.editNoticeHTML(roomId, reply, arc1Str(refresh)); let tries = 30; while (tries--) { await sleep(10000); const { title, date, id } = await archive(instance, userInput); if (rearchive == false && title !== undefined) - return await editNoticeHTML(matrixClient, roomId, reply, arc2Str(`${config.archive.domain}${id}`, title, date)); + return await matrix.utils.editNoticeHTML(roomId, reply, arc2Str(`${config.archive.domain}${id}`, title, date)); const { request: { path: reqPath }, headers: { 'memento-datetime': rearchiveDate } } = await instance({ method: 'HEAD', url: path[1] }) .catch(e => ({ request: { path: path[1] } })); if (rearchive == true && reqPath !== path[1]) - return await editNoticeHTML(matrixClient, roomId, reply, arc2Str(`${config.archive.domain}${reqPath}`, title, rearchiveDate)); + return await matrix.utils.editNoticeHTML(roomId, reply, arc2Str(`${config.archive.domain}${reqPath}`, title, rearchiveDate)); } - return await editNoticeHTML(matrixClient, roomId, reply, arc3Str(refresh)); + return await matrix.utils.editNoticeHTML(roomId, reply, arc3Str(refresh)); } throw 'sad'; } catch (e) { const sad = `Sad!
${`${e}`.replace(/<[^<]+?>/g, '').substr(0, 100)}`; if (reply) - editNoticeHTML(matrixClient, roomId, reply, sad, 'sad').catch(() => {}); + matrix.utils.editNoticeHTML(roomId, reply, sad, 'sad').catch(() => {}); else matrixClient.sendHtmlNotice(roomId, 'sad', sad).catch(() => {}); } diff --git a/commands/fediverse/beg.js b/commands/fediverse/beg.js index a59b0bf..773b1d6 100644 --- a/commands/fediverse/beg.js +++ b/commands/fediverse/beg.js @@ -1,19 +1,15 @@ -exports.runQuery = function (matrixClient, room) { +exports.runQuery = function (roomId, event) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - data: { status: `@10grans@fedi.cc beg` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` -
You have begged for 10grans.
- (id: ${response.data.id}
) -

`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + data: { status: '@10grans@fedi.cc beg' }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/boo.js b/commands/fediverse/boo.js index a77b3a0..5605eef 100644 --- a/commands/fediverse/boo.js +++ b/commands/fediverse/boo.js @@ -1,16 +1,14 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}/unfavourite`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `You have boo'd: ${response.data.account.acct} -
${response.data.content}`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/clap.js b/commands/fediverse/clap.js index 7664855..715c9ad 100644 --- a/commands/fediverse/clap.js +++ b/commands/fediverse/clap.js @@ -1,16 +1,14 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}/favourite`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `You have clapped: ${response.data.account.acct}: -
${response.data.content}`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/copy.js b/commands/fediverse/copy.js index e15341e..974a11b 100644 --- a/commands/fediverse/copy.js +++ b/commands/fediverse/copy.js @@ -1,16 +1,14 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}/reblog`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `You have repeated: -
${response.data.content}`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/flood.js b/commands/fediverse/flood.js index 8d214a5..eec2f4a 100644 --- a/commands/fediverse/flood.js +++ b/commands/fediverse/flood.js @@ -1,39 +1,26 @@ -exports.runQuery = function (matrixClient, room) { +exports.runQuery = function (roomId) { setInterval(() => { axios({ method: 'GET', url: `${config.fediverse.domain}/api/v1/timelines/home`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((events) => { - let lastEvent = JSON.parse(localStorage.getItem('timeline')); - localStorage.setItem('timeline', JSON.stringify(events.data[0].created_at, null, 2)); - - if (lastEvent !== events.data[0].created_at) { - if (events.data[0].reblog === null) { - matrixClient.sendHtmlNotice(room.roomId, - '', - `${events.data[0].account.acct} -
${events.data[0].content}
- ${events.data[0].media_attachments.map(media => - ``+`${media.description}`+'' - ).join('
')} - (id: ${events.data[0].id}) ${registrar.media.visibilityEmoji(events.data[0].visibility)} -
`); - } else { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` - ${events.data[0].account.acct} - has repeated: -
${events.data[0].reblog.account.acct}
-
${events.data[0].content}
- ${events.data[0].media_attachments.map(media => - ``+`Proxied image, no description available.`+'' - ).join('
')} -
(id: ${events.data[0].id}) ${registrar.media.visibilityEmoji(events.data[0].visibility)} -
`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }) + .then((res) => { + let past = JSON.parse(localStorage.getItem('timeline')); + if (past.length === 0) past = res.data; + const events = res.data; + const len = events.length; + for (let i = len - 1; i >= 0; i--) { + if (past.findIndex((x) => x.created_at === events[i].created_at) === -1) { + if (events[i].created_at < past.slice(18, 19)[0].created_at) return; + events[i].label = 'status'; + fediverse.utils.formatter(events[i], roomId); + } } - } - }); - }, 8000); + localStorage.setItem('timeline', JSON.stringify(events, null, 2)); + }) + .catch((e) => { + matrix.utils.sendError(null, roomId, e); + }); + }, 30000); }; diff --git a/commands/fediverse/follow.js b/commands/fediverse/follow.js index a60cd1e..ebebcd0 100644 --- a/commands/fediverse/follow.js +++ b/commands/fediverse/follow.js @@ -1,21 +1,19 @@ -const axios = require('axios'); -const fediverse_auth = JSON.parse(localStorage.getItem('fediverse_auth')); - -exports.runQuery = function (matrixClient, room, userInput) { - axios.get(`${config.fediverse.domain}/api/v1/accounts/${userInput}`).then((findUID) => { - axios({ - method: 'POST', - url: `${config.fediverse.domain}/api/v1/accounts/${findUID.data.id}/follow`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }) - .then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `Subscribed: -
${config.fediverse.domain}/${response.data.id}`); - }); - }).catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); +exports.runQuery = async function (roomId, event, userInput) { + const loadingString = `Searching for ${userInput}...`; + const original = await matrixClient.sendHtmlNotice(roomId, `${loadingString}`, `${loadingString}`); + const found = []; + const suggest = []; + axios({ + method: 'GET', + url: `${config.fediverse.domain}/api/v2/search?q=${userInput}&type=accounts`, + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }).then((findUserId) => { + const results = findUserId.data.accounts; + const len = results.length; + for (let i = 0; i < len; i++) results[i].acct !== userInput ? suggest.push(results[i].acct) : found.push(results[i]); + if (found.length > 0) return fediverse.utils.follow(roomId, found, event, original); + if (suggest.length > 0) msg = `${userInput} was not found, suggesting:
${suggest.join('
')}
`; + if (suggest.length === 0) msg = `No results found for: ${userInput}.`; + return matrix.utils.editNoticeHTML(roomId, original, msg); }); }; diff --git a/commands/fediverse/mordy.js b/commands/fediverse/mordy.js index 88a4ae0..0b0ce33 100644 --- a/commands/fediverse/mordy.js +++ b/commands/fediverse/mordy.js @@ -1,24 +1,20 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, data: { status: `@mordekai ${userInput}`, - content_type: `text/markdown`, + content_type: 'text/markdown', visibility: 'unlisted', - expires_in: '7200' + expires_in: '7200', }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` -
${response.data.content}
- (id: ${response.data.id}) -

`); }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/notify.js b/commands/fediverse/notify.js index e725ac2..04a3f18 100644 --- a/commands/fediverse/notify.js +++ b/commands/fediverse/notify.js @@ -1,47 +1,26 @@ -exports.runQuery = function (matrixClient, room) { +exports.runQuery = function (roomId) { setInterval(() => { axios({ method: 'GET', url: `${config.fediverse.domain}/api/v1/notifications`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((events) => { - let lastEvent = JSON.parse(localStorage.getItem('notifications')); - localStorage.setItem('notifications', JSON.stringify(events.data[0].created_at, null, 2)); - if (lastEvent !== events.data[0].created_at) { - if (events.data[0].type === 'follow') { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` - ${events.data[0].account.acct} - has followed you. -
${events.data[0].account.note}`); - } else if (events.data[0].type === 'favourite') { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` - ${events.data[0].account.acct} - has favorited - your post: -
${events.data[0].status.content}
`); - } else if (events.data[0].type === 'mention') { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` - ${events.data[0].account.acct} - has mentioned - you:
${events.data[0].status.content} -
(id: ${events.data[0].status.id}) ${registrar.media.visibilityEmoji(events.data[0].status.visibility)}
-
`); - } else if (events.data[0].type === 'reblog') { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` - ${events.data[0].account.acct} - has repeated - your post:
-
${events.data[0].status.content}
`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }) + .then((res) => { + let past = JSON.parse(localStorage.getItem('notifications')); + if (past.length === 0) past = res.data; + const events = res.data; + const len = events.length; + for (let i = len - 1; i >= 0; i--) { + if (past.findIndex((x) => x.created_at === events[i].created_at) === -1) { + if (events[i].created_at < past.slice(18, 19)[0].created_at) return; + events[i].label = 'notifications'; + fediverse.utils.formatter(events[i], roomId); + } } - } - }); - }, 8000); + localStorage.setItem('notifications', JSON.stringify(events, null, 2)); + }) + .catch((e) => { + matrix.utils.sendError(null, roomId, e); + }); + }, 30000); }; diff --git a/commands/fediverse/pin.js b/commands/fediverse/pin.js index 0b45912..ebca9ca 100644 --- a/commands/fediverse/pin.js +++ b/commands/fediverse/pin.js @@ -1,18 +1,14 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}/pin`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `Pinned: -
- ${response.data.content} -
`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/post.js b/commands/fediverse/post.js index 7f0b0db..2536333 100644 --- a/commands/fediverse/post.js +++ b/commands/fediverse/post.js @@ -1,19 +1,90 @@ -exports.runQuery = function (matrixClient, room, userInput) { - axios({ +const qs = require('qs'); +const FormData = require('form-data'); + +const emojis = { public: '🌐', unlisted: '📝', private: '🔒️', direct: '✉️' }; +exports.visibilityEmoji = (v) => emojis[v] || v; + +const getFilename = (header) => { + if (typeof header !== 'string') return null; + try { + const m = header.match(/inline; filename(?:=(.+)|\*=utf-8''(.+))/); + return !m ? null : m[2] && decodeURIComponent(m[2]) || m[1]; + } catch (e) { + return null; + } +}; + +const mediaDownload = async (url, { whitelist, blacklist }) => { + const media = await axios({ method: 'GET', url, responseType: 'arraybuffer' }); + if (media.statusText !== 'OK' || blacklist.includes(media.headers['content-type'])) throw media; + if (whitelist.length && !whitelist.includes(media.headers['content-type'])) throw media; + return { + data: media.data, + filename: getFilename(media.headers['content-disposition']), + mimetype: media.headers['content-type'], + }; +}; + +const mediaUpload = async ({ domain }, { data, filename, mimetype }) => { + const form = new FormData(); + form.append('file', data, { + filename: filename || 'upload', + contentType: mimetype, + }); + const upload = await axios({ + method: 'POST', + url: `${domain}/api/v1/media`, + headers: form.getHeaders({ Authorization: `Bearer ${fediverse.auth.access_token}` }), + data: form, + }); + if (upload.statusText !== 'OK') throw upload; + return upload.data.id; +}; + +const run = async (roomId, content, replyId, mediaURL, subject) => { + let mediaId = null; + if (mediaURL) { + const media = await mediaDownload(mediaURL, config.fediverse.mimetypes); + mediaId = await mediaUpload(config.fediverse, media); + } + const response = await axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - data: { status: userInput, content_type: `text/markdown` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` -
${response.data.content}
- (id: ${response.data.id}) -

`); - }) - .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); - }); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}`, 'Content-Type': 'application/x-www-form-urlencoded' }, + data: qs.stringify({ + status: content, + content_type: 'text/markdown', + media_ids: mediaURL && [mediaId] || undefined, + in_reply_to_id: replyId || undefined, + spoiler_text: subject || undefined, + }, { arrayFormat: 'brackets' }), + }); + return fediverse.utils.sendEventWithMeta(roomId, `${response.data.id}`, `redact ${response.data.id}`); +}; + +exports.runQuery = async (roomId, userInput, { isReply, hasMedia, hasSubject }) => { + try { + const chunks = userInput.trim().split(' '); + if (!chunks.length || chunks.length < !!isReply + !!hasMedia) throw ''; + let replyId = null; + let mediaURL = null; + const subject = hasSubject ? config.fediverse.subject : null; + if (isReply) { + replyId = chunks[0]; + chunks.shift(); + } + if (hasMedia) { + let url = new URL(chunks[0]); + chunks.shift(); + if (url.protocol === 'mxc:' && url.hostname && url.pathname) + url = new URL(`${config.matrix.domain}/_matrix/media/r0/download/${url.hostname}${url.pathname}`); + if (url.protocol !== 'https:') throw ''; + if (!config.matrix.domains.includes(url.hostname)) throw ''; + if (!/^\/_matrix\/media\/r0\/download\/[^/]+\/[^/]+\/?$/.test(url.pathname)) throw ''; + mediaURL = url.toString(); + } + return await run(roomId, chunks.join(' '), replyId, mediaURL, subject); + } catch (e) { + return matrixClient.sendHtmlNotice(roomId, 'Sad!', 'Sad!').catch(() => {}); + } }; diff --git a/commands/fediverse/redact.js b/commands/fediverse/redact.js index 75ead79..d1ca217 100644 --- a/commands/fediverse/redact.js +++ b/commands/fediverse/redact.js @@ -1,15 +1,14 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'DELETE', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - '
Redacted. { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/status.js b/commands/fediverse/status.js index 0c042a8..a15845e 100644 --- a/commands/fediverse/status.js +++ b/commands/fediverse/status.js @@ -1,17 +1,15 @@ -exports.runQuery = function (matrixClient, room, userInput, ) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'GET', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `${response.data.account.acct} -
${response.data.content}
- ${response.data.media_attachments.map(media => - `${media.description}`) - .join('
')} - (id: ${response.data.id}) -
`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }) + .then((response) => { + response.label = 'status'; + fediverse.utils.formatter(response, roomId); + }) + .catch((e) => { + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/tip.js b/commands/fediverse/tip.js index 0ddbb38..d75932d 100644 --- a/commands/fediverse/tip.js +++ b/commands/fediverse/tip.js @@ -1,19 +1,15 @@ -exports.runQuery = function (matrixClient, room, address, flaggedInput) { +exports.runQuery = function (roomId, address, flaggedInput, event) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - data: { status: `@10grans@fedi.cc tip `+ flaggedInput + ` to `+address }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - ` -
Tipping ${response.data.content}
- (id: ${response.data.id}) -

`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + data: { status: `@10grans@fedi.cc tip ${flaggedInput} to ${address}` }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/unfollow.js b/commands/fediverse/unfollow.js index e45923f..9f5cd6b 100644 --- a/commands/fediverse/unfollow.js +++ b/commands/fediverse/unfollow.js @@ -1,18 +1,19 @@ -exports.runQuery = function (matrixClient, room, userInput) { - axios.get(`${config.fediverse.domain}/api/v1/accounts/${userInput}`).then((findUID) => { - axios({ - method: 'POST', - url: `${config.fediverse.domain}/api/v1/accounts/${findUID.data.id}/unfollow`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }) - .then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `Unsubscribed: -
${config.fediverse.domain}/${response.data.id}`); - }); - }).catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); +exports.runQuery = async function (roomId, event, userInput) { + const loadingString = `Searching for ${userInput}...`; + const original = await matrixClient.sendHtmlNotice(roomId, `${loadingString}`, `${loadingString}`); + const found = []; + const suggest = []; + axios({ + method: 'GET', + url: `${config.fediverse.domain}/api/v2/search?q=${userInput}&type=accounts`, + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }).then((findUserId) => { + const results = findUserId.data.accounts; + const len = results.length; + for (let i = 0; i < len; i++) results[i].acct !== userInput ? suggest.push(results[i].acct) : found.push(results[i]); + if (found.length > 0) return fediverse.utils.unfollow(roomId, found, event, original); + if (suggest.length > 0) msg = `${userInput} was not found, suggesting:
${suggest.join('
')}
`; + if (suggest.length === 0) msg = `No results found for: ${userInput}.`; + return matrix.utils.editNoticeHTML(roomId, original, msg); }); }; diff --git a/commands/fediverse/unpin.js b/commands/fediverse/unpin.js index 2e14690..de032a1 100644 --- a/commands/fediverse/unpin.js +++ b/commands/fediverse/unpin.js @@ -1,18 +1,14 @@ -exports.runQuery = function (matrixClient, room, userInput) { +exports.runQuery = function (roomId, event, userInput) { axios({ method: 'POST', url: `${config.fediverse.domain}/api/v1/statuses/${userInput}/unpin`, - headers: { Authorization: `Bearer ${fediverse_auth.access_token}` }, - }).then((response) => { - matrixClient.sendHtmlNotice(room.roomId, - '', - `Unpinned: -
- ${response.data.content} -
`); + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) .catch((e) => { - matrixClient.sendHtmlNotice(room.roomId, - '', `${e}`); + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); }); }; diff --git a/commands/fediverse/unreblog.js b/commands/fediverse/unreblog.js new file mode 100644 index 0000000..8c7dee8 --- /dev/null +++ b/commands/fediverse/unreblog.js @@ -0,0 +1,14 @@ +exports.runQuery = function (roomId, event, userInput) { + axios({ + method: 'POST', + url: `${config.fediverse.domain}/api/v1/statuses/${userInput}/unreblog`, + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }) + .then(() => { + matrix.utils.addReact(event, '✅'); + }) + .catch((e) => { + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); + }); +}; diff --git a/commands/fediverse/utils.js b/commands/fediverse/utils.js new file mode 100644 index 0000000..782428b --- /dev/null +++ b/commands/fediverse/utils.js @@ -0,0 +1,136 @@ +const sendEventWithMeta = async (roomId, content, meta) => { + await matrixClient.sendEvent(roomId, 'm.room.message', { + body: content.replace(/<[^<]+?>/g, ''), + msgtype: 'm.notice', + formatted_body: content, + meta: meta, + format: 'org.matrix.custom.html', + }); +}; + +const hasAttachment = (res) => { + if (!res.media_attachments) return '
'; + return res.media_attachments.map((media) => { + const mediaURL = new URL(media.remote_url); + media.name = new URLSearchParams(mediaURL.search).get('name') || 'Unknown file name.'; + return `File attachment: ${media.name}
`; + }).join('
'); +}; + +const notifyFormatter = (res, roomId) => { + userDetails = ` + ${res.account.acct}`; + switch (res.type) { + case 'follow': + fediverse.auth.me !== res.account.url ? res.meta = 'follow' : res.meta = 'redact'; + meta = `${res.meta} ${res.account.id}`; + content = `${userDetails} + has followed you. +
${res.account.note}
`; + sendEventWithMeta(roomId, content, meta); + break; + case 'favourite': + fediverse.auth.me !== res.account.url ? res.meta = 'favourite' : res.meta = 'redact'; + meta = `${res.meta} ${res.status.id}`; + content = `${userDetails} + has favorited + your post: +
${res.status.content}
`; + sendEventWithMeta(roomId, content, res.meta); + break; + case 'mention': + fediverse.auth.me !== res.account.url ? res.meta = 'mention' : res.meta = 'redact'; + meta = `${res.meta} ${res.status.id}`; + content = `${userDetails} + has mentioned + you:
${res.status.content} +
(id: ${res.status.id}) ${registrar.post.visibilityEmoji(res.status.visibility)}
+
`; + sendEventWithMeta(roomId, content, meta); + break; + case 'reblog': + fediverse.auth.me !== res.account.url ? res.meta = 'reblog' : res.meta = 'redact'; + meta = `${res.meta} ${res.status.id}`; + content = `${userDetails} + has repeated + your post:
+
${res.status.content}
`; + sendEventWithMeta(roomId, content, meta); + break; + default: + throw 'Unknown notification type.'; + } +}; + +const isOriginal = (res, roomId) => { + if (res.data) res = res.data; + userDetails = ` + ${res.account.acct}`; + fediverse.auth.me !== res.account.url ? res.meta = 'status' : res.meta = 'redact'; + meta = `${res.meta} ${res.id}`; + content = `${userDetails} +
${res.content}
+ ${hasAttachment(res)} +
(id: ${res.id}) ${registrar.post.visibilityEmoji(res.visibility)} +
`; + sendEventWithMeta(roomId, content, meta); +}; + +const isReblog = (res, roomId) => { + if (res.data) res = res.data; + userDetails = ` + ${res.account.acct}`; + fediverse.auth.me !== res.account.url ? res.meta = 'status' : res.meta = 'unreblog'; + meta = `${res.meta} ${res.reblog.id}`; + content = `${userDetails} + has repeated + ${res.reblog.account.acct}'s post: +
${res.content}
+ ${hasAttachment(res)} +
(id: ${res.reblog.id}) ${registrar.post.visibilityEmoji(res.visibility)} +
`; + sendEventWithMeta(roomId, content, meta); +}; + +module.exports.sendEventWithMeta = sendEventWithMeta; + +module.exports.formatter = (res, roomId) => { + const filtered = (res.label === 'notifications') + ? notifyFormatter(res, roomId) + : (res.reblog == null) + ? isOriginal(res, roomId) + : isReblog(res, roomId); + return filtered; +}; + +module.exports.follow = (roomId, account, event, original) => { + axios({ + method: 'POST', + url: `${config.fediverse.domain}/api/v1/accounts/${account[0].id}/follow`, + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }) + .then(() => { + matrix.utils.addReact(event, '✅'); + matrix.utils.editNoticeHTML(roomId, original, `Followed ${account[0].acct}.`); + }) + .catch((e) => { + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); + }); +}; + +module.exports.unfollow = (roomId, account, event, original) => { + axios({ + method: 'POST', + url: `${config.fediverse.domain}/api/v1/accounts/${account[0].id}/unfollow`, + headers: { Authorization: `Bearer ${fediverse.auth.access_token}` }, + }) + .then(() => { + matrix.utils.addReact(event, '✅'); + matrix.utils.editNoticeHTML(roomId, original, `Unfollowed ${account[0].acct}.`); + }) + .catch((e) => { + matrix.utils.addReact(event, '❌'); + matrix.utils.sendError(event, roomId, e); + }); +}; diff --git a/commands/help.js b/commands/help.js index d160760..678e7f1 100644 --- a/commands/help.js +++ b/commands/help.js @@ -1,5 +1,5 @@ -exports.runQuery = function (matrixClient, room) { - matrixClient.sendHtmlNotice(room.roomId, +exports.runQuery = function (roomId) { + matrixClient.sendHtmlNotice(roomId, '', '
fediverse commands
' + '+post [your message] : post
' diff --git a/commands/invidious.js b/commands/invidious.js index 2f5f09e..c1fe773 100644 --- a/commands/invidious.js +++ b/commands/invidious.js @@ -1,6 +1,6 @@ const headers = ({ domain, userAgent }) => ({ - 'Host': `${domain}`, - 'User-Agent': `${userAgent}` + Host: `${domain}`, + 'User-Agent': `${userAgent}`, }); const invidious = async (instance, url) => { @@ -14,7 +14,7 @@ const invidious = async (instance, url) => { author: video.author, views: video.viewCount, likes: video.likeCount, - dislikes: video.dislikeCount + dislikes: video.dislikeCount, }; }; @@ -29,28 +29,26 @@ const card = (video, base, path) => `
(${video.date})

`; -const run = async (matrixClient, { roomId }, userInput) => { +const run = async (roomId, userInput) => { const instance = axios.create({ baseURL: `https://${config.invidious.domain}/api/v1/videos/`, headers: headers(config.invidious), transformResponse: [], - timeout: 10 * 1000 + timeout: 10 * 1000, }); const video = await invidious(instance, userInput); return await matrixClient.sendHtmlNotice(roomId, '', card(video, `https://${config.invidious.domain}`, userInput)); -} +}; -exports.runQuery = async (client, room, userInput) => { +exports.runQuery = async (roomId, event, userInput) => { try { const url = new URL(userInput); - if(!config.invidious.domains.includes(url.hostname)) throw ''; - if(/^\/[\w-]{11}$/.test(url.pathname)) - return await run(client, room, url.pathname.slice(1)); - const params = new URLSearchParams(url.search).get("v"); - if(!/^[\w-]{11}$/.test(params)) throw ''; - return await run(client, room, params); - } catch(e) { - console.log(e); - return client.sendHtmlNotice(room.roomId, 'Sad!', `Sad!`).catch(()=>{}); + if (!config.invidious.domains.includes(url.hostname)) throw ''; + if (/^\/[\w-]{11}$/.test(url.pathname)) return await run(roomId, url.pathname.slice(1)); + const params = new URLSearchParams(url.search).get('v'); + if (!/^[\w-]{11}$/.test(params)) throw ''; + return await run(roomId, params); + } catch (e) { + return matrixClient.sendHtmlNotice(roomId, 'Sad!', 'Sad!').catch(() => {}); } }; diff --git a/commands/nitter.js b/commands/nitter.js index 4146fdd..823272d 100644 --- a/commands/nitter.js +++ b/commands/nitter.js @@ -1,15 +1,15 @@ -const { JSDOM } = require("jsdom"); +const { JSDOM } = require('jsdom'); const headers = ({ domain, userAgent }) => ({ - 'Host': `${domain}`, - 'User-Agent': `${userAgent}` + Host: `${domain}`, + 'User-Agent': `${userAgent}`, }); const nitter = async (instance, url) => { const req = await instance({ method: 'GET', url }); if (req.statusText !== 'OK') throw req; const dom = new JSDOM(req.data); - const document = dom.window.document; + const { document } = dom.window; const tweet = document.querySelector('#m'); const stats = tweet.querySelectorAll('.tweet-body > .tweet-stats .icon-container'); const quote = tweet.querySelector('.tweet-body > .quote'); @@ -33,8 +33,8 @@ const nitter = async (instance, url) => { stats: { replies: stats[0].textContent.trim(), retweets: stats[1].textContent.trim(), - favorites: stats[2].textContent.trim() - } + favorites: stats[2].textContent.trim(), + }, }; }; @@ -49,24 +49,25 @@ const card = (tweet, base, check, path) => (tweet.hasAttachments ? '
This tweet has attached media.
' : '') + (tweet.isReply ? tweet.isReply === 'unavailable' ? '
Replied Tweet is unavailable
' : `
Replied Tweet
${tweet.isReply.text.replace('\n', '
')}
` : '') + (tweet.quote ? `
Quoted Tweet
${tweet.quote.text.replace('\n', '
')}
` : ''); -const run = async (matrixClient, { roomId }, userInput) => { + +const run = async (roomId, userInput) => { const instance = axios.create({ baseURL: `https://${config.nitter.domain}`, headers: headers(config.nitter), transformResponse: [], - timeout: 10 * 1000 + timeout: 10 * 1000, }); const tweet = await nitter(instance, userInput); return await matrixClient.sendHtmlNotice(roomId, '', card(tweet, `https://${config.nitter.domain}`, config.nitter.check, userInput)); -} +}; -exports.runQuery = async (client, room, userInput) => { +exports.runQuery = async (roomId, event, userInput) => { try { const url = new URL(userInput); - if(!config.nitter.domains.includes(url.hostname)) throw ''; - if(!/^\/[^/]+\/status\/\d+\/?$/.test(url.pathname)) throw ''; - return await run(client, room, url.pathname); - } catch(e) { - return client.sendHtmlNotice(room.roomId, 'Sad!', `Sad!`).catch(()=>{}); + if (!config.nitter.domains.includes(url.hostname)) throw ''; + if (!/^\/[^/]+\/status\/\d+\/?$/.test(url.pathname)) throw ''; + return await run(roomId, url.pathname); + } catch (e) { + return matrixClient.sendHtmlNotice(roomId, 'Sad!', 'Sad!').catch(() => {}); } }; diff --git a/main.js b/main.js index 18f76dd..e96f1f5 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ global.registrar = require('./registrar.js'); -matrix_auth.access_token ? auth.matrixTokenLogin() : auth.getMatrixToken(); -if (!fediverse_auth.access_token && config.fediverse.username) auth.registerFediverseApp(); +matrix.auth.access_token ? auth.matrixTokenLogin() : auth.getMatrixToken(); +if (!fediverse.auth.access_token && config.fediverse.username) auth.registerFediverseApp(); matrixClient.on('RoomMember.membership', (event, member) => { if (member.membership === 'invite' && member.userId === matrixClient.credentials.userId) { @@ -17,57 +17,26 @@ matrixClient.on('RoomMember.membership', (event, member) => { } }); -matrixClient.on('Room.timeline', async function (event, room, member, toStartOfTimeline) { +matrixClient.on('event', async (event) => { + if (event.getSender() === matrixClient.credentials.userId) return matrix.utils.selfReact(event); + if (!event.getContent()['m.relates_to']) return; + if (event.event.unsigned.age > 10000) return; + return event.getType() === 'm.room.message' + ? matrix.utils.handleReply(event) : matrix.utils.handleReact(event); +}); + +matrixClient.on('Room.timeline', async (event, member, toStartOfTimeline) => { if (toStartOfTimeline) return; if (event.isEncrypted()) await event._decryptionPromise; if (event.getType() !== 'm.room.message') return; if (event.getSender() === matrixClient.credentials.userId) return; if (event.event.unsigned.age > 10000) return; - if (event.getContent().body.charAt(0) === '+') { - console.log(`Logs: ${event.event.sender} - ${event.getContent().body}`); - let args = event.getContent().body.slice(1).trim().split(/ +/g); - let command = args.shift().toLowerCase(); - const userInput = args.join(' '); - const flaggedInput = userInput.substr(userInput.indexOf(' ') + 1); - const address = args.slice(0, 1).join(' ').replace(/"/g, ''); - - args = []; - - switch(command) { - case 'config': - return; - case 'help': case 'beg': case 'flood': case 'asdf': - args.push(matrixClient, room); - break; - case 'tip': - args.push(matrixClient, room, address, flaggedInput); - break; - case 'archive': case 'rearchive': - args.push(matrixClient, room, userInput, !!~command.indexOf('re')); - command = 'archive'; - break; - case 'post': case 'reply': case 'media': case 'mediareply': - case 'random': case 'randomreply': case 'randommedia': case 'randommediareply': - args.push(matrixClient, room, userInput, { - isReply: !!~command.indexOf('reply'), - hasMedia: !!~command.indexOf('media'), - hasSubject: !!~command.indexOf('random'), - }); - command = 'media'; - break; - case 'proxy': - try { - const url = new URL(userInput); - command = config.invidious.domains.includes(url.hostname) - ? 'invidious' - : config.nitter.domains.includes(url.hostname) - ? 'nitter' - : 'proxy'; - } catch(e) {} - //fallthrough - default: - args.push(matrixClient, room, userInput); - } - registrar[command] && registrar[command].runQuery.apply(null, args); + roomId = event.event.room_id; + content = event.getContent().body; + if (content.charAt(0) === '+') { + const args = content.slice(1).trim().split(/ +/g); + const command = args.shift().toLowerCase(); + console.log(`Logs: ${event.event.sender} - ${content}`); + matrix.utils.eventHandler(args, roomId, command, event); } }); diff --git a/package.json b/package.json index 90d3be9..8c2b7fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ligh7hau5", - "version": "1.1.0", + "version": "1.2.0", "description": "A Matrix to Fediverse client", "main": "main.js", "scripts": { diff --git a/registrar.js b/registrar.js index 65cf232..817f90c 100644 --- a/registrar.js +++ b/registrar.js @@ -6,16 +6,23 @@ global.auth = require('./auth.js'); const { LocalStorage } = require('node-localstorage'); global.localStorage = new LocalStorage('./keys'); -if (!localStorage.getItem('matrix_auth')){ +if (!localStorage.getItem('matrix_auth')) { localStorage.clear(); - localStorage.setItem('matrix_auth', "{}"); + localStorage.setItem('matrix_auth', '[]'); } -if (!localStorage.getItem('fediverse_auth')) localStorage.setItem('fediverse_auth', "{}"); -if (!localStorage.getItem('timeline')) localStorage.setItem('timeline', "{}"); -if (!localStorage.getItem('notifications')) localStorage.setItem('notifications', "{}"); +if (!localStorage.getItem('fediverse_auth')) localStorage.setItem('fediverse_auth', '[]'); +if (!localStorage.getItem('timeline')) localStorage.setItem('timeline', '[]'); +if (!localStorage.getItem('notifications')) localStorage.setItem('notifications', '[]'); -global.matrix_auth = JSON.parse(localStorage.getItem('matrix_auth')); -global.fediverse_auth = JSON.parse(localStorage.getItem('fediverse_auth')); + +global.fediverse = { + auth: JSON.parse(localStorage.getItem('fediverse_auth')), + utils: require('./commands/fediverse/utils.js'), +}; +global.matrix = { + auth: JSON.parse(localStorage.getItem('matrix_auth')), + utils: require('./utils.js'), +}; module.exports = { config: require('./config.js'), @@ -29,15 +36,14 @@ module.exports = { flood: require('./commands/fediverse/flood.js'), follow: require('./commands/fediverse/follow.js'), help: require('./commands/help.js'), - media: require('./commands/fediverse/media.js'), mordy: require('./commands/fediverse/mordy.js'), notify: require('./commands/fediverse/notify.js'), pin: require('./commands/fediverse/pin.js'), post: require('./commands/fediverse/post.js'), redact: require('./commands/fediverse/redact.js'), - reply: require('./commands/fediverse/reply.js'), status: require('./commands/fediverse/status.js'), tip: require('./commands/fediverse/tip.js'), unfollow: require('./commands/fediverse/unfollow.js'), - unpin: require('./commands/fediverse/unpin.js') + unpin: require('./commands/fediverse/unpin.js'), + unreblog: require('./commands/fediverse/unreblog.js') }; diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..4f920af --- /dev/null +++ b/utils.js @@ -0,0 +1,131 @@ +const sendError = async (event, roomId, e) => { + e.response ? error = `Error(${e.response.status}): ${e.response.data.error}` + : e.data ? error = `Error(${e.errcode}): ${e.data.error}` + : error = `Error: ${e.syscall}, ${e.code}`; + return matrixClient.sendHtmlNotice(roomId, + '', error); +}; + +const addReact = async (event, key) => { + const roomId = event.event.room_id; + return matrixClient.sendEvent(event.event.room_id, 'm.reaction', { + 'm.relates_to': { + rel_type: 'm.annotation', + event_id: event.getId(), + key, + }, + }).catch((e) => sendError(null, roomId, e)); +}; + +const eventHandler = (args, roomId, command, event) => { + const userInput = args.join(' '); + const flaggedInput = userInput.substr(userInput.indexOf(' ') + 1); + const address = args.slice(0, 1).join(' ').replace(/"/g, ''); + + args = []; + + switch (command) { + case 'config': + return; + case 'help': case 'flood': case 'notify': + args.push(roomId); + break; + case 'tip': + args.push(roomId, address, flaggedInput); + break; + case 'archive': case 'rearchive': + args.push(roomId, userInput, !!~command.indexOf('re')); + command = 'archive'; + break; + case 'post': case 'reply': case 'media': case 'mediareply': + case 'random': case 'randomreply': case 'randommedia': case 'randommediareply': + args.push(roomId, userInput, { + isReply: !!~command.indexOf('reply'), + hasMedia: !!~command.indexOf('media'), + hasSubject: !!~command.indexOf('random'), + }); + command = 'post'; + break; + case 'proxy': + try { + const url = new URL(userInput); + command = config.invidious.domains.includes(url.hostname) + ? 'invidious' + : config.nitter.domains.includes(url.hostname) + ? 'nitter' + : 'proxy'; + } catch (e) { sendError(event, roomId, e); } + // fallthrough + default: + args.push(roomId, event, userInput); + } + registrar[command] && registrar[command].runQuery.apply(null, args); +}; + +module.exports.sendError = sendError; + +module.exports.addReact = addReact; + +module.exports.eventHandler = eventHandler; + +module.exports.editNoticeHTML = (roomId, event, html, plain) => matrixClient.sendMessage(roomId, { + body: ` * ${plain || html.replace(/<[^<]+?>/g, '')}`, + formatted_body: ` * ${html}`, + format: 'org.matrix.custom.html', + msgtype: 'm.notice', + 'm.new_content': { + body: plain || html.replace(/<[^<]+?>/g, ''), + formatted_body: html, + format: 'org.matrix.custom.html', + msgtype: 'm.notice', + }, + 'm.relates_to': { + rel_type: 'm.replace', + event_id: event.event_id, + }, +}); + +module.exports.handleReact = async (event) => { + const roomId = event.event.room_id; + const reaction = event.getContent()['m.relates_to']; + if (!reaction) return; + const metaEvent = await matrixClient.fetchRoomEvent(roomId, reaction.event_id); + if (!metaEvent.content.meta || metaEvent.sender !== config.matrix.user) return; + const args = metaEvent.content.meta.split(' '); + isMeta = ['status', 'reblog', 'mention', 'redact', 'unreblog']; + if (!isMeta.includes(args[0])) return; + let command = []; + args.shift().toLowerCase(); + if (reaction.key === '🔃') command = 'copy'; + if (reaction.key === '👏') command = 'clap'; + if (reaction.key === '🗑') command = 'redact'; + eventHandler(args, roomId, command, event); +}; + +module.exports.handleReply = async (event) => { + const roomId = event.event.room_id; + const reply = event.getContent()['m.relates_to']['m.in_reply_to']; + if (!reply) return; + const metaEvent = await matrixClient.fetchRoomEvent(roomId, reply.event_id); + if (!metaEvent.content.meta || metaEvent.sender !== config.matrix.user) return; + const args = metaEvent.content.meta.split(' '); + args.push(event.event.content.formatted_body.trim().split('')[1]); + isMeta = ['status', 'reblog', 'mention', 'redact', 'unreblog']; + if (!isMeta.includes(args[0])) return; + args.shift().toLowerCase(); + command = 'reply'; + eventHandler(args, roomId, command, event); +}; + +module.exports.selfReact = async (event) => { + if (event.getType() !== 'm.room.message') return; + if (event.event.unsigned.age > 10000) return; + const { meta } = event.getContent(); + if (!meta) return; + const type = meta.split(' ')[0]; + if (type === 'redact' || type === 'unreblog') addReact(event, '🗑️'); + if (type === 'status' || type === 'reblog' || type === 'mention') { + addReact(event, '🔃'); + addReact(event, '👏'); + } +};