Compare commits

...

17 commits
v1.1.0 ... main

Author SHA1 Message Date
Hugh Rundle 08c32b8647 Merge pull request 'update' (#9) from update into main
Reviewed-on: #9
2024-08-10 12:19:11 +10:00
Hugh Rundle a85e056b4b
Merge branch 'main' into update 2024-08-10 12:18:25 +10:00
Hugh Rundle e2ece557c2
add some more options
- add extra reply options
- fix mentions
2024-08-10 12:09:36 +10:00
Hugh Rundle 2a6cf879b6
update readme and deps 2024-08-10 11:44:48 +10:00
Hugh Rundle fa47c7a0fb
Update README.md 2024-07-29 20:16:28 +10:00
Hugh Rundle e10a3ef7ce
Merge pull request #7 from hughrun/dependabot/npm_and_yarn/ws-7.5.10
Bump ws from 7.5.2 to 7.5.10
2024-06-29 12:32:46 +10:00
dependabot[bot] 85d6589dce
Bump ws from 7.5.2 to 7.5.10
Bumps [ws](https://github.com/websockets/ws) from 7.5.2 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.2...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 02:12:09 +00:00
Hugh Rundle a99bd4bf92
Merge pull request #5 from hughrun/dependabot/npm_and_yarn/follow-redirects-1.15.6
Bump follow-redirects from 1.14.7 to 1.15.6
2024-04-15 15:45:30 +10:00
dependabot[bot] b86560f033
Bump follow-redirects from 1.14.7 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 05:40:05 +00:00
Hugh Rundle 391e7524ca
Update README.md 2022-10-23 21:22:16 +11:00
Hugh Rundle 7b9a90030a
Update README.md 2022-10-23 15:22:39 +11:00
Hugh Rundle 2bfa31907f
Merge pull request #4 from hughrun/dependabot/npm_and_yarn/follow-redirects-1.14.7
Bump follow-redirects from 1.14.1 to 1.14.7
2022-01-17 16:54:30 +11:00
dependabot[bot] d62209587e
Bump follow-redirects from 1.14.1 to 1.14.7
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.1 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.1...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-17 05:32:17 +00:00
Hugh Rundle d51bd3014e
Merge pull request #3 from hughrun/deps
bump axios and version
2021-11-06 10:16:18 +11:00
Hugh Rundle c337614fe6
bump axios and version 2021-11-06 10:15:33 +11:00
Hugh Rundle e5728006bc improve error-checking and add username to some responses
Previously clippy was left in an unresponsive state if an authentication error occured, or in some cases if the remote server dropped the connection without sending a close() message. Clippy now terminates the socket connection on every connection error or authentication error, waits 5 seconds, and tries again.

Also added username to the response function, and added a couple of extra responses.
2021-07-09 18:48:15 +10:00
Hugh Rundle 0ba65f833c bump ws module 2021-07-09 18:47:50 +10:00
4 changed files with 114 additions and 73 deletions

View file

@ -45,6 +45,14 @@ Then run `npm install .` to install npm modules `axios` and `ws`.
Start the bot with the traditional `node index.js`. Start the bot with the traditional `node index.js`.
However, you probably want this to run automatically on a web server. You can do this with `systemd` using the example unit file: `mastodon-clippy.service.example`. Adjust this for your user and paths, and then activate it:
```bash
sudo cp mastodon-clippy.service.example /etc/systemd/system/mastodon-clippy.service
sudo systemctl enable mastodon-clippy.service
sudo systemctl start mastodon-clippy service
```
## interacting with the bot ## interacting with the bot
To "sign up" for notification from your bot, users have two options: To "sign up" for notification from your bot, users have two options:

145
index.js
View file

@ -26,8 +26,14 @@ const headers = {
'Authorization' : `Bearer ${access_token}` 'Authorization' : `Bearer ${access_token}`
} }
function resetConnection(socket) {
terminate(socket)
console.log(`waiting after error`)
setTimeout( function() { listen() }, 5000)
}
// set up bot account with correct settings // set up bot account with correct settings
function initiateSettings() { function initiateSettings(socket) {
let account = { let account = {
locked: false, locked: false,
@ -40,37 +46,50 @@ function initiateSettings() {
return axios.patch(`https://${domain}/api/v1/accounts/update_credentials`, account, { headers: headers }) return axios.patch(`https://${domain}/api/v1/accounts/update_credentials`, account, { headers: headers })
.catch( err => { .catch( err => {
console.error('ERROR applying bot user settings: ', err.message) console.error('ERROR applying bot user settings: ', err.message)
terminate(socket)
}) })
} }
// return random suggestion string // return random suggestion string
function suggestion() { function suggestion(username) {
const n = crypto.randomInt(6) const n = crypto.randomInt(12)
switch(n) { switch(n) {
case 0: case 0:
return 'How about logging off instead?'; return 'How about logging off instead?';
case 1: case 1:
return 'Would you like to delete your toot?'; return `Would you like to delete your toot, @${username}?`;
case 2: case 2:
return 'Can I help you take a walk outside?'; return 'Can I help you take a walk outside? 🚶‍➡️';
case 3: case 3:
return 'You may like to reconsider your life choices.'; return 'You may like to reconsider your life choices.';
case 4: case 4:
return 'Why not try looking at #CatsOfInstagram instead?'; return 'Why not try looking at #CatsOfMastodon instead?';
case 5: case 5:
return `You're better than this, come on.`; return `Come on @${username}, we've talked about this. 🤷‍♂️`;
case 6:
return `You should go look at some trees. Trees are calming 🌳`;
case 7:
return `I'm not angry. I'm just very disappointed. 😔`;
case 8:
return `You said you were going to stop doing that ...and yet here we are.`;
case 9:
return `Time to touch some grass 🌱`;
case 10:
return `Why not have a nice cup of tea instead? 🫖`;
case 11:
return `And yet you still haven't read all of those books in your TBR pile. 🤔`;
} }
} }
// send a message when someone toots about the topic // send a message when someone toots about the topic
function sendResponse(rip, user) { function sendResponse(rid, user, username) {
let payload = { let payload = {
'status' : `@${user} It looks like you're posting about '${topic}'. ${suggestion()}`, 'status' : `@${user} It looks like you're posting about '${topic}'. ${suggestion(username)}`,
'spoiler_text' : topic, 'spoiler_text' : topic,
'in_reply_to_id' : rip, 'in_reply_to_id' : rid,
} }
axios.post(`https://${domain}/api/v1/statuses`, payload, { headers: headers }) axios.post(`https://${domain}/api/v1/statuses`, payload, { headers: headers })
@ -108,66 +127,80 @@ function filterMentions(text, mentions) {
// STREAMING USER TIMELINE // STREAMING USER TIMELINE
// This is where the action is! // This is where the action is!
// *********************** // ***********************
const ws = new WebSocket(`wss://${domain}/api/v1/streaming?access_token=${access_token}&stream=user`)
// make sure bot is set up correctly each time it starts function terminate(socket) {
initiateSettings() console.error(`Terminating connection...`)
socket.terminate()
console.log(`Terminated`)
}
// errors function listen() {
ws.on('error', err => {
console.error(`WebSocket error: ${err.message}`)
})
// check updates and notifications in the stream console.log(`Listening...`)
ws.on('message', msg => { const ws = new WebSocket(`wss://${domain}/api/v1/streaming?access_token=${access_token}&stream=user`)
let packet = JSON.parse(msg)
let data = JSON.parse(packet.payload)
// notifications // make sure bot is set up correctly each time it starts
if (packet.event == 'notification') { initiateSettings(ws)
// always follow back // errors
if (data.type == 'follow') { ws.on('error', err => {
followAction(data.account.id, 'follow') console.error(`WebSocket error: ${err.message}`)
} resetConnection(ws)
})
if (data.type == 'mention') { // check updates and notifications in the stream
ws.on('message', msg => {
let packet = JSON.parse(msg)
let data = JSON.parse(packet.payload)
let post = data.status.content // notifications
if (packet.event == 'notification') {
// check start requests // always follow back
if (post.match(/\bSTART\b/)) { if (data.type == 'follow') {
followAction(data.account.id, 'follow') followAction(data.account.id, 'follow')
} }
// check stop requests if (data.type == 'mention') {
if (post.match(/\STOP\b/)) {
followAction(data.account.id, 'unfollow') let post = data.status.content
// check start requests
if (post.match(/\bSTART\b/)) {
followAction(data.account.id, 'follow')
}
// check stop requests
if (post.match(/\STOP\b/)) {
followAction(data.account.id, 'unfollow')
}
} }
} }
}
// updates (posts) // updates (posts)
if (packet.event == 'update') { if (packet.event == 'update') {
let rip = data.id let rid = data.id
let user = data.account.acct let user = data.account.acct
// get just the account names (@name@domain.tld) let username = data.account.username
let mentions = data.mentions.map( mention => mention.acct) // get just the account names (@name@domain.tld)
// exclude own toots and @mentions to avoid an infinite loops let mentions = data.mentions.map( mention => mention.acct)
if (data.account.username !== clippy && !mentions.includes(clippy)) { // exclude own toots and @mentions to avoid an infinite loops
// get rid of mentions in case topic is within a username if (username !== clippy && !mentions.includes(clippy)) {
let text = filterMentions(data.content, mentions) // get rid of mentions in case topic is within a username
if ( text.toLowerCase().includes(topic) ) { let text = filterMentions(data.content, mentions)
sendResponse(rip, user) if ( text.toLowerCase().includes(topic) ) {
} sendResponse(rid, user, username)
else if (data.spoiler_text.toLowerCase().includes(topic)) { }
sendResponse(rip, user) else if (data.spoiler_text.toLowerCase().includes(topic)) {
} sendResponse(rid, user, username)
else if (data.tags.map(tag => tag.name.toLowerCase()).includes(topic)) { }
sendResponse(rip, user) else if (data.tags.map(tag => tag.name.toLowerCase()).includes(topic)) {
sendResponse(rid, user, username)
}
} }
} }
} })
}) }
// let's go
listen()

22
package-lock.json generated
View file

@ -1,26 +1,26 @@
{ {
"name": "mastodon-clippy", "name": "mastodon-clippy",
"version": "1.1.0", "version": "1.1.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"axios": { "axios": {
"version": "0.21.1", "version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": { "requires": {
"follow-redirects": "^1.10.0" "follow-redirects": "^1.14.0"
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.14.1", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
}, },
"ws": { "ws": {
"version": "7.4.5", "version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="
} }
} }
} }

View file

@ -1,8 +1,8 @@
{ {
"name": "mastodon-clippy", "name": "mastodon-clippy",
"version": "1.1.0", "version": "1.1.2",
"description": "Mastodon clippy bot", "description": "Mastodon clippy bot",
"repository": "https://github.com/hughrun/mastodon-clippy.git", "repository": "https://git.suboptimal.solutions/hugh/mastodon-clippy",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@ -10,7 +10,7 @@
"author": "Hugh Rundle <hugh@hughrundle.net> (https://www.hughrundle.net)", "author": "Hugh Rundle <hugh@hughrundle.net> (https://www.hughrundle.net)",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^1.7.3",
"ws": "^7.4.5" "ws": "^8.18"
} }
} }