now processes mentions and sends replies

This commit is contained in:
Andrzej Stepien 2023-08-15 01:40:05 +02:00
parent aa6d851e49
commit 972fa0c519
13 changed files with 437 additions and 211 deletions

BIN
data/database-testing Normal file

Binary file not shown.

View File

@ -7,6 +7,7 @@ export const db = Knex({
client: 'sqlite3', // or 'better-sqlite3' client: 'sqlite3', // or 'better-sqlite3'
connection: { connection: {
filename: "data/database" filename: "data/database"
//filename: "data/database-testing"
}, },
useNullAsDefault: true useNullAsDefault: true
}) })
@ -49,7 +50,18 @@ export const getWords = async () => {
.catch(error => { throw error }) .catch(error => { throw error })
} }
export const insertIntoBuffer = async (word,timestamp) => {
try {
db
.insert({
word,
timestamp
})
.into('buffer')
} catch (error) {
throw error
}
}
@ -109,3 +121,12 @@ export const deleteFromBuffer = async (word) => {
} }
} }
export const getDatePublished = async (word) => {
try {
return db('published')
.select('date')
.where('word',word)
} catch (error) {
throw error
}
}

View File

@ -12,12 +12,13 @@ import logger from '../logger.mjs'
export default async function createNote(text) { export default async function createNote(text,replyId) {
logger.trace("createNote called") logger.trace("createNote called")
const body = { const body = {
text: text, text: text,
cw:"Today's #micro365 prompt is:" cw:"Today's #micro365 prompt is:"
} }
if(replyId){body.replyId=replyId}
try { try {
const response = await firefish.post("notes/create",body) const response = await firefish.post("notes/create",body)
logger.info({ logger.info({

View File

@ -3,7 +3,7 @@ import bodyParser from "body-parser";
import logger from "./logger.mjs"; import logger from "./logger.mjs";
import pinoHTTP from 'pino-http' import pinoHTTP from 'pino-http'
import start from "./start.mjs"; import start from "./start.mjs";
import receiveMention from "./social-interaction/receiveMention.mjs"; import handleMention from "./social-interaction/handleMention.mjs";
const app = express() const app = express()
const port = 4000 const port = 4000
app.use(bodyParser.json()) app.use(bodyParser.json())
@ -12,12 +12,11 @@ app.use(
logger, logger,
}) })
) )
app.post('/api', (req,res) => { app.post('/api', async (req,res) => {
//receiveMention(req.body)
logger.info({body:req.body.body},"webhook received!") logger.info({body:req.body.body},"webhook received!")
//logger.info(req.body.body)
//logger.info("webhook received:",req.body.body.note.text)
res.sendStatus(200) res.sendStatus(200)
const result = await handleMention(req.body.body)
logger.info(`handleMention returned ${result.code}`)
}) })
app.listen(port, () => { app.listen(port, () => {

View File

@ -16,7 +16,7 @@ const __dirname = path.dirname(__filename);
// }) // })
export default pino( export default pino(
{ {
level: 'trace', level: 'info',
formatters: { formatters: {
level: (label) => { level: (label) => {
return { level: label.toUpperCase() }; return { level: label.toUpperCase() };

View File

@ -4,21 +4,21 @@
"description": "a server that delivers daily writing prompts via REST API", "description": "a server that delivers daily writing prompts via REST API",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "mocha" "test": "mocha --timeout 15000"
}, },
"author": "Andrzej Stepien", "author": "Andrzej Stepien",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"knex": "^2.5.1", "chai": "^4.3.7",
"spellchecker": "^3.7.1", "mocha": "^10.2.0"
"sqlite3": "^5.1.6"
}, },
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.4.0",
"sqlite3": "^5.1.6",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"chai": "^4.3.7", "spellchecker": "^3.7.1",
"express": "^4.18.2", "express": "^4.18.2",
"mocha": "^10.2.0", "knex": "^2.5.1",
"node-cron": "^3.0.2", "node-cron": "^3.0.2",
"pino": "^8.15.0", "pino": "^8.15.0",
"pino-http": "^8.4.0", "pino-http": "^8.4.0",

View File

@ -1,4 +1,5 @@
import { isMisspelled } from "spellchecker" import { isMisspelled } from "spellchecker"
import logger from "../logger.mjs"
const sampleNote = { const sampleNote = {
"id": "9id213fllx9y189f", "id": "9id213fllx9y189f",
"createdAt": "2023-08-13T13:37:09.537Z", "createdAt": "2023-08-13T13:37:09.537Z",
@ -79,6 +80,7 @@ const sampleNote = {
export default class { export default class {
constructor(raw){ constructor(raw){
this.raw = raw this.raw = raw
logger.trace({id:raw.id},"new note cosntructed!")
} }
#handle = /@[a-z,A-Z,0-9]* /g #handle = /@[a-z,A-Z,0-9]* /g
@ -100,11 +102,15 @@ get id(){
} }
get isSingleWord() { get isSingleWord() {
return this.cleanText.match(/[a-z]+/ig).length===1 return this.cleanText.match(/[a-z]+/ig)?.length===1
} }
get isRealWord(){ get isRealWord(){
return !isMisspelled(this.cleanText) return !isMisspelled(this.cleanText)
} }
get author(){
return this.raw.user.username
}
} }

View File

@ -0,0 +1,124 @@
import logger from "../logger.mjs"
import Note from "./Note.mjs"
import createNote from "../firefish-calls/createNote.mjs"
import { getDatePublished, wordIsAlreadyInBuffer, getAcceptablePrompts, valueExistsInColumn } from "../database-calls/db.mjs"
export default async function handleMentions(body) {
const note = new Note(body.note)
if (!note.isSingleWord) {
createNote("If you're trying to suggest a prompt, please message me a with *single word*.",note.id)
return { code: "NOTONEWORD" }
}
if (!note.isRealWord) {
createNote(`I'm afraid I can't do that, ${note.author}. That's not a 'real' word, at least as far as I'm aware! Have you checked the spelling?
You might just be too cool for me.`,note.id)
return { code: "NOTREAL" }
}
const word = note.cleanText
if (await wordIsAlreadyInBuffer(word)) {
createNote(`Believe it or not, somebody has already suggested that! Watch this space!`,note.id)
return { code: "INBUFFER" }
}
let unacceptable = await getAcceptablePrompts(word)
unacceptable = unacceptable.length===0
if (unacceptable) {
if (await valueExistsInColumn('medical_dictionary', 'word', word)) {
createNote("I'm afraid I can't use any word that appears in my medical dictionary. I know this delivers some false positives, but it was the only way to avoid accidentally triggering people!",note.id)
return { code: "MEDICAL" }
}
if (await valueExistsInColumn('bad_words', 'word', word)) {
createNote("That word is on my blocklist.",note.id)
return { code: "BLOCKLIST" }
}
if(await valueExistsInColumn('published','word',word)){
let datePublished = await getDatePublished(word)
datePublished = datePublished[0].date
createNote(`I already used that prompt on ${datePublished}, actually!`,note.id)
return {code: "PUBLISHED"}
}
createNote(`I'm afraid I can't do that, ${note.author}. The word you've suggested is either too common or too uncommon. Standards must be maintained!`,note.id)
return { code: "RARITY" }
} else {
createNote(`OK!`,note.id)
return { code: "OK" }
}
}
const sampleBody = {
note: {
"id": "9id213fllx9y189f",
"createdAt": "2023-08-13T13:37:09.537Z",
"userId": "9i5z4skgqvv58swy",
"user": {
"id": "9i5z4skgqvv58swy",
"name": null,
"username": "admin",
"host": null,
"avatarUrl": "https://localhost:3000/identicon/9i5z4skgqvv58swy",
"avatarBlurhash": null,
"avatarColor": null,
"isAdmin": true,
"isLocked": false,
"speakAsCat": true,
"emojis": [],
"onlineStatus": "online",
"driveCapacityOverrideMb": null
},
"text": "@micro365 bananas",
"cw": "Today's #micro365 prompt is:",
"visibility": "public",
"renoteCount": 0,
"repliesCount": 0,
"reactions": {},
"reactionEmojis": [],
"emojis": [],
"tags": [
"micro365"
],
"fileIds": [],
"files": [],
"replyId": "9id1ffugrao33bm4",
"renoteId": null,
"mentions": [
"9i5z5o9zy11l3skl"
],
"reply": {
"id": "9id1ffugrao33bm4",
"createdAt": "2023-08-13T13:20:19.192Z",
"userId": "9i5z5o9zy11l3skl",
"user": {
"id": "9i5z5o9zy11l3skl",
"name": null,
"username": "micro365",
"host": null,
"avatarUrl": "https://localhost:3000/files/thumbnail-4e0e8b82-df72-48f7-8100-b7515173da9d",
"avatarBlurhash": "ySPjGct7xu%M-;xu-;%MRjWBoffQofWB~qRjRjayRjfQM{M{t7ofWBt7ayfQ~qj[WBj[M{WBof?bofayfQM{WBfQt7xuofWBofofM{",
"avatarColor": null,
"isLocked": false,
"speakAsCat": true,
"emojis": [],
"onlineStatus": "active",
"driveCapacityOverrideMb": null
},
"text": "<small><small><small># </small></small></small>$[x2 $[font.serif **nudism**]]\n/njˈuːdɪzəm/\n<small>**noun**:\n- The belief in or practice of going nude in social, nonsexualized and frequently mixed-gender groups specifically in cultures where going nude in the social situation is not the norm.\n</small>#writing #microfiction #vss #nudism",
"cw": "Today's #micro365 prompt is:",
"visibility": "public",
"renoteCount": 0,
"repliesCount": 0,
"reactions": {},
"reactionEmojis": [],
"emojis": [],
"tags": [
"writing",
"microfiction",
"vss",
"nudism",
"micro365"
],
"fileIds": [],
"files": [],
"replyId": null,
"renoteId": null
}
}
}

View File

@ -1,8 +0,0 @@
import logger from "../logger.mjs"
import { checkSpelling } from "spellchecker"
import { wordIsAlreadyInBuffer, getAcceptablePrompts } from "../database-calls/db.mjs"
export default async function (note) {
}

View File

@ -1,5 +1,5 @@
import logger from "../logger.mjs"
export default async (text) => {
export default async () => { logger.trace(text)
} }

View File

@ -19,7 +19,7 @@ const sampleNote = {
"onlineStatus": "online", "onlineStatus": "online",
"driveCapacityOverrideMb": null "driveCapacityOverrideMb": null
}, },
"text": "@micro365 1", "text": "@micro365 hello",
"cw": "Today's #micro365 prompt is:", "cw": "Today's #micro365 prompt is:",
"visibility": "public", "visibility": "public",
"renoteCount": 0, "renoteCount": 0,
@ -129,5 +129,22 @@ describe("Testing Note getters", function(){
expect(N1.isRealWord).to.equal(false) expect(N1.isRealWord).to.equal(false)
done() done()
}) })
it("11.1 isRealWord should return false when text = 'awjfdihfeauigfieau'", function (done) {
N1.raw.text = "awjfdihfeauigfieau"
expect(N1.isRealWord).to.equal(false)
done()
})
it("12. author should return a string", function (done) {
expect(N1.author).is.a('string')
done()
})
it("13. author should return the string 'admin'", function (done) {
expect(N1.author).to.equal('admin')
done()
})
it("14. .id should return the string '9id213fllx9y189f'", function (done) {
expect(N1.id).to.equal('9id213fllx9y189f')
done()
})
}) })

View File

@ -0,0 +1,146 @@
import handleMentions from "../social-interaction/handleMention.mjs";
import { expect } from "chai";
import { insertIntoBuffer } from "../database-calls/db.mjs";
const sampleBody = {
note: {
"id": "9id213fllx9y189f",
"createdAt": "2023-08-13T13:37:09.537Z",
"userId": "9i5z4skgqvv58swy",
"user": {
"id": "9i5z4skgqvv58swy",
"name": null,
"username": "admin",
"host": null,
"avatarUrl": "https://localhost:3000/identicon/9i5z4skgqvv58swy",
"avatarBlurhash": null,
"avatarColor": null,
"isAdmin": true,
"isLocked": false,
"speakAsCat": true,
"emojis": [],
"onlineStatus": "online",
"driveCapacityOverrideMb": null
},
"text": "@micro365 hello",
"cw": "Today's #micro365 prompt is:",
"visibility": "public",
"renoteCount": 0,
"repliesCount": 0,
"reactions": {},
"reactionEmojis": [],
"emojis": [],
"tags": [
"micro365"
],
"fileIds": [],
"files": [],
"replyId": "9id1ffugrao33bm4",
"renoteId": null,
"mentions": [
"9i5z5o9zy11l3skl"
],
"reply": {
"id": "9id1ffugrao33bm4",
"createdAt": "2023-08-13T13:20:19.192Z",
"userId": "9i5z5o9zy11l3skl",
"user": {
"id": "9i5z5o9zy11l3skl",
"name": null,
"username": "micro365",
"host": null,
"avatarUrl": "https://localhost:3000/files/thumbnail-4e0e8b82-df72-48f7-8100-b7515173da9d",
"avatarBlurhash": "ySPjGct7xu%M-;xu-;%MRjWBoffQofWB~qRjRjayRjfQM{M{t7ofWBt7ayfQ~qj[WBj[M{WBof?bofayfQM{WBfQt7xuofWBofofM{",
"avatarColor": null,
"isLocked": false,
"speakAsCat": true,
"emojis": [],
"onlineStatus": "active",
"driveCapacityOverrideMb": null
},
"text": "<small><small><small># </small></small></small>$[x2 $[font.serif **nudism**]]\n/njˈuːdɪzəm/\n<small>**noun**:\n- The belief in or practice of going nude in social, nonsexualized and frequently mixed-gender groups specifically in cultures where going nude in the social situation is not the norm.\n</small>#writing #microfiction #vss #nudism",
"cw": "Today's #micro365 prompt is:",
"visibility": "public",
"renoteCount": 0,
"repliesCount": 0,
"reactions": {},
"reactionEmojis": [],
"emojis": [],
"tags": [
"writing",
"microfiction",
"vss",
"nudism",
"micro365"
],
"fileIds": [],
"files": [],
"replyId": null,
"renoteId": null
}
}}
describe("Testing handleMentions responses", async function(){
it("1. handleMentions() returns code MEDICAL when text = '@micro365 hysterectomy'", async function(){
sampleBody.note.text = "@micro365 hysterectomy"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("MEDICAL")
//done()
})
it("2. handleMentions() returns code BLOCKLIST when text = '@micro365 knockers'", async function(){
sampleBody.note.text = "@micro365 knockers"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("BLOCKLIST")
//done()
})
it("3. handleMentions() returns code RARITY when text = '@micro365 the'", async function(){
sampleBody.note.text = "@micro365 the"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("RARITY")
//done()
})
it("4. handleMentions() returns code INBUFFER when text = '@micro365 incapacity'", async function(){
sampleBody.note.text = "@micro365 incapacity"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("INBUFFER")
//done()
})
it("5. handleMentions() returns code NOTREAL when text = '@micro365 embiggensly'", async function(){
sampleBody.note.text = "@micro365 embiggensly"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("NOTREAL")
//done()
})
it("5.1 handleMentions() returns code NOTREAL when text = '@micro365 uydwgqi'", async function(){
sampleBody.note.text = "@micro365 uydwgqi"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("NOTREAL")
//done()
})
it("6. handleMentions() returns code NOTONEWORD when text = '@micro365 apple banana'", async function(){
sampleBody.note.text = "@micro365 apple apple"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("NOTONEWORD")
//done()
})
it("7. handleMentions() returns code OK when text = '@micro365 howler'", async function(){
sampleBody.note.text = "@micro365 howler"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("OK")
//done()
})
it("8. handleMentions() returns code PUBLISHED when text = '@micro365 nudism'", async function(){
sampleBody.note.text = "@micro365 nudism"
const result = await handleMentions(sampleBody)
expect(result.code).to.equal("PUBLISHED")
//done()
})
})

View File

@ -1,80 +0,0 @@
import { expect } from "chai";
import receiveMention from "../social-interaction/receiveMention.mjs";
const sampleNote = {
"id": "9id213fllx9y189f",
"createdAt": "2023-08-13T13:37:09.537Z",
"userId": "9i5z4skgqvv58swy",
"user": {
"id": "9i5z4skgqvv58swy",
"name": null,
"username": "admin",
"host": null,
"avatarUrl": "https://localhost:3000/identicon/9i5z4skgqvv58swy",
"avatarBlurhash": null,
"avatarColor": null,
"isAdmin": true,
"isLocked": false,
"speakAsCat": true,
"emojis": [],
"onlineStatus": "online",
"driveCapacityOverrideMb": null
},
"text": "@micro365 1",
"cw": "Today's #micro365 prompt is:",
"visibility": "public",
"renoteCount": 0,
"repliesCount": 0,
"reactions": {},
"reactionEmojis": [],
"emojis": [],
"tags": [
"micro365"
],
"fileIds": [],
"files": [],
"replyId": "9id1ffugrao33bm4",
"renoteId": null,
"mentions": [
"9i5z5o9zy11l3skl"
],
"reply": {
"id": "9id1ffugrao33bm4",
"createdAt": "2023-08-13T13:20:19.192Z",
"userId": "9i5z5o9zy11l3skl",
"user": {
"id": "9i5z5o9zy11l3skl",
"name": null,
"username": "micro365",
"host": null,
"avatarUrl": "https://localhost:3000/files/thumbnail-4e0e8b82-df72-48f7-8100-b7515173da9d",
"avatarBlurhash": "ySPjGct7xu%M-;xu-;%MRjWBoffQofWB~qRjRjayRjfQM{M{t7ofWBt7ayfQ~qj[WBj[M{WBof?bofayfQM{WBfQt7xuofWBofofM{",
"avatarColor": null,
"isLocked": false,
"speakAsCat": true,
"emojis": [],
"onlineStatus": "active",
"driveCapacityOverrideMb": null
},
"text": "<small><small><small># </small></small></small>$[x2 $[font.serif **nudism**]]\n/njˈuːdɪzəm/\n<small>**noun**:\n- The belief in or practice of going nude in social, nonsexualized and frequently mixed-gender groups specifically in cultures where going nude in the social situation is not the norm.\n</small>#writing #microfiction #vss #nudism",
"cw": "Today's #micro365 prompt is:",
"visibility": "public",
"renoteCount": 0,
"repliesCount": 0,
"reactions": {},
"reactionEmojis": [],
"emojis": [],
"tags": [
"writing",
"microfiction",
"vss",
"nudism",
"micro365"
],
"fileIds": [],
"files": [],
"replyId": null,
"renoteId": null
}
}