matrix-motus-bot/index.js

247 lines
6.1 KiB
JavaScript

import path from 'path'
import Game from './game.js'
import emoji from 'node-emoji'
import logTimestamp from 'log-timestamp'
import commander from 'bot-commander'
import showdown from 'showdown'
import uniqueRandomArray from 'unique-random-array'
import words from './assets/words.js'
import allWords from './assets/all_words.js'
import {
fileURLToPath
} from 'url'
import {
MatrixClient,
MatrixAuth,
SimpleFsStorageProvider,
RustSdkCryptoStorageProvider,
AutojoinRoomsMixin
} from 'matrix-bot-sdk'
const converter = new showdown.Converter()
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const datasPath = process.env.MATRIX_DATA ?? (__dirname + '/datas')
const storage = new SimpleFsStorageProvider(datasPath + '/bot.json')
const cryptoProvider = new RustSdkCryptoStorageProvider(datasPath + '/crypto')
const url = process.env.MATRIX_URL
const username = process.env.MATRIX_USERNAME
const password = process.env.MATRIX_PASSWORD
const auth = new MatrixAuth(url)
const authClient = await auth.passwordLogin(username, password)
const token = authClient.accessToken
const client = new MatrixClient(url, token, storage, cryptoProvider)
const games = []
const randomSeeds = uniqueRandomArray(words.seeds);
const sendMessage = async (room, text) => {
client.sendMessage(room, {
'msgtype': 'm.text',
'format': 'org.matrix.custom.html',
'body': text,
'formatted_body': converter.makeHtml(text),
});
}
const renderMatrix = (game, options) => {
const chars = {
0: ':large_blue_square:',
1: ':large_blue_square:',
2: ':large_yellow_circle:',
3: ':large_red_square:',
}
options = options ?? {}
options.onlySigns = options.onlySigns ?? false
options.hideNotPlayedLines = options.hideNotPlayedLines ?? false
let render = '<table>'
for (let row of game.matrix()) {
const letters = (row.word ?? '.'.repeat(game.expectedWord.length)).split('')
if (options.hideNotPlayedLines && !row.word) {
continue
}
let signs = []
for (let index in row.result) {
signs.push(chars[row.result[index]])
}
const elements = options.onlySigns ? {signs} : {letters, signs}
for (let key in elements) {
render += '<tr>'
for (let item of elements[key]) {
render += `<td>${item}</td>`
}
render += '</tr>'
}
}
render + '</table>'
const markdown = emoji.emojify(render)
return markdown
}
const startGame = async (meta, seed) => {
let data
if (seed) {
const index = words.seeds.indexOf(seed)
if (index === -1) {
return await sendMessage(meta.room, 'Désolé mais cette seed n\'existe pas.')
}
data = words.datas[index]
} else {
seed = randomSeeds()
data = words.datas[words.seeds.indexOf(seed)]
}
games[meta.room] = new Game(data, allWords)
const firstLetter = data.substr(0, 1)
const size = data.length
await sendMessage(meta.room, `Le mot commence par la lettre \`${firstLetter}\` et contient ${size} lettres. Vous pouvez partager cette grille avec sa seed : \`${seed}\`.`)
await sendMessage(meta.room, renderMatrix(games[meta.room]))
console.log(`${meta.sender} on room ${meta.room} starts the game (${data})`)
}
commander
.command('help')
.description('`help` : affiche l\'aide')
.action(async (meta) => {
let message = []
for (let command of commander.commands) {
message.push('* ' + command._description)
}
const text = message.join("\n")
sendMessage(meta.room, text);
})
commander
.command('start [seed]')
.description('`start` ou `start seed` : démarrer une partie avec éventuellement la seed d\'une grille')
.action(async (meta, seed) => {
if (games.hasOwnProperty(meta.room)) {
const currentGame = games[meta.room]
if (!currentGame.isFinish()) {
for (let item of [
'Il y a une partie en cours !',
'Le mot commence par la lettre `' + currentGame.expectedWord.substr(0, 1) + '`',
renderMatrix(currentGame),
]) {
await sendMessage(meta.room, item)
}
return
}
}
startGame(meta, seed)
})
commander
.command('restart [seed]')
.description('`restart` ou `restart seed` : démarrer une nouvelle partie avec éventuellement la seed d\'une grille')
.action(async (meta, seed) => {
startGame(meta, seed)
})
commander
.command('test [word]')
.description('`test mot` : tester le mot `mot`')
.action(async (meta, word) => {
if (!games.hasOwnProperty(meta.room)) {
return await sendMessage(
meta.room,
'Il n\'y a pas de partie en cours. Taper `start` pour en lancer une.'
)
}
const currentGame = games[meta.room]
if (currentGame.isFinish()) {
return await sendMessage(
meta.room,
'La partie est terminée. Taper `restart` pour en lancer une nouvelle.'
)
}
if (!currentGame.tryWord(word)) {
return await sendMessage(
meta.room,
'Le mot de ne fait pas la bonne longueur ou n\'apparait pas dans le dictionnaire.'
)
}
if (currentGame.isWon()) {
await sendMessage(meta.room, 'Bravo ! Tu as découvert le mot !')
return await sendMessage(meta.room, renderMatrix(currentGame, {
onlySigns: true,
hideNotPlayedLines: true,
}))
}
if (currentGame.isFinish()) {
const expectedWord = currentGame.expectedWord
await sendMessage(
meta.room,
`Tes propositions sont épuisées… Le mot a deviner était \`${expectedWord}\``
)
return await sendMessage(meta.room, renderMatrix(currentGame, {
onlySigns: true,
hideNotPlayedLines: true,
}))
}
await sendMessage(meta.room, renderMatrix(currentGame))
})
AutojoinRoomsMixin.setupOnClient(client)
client
.start()
.then(() => console.log('Client started'))
.catch(() => {
console.error('An error occurred')
})
client.on('room.message', (roomId, event) => {
if (!event['content']) {
return
}
const meta = {
sender: event['sender'],
body: event['content']['body'],
room: roomId,
}
commander.parse(meta.body.toLowerCase(), meta)
})