Compare commits
93 commits
Author | SHA1 | Date | |
---|---|---|---|
73dac81f2d | |||
|
296cdc75a6 | ||
|
4cb8d1da7e | ||
|
17c7f819ac | ||
|
12c422c324 | ||
|
8e2ce18f26 | ||
|
5924009154 | ||
|
3122361c6c | ||
|
3b16a0495c | ||
|
35cec7751f | ||
|
9067ae600b | ||
|
25e49ffc78 | ||
|
ccdcf69bea | ||
|
dded657009 | ||
|
06e0ce26a9 | ||
|
6273452876 | ||
|
44e4138b80 | ||
|
be3e68f221 | ||
|
b7e73fc533 | ||
|
2f58d6bb84 | ||
|
472552c33b | ||
|
54631f5c2a | ||
|
c7e3f26f60 | ||
|
a371b7c501 | ||
|
39a88b2722 | ||
|
340fed6346 | ||
|
bfde4265c6 | ||
|
50e9f808da | ||
|
dddb8ad014 | ||
|
1e8577f865 | ||
|
f37e2471c0 | ||
|
7a9bac2bf4 | ||
|
e916778c49 | ||
|
14006d9209 | ||
|
58ef31356a | ||
|
fcadc5addc | ||
|
9da2d13dbf | ||
|
a9073b0b9d | ||
|
58fe0c19d2 | ||
|
1e2d159053 | ||
|
42563ebc35 | ||
|
430fb350c1 | ||
|
695a3bd0ee | ||
|
33a262990c | ||
|
1a7d361b5d | ||
|
35899957eb | ||
|
67b88f9c96 | ||
|
0cd373fb8a | ||
|
ef00f3c8d5 | ||
|
a0d7852e90 | ||
|
ab86f0a3e1 | ||
|
c1b8e37e70 | ||
|
854323cbc4 | ||
|
65b0a07c4a | ||
|
66e751abd5 | ||
|
f69b52261a | ||
|
9e17440abc | ||
|
01e43916f2 | ||
|
57fe623ebe | ||
|
65809d235b | ||
|
78c45451c6 | ||
|
b287d961f3 | ||
|
cdd4429549 | ||
|
c1e8a44dd0 | ||
![]() |
a7e32b5a3f | ||
|
80dcff0440 | ||
|
a94c21fdd1 | ||
|
f201637677 | ||
|
5ed4b932d5 | ||
|
a16da9a4cf | ||
|
2310bd29f2 | ||
|
4a4cd304df | ||
|
beb8a4520c | ||
|
a1dc8200bc | ||
|
2e483e4744 | ||
|
292cca6f29 | ||
|
748ab6ca0a | ||
|
6fe3c992e7 | ||
|
38ca35bdd4 | ||
|
59afdde7d4 | ||
|
2d714943e7 | ||
|
402594f109 | ||
|
f013eb1c98 | ||
|
dc0a6ff922 | ||
|
f28d5655d8 | ||
|
34b1f1b9d8 | ||
|
2aeea1bc37 | ||
|
d7396b8786 | ||
|
751dbb335f | ||
|
2d23f24a6a | ||
|
2c1ef158e9 | ||
|
8dadc6caa4 | ||
|
e95cefafb9 |
48 changed files with 2885 additions and 1699 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
# Patches
|
||||
*.patch
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
@ -6,11 +9,10 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
# Ignore config
|
||||
/config.js
|
||||
config.js
|
||||
|
||||
# Ignore JSON
|
||||
/timeline.json
|
||||
/notification.json
|
||||
# Ignore localstorage
|
||||
keys
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
|
73
README.md
73
README.md
|
@ -1,34 +1,47 @@
|
|||
# Plemara
|
||||
Plemara acts as a [Matrix](https://matrix.org/docs/spec/) bridge to the Fediverse. This application should allow you to do most actions on the Fediverse including livefeed, posting, subscribing, etc. via Matrix. Configuration for the app can be found in [config.js](https://github.com/vulet/plemara/blob/master/config.js). You will need to provide a Matrix username and password for the bridge to work, this can be done through an account made on @matrix.org, or your own homeserver. For the Fediverse side, you will need an access_token, this can be created through the CURL steps below. You would replace `fediverse.site` with where you would like to run the bridge from.
|
||||
# ligh7hau5
|
||||
|
||||
The ligh7hau5 project is used on the Matrix protocol to communicate with the Fediverse. It is also used to proxy popular media networks(Twitter, YouTube, etc) to alternative front ends(Nitter, Invidious, etc). This repository can be ran locally, as on a RPi, or on a VPS.
|
||||
# Archive (+archive URL)
|
||||
|
||||
This command will send a given URL to archive.is, and return an archive.is URL. This can be beneficial in two ways. One, archive.is receives your traffic instead of the URL that you wish to archive. Two, you are creating a historical context of a given URL with a dated attribute. Additionally, if there are changes that have occurred on a page, since the time of last archive, you can also use the rearchive(+rearchive URL) command. If you wish to use a different archiver, this can be configured, see the config.example.js file.
|
||||
# Social Media (+proxy URL)
|
||||
|
||||
This command is given a Twitter or YouTube post, and then returned a respective Nitter/Invidious URL. Additionally, some data is returned about what the URL is, such as: title, description, etc. Instances can also be configured like in the above, see the config.example.js file.
|
||||
# Fediverse
|
||||
|
||||
The ligh7hau5 works as a lite client for the Fediverse. It was built to communicate with a Pleroma instance, but it most likely works on Mastodon as well. Assuming you already have a registered account in regards to the bot, just change the config.js file and fediverse_auth.json will fill out once the bot starts.
|
||||
|
||||
Commands for the Fediverse include:
|
||||
|
||||
`+flood : turn on timeline in channel`
|
||||
|
||||
`+notify : show notifications in channel`
|
||||
|
||||
`+post <your message> : post`
|
||||
|
||||
`+reply <post id> <message> : reply to message`
|
||||
|
||||
`+media <URL> <optional message> : post media`
|
||||
|
||||
`+redact <post id> : delete post`
|
||||
|
||||
`+follow <user id> : follow`
|
||||
|
||||
`+unfollow <user id> : unfollow`
|
||||
|
||||
`+copy <post id> : repeat/repost/retweet`
|
||||
|
||||
`+clap <post id> : favorite`
|
||||
|
||||
`+boo <post id> : unfavorite`
|
||||
|
||||
# Installation
|
||||
1. `git clone https://github.com/vulet/plemara`
|
||||
2. `cd plemara && yarn install`
|
||||
|
||||
First, set up your config.js file, you can see config.example.js as an example. The Matrix & Fediverse login information is then used to populate keys/matrix_auth and keys/fediverse_auth during your initial login. These tokens are then used on sequential logins.
|
||||
|
||||
1. `git clone https://github.com/vulet/ligh7hau5`
|
||||
2. `cd ligh7hau5 && yarn install`
|
||||
3. `node main.js`
|
||||
# Generating an access_token
|
||||
1. `curl -X POST -d "client_name=<NAME HERE>&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=write follow read&website=http://fediverse.site" https://fediverse.site/api/v1/apps`
|
||||
|
||||
Result:
|
||||
```json
|
||||
{"client_id":"result",
|
||||
"client_secret":"result",
|
||||
"id":"result",
|
||||
"name":"result",
|
||||
"redirect_uri":"urn:ietf:wg:oauth:2.0:oob",
|
||||
"website":"http://fediverse.site",
|
||||
"vapid_key":"vapid_key"}
|
||||
```
|
||||
|
||||
2. `curl -X POST -d "client_id=sekret&client_secret=sekret&scope=write follow read&grant_type=password&username=sekret@email.com&password=sekret" https://fediverse.site/oauth/token`
|
||||
|
||||
Result:
|
||||
```json
|
||||
{"token_type":"Bearer",
|
||||
"scope":"write read",
|
||||
"me":"https://fediverse.site/users/<your username>",
|
||||
"access_token":"result"}
|
||||
```
|
||||
|
||||
The access_token from the above command is then stored in the [config.js](https://github.com/vulet/plemara/blob/master/config.js) file.
|
||||
# Images
|
||||

|
||||
# Contributors
|
||||
CryptoMooners
|
||||
|
|
113
auth.js
Normal file
113
auth.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
const { LocalStorageCryptoStore } = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
|
||||
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,
|
||||
cryptoStore: new LocalStorageCryptoStore(localStorage),
|
||||
});
|
||||
matrixClient.initCrypto()
|
||||
.then(() => {
|
||||
if (!localStorage.getItem('crypto.device_data')) {
|
||||
return console.log(
|
||||
'====================================================\n'
|
||||
+ 'New OLM Encryption Keys created, please restart ligh7hau5.\n'
|
||||
+ '====================================================',
|
||||
);
|
||||
}
|
||||
matrixClient.setGlobalErrorOnUnknownDevices(config.matrix.manualVerify);
|
||||
matrixClient.startClient();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.matrixTokenLogin = matrixTokenLogin;
|
||||
|
||||
module.exports.getMatrixToken = async () => {
|
||||
matrixClient = sdk.createClient({ baseUrl: 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);
|
||||
});
|
||||
};
|
||||
|
||||
const getFediverseLink = (domain,roomId) => {
|
||||
let apps = {}
|
||||
apps = JSON.parse(localStorage.getItem("apps"));
|
||||
if(!apps[domain]){
|
||||
axios.post(`https://${domain}/api/v1/apps`,
|
||||
{
|
||||
client_name: config.fediverse.client_name,
|
||||
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
||||
scopes: 'read write follow push',
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response.data)
|
||||
if(!response.data.client_id || !response.data.client_secret) return false;
|
||||
apps[domain] = {
|
||||
client_id: response.data.client_id,
|
||||
client_secret: response.data.client_secret
|
||||
}
|
||||
localStorage.setItem("apps",JSON.stringify(apps))
|
||||
matrixClient.sendHtmlNotice(roomId,"Приложение зарегистрировано. Введите команду еще раз для создания ссылки")
|
||||
// return getFediverseLink(domain)
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}else{
|
||||
const app = apps[domain]
|
||||
const uri = "urn:ietf:wg:oauth:2.0:oob".replace(/:/g,"%3A")
|
||||
const scope = "read write follow push".replace(/ /g,"%20")
|
||||
return `https://${domain}/oauth/authorize?client_id=${app.client_id}&response_type=code&redirect_uri=${uri}&scope=${scope}`
|
||||
}
|
||||
return "nothing"
|
||||
};
|
||||
|
||||
const obtainAccessToken = (domain,code,event) => {
|
||||
const apps = JSON.parse(localStorage.getItem("apps"));
|
||||
console.log(domain,code)
|
||||
const app = apps[domain];
|
||||
axios.post(`https://${domain}/oauth/token`, {
|
||||
client_id: app.client_id,
|
||||
client_secret: app.client_secret,
|
||||
redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
|
||||
grant_type: "authorization_code",
|
||||
code: code,
|
||||
scopes: "read write follow push"
|
||||
}).then(response => {
|
||||
if(!response.data.access_token) return console.log(response.data)
|
||||
fediverse.auth[event.getSender()] = {
|
||||
domain: domain,
|
||||
access_token: response.data.access_token
|
||||
}
|
||||
localStorage.setItem("fediverse_auth", JSON.stringify(fediverse.auth))
|
||||
getFediverseUserInfo(event)
|
||||
}).catch(e => console.error(e))
|
||||
}
|
||||
|
||||
const getFediverseUserInfo = (event) => {
|
||||
const user = event.getSender()
|
||||
axios({
|
||||
method: "GET",
|
||||
url: `https://${fediverse.auth[user].domain}/api/v1/accounts/verify_credentials`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${fediverse.auth[user].access_token}`
|
||||
}
|
||||
}).then(response => {
|
||||
if(response.data.username){
|
||||
matrixClient.sendHtmlNotice(event.getRoomId(), `Успешный вход в аккаунт ${response.data.display_name || response.data.username} (@${response.data.username}@${fediverse.auth[user].domain})`)
|
||||
}else{
|
||||
console.log(response.data)
|
||||
}
|
||||
}).catch(e => console.error(e))
|
||||
}
|
||||
module.exports.getFediverseLink = getFediverseLink;
|
||||
module.exports.obtainAccessToken = obtainAccessToken;
|
78
commands/archive.js
Normal file
78
commands/archive.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const { JSDOM } = require('jsdom');
|
||||
const qs = require('qs');
|
||||
const https = require('https');
|
||||
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
const headers = ({ domain, userAgent }) => ({
|
||||
'Host': `${domain}`,
|
||||
'User-Agent': `${userAgent}`
|
||||
});
|
||||
|
||||
const archive = async (instance, url, rearchive) => {
|
||||
const form = await instance({ method: 'GET', url: '/' });
|
||||
if (form.statusText !== 'OK') throw form;
|
||||
const submitId = form.data.match(/name="submitid" value="([^"]+)/);
|
||||
const submit = await instance({
|
||||
method: 'POST',
|
||||
url: '/submit/',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
data: qs.stringify({ anyway: rearchive ? '1' : undefined, submitid: submitId ? submitId[1] : undefined, url })
|
||||
});
|
||||
submit.title = new JSDOM(submit.data).window.document.title;
|
||||
if (submit.statusText !== 'OK') throw submit;
|
||||
if (submit.request.path !== '/submit/')
|
||||
return { id: submit.request.path, date: submit.headers['memento-datetime'], title: submit.title };
|
||||
if (submit.headers.refresh)
|
||||
return { refresh: submit.headers.refresh.split(';url=')[1] };
|
||||
throw submit;
|
||||
};
|
||||
|
||||
const reqStr = str => `<em>Sending archive request for <code>${str}</code></em>`;
|
||||
const arc1Str = str => `<em>Archiving page <code>${str}</code></em>`;
|
||||
const arc2Str = (str, title, date) => `<em>Archived page <code><a href="https://${str}">${str}</code> [${date}]</em><br /><b>${title}</b>`;
|
||||
const arc3Str = str => `<em>Timed out <code>${str}</code></em>`;
|
||||
|
||||
const run = async (roomId, userInput, rearchive) => {
|
||||
const instance = axios.create({
|
||||
baseURL: `https://${config.archive.domain}`,
|
||||
httpsAgent: https.Agent({ maxVersion: "TLSv1.2"}),
|
||||
headers: headers(config.archive),
|
||||
transformResponse: [],
|
||||
timeout: 10 * 1000
|
||||
});
|
||||
|
||||
let reply = null;
|
||||
try {
|
||||
reply = await matrixClient.sendHtmlNotice(roomId, ' ', reqStr(userInput));
|
||||
const { refresh, id, title, date } = await archive(instance, userInput, rearchive);
|
||||
if (id)
|
||||
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 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 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 matrix.utils.editNoticeHTML(roomId, reply, arc2Str(`${config.archive.domain}${reqPath}`, title, rearchiveDate));
|
||||
}
|
||||
return await matrix.utils.editNoticeHTML(roomId, reply, arc3Str(refresh));
|
||||
}
|
||||
throw 'sad';
|
||||
} catch (e) {
|
||||
const sad = `<strong>Sad!</strong><br /><code>${`${e}`.replace(/<[^<]+?>/g, '').substr(0, 100)}</code>`;
|
||||
if (reply)
|
||||
matrix.utils.editNoticeHTML(roomId, reply, sad, 'sad').catch(() => {});
|
||||
else
|
||||
matrixClient.sendHtmlNotice(roomId, 'sad', sad).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
exports.runQuery = run;
|
|
@ -1,21 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
data: { status: `@10grans@fedi.cc beg` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b>
|
||||
<blockquote><i>You have begged for 10grans.<br>
|
||||
(id: ${response.data.id}</a>)
|
||||
</blockquote><br>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses/${userInput}/unfavourite`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`You have boo'd: <a href="${response.data.url}">${response.data.account.acct}</a>
|
||||
<blockquote>${response.data.content}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses/${userInput}/favourite`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`You have clapped: <a href="${response.data.url}">${response.data.account.acct}</a>:
|
||||
<blockquote>${response.data.content}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses/${userInput}/reblog`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`You have repeated:
|
||||
<blockquote>${response.data.content}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
9
commands/expand.js
Normal file
9
commands/expand.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
exports.runQuery = async (roomId, event, userInput) => {
|
||||
return matrix.utils.fetchEncryptedOrNot(roomId, { event_id: userInput })
|
||||
.then(event => matrix.utils.expandReact(event))
|
||||
.catch(e => {
|
||||
matrixClient.sendHtmlNotice(roomId, 'Sad!', '<strong>Sad!</strong>')
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
12
commands/fediverse/auth.js
Normal file
12
commands/fediverse/auth.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
exports.runQuery = (roomId,event,userInput) => {
|
||||
|
||||
//matrixClient.sendHtmlNotice(roomId,"Проверка связи","Проверка связи");
|
||||
const link = auth.getFediverseLink(userInput)
|
||||
if(!link){
|
||||
matrixClient.sendHtmlNotice(roomId,"Не удалось получить ссылку")
|
||||
}else if(link == "nothing"){
|
||||
}else{
|
||||
authEvents.push(event.event_id)
|
||||
matrixClient.sendHtmlNotice(roomId,`Перейдите по ссылке для входа в аккаунт. Для завершения ответьте на это сообщение кодом (еще не готово, пж не переходе по ссылке): ${link}`)
|
||||
}
|
||||
}
|
14
commands/fediverse/boo.js
Normal file
14
commands/fediverse/boo.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}/unfavourite`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
14
commands/fediverse/clap.js
Normal file
14
commands/fediverse/clap.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}/favourite`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
14
commands/fediverse/copy.js
Normal file
14
commands/fediverse/copy.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}/reblog`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
39
commands/fediverse/flood.js
Normal file
39
commands/fediverse/flood.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
let intervalId = null;
|
||||
|
||||
exports.runQuery = function (roomId, disable) {
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
|
||||
if (disable) return;
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/timelines/home`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then((res) => {
|
||||
let timeline = JSON.parse(localStorage.getItem('timeline'));
|
||||
past = timeline[event.getSender()] || {}
|
||||
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) {
|
||||
const lastStored = past.slice(past.length - 1, past.length);
|
||||
if (events[i].created_at < lastStored[0].created_at) return;
|
||||
events[i].label = 'status';
|
||||
fediverse.utils.formatter(events[i], roomId);
|
||||
}
|
||||
}
|
||||
timeline[event.getSender()] = events
|
||||
localStorage.setItem('timeline', JSON.stringify(timeline, null, 2));
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.sendError(null, roomId, e);
|
||||
});
|
||||
}, 30000);
|
||||
};
|
19
commands/fediverse/follow.js
Normal file
19
commands/fediverse/follow.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
exports.runQuery = async function (roomId, event, userInput) {
|
||||
const loadingString = `Searching for ${userInput}...`;
|
||||
const original = await matrixClient.sendHtmlNotice(roomId, `${loadingString}`, `<code>${loadingString}</code>`);
|
||||
const found = [];
|
||||
const suggest = [];
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v2/search?q=${userInput}&type=accounts`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].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 = `<code>${userInput} was not found, suggesting:</code><blockquote>${suggest.join('<br>')}</blockquote>`;
|
||||
if (suggest.length === 0) msg = `<code>No results found for: ${userInput}.</code>`;
|
||||
return matrix.utils.editNoticeHTML(roomId, original, msg);
|
||||
});
|
||||
};
|
39
commands/fediverse/notify.js
Normal file
39
commands/fediverse/notify.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
let intervalId = null;
|
||||
|
||||
exports.runQuery = function (roomId, disable) {
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
|
||||
if (disable) return;
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/notifications`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then((res) => {
|
||||
let notifications = JSON.parse(localStorage.getItem('notifications'));
|
||||
let past = notifications[event.getSender()] || {}
|
||||
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) {
|
||||
const lastStored = past.slice(past.length - 1, past.length);
|
||||
if (events[i].created_at < lastStored[0].created_at) return;
|
||||
events[i].label = 'notifications';
|
||||
fediverse.utils.formatter(events[i], roomId);
|
||||
}
|
||||
}
|
||||
notifications[event.getSender()] = events
|
||||
localStorage.setItem('notifications', JSON.stringify(notifications, null, 2));
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.sendError(null, roomId, e);
|
||||
});
|
||||
}, 30000);
|
||||
};
|
14
commands/fediverse/pin.js
Normal file
14
commands/fediverse/pin.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}/pin`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
132
commands/fediverse/post.js
Normal file
132
commands/fediverse/post.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
const qs = require('qs');
|
||||
const crypto = require('crypto');
|
||||
const FormData = require('form-data');
|
||||
|
||||
const emojis = { public: '🌐', unlisted: '📝', private: '🔒️', direct: '✉️' };
|
||||
exports.visibilityEmoji = (v) => emojis[v] || v;
|
||||
|
||||
const mediaPathRegex = /^\/_matrix\/media\/r0\/download\/[^/]+\/[^/]+\/?$/;
|
||||
|
||||
const decryptMedia = (media, file) => {
|
||||
const { v, key: { alg, ext, k, }, iv } = file;
|
||||
|
||||
if (v !== 'v2' || ext !== true || alg !== 'A256CTR')
|
||||
throw new Error('Unsupported file encryption');
|
||||
|
||||
const key = Buffer.from(k, 'base64');
|
||||
const _iv = Buffer.from(iv, 'base64');
|
||||
const cipher = crypto.createDecipheriv('aes-256-ctr', key, _iv);
|
||||
const data = Buffer.concat([ cipher.update(media.data), cipher.final() ]);
|
||||
return Object.assign({}, media, { data });
|
||||
};
|
||||
|
||||
const getMediaInfoFromEvent = async (roomId, event_id) => {
|
||||
const event = await matrix.utils.fetchEncryptedOrNot(roomId, { event_id });
|
||||
if (event.getType() !== 'm.room.message') throw new Error('Invalid type');
|
||||
const content = event.getContent();
|
||||
if (content.msgtype !== 'm.image') throw new Error('Invalid msgtype');
|
||||
if (content.url) return { url: getMediaUrl(content.url) };
|
||||
if (content.file) return {
|
||||
url: getMediaUrl(content.file.url),
|
||||
filename: content.body,
|
||||
mimetype: content.info ? content.info.mimetype : null,
|
||||
file: content.file
|
||||
};
|
||||
throw new Error('Invalid event');
|
||||
};
|
||||
|
||||
const getMediaUrl = string => {
|
||||
let url = new URL(string);
|
||||
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:' ||
|
||||
!config.matrix.domains.includes(url.hostname) ||
|
||||
!mediaPathRegex.test(url.pathname))
|
||||
throw new Error('Invalid URL');
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
const getMedia = async (roomId, string) => {
|
||||
let opts = {};
|
||||
if (string.startsWith('mxe://'))
|
||||
opts = await getMediaInfoFromEvent(roomId, string.substring(6));
|
||||
else
|
||||
opts.url = getMediaUrl(string);
|
||||
const media = await mediaDownload(opts);
|
||||
return opts.file ? decryptMedia(media, opts.file) : media;
|
||||
};
|
||||
|
||||
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 (opts) => {
|
||||
const { whitelist, blacklist } = config.fediverse.mimetypes;
|
||||
const media = await axios({ method: 'GET', url: opts.url, responseType: 'arraybuffer' });
|
||||
const filename = opts.filename || getFilename(media.headers['content-disposition']);
|
||||
const mimetype = opts.mimetype || media.headers['content-type'];
|
||||
if (media.statusText !== 'OK' || blacklist.includes(mimetype)) throw media;
|
||||
if (whitelist.length && !whitelist.includes(mimetype)) throw media;
|
||||
return { data: media.data, filename, mimetype };
|
||||
};
|
||||
|
||||
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: `https://${fediverse.auth[event.getSender()].domain}/api/v1/media`,
|
||||
headers: form.getHeaders({ Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` }),
|
||||
data: form,
|
||||
});
|
||||
if (upload.statusText !== 'OK') throw upload;
|
||||
return upload.data.id;
|
||||
};
|
||||
|
||||
const run = async (roomId, event, content, replyId, mediaURL, subject, visibility) => {
|
||||
let mediaId = null;
|
||||
if (mediaURL) {
|
||||
const media = await getMedia(roomId, mediaURL);
|
||||
mediaId = await mediaUpload(config.fediverse, media);
|
||||
}
|
||||
if (replyId) content = await fediverse.utils.getStatusMentions(replyId, event).then(m => m.concat(content).join(' '));
|
||||
const response = await axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}`, 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
data: qs.stringify({
|
||||
status: content,
|
||||
content_type: 'text/markdown',
|
||||
visibility: visibility || undefined,
|
||||
media_ids: mediaURL && [mediaId] || undefined,
|
||||
in_reply_to_id: replyId || undefined,
|
||||
spoiler_text: subject || undefined,
|
||||
}, { arrayFormat: 'brackets' }),
|
||||
});
|
||||
return fediverse.utils.sendEventWithMeta(roomId, `<a href="${response.data.url}">${response.data.id}</a>`, `redact ${response.data.id}`);
|
||||
};
|
||||
|
||||
exports.runQuery = async (roomId, event, userInput, { isReply, hasMedia, hasSubject, visibility }) => {
|
||||
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.shift();
|
||||
if (hasMedia) mediaURL = chunks.shift();
|
||||
return await run(roomId, event, chunks.join(' '), replyId, mediaURL, subject, visibility);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return matrixClient.sendHtmlNotice(roomId, 'Sad!', '<strong>Sad!</strong>').catch(() => {});
|
||||
}
|
||||
};
|
26
commands/fediverse/react.js
vendored
Normal file
26
commands/fediverse/react.js
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
const run = async (roomId, event, id, emoji, remove) => {
|
||||
axios({
|
||||
method: remove ? 'DELETE' : 'PUT',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/pleroma/statuses/${id}/reactions/${emoji}`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
||||
|
||||
exports.runQuery = async (roomId, event, userInput, remove) => {
|
||||
try {
|
||||
const chunks = userInput.trim().split(' ');
|
||||
if (chunks.length !== 2) throw '';
|
||||
const id = encodeURIComponent(chunks[0]);
|
||||
const emoji = encodeURIComponent(chunks[1]);
|
||||
return run(roomId, event, id, emoji, remove);
|
||||
} catch (e) {
|
||||
return matrixClient.sendHtmlNotice(roomId, 'Sad!', '<strong>Sad!</strong>').catch(() => {});
|
||||
}
|
||||
};
|
14
commands/fediverse/redact.js
Normal file
14
commands/fediverse/redact.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'DELETE',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
15
commands/fediverse/status.js
Normal file
15
commands/fediverse/status.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then((response) => {
|
||||
response.label = 'status';
|
||||
fediverse.utils.formatter(response, roomId);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
19
commands/fediverse/unfollow.js
Normal file
19
commands/fediverse/unfollow.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
exports.runQuery = async function (roomId, event, userInput) {
|
||||
const loadingString = `Searching for ${userInput}...`;
|
||||
const original = await matrixClient.sendHtmlNotice(roomId, `${loadingString}`, `<code>${loadingString}</code>`);
|
||||
const found = [];
|
||||
const suggest = [];
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v2/search?q=${userInput}&type=accounts`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].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 = `<code>${userInput} was not found, suggesting:</code><blockquote>${suggest.join('<br>')}</blockquote>`;
|
||||
if (suggest.length === 0) msg = `<code>No results found for: ${userInput}.</code>`;
|
||||
return matrix.utils.editNoticeHTML(roomId, original, msg);
|
||||
});
|
||||
};
|
14
commands/fediverse/unpin.js
Normal file
14
commands/fediverse/unpin.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}/unpin`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
14
commands/fediverse/unreblog.js
Normal file
14
commands/fediverse/unreblog.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${userInput}/unreblog`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
28
commands/fediverse/unroll.js
Normal file
28
commands/fediverse/unroll.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
exports.runQuery = function (roomId, event, userInput) {
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://' + fediverse.auth[event.getSender()].domain,
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
});
|
||||
instance.get(`/api/v1/statuses/${userInput}/context`)
|
||||
.then(async (response) => {
|
||||
let story = [];
|
||||
const rel = event.getContent()['m.relates_to'];
|
||||
const eventId = rel && rel.event_id ? rel.event_id : event.getId();
|
||||
const original = await instance.get(`/api/v1/statuses/${userInput}`);
|
||||
const ancestors = response.data.ancestors;
|
||||
const descendants = response.data.descendants;
|
||||
story = [...story, ancestors, original.data, descendants];
|
||||
const book = story.flat();
|
||||
await fediverse.utils.thread(roomId, eventId, '<br><hr><h3>...Beginning thread...</h3><hr><br>');
|
||||
for (const [i, entry] of book.entries()) {
|
||||
entry.label = 'thread';
|
||||
fediverse.utils.formatter(entry, roomId, eventId);
|
||||
}
|
||||
await fediverse.utils.thread(roomId, eventId, '<br><hr><h3>...Thread ended...</h3><hr><br>');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
193
commands/fediverse/utils.js
Normal file
193
commands/fediverse/utils.js
Normal file
|
@ -0,0 +1,193 @@
|
|||
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 thread = async (roomId, eventId, 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',
|
||||
'm.relates_to': {
|
||||
rel_type: 'm.thread',
|
||||
event_id: eventId,
|
||||
},
|
||||
})
|
||||
};
|
||||
|
||||
const hasAttachment = (res) => {
|
||||
if (res.status) res = res.status;
|
||||
if (!res.media_attachments) return '<br>';
|
||||
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: <a href="${media.remote_url}">${media.name}</a><br>`;
|
||||
}).join('<br>');
|
||||
};
|
||||
|
||||
const notifyFormatter = (res, roomId) => {
|
||||
userDetails = `<b><a href="${config.fediverse.domain}/${res.account.id}">
|
||||
${res.account.acct}</a>`;
|
||||
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}
|
||||
<font color="#03b381"><b>has followed you.</font>
|
||||
<blockquote><i>${res.account.note}</i></blockquote>`;
|
||||
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}
|
||||
<font color="#03b381"><b>has <a href="${config.fediverse.domain}/notice/${res.status.id}">favorited</a>
|
||||
your post:</font>
|
||||
<blockquote><i>${res.status.content}</i><br>
|
||||
${hasAttachment(res)}
|
||||
<br>(id: ${res.status.id}) ${registrar.post.visibilityEmoji(res.status.visibility)}
|
||||
</blockquote>`;
|
||||
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}
|
||||
<font color="#03b381"><b>has <a href="${config.fediverse.domain}/notice/${res.status.id}">mentioned</a>
|
||||
you:</font><blockquote><i>${res.status.content}</i><br>
|
||||
${hasAttachment(res)}
|
||||
<br>(id: ${res.status.id}) ${registrar.post.visibilityEmoji(res.status.visibility)}
|
||||
</blockquote>`;
|
||||
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}
|
||||
<font color="#03b381"><b>has <a href="${config.fediverse.domain}/notice/${res.status.id}">repeated</a>
|
||||
your post:</font><blockquote><i>${res.status.content}</i><br>
|
||||
${hasAttachment(res)}
|
||||
<br>(id: ${res.status.id}) ${registrar.post.visibilityEmoji(res.status.visibility)}
|
||||
</blockquote>`;
|
||||
sendEventWithMeta(roomId, content, meta);
|
||||
break;
|
||||
case 'pleroma:emoji_reaction':
|
||||
fediverse.auth.me !== res.account.url ? res.meta = 'react' : res.meta = 'redact';
|
||||
meta = `${res.meta} ${res.status.id}`;
|
||||
content = `${userDetails}
|
||||
<font color="#03b381"><b>has <a href="${config.fediverse.domain}/notice/${res.status.id}">reacted</a> with
|
||||
${ res.emoji_url ? `<a href="${res.emoji_url}">${res.emoji}</a>` : `<span>${res.emoji}</span>` }
|
||||
to your post:</font><blockquote><i>${res.status.content}</i><br>
|
||||
${hasAttachment(res)}
|
||||
<br>(id: ${res.status.id}) ${registrar.post.visibilityEmoji(res.status.visibility)}
|
||||
</blockquote>`;
|
||||
sendEventWithMeta(roomId, content, meta);
|
||||
break;
|
||||
default:
|
||||
return console.log('Unknown notification type.');
|
||||
}
|
||||
};
|
||||
|
||||
const isOriginal = (res, roomId, event) => {
|
||||
if (res.data) res = res.data;
|
||||
userDetails = `<b><a href="${config.fediverse.domain}/notice/${res.id}">
|
||||
${res.account.acct}</a>`;
|
||||
fediverse.auth.me !== res.account.url ? res.meta = 'status' : res.meta = 'redact';
|
||||
meta = `${res.meta} ${res.id}`;
|
||||
content = `${userDetails}
|
||||
<blockquote><i>${res.content}</i><br>
|
||||
${hasAttachment(res)}
|
||||
<br>(id: ${res.id}) ${registrar.post.visibilityEmoji(res.visibility)}
|
||||
</blockquote>`;
|
||||
if (res.label == 'thread') thread(roomId, event, content, meta);
|
||||
else sendEventWithMeta(roomId, content, meta);
|
||||
};
|
||||
|
||||
const isReblog = (res, roomId) => {
|
||||
if (res.data) res = res.data;
|
||||
userDetails = `<b><a href="${config.fediverse.domain}/${res.id}">
|
||||
${res.account.acct}</a>`;
|
||||
fediverse.auth.me !== res.account.url ? res.meta = 'status' : res.meta = 'unreblog';
|
||||
meta = `${res.meta} ${res.reblog.id}`;
|
||||
content = `${userDetails}
|
||||
<font color="#7886D7"><b>has repeated</a>
|
||||
<a href="${config.fediverse.domain}/notice/${res.reblog.id}">${res.reblog.account.acct}</a>'s post:</font>
|
||||
<blockquote><i>${res.content}</i><br>
|
||||
${hasAttachment(res)}
|
||||
<br>(id: ${res.reblog.id}) ${registrar.post.visibilityEmoji(res.visibility)}
|
||||
</blockquote>`;
|
||||
sendEventWithMeta(roomId, content, meta);
|
||||
};
|
||||
|
||||
module.exports.sendEventWithMeta = sendEventWithMeta;
|
||||
|
||||
module.exports.thread = thread;
|
||||
|
||||
module.exports.formatter = (res, roomId, event) => {
|
||||
const filtered = (res.label === 'notifications')
|
||||
? notifyFormatter(res, roomId)
|
||||
: (res.reblog == null)
|
||||
? isOriginal(res, roomId, event)
|
||||
: isReblog(res, roomId);
|
||||
return filtered;
|
||||
};
|
||||
|
||||
module.exports.follow = (roomId, account, event, original) => {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${config.fediverse[event.getSender()].domain}/api/v1/accounts/${account[0].id}/follow`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
matrix.utils.editNoticeHTML(roomId, original, `<code>Followed ${account[0].acct}.</code>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.unfollow = (roomId, account, event, original) => {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/accounts/${account[0].id}/unfollow`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
})
|
||||
.then(() => {
|
||||
matrix.utils.addReact(event, '✅');
|
||||
matrix.utils.editNoticeHTML(roomId, original, `<code>Unfollowed ${account[0].acct}.</code>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getStatusMentions = (notice, event) => {
|
||||
const users = axios({
|
||||
method: 'GET',
|
||||
url: `https://${fediverse.auth[event.getSender()].domain}/api/v1/statuses/${notice}`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth[event.getSender()].access_token}` },
|
||||
}).then((notice) => {
|
||||
const users = [];
|
||||
users.push('@' + notice.data.account.acct);
|
||||
for(let i = 0; i < notice.data.mentions.length; i++) {
|
||||
if(!config.fediverse.username.includes(notice.data.mentions[i].acct))
|
||||
users.push('@' + notice.data.mentions[i].acct)
|
||||
}
|
||||
return users;
|
||||
})
|
||||
.catch((e) => {
|
||||
matrix.utils.addReact(event, '❌');
|
||||
matrix.utils.sendError(event, roomId, e);
|
||||
});
|
||||
return users;
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, registrar) {
|
||||
setInterval(() => {
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `${registrar.config.fediverse}/api/v1/timelines/home`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((events) => {
|
||||
const event = fs.readFileSync('timeline.json', 'utf8');
|
||||
fs.writeFileSync('timeline.json', events.data[0].created_at, 'utf8');
|
||||
if (event !== events.data[0].created_at) {
|
||||
if (events.data[0].reblog === null) {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b><a href="${registrar.config.fediverse}/notice/${events.data[0].id}">${events.data[0].account.acct}</a>
|
||||
<blockquote><i>${events.data[0].content}<br>
|
||||
${events.data[0].media_attachments.map(media =>
|
||||
`<a href="${media.remote_url}">`+`${media.description}`+'</a>'
|
||||
).join('<br>')}
|
||||
(id: ${events.data[0].id}</a>)
|
||||
</blockquote>`);
|
||||
} else {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b><a href="${registrar.config.fediverse}/${events.data[0].account.id}">
|
||||
${events.data[0].account.acct}</a>
|
||||
<font color="#7886D7">has <a href="${registrar.config.fediverse}/notice/${events.data[0].id}">repeated</a>:
|
||||
<blockquote><a href="${events.data[0].reblog.account.url}">${events.data[0].reblog.account.acct}</a></blockquote>
|
||||
<blockquote>${events.data[0].content}<br>
|
||||
${events.data[0].media_attachments.map(media =>
|
||||
`<a href="${media.remote_url}">`+`Proxied image, no description available.`+'</a>'
|
||||
).join('<br>')}
|
||||
<br>(id: ${events.data[0].id})
|
||||
</blockquote>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 8000);
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios.get(`${registrar.config.fediverse}/api/v1/accounts/${userInput}`).then((findUID) => {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/accounts/${findUID.data.id}/follow`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
})
|
||||
.then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`Subscribed:
|
||||
<blockquote>${registrar.config.fediverse}/${response.data.id}`);
|
||||
});
|
||||
}).catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,18 +1,33 @@
|
|||
exports.runQuery = function (matrixClient, room) {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
'<blockquote><b>+plemara [your message] : post<br>'
|
||||
exports.runQuery = function (roomId) {
|
||||
matrixClient.sendHtmlNotice(roomId,
|
||||
' ',
|
||||
'<blockquote><b>fediverse commands<br>'
|
||||
+ '+post [your message] : post<br>'
|
||||
+ '+direct [@recipient] [message] : direct message<br>'
|
||||
+ '+private [message] : follower-only message<br>'
|
||||
+ '+redact [post id] : delete post<br>'
|
||||
+ '+fren [user id] : follow<br>'
|
||||
+ '+unfren [user id] : unfollow<br>'
|
||||
+ '+follow [user id] : follow<br>'
|
||||
+ '+unfollow [user id] : unfollow<br>'
|
||||
+ '+media [homeserver image URL or MXC] [optional message] : post media<br>'
|
||||
+ '+copy [post id] : repeat/repost/retweet<br>'
|
||||
+ '+crossblog [status URL]: cross blog twitter post to fediverse post<br>'
|
||||
+ '+reply [post id] [content] : reply to post<br>'
|
||||
+ '+tip [@user@fedi.url] [amount] : tip 10grans'
|
||||
+ '+beg : beg for 10grans'
|
||||
+ '+clap [post id] : favorite<br>'
|
||||
+ '+boo [post id] : unfavorite</blockquote>'
|
||||
+ '<blockquote><b>channel commands<br>'
|
||||
+ '+flood : turn on timeline<br>'
|
||||
+ '+notify : show notifications</b></blockquote>'
|
||||
+ '<blockquote><b>--- <i>docs by lint</i> ---</b></blockquote>');
|
||||
+ '+flood : turn on timeline in channel<br>'
|
||||
+ '+notify : show notifications in channel<br>'
|
||||
+ '+unflood : stop timeline in channel<br>'
|
||||
+ '+unnotify : stop notifications in channel<br>'
|
||||
+ '+archive [URL] : archive content<br>'
|
||||
+ '+rearchive [URL] : re-archive content<br>'
|
||||
+ '+nitter [status URL] : redirect twitter to nitter, also embed tweet<br>'
|
||||
+ '+invidious [video URL] : redirect youtube to invidious, also embed description<br>'
|
||||
+ '+proxy [twitter/youtube]: both +nitter and +invidious commands combined</b><br></blockquote>'
|
||||
+ `<blockquote><b>ligh7hau5 version ${require('../package.json').version}</b><br>`
|
||||
+ '<b>--- <i>Contributors🐱</i> ---</b><br>'
|
||||
+ '<b>CRYPTOMOONERS</b><br>'
|
||||
+ '<b>doesnm</b><br>'
|
||||
+ '<b><i>docs by LINT</i></b></blockquote>'
|
||||
);
|
||||
};
|
||||
|
|
58
commands/invidious.js
Normal file
58
commands/invidious.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
const invidious = async (instance, url) => {
|
||||
const req = await instance({ method: 'GET', url });
|
||||
if (req.statusText !== 'OK') throw req;
|
||||
const { headers } = instance.defaults;
|
||||
const video = JSON.parse(req.data);
|
||||
return {
|
||||
url: headers['Host'],
|
||||
name: video.title,
|
||||
date: video.publishedText,
|
||||
description: video.descriptionHtml,
|
||||
author: video.author,
|
||||
views: video.viewCount,
|
||||
likes: video.likeCount,
|
||||
dislikes: video.dislikeCount,
|
||||
};
|
||||
};
|
||||
|
||||
const card = (video, path) =>
|
||||
`<a href="https://${video.url}/${path}"><b>${video.name}</a></b><blockquote><b><i>` +
|
||||
((video.description.length > 300) ? `${video.description.substr(0, 300)}…` : ``)+
|
||||
((video.description === '<p></p>') ? `No description.`: ``)+
|
||||
((video.description.length < 300 && video.description !== '<p></p>') ? `${video.description}` : ``)+
|
||||
`<br /><span>🔍️ ${video.views.toLocaleString()}</span> ` +
|
||||
`<span>❤️ ${video.likes.toLocaleString()}</span> ` +
|
||||
`<span>❌ ${video.dislikes.toLocaleString()}</span>`+
|
||||
`<br />(${video.date})</b> <br />
|
||||
</blockquote>`;
|
||||
|
||||
const getInstance = (domain, config) =>
|
||||
axios.create({
|
||||
baseURL: `https://${domain}/api/v1/videos`,
|
||||
headers: {
|
||||
Host: `${domain}`,
|
||||
'User-Agent': `${config.userAgent}`,
|
||||
},
|
||||
transformResponse: [],
|
||||
timeout: 10 * 1000,
|
||||
});
|
||||
|
||||
const run = async (roomId, userInput) => {
|
||||
const cfg = config.invidious;
|
||||
const video = await matrix.utils.retryPromise(cfg.domains.redirect, domain => invidious(getInstance(domain, cfg), userInput));
|
||||
return matrixClient.sendHtmlNotice(roomId, ' ', card(video, userInput));
|
||||
};
|
||||
|
||||
exports.runQuery = async (roomId, event, userInput) => {
|
||||
try {
|
||||
const url = new URL(userInput);
|
||||
const { redirect, original } = config.invidious.domains;
|
||||
if (!redirect.includes(url.hostname) && !original.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!', '<strong>Sad!</strong>').catch(() => {});
|
||||
}
|
||||
};
|
88
commands/nitter.js
Normal file
88
commands/nitter.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
const { JSDOM } = require('jsdom');
|
||||
|
||||
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;
|
||||
const tweet = document.querySelector('#m');
|
||||
const stats = tweet.querySelectorAll('.tweet-body > .tweet-stats .icon-container');
|
||||
const quote = tweet.querySelector('.tweet-body > .quote');
|
||||
const isReply = tweet.querySelector('.tweet-body > .replying-to');
|
||||
const replies = document.querySelectorAll('.main-thread > .before-tweet > .timeline-item');
|
||||
const { defaults } = instance;
|
||||
return {
|
||||
url: defaults.baseURL,
|
||||
text: tweet.querySelector('.tweet-body > .tweet-content').innerHTML,
|
||||
date: tweet.querySelector('.tweet-body > .tweet-published').textContent,
|
||||
name: tweet.querySelector('.tweet-body > div .fullname').textContent,
|
||||
check: !!tweet.querySelector('.tweet-body > div .fullname .icon-ok'),
|
||||
handle: tweet.querySelector('.tweet-body > div .username').textContent,
|
||||
hasAttachments: !!tweet.querySelector('.tweet-body > .attachments'),
|
||||
quote: quote ? {
|
||||
path: quote.querySelector('a.quote-link').href,
|
||||
text: quote.querySelector('.quote-text') ? quote.querySelector('.quote-text').innerHTML : '',
|
||||
} : null,
|
||||
isReply: isReply && replies.length > 0 ? replies[replies.length - 1].classList.contains('unavailable') ? 'unavailable' : {
|
||||
path: replies[replies.length - 1].querySelector('a.tweet-link').href,
|
||||
text: replies[replies.length - 1].querySelector('.tweet-content').innerHTML,
|
||||
} : null,
|
||||
isThread: !isReply && replies.length > 0 ? replies[replies.length - 1].classList.contains('unavailable') ? 'unavailable' : {
|
||||
path: replies[replies.length - 1].querySelector('a.tweet-link').href,
|
||||
text: replies[replies.length - 1].querySelector('.tweet-content').innerHTML,
|
||||
} : null,
|
||||
stats: {
|
||||
replies: stats[0].textContent.trim(),
|
||||
retweets: stats[1].textContent.trim(),
|
||||
favorites: stats[2].textContent.trim(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const card = (tweet, check, path) =>
|
||||
`<a href="${tweet.url}/${tweet.handle.replace(/^@/, '')}"><b>${tweet.name}</b></a> ` +
|
||||
(tweet.check ? `${check} ` : '') +
|
||||
`<a href="${tweet.url}${path}"><b>${tweet.date}</b></a> ` +
|
||||
`<span>🗨️ ${tweet.stats.replies}</span> ` +
|
||||
`<span>🔁 ${tweet.stats.retweets}</span> ` +
|
||||
`<span>❤️ ${tweet.stats.favorites}</span> ` +
|
||||
`<br /><blockquote><b><i>${tweet.text.replace('\n', '<br />')}</i></b></blockquote>` +
|
||||
(tweet.hasAttachments ? '<blockquote><b>This tweet has attached media.</b></blockquote>' : '') +
|
||||
(tweet.isReply ? tweet.isReply === 'unavailable' ? '<blockquote>Replied Tweet is unavailable</blockquote>' : `<blockquote><b><a href="${tweet.url}${tweet.isReply.path}">Replied Tweet</a></b><br /><b><i>${tweet.isReply.text.replace('\n', '<br />')}</i></b></blockquote>` : '') +
|
||||
(tweet.isThread ? tweet.isThread === 'unavailable' ? '<blockquote>Previous Tweet is unavailable</blockquote>' : `<blockquote><b><a href="${tweet.url}${tweet.isThread.path}">Previous Tweet</a></b><br /><b><i>${tweet.isThread.text.replace('\n', '<br />')}</i></b></blockquote>` : '') +
|
||||
(tweet.quote ? `<blockquote><b><a href="${tweet.url}${tweet.quote.path}">Quoted Tweet</a></b><br /><b><i>${tweet.quote.text.replace('\n', '<br />')}</i></b></blockquote>` : '');
|
||||
|
||||
const getInstance = (domain, config) =>
|
||||
axios.create({
|
||||
baseURL: `https://${domain}`,
|
||||
headers: {
|
||||
Host: `${domain}`,
|
||||
'User-Agent': `${config.userAgent}`,
|
||||
},
|
||||
transformResponse: [],
|
||||
timeout: 10 * 1000,
|
||||
});
|
||||
|
||||
const run = async (roomId, userInput, fedi) => {
|
||||
const cfg = config.nitter;
|
||||
const tweet = await matrix.utils.retryPromise(cfg.domains.redirect, domain => nitter(getInstance(domain, cfg), userInput));
|
||||
const tweetCard = card(tweet, cfg.check, userInput);
|
||||
return fedi ? axios({
|
||||
method: 'POST',
|
||||
url: `${config.fediverse.domain}/api/v1/statuses`,
|
||||
headers: { Authorization: `Bearer ${fediverse.auth.access_token}` },
|
||||
data: { status: tweetCard, content_type: 'text/html' }
|
||||
}) : matrixClient.sendHtmlNotice(roomId, ' ', tweetCard);
|
||||
};
|
||||
|
||||
exports.runQuery = async (roomId, event, userInput, fedi) => {
|
||||
try {
|
||||
const url = new URL(userInput);
|
||||
const { redirect, original } = config.nitter.domains;
|
||||
if (!redirect.includes(url.hostname) && !original.includes(url.hostname)) throw '';
|
||||
if (!/^\/[^/]+\/status\/\d+\/?$/.test(url.pathname)) throw '';
|
||||
return await run(roomId, url.pathname, fedi);
|
||||
} catch (e) {
|
||||
return matrixClient.sendHtmlNotice(roomId, 'Sad!', '<strong>Sad!</strong>').catch(() => {});
|
||||
}
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, registrar) {
|
||||
setInterval(() => {
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: `${registrar.config.fediverse}/api/v1/notifications`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((notifications) => {
|
||||
const event = fs.readFileSync('notification.json', 'utf8');
|
||||
fs.writeFileSync('notification.json', notifications.data[0].created_at, 'utf8');
|
||||
|
||||
if (event !== notifications.data[0].created_at) {
|
||||
if (notifications.data[0].type === 'follow') {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b><a href="${registrar.config.fediverse}/${notifications.data[0].account.id}">
|
||||
${notifications.data[0].account.acct}</a></b>
|
||||
<font color="#03b381"><b>has followed you.</b></font>
|
||||
<br><i>${notifications.data[0].account.note}</i>`);
|
||||
} else if (notifications.data[0].type === 'favourite') {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b><a href="${registrar.config.fediverse}/${notifications.data[0].account.id}">
|
||||
${notifications.data[0].account.acct}</a></b>
|
||||
<font color="#03b381"><b>has <a href="${notifications.data[0].status.uri}">favorited</a>
|
||||
your post:</b></font>
|
||||
<br><blockquote><i><b>${notifications.data[0].status.content}</i></b></blockquote>`);
|
||||
} else if (notifications.data[0].type === 'mention') {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b><a href="${registrar.config.fediverse}/${notifications.data[0].account.id}">
|
||||
${notifications.data[0].account.acct}</a></b>
|
||||
<font color="#03b381"><b>has <a href="${notifications.data[0].status.uri}">mentioned</a>
|
||||
you:</b></font><br><blockquote><i><b>${notifications.data[0].status.content}
|
||||
<br>(id: ${notifications.data[0].status.id})</i></b>
|
||||
</blockquote>`);
|
||||
} else if (notifications.data[0].type === 'reblog') {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b><a href="${registrar.config.fediverse}/${notifications.data[0].account.id}">
|
||||
${notifications.data[0].account.acct}</a></b>
|
||||
<font color="#03b381"><b>has <a href="${notifications.data[0].status.uri}">repeated</a>
|
||||
your post:</b></font><br>
|
||||
<blockquote><i><b>${notifications.data[0].status.content}</i></b></blockquote>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 8000);
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses/${userInput}/pin`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`Pinned:
|
||||
<blockquote><i><a href="${registrar.config.fediverse}/notice/${response.data.id}">
|
||||
${response.data.content}</a></i>
|
||||
</blockquote>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
data: { status: userInput, content_type: `text/markdown` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b>
|
||||
<blockquote><i>${response.data.content}<br>
|
||||
(id: ${response.data.id}</a>)
|
||||
</blockquote><br>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'DELETE',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses/${userInput}`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
'<blockquote>Redacted.</blockquote');
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, address, flaggedInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
data: { status: flaggedInput, in_reply_to_id: address, content_type: `text/markdown` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`${response.data.content} ${response.data.url}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, address, flaggedInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
data: { status: `@10grans@fedi.cc tip `+ flaggedInput + ` to `+address },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`<b>
|
||||
<blockquote><i>Tipping ${response.data.content}<br>
|
||||
(id: ${response.data.id}</a>)
|
||||
</blockquote><br>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios.get(`${registrar.config.fediverse}/api/v1/accounts/${userInput}`).then((findUID) => {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/accounts/${findUID.data.id}/unfollow`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
})
|
||||
.then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`Unsubscribed:
|
||||
<blockquote>${registrar.config.fediverse}/${response.data.id}`);
|
||||
});
|
||||
}).catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
const axios = require('axios');
|
||||
|
||||
exports.runQuery = function (matrixClient, room, userInput, registrar) {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `${registrar.config.fediverse}/api/v1/statuses/${userInput}/unpin`,
|
||||
headers: { Authorization: `Bearer ${registrar.config.fediverseToken}` },
|
||||
}).then((response) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
`Unpinned:
|
||||
<blockquote><i><a href="${registrar.config.fediverse}/notice/${response.data.id}">
|
||||
${response.data.content}</a></i>
|
||||
</blockquote>`);
|
||||
})
|
||||
.catch((e) => {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'', `${e}`);
|
||||
});
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
module.exports = {
|
||||
matrixServer: 'https://server.com',
|
||||
userId: '@matrixUser:server.com',
|
||||
matrixUser: 'hello',
|
||||
matrixPass: 'password',
|
||||
fediverse: 'https://server.com',
|
||||
fediverseToken: 'access_token',
|
||||
};
|
144
main.js
144
main.js
|
@ -1,110 +1,44 @@
|
|||
const sdk = require('matrix-js-sdk');
|
||||
const axios = require('axios');
|
||||
const registrar = require('./registrar.js');
|
||||
global.registrar = require('./registrar.js');
|
||||
|
||||
const auth = {
|
||||
type: 'm.login.password',
|
||||
user: registrar.config.matrixUser,
|
||||
password: registrar.config.matrixPass,
|
||||
};
|
||||
matrix.auth.access_token ? auth.matrixTokenLogin() : auth.getMatrixToken();
|
||||
//if (!fediverse.auth.access_token && config.fediverse.username) auth.registerFediverseApp();
|
||||
|
||||
axios.post(`${registrar.config.matrixServer}/_matrix/client/r0/login`, auth).then((response) => {
|
||||
CreateClient(response.data.access_token);
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
matrixClient.on('RoomMember.membership', (event, member) => {
|
||||
if (member.membership === 'invite' && member.userId === matrixClient.credentials.userId) {
|
||||
matrixClient.joinRoom(member.roomId).then(() => {
|
||||
console.log('Auto-joined %s', member.roomId);
|
||||
});
|
||||
}
|
||||
|
||||
if (member.membership === 'leave' && member.userId === matrixClient.credentials.userId) {
|
||||
matrixClient.forget(member.roomId).then(() => {
|
||||
console.log('Kicked %s', member.roomId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let CreateClient = (token) => {
|
||||
const matrixClient = sdk.createClient({
|
||||
baseUrl: registrar.config.matrixServer,
|
||||
accessToken: token,
|
||||
userId: registrar.config.userId,
|
||||
timelineSupport: true,
|
||||
});
|
||||
matrixClient.on('event', async (event) => {
|
||||
if (event.isEncrypted()) await matrixClient.decryptEventIfNeeded(event, { emit: false, isRetry: false });
|
||||
if (event.getSender() === matrixClient.credentials.userId) return matrix.utils.selfReact(event);
|
||||
if (!event.event.content['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('RoomMember.membership', (event, member) => {
|
||||
if (member.membership === 'invite' && member.userId === registrar.config.userId) {
|
||||
matrixClient.joinRoom(member.roomId).done(() => {
|
||||
console.log('Auto-joined %s', member.roomId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
matrixClient.on('Room.timeline', (event, room, toStartOfTimeline) => {
|
||||
if (toStartOfTimeline) return;
|
||||
if (event.getType() !== 'm.room.message') return;
|
||||
if (event.getSender() === registrar.config.userId) return;
|
||||
if (event.event.unsigned.age > 10000) return;
|
||||
if (event.event.content.body.charAt(0) === '+') {
|
||||
console.log(`Logs: ${event.event.sender} - ${event.event.content.body}`);
|
||||
const args = event.event.content.body.slice(1).trim().split(/ +/g);
|
||||
const command = args.shift().toLowerCase();
|
||||
const userInput = args.join(' ');
|
||||
const flaggedInput = userInput.substr(userInput.indexOf(' ') + 1);
|
||||
const address = args.slice(0, 1).join(' ').replace(/"/g, '');
|
||||
|
||||
if (command === 'boo') {
|
||||
registrar.boo.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'beg') {
|
||||
registrar.beg.runQuery(matrixClient, room, registrar);
|
||||
}
|
||||
|
||||
if (command === 'clap') {
|
||||
registrar.clap.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'copy') {
|
||||
registrar.copy.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'flood') {
|
||||
registrar.flood.runQuery(matrixClient, room, registrar);
|
||||
}
|
||||
|
||||
if (command === 'fren') {
|
||||
registrar.fren.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'help') {
|
||||
registrar.help.runQuery(matrixClient, room);
|
||||
}
|
||||
|
||||
if (command === 'pin') {
|
||||
registrar.pin.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'plemara') {
|
||||
registrar.plemara.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'notify') {
|
||||
registrar.notify.runQuery(matrixClient, room, registrar);
|
||||
}
|
||||
|
||||
if (command === 'redact') {
|
||||
registrar.redact.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'reply') {
|
||||
registrar.reply.runQuery(matrixClient, room, address, flaggedInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'tip') {
|
||||
registrar.tip.runQuery(matrixClient, room, address, flaggedInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'unfren') {
|
||||
registrar.unfren.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
|
||||
if (command === 'unpin') {
|
||||
registrar.unpin.runQuery(matrixClient, room, userInput, registrar);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
matrixClient.startClient();
|
||||
module.exports = matrixClient;
|
||||
};
|
||||
matrixClient.on('Room.timeline', async (event, member, toStartOfTimeline) => {
|
||||
if (toStartOfTimeline) return;
|
||||
if (event.isEncrypted()) await matrixClient.decryptEventIfNeeded(event, { emit: false, isRetry: false });
|
||||
if (event.getType() !== 'm.room.message') return;
|
||||
if (event.getSender() === matrixClient.credentials.userId) return;
|
||||
if (event.event.unsigned.age > 10000) return;
|
||||
roomId = event.event.room_id;
|
||||
content = event.getContent().body;
|
||||
if (!typeof content === 'string') return;
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
|
1027
package-lock.json
generated
Normal file
1027
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
package.json
29
package.json
|
@ -1,7 +1,10 @@
|
|||
{
|
||||
"name": "plemara",
|
||||
"version": "0.2.1",
|
||||
"name": "ligh7hau5",
|
||||
"version": "3.0.1",
|
||||
"description": "A Matrix to Fediverse client",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
|
@ -9,22 +12,22 @@
|
|||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vulet/plemara.git"
|
||||
"url": "git+https://github.com/vulet/ligh7hau5.git"
|
||||
},
|
||||
"author": "vul",
|
||||
"license": "AGPL-3.0-only",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vulet/plemara/issues"
|
||||
"url": "https://github.com/vulet/lighthau5/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vulet/plemara#readme",
|
||||
"homepage": "https://github.com/vulet/lighthau5#readme",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.2",
|
||||
"file-system": "^2.2.2",
|
||||
"matrix-js-sdk": "^2.4.6"
|
||||
"axios": "^0.25.0",
|
||||
"form-data": "^4.0.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"matrix-js-sdk": "^27.2.0",
|
||||
"node-localstorage": "^2.2.1",
|
||||
"olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
|
||||
"qs": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-plugin-import": "^2.17.3"
|
||||
}
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
|
63
registrar.js
63
registrar.js
|
@ -1,18 +1,51 @@
|
|||
global.Olm = require('olm');
|
||||
global.sdk = require('matrix-js-sdk');
|
||||
global.axios = require('axios');
|
||||
global.config = require('./config.js');
|
||||
global.auth = require('./auth.js');
|
||||
global.authEvents = [];
|
||||
const { LocalStorage } = require('node-localstorage');
|
||||
global.localStorage = new LocalStorage('./keys');
|
||||
if (!localStorage.getItem('matrix_auth')) {
|
||||
localStorage.clear();
|
||||
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', '{}');
|
||||
|
||||
|
||||
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'),
|
||||
boo: require('./commands/boo.js'),
|
||||
beg: require('./commands/beg.js'),
|
||||
clap: require('./commands/clap.js'),
|
||||
copy: require('./commands/copy.js'),
|
||||
flood: require('./commands/flood.js'),
|
||||
fren: require('./commands/fren.js'),
|
||||
help: require('./commands/help.js'),
|
||||
pin: require('./commands/pin.js'),
|
||||
plemara: require('./commands/plemara.js'),
|
||||
redact: require('./commands/redact.js'),
|
||||
notify: require('./commands/notify.js'),
|
||||
reply: require('./commands/reply.js'),
|
||||
tip: require('./commands/tip.js'),
|
||||
unfren: require('./commands/unfren.js'),
|
||||
unpin: require('./commands/unpin.js'),
|
||||
archive: require('./commands/archive.js'),
|
||||
proxy: require('./commands/proxy.js'),
|
||||
invidious: require('./commands/invidious.js'),
|
||||
nitter: require('./commands/nitter.js'),
|
||||
boo: require('./commands/fediverse/boo.js'),
|
||||
clap: require('./commands/fediverse/clap.js'),
|
||||
copy: require('./commands/fediverse/copy.js'),
|
||||
flood: require('./commands/fediverse/flood.js'),
|
||||
follow: require('./commands/fediverse/follow.js'),
|
||||
help: require('./commands/help.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'),
|
||||
status: require('./commands/fediverse/status.js'),
|
||||
unfollow: require('./commands/fediverse/unfollow.js'),
|
||||
unpin: require('./commands/fediverse/unpin.js'),
|
||||
unreblog: require('./commands/fediverse/unreblog.js'),
|
||||
unroll: require('./commands/fediverse/unroll.js'),
|
||||
react: require('./commands/fediverse/react.js'),
|
||||
expand: require('./commands/expand.js'),
|
||||
auth: require("./commands/fediverse/auth.js")
|
||||
};
|
||||
|
|
211
utils.js
Normal file
211
utils.js
Normal file
|
@ -0,0 +1,211 @@
|
|||
const { MatrixEvent } = require('matrix-js-sdk/lib/models/event');
|
||||
const url = require("url")
|
||||
const isEmoji = string => true;
|
||||
|
||||
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 = [];
|
||||
let visibility = null;
|
||||
|
||||
switch (command) {
|
||||
case 'config':
|
||||
return;
|
||||
case 'help': case 'flood': case 'notify':
|
||||
args.push(roomId);
|
||||
break;
|
||||
case 'unflood': case 'unnotify':
|
||||
args.push(roomId, true);
|
||||
command = command.substring(2);
|
||||
break;
|
||||
case 'unreact':
|
||||
args.push(roomId, event, userInput, true);
|
||||
command = 'react';
|
||||
break;
|
||||
case 'tip': case 'makeitrain':
|
||||
args.push(roomId, event, 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':
|
||||
case 'direct': case 'directreply': case 'directmedia': case 'directmediareply':
|
||||
case 'private': case 'privatereply': case 'privatemedia': case 'privatemediareply':
|
||||
case 'unlisted': case 'unlistedreply': case 'unlistedmedia': case 'unlistedmediareply':
|
||||
visibility = command.match(/^(direct|private|unlisted)/);
|
||||
args.push(roomId, event, userInput, {
|
||||
isReply: !!~command.indexOf('reply'),
|
||||
hasMedia: !!~command.indexOf('media'),
|
||||
hasSubject: !!~command.indexOf('random'),
|
||||
visibility: visibility ? visibility[1] : null
|
||||
});
|
||||
command = 'post';
|
||||
break;
|
||||
case 'crossblog':
|
||||
args.push(roomId, event, userInput, true);
|
||||
command = 'nitter'
|
||||
break;
|
||||
// fallthrough
|
||||
default:
|
||||
args.push(roomId, event, userInput);
|
||||
}
|
||||
if(["boo","clap","copy","flood","follow","notify","pin","unpin","post","react","redact","status","unfollow","unreblog","unroll"].includes(command) && !fediverse.auth[event.getSender()]) return matrixClient.sendHtmlNotice(roomId, ' ',`${event.getSender()}, для использования команды ${command} нужно привязать аккаунт Fediverse. Используйте для этого команду +auth <имя сервера>`)
|
||||
registrar[command] && registrar[command].runQuery.apply(null, args);
|
||||
};
|
||||
|
||||
/**
|
||||
matrixClient.fetchRoomEvent() does not return an Event class
|
||||
however, this class is necessary for decryption, so reinstate it.
|
||||
afterwards, decrypt.
|
||||
*/
|
||||
const fetchEncryptedOrNot = async (roomId, event) => {
|
||||
const fetchedEvent = await matrixClient.fetchRoomEvent(roomId, event.event_id)
|
||||
const realEvent = new MatrixEvent(fetchedEvent);
|
||||
if (realEvent.isEncrypted()) {
|
||||
await matrixClient.decryptEventIfNeeded(realEvent, { emit: false, isRetry: false });
|
||||
}
|
||||
return realEvent;
|
||||
}
|
||||
|
||||
module.exports.sendError = sendError;
|
||||
|
||||
module.exports.addReact = addReact;
|
||||
|
||||
module.exports.eventHandler = eventHandler;
|
||||
|
||||
module.exports.fetchEncryptedOrNot = fetchEncryptedOrNot
|
||||
|
||||
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 reactions = config.matrix.reactions;
|
||||
const roomId = event.event.room_id;
|
||||
if (!event.getContent()['m.relates_to']) return;
|
||||
const reaction = event.getContent()['m.relates_to'];
|
||||
const metaEvent = await fetchEncryptedOrNot(roomId, reaction);
|
||||
if (!metaEvent.getContent().meta || metaEvent.event.sender !== config.matrix.user) return;
|
||||
let args = metaEvent.getContent().meta.split(' ');
|
||||
isMeta = ['status', 'reblog', 'mention', 'redact', 'unreblog'];
|
||||
if (!isMeta.includes(args[0])) return;
|
||||
let command = [];
|
||||
args.shift().toLowerCase();
|
||||
switch (reaction.key) {
|
||||
case reactions.copy: command = 'copy'; break;
|
||||
case reactions.clap: command = 'clap'; break;
|
||||
case reactions.redact: command = 'redact'; break;
|
||||
case reactions.rain: command = 'makeitrain'; break;
|
||||
case reactions.unroll: command = 'unroll'; break;
|
||||
case reactions.expand:
|
||||
command = 'expand';
|
||||
args = [ reaction.event_id ];
|
||||
break;
|
||||
default:
|
||||
if (isEmoji(reaction.key)) {
|
||||
command = 'react';
|
||||
args.push(reaction.key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
eventHandler(args, roomId, command, event);
|
||||
};
|
||||
|
||||
module.exports.handleReply = async (event) => {
|
||||
const roomId = event.event.room_id;
|
||||
if(!event.event.content['m.relates_to']['m.in_reply_to']) return;
|
||||
const reply = event.event.content['m.relates_to']['m.in_reply_to'];
|
||||
const metaEvent = await fetchEncryptedOrNot(roomId, reply);
|
||||
if(authEvents.includes(metaEvent.event_id)){
|
||||
const domain = metaEvent.event.content.body.match(/https?:\/\/[^\s]+/);
|
||||
if(domain && domain[0]){
|
||||
domain[0] = url.parse(domain[0]).host
|
||||
let code = event.getContent().body.split("\n");
|
||||
code = code[code.length-1].trim()
|
||||
auth.obtainAccessToken(domain[0],code,event)
|
||||
authEvents = authEvents.filter(f => f != event.event_id)
|
||||
}
|
||||
}
|
||||
if (!metaEvent.getContent().meta || metaEvent.event.sender !== config.matrix.user) return;
|
||||
const args = metaEvent.getContent().meta.split(' ');
|
||||
args.push(event.getContent().formatted_body.trim().split('</mx-reply>')[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) => {
|
||||
const reactions = config.matrix.reactions;
|
||||
if (event.getType() !== 'm.room.message') return;
|
||||
if (event.event.unsigned.age > 10000) return;
|
||||
if (!event.getContent().meta) return;
|
||||
const { meta } = event.getContent();
|
||||
const type = meta.split(' ')[0];
|
||||
if (type === 'redact' || type === 'unreblog')
|
||||
addReact(event, reactions.redact);
|
||||
if (type === 'status' || type === 'reblog' || type === 'mention')
|
||||
addReact(event, reactions.expand);
|
||||
};
|
||||
|
||||
module.exports.expandReact = async (event) => {
|
||||
const reactions = config.matrix.reactions;
|
||||
if (event.getSender() !== matrixClient.credentials.userId) return;
|
||||
if (!event.getContent().meta) return;
|
||||
const { meta } = event.getContent();
|
||||
const type = meta.split(' ')[0];
|
||||
if (type === 'status' || type === 'reblog' || type === 'mention') {
|
||||
addReact(event, reactions.unroll);
|
||||
addReact(event, reactions.copy);
|
||||
addReact(event, reactions.clap);
|
||||
if (config.fediverse.tipping)
|
||||
addReact(event, reactions.rain);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.retryPromise = async (argList, promiseFn) => {
|
||||
let err;
|
||||
for(var arg of argList) {
|
||||
try {
|
||||
return await promiseFn(arg);
|
||||
} catch(e) { err = e; }
|
||||
}
|
||||
throw err || new Error('retryPromise error');
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue