Compare commits
No commits in common. "master" and "v0.2.1" have entirely different histories.
48 changed files with 1724 additions and 2910 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,6 +1,3 @@
|
|||
# Patches
|
||||
*.patch
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
@ -9,10 +6,11 @@ yarn-debug.log*
|
|||
yarn-error.log*
|
||||
|
||||
# Ignore config
|
||||
config.js
|
||||
/config.js
|
||||
|
||||
# Ignore localstorage
|
||||
keys
|
||||
# Ignore JSON
|
||||
/timeline.json
|
||||
/notification.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
|
73
README.md
73
README.md
|
@ -1,47 +1,34 @@
|
|||
# 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`
|
||||
# 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.
|
||||
|
||||
# Installation
|
||||
|
||||
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`
|
||||
1. `git clone https://github.com/vulet/plemara`
|
||||
2. `cd plemara && 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`
|
||||
|
||||
# Contributors
|
||||
CryptoMooners
|
||||
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
|
||||

|
113
auth.js
113
auth.js
|
@ -1,113 +0,0 @@
|
|||
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;
|
|
@ -1,78 +0,0 @@
|
|||
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;
|
21
commands/beg.js
Normal file
21
commands/beg.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
18
commands/boo.js
Normal file
18
commands/boo.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
18
commands/clap.js
Normal file
18
commands/clap.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
18
commands/copy.js
Normal file
18
commands/copy.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
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(() => {});
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
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}`)
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
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);
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
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);
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,132 +0,0 @@
|
|||
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
26
commands/fediverse/react.js
vendored
|
@ -1,26 +0,0 @@
|
|||
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(() => {});
|
||||
}
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
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);
|
||||
});
|
||||
};
|
|
@ -1,193 +0,0 @@
|
|||
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;
|
||||
};
|
41
commands/flood.js
Normal file
41
commands/flood.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
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);
|
||||
};
|
20
commands/fren.js
Normal file
20
commands/fren.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
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,33 +1,18 @@
|
|||
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>'
|
||||
exports.runQuery = function (matrixClient, room) {
|
||||
matrixClient.sendHtmlNotice(room.roomId,
|
||||
'',
|
||||
'<blockquote><b>+plemara [your message] : post<br>'
|
||||
+ '+redact [post id] : delete post<br>'
|
||||
+ '+follow [user id] : follow<br>'
|
||||
+ '+unfollow [user id] : unfollow<br>'
|
||||
+ '+media [homeserver image URL or MXC] [optional message] : post media<br>'
|
||||
+ '+fren [user id] : follow<br>'
|
||||
+ '+unfren [user id] : unfollow<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 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>'
|
||||
);
|
||||
+ '+flood : turn on timeline<br>'
|
||||
+ '+notify : show notifications</b></blockquote>'
|
||||
+ '<blockquote><b>--- <i>docs by lint</i> ---</b></blockquote>');
|
||||
};
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
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(() => {});
|
||||
}
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
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(() => {});
|
||||
}
|
||||
};
|
51
commands/notify.js
Normal file
51
commands/notify.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
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);
|
||||
};
|
20
commands/pin.js
Normal file
20
commands/pin.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
21
commands/plemara.js
Normal file
21
commands/plemara.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
17
commands/redact.js
Normal file
17
commands/redact.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
18
commands/reply.js
Normal file
18
commands/reply.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
21
commands/tip.js
Normal file
21
commands/tip.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
20
commands/unfren.js
Normal file
20
commands/unfren.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
20
commands/unpin.js
Normal file
20
commands/unpin.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
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}`);
|
||||
});
|
||||
};
|
8
config.js
Normal file
8
config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
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,44 +1,110 @@
|
|||
global.registrar = require('./registrar.js');
|
||||
const sdk = require('matrix-js-sdk');
|
||||
const axios = require('axios');
|
||||
const registrar = require('./registrar.js');
|
||||
|
||||
matrix.auth.access_token ? auth.matrixTokenLogin() : auth.getMatrixToken();
|
||||
//if (!fediverse.auth.access_token && config.fediverse.username) auth.registerFediverseApp();
|
||||
const auth = {
|
||||
type: 'm.login.password',
|
||||
user: registrar.config.matrixUser,
|
||||
password: registrar.config.matrixPass,
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
axios.post(`${registrar.config.matrixServer}/_matrix/client/r0/login`, auth).then((response) => {
|
||||
CreateClient(response.data.access_token);
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
let CreateClient = (token) => {
|
||||
const matrixClient = sdk.createClient({
|
||||
baseUrl: registrar.config.matrixServer,
|
||||
accessToken: token,
|
||||
userId: registrar.config.userId,
|
||||
timelineSupport: true,
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
|
0
notification.json
Normal file
0
notification.json
Normal file
1027
package-lock.json
generated
1027
package-lock.json
generated
File diff suppressed because it is too large
Load diff
29
package.json
29
package.json
|
@ -1,10 +1,7 @@
|
|||
{
|
||||
"name": "ligh7hau5",
|
||||
"version": "3.0.1",
|
||||
"name": "plemara",
|
||||
"version": "0.2.1",
|
||||
"description": "A Matrix to Fediverse client",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
|
@ -12,22 +9,22 @@
|
|||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vulet/ligh7hau5.git"
|
||||
"url": "git+https://github.com/vulet/plemara.git"
|
||||
},
|
||||
"author": "vul",
|
||||
"license": "AGPL-3.0-only",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vulet/lighthau5/issues"
|
||||
"url": "https://github.com/vulet/plemara/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vulet/lighthau5#readme",
|
||||
"homepage": "https://github.com/vulet/plemara#readme",
|
||||
"dependencies": {
|
||||
"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"
|
||||
"axios": "^0.19.2",
|
||||
"file-system": "^2.2.2",
|
||||
"matrix-js-sdk": "^2.4.6"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"devDependencies": {
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-plugin-import": "^2.17.3"
|
||||
}
|
||||
}
|
||||
|
|
63
registrar.js
63
registrar.js
|
@ -1,51 +1,18 @@
|
|||
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'),
|
||||
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")
|
||||
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'),
|
||||
};
|
||||
|
|
0
timeline.json
Normal file
0
timeline.json
Normal file
211
utils.js
211
utils.js
|
@ -1,211 +0,0 @@
|
|||
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