Merge branch 'dev' of https://github.com/Piterden/chessbot into inline

This commit is contained in:
root
2019-04-25 08:53:00 +00:00
11 changed files with 322 additions and 136 deletions
+2 -13
View File
@@ -1,34 +1,23 @@
root: true
parser: babel-eslint
extends:
- '@atomix'
# - '@atomix'
- standard
env:
es6: true
node: true
browser: true
parserOptions:
sourceType: module
ecmaVersion: 2018
ecmaFeatures:
globalReturn: false
modules: true
modules: false
experimentalObjectRestSpread: true
globals:
BigInt: true
rules:
max-len: off
import/no-cycle: off
no-param-reassign: off
prettier/prettier: off
prefer-rest-params: off
prefer-destructuring: off
no-underscore-dangle: off
unicorn/filename-case: off
unicorn/prefer-spread: off
class-methods-use-this: off
function-paren-newline: off
import/no-default-export: off
curly:
- error
comma-dangle:
+1 -1
View File
@@ -1,4 +1,4 @@
require('dotenv').load()
require('dotenv').config()
const {
DB_CLIENT,
+8 -8
View File
@@ -15,22 +15,22 @@
},
"license": "MIT",
"dependencies": {
"chess": "^0.2.8",
"dotenv": "^5.0.1",
"knex": "^0.14.6",
"chess": "^0.4.1",
"dotenv": "^7.0.0",
"knex": "^0.16.5",
"module-alias": "^2.2.0",
"pg": "^7.8.1",
"telegraf": "^3.27.1"
"pg": "^7.10.0",
"telegraf": "^3.29.0"
},
"devDependencies": {
"@atomix/eslint-config": "^7.0.0-next.1",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
"nodemon": "^1.18.10",
"prettier": "^1.17.0",
"nodemon": "^1.18.11",
"sqlite3": "^4.0.6"
},
"_moduleAliases": {
+6 -2
View File
@@ -7,7 +7,9 @@ const startHandler = require('./start')
const actionsHandler = require('./actions')
const messageHandler = require('./message')
const optionsHandler = require('./options')
const inlineHandler = require('./inline')
const inlineJoinHandler = require('./inlineJoin')
const inlineMoveHandler = require('./inlineMove')
const inlineQueryHandler = require('./inlineQuery')
module.exports = {
newHandler,
@@ -19,5 +21,7 @@ module.exports = {
actionsHandler,
messageHandler,
optionsHandler,
inlineHandler,
inlineJoinHandler,
inlineMoveHandler,
inlineQueryHandler,
}
-70
View File
@@ -1,70 +0,0 @@
const chess = require('chess')
const { board } = require('@/keyboards')
const { debug, unescapeUser, escapeUser } = require('@/helpers')
const gameClient = chess.create({ PGN: true })
const status = gameClient.getStatus()
module.exports = () => [
'inline_query',
async (ctx) => {
debug(ctx.update)
let user = await ctx.db('users')
.where('id', ctx.update.inline_query.from.id)
.first()
if (user) {
user = unescapeUser(user)
if (JSON.stringify(user) !== JSON.stringify(ctx.update.inline_query.from)) {
await ctx.db('users')
.where('id', user.id)
.update(escapeUser(ctx.update.inline_query.from))
}
} else {
const users = await ctx.db('users')
.insert(escapeUser(ctx.update.inline_query.from))
.returning(Object.keys(ctx.update.inline_query.from))
user = unescapeUser(users[0])
}
await ctx.answerInlineQuery([
{
id: 1,
type: 'photo',
thumb_url: 'https://crossword.live/img/w.png',
photo_url: 'https://crossword.live/img/w.png',
title: 'Play with white pieces',
input_message_content: {
message_text: `Black (top): ?
White (bottom): ${user.first_name}`,
},
...board(status.board.squares, true, [{
text: 'Join the game',
callback_data: `join::w::${user.id}`,
}]),
},
{
id: 2,
type: 'photo',
thumb_url: 'https://crossword.live/img/b.png',
photo_url: 'https://crossword.live/img/b.png',
title: 'Play with black pieces',
input_message_content: {
message_text: `White (top): ?
Black (bottom): ${user.first_name}`,
},
...board(status.board.squares, false, [{
text: 'Join the game',
callback_data: `join::b::${user.id}`,
}]),
},
], {
is_personal: true,
cache_time: 0,
})
},
]
+53
View File
@@ -0,0 +1,53 @@
const chess = require('chess')
const { board } = require('@/keyboards')
const { debug, escapeUser, unescapeUser } = require('@/helpers')
module.exports = () => [
/^join::([wb])::(\d+)/,
async (ctx) => {
debug(ctx.update)
const userId = Number(ctx.match[2])
const iAmWhite = () => ctx.match[1] !== 'w'
if (ctx.from.id === userId) {
return ctx.answerCbQuery('You can\'t join yourself!')
}
let enemy = await ctx.db('users').where('id', userId).first().catch(debug)
if (enemy) {
enemy = unescapeUser(enemy)
} else {
enemy = ctx.tg.getChatMember(
ctx.callbackQuery.chat_instance,
userId
)
await ctx.db('users').insert(escapeUser(enemy)).catch(debug)
}
const [gameId] = await ctx.db('games').returning('id').insert({
user_w: !iAmWhite() ? enemy.id : ctx.from.id,
user_b: !iAmWhite() ? ctx.from.id : enemy.id,
inline_id: ctx.update.callback_query.inline_message_id,
}).catch(debug)
ctx.session.gameId = gameId
const gameClient = chess.create({ PGN: true })
const status = gameClient.getStatus()
await ctx.editMessageText(
iAmWhite()
? `Black (top): ${enemy.first_name}
White (bottom): ${ctx.from.first_name}
White's turn`
: `Black (top): ${ctx.from.first_name}
White (bottom): ${enemy.first_name}
White's turn`,
board(status.board.squares, true)
)
return ctx.answerCbQuery('Now play!')
},
]
+153
View File
@@ -0,0 +1,153 @@
const chess = require('chess')
const { board } = require('@/keyboards')
const { debug, escapeUser, unescapeUser } = require('@/helpers')
const isWhiteTurn = (moves) => !(moves.length % 2)
const isWhiteUser = (game, ctx) => Number(game.user_w) === ctx.from.id
const isBlackUser = (game, ctx) => Number(game.user_b) === ctx.from.id
const statusMessage = ({ isCheck, isCheckmate, isRepetition }) => `
${isCheck ? '|CHECK|' : ''}
${isCheckmate ? '|CHECKMATE|' : ''}
${isRepetition ? '|REPETITION|' : ''}`
const topMessage = (moves, player, enemy) => isWhiteTurn(moves)
? `White (top): ${player.first_name}
Black (bottom): ${enemy.first_name}
Black's turn`
: `Black (top): ${player.first_name}
White (bottom): ${enemy.first_name}
White's turn`
const isReady = (game) => !!(game.user_w && game.user_b)
module.exports = () => [
/^([a-h])([1-8])$/,
async (ctx) => {
const gameState = await ctx.db('games')
.where('id', ctx.session.gameId)
.first()
debug(gameState)
if (!isReady(gameState)) {
return ctx.answerCbQuery('Join the game to move pieces!')
}
if (![Number(gameState.user_w), Number(gameState.user_b)]
.includes(ctx.from.id)) {
return ctx.answerCbQuery('This board is full, please start a new one.')
}
const movesState = await ctx.db('moves')
.where('game_id', gameState.id)
.orderBy('created_at', 'asc')
.select()
if ((isWhiteTurn(movesState) && isBlackUser(gameState, ctx)) ||
(!isWhiteTurn(movesState) && isWhiteUser(gameState, ctx))) {
return ctx.answerCbQuery('Wait, please. Now is not your turn.')
}
const gameClient = chess.create({ PGN: true })
movesState.forEach(({ move }) => {
try {
gameClient.move(move)
} catch (error) {
debug(error)
}
})
let moves = []
let status = gameClient.getStatus()
const square = status.board.squares
.find(({ file, rank }) => file === ctx.match[1] && rank === Number(ctx.match[2]))
if (!ctx.session.moves) {
if (
!square ||
!square.piece ||
(square.piece.side.name === 'black' && isWhiteTurn(movesState)) ||
(square.piece.side.name === 'white' && !isWhiteTurn(movesState))
) {
return ctx.answerCbQuery()
}
moves = Object.keys(status.notatedMoves)
.filter((key) => status.notatedMoves[key].src === square)
.map((key) => ({ ...status.notatedMoves[key], key }))
await ctx.editMessageReplyMarkup(board(
status.board.squares.map((sqr) => {
const move = moves
.find((({ file, rank }) => ({ dest }) => dest.file === file &&
dest.rank === rank)(sqr))
return move ? { ...sqr, destination: move } : sqr
}),
isWhiteTurn(movesState)
).reply_markup)
.catch(debug)
ctx.session.moves = moves
ctx.session.selected = square
} else {
const moving = ctx.session.moves
.find(({ dest: { file, rank } }) => file === square.file && rank === square.rank)
if (moving && !movesState.find(({ move }) => move === moving.key)) {
try {
gameClient.move(moving.key)
} catch (error) {
debug(error)
}
status = gameClient.getStatus()
await ctx.db('moves').insert({
game_id: ctx.session.gameId,
move: moving.key,
})
.catch(debug)
}
ctx.session.moves = null
ctx.session.selected = null
let enemy = await ctx.db('users')
.where('id', isWhiteTurn(movesState)
? Number(gameState.user_w)
: Number(gameState.user_b))
.first()
.catch(debug)
if (enemy) {
enemy = unescapeUser(enemy)
} else {
enemy = ctx.tg.getChatMember(
ctx.callbackQuery.chat_instance,
isWhiteTurn(movesState)
? Number(gameState.user_w)
: Number(gameState.user_b)
)
await ctx.db('users').insert(escapeUser(enemy)).catch(debug)
}
debug(enemy)
await ctx.editMessageText(
topMessage(
movesState,
ctx.update.callback_query.from,
enemy
) + statusMessage(status),
board(status.board.squares, !isWhiteTurn(movesState))
)
.catch(debug)
}
return ctx.answerCbQuery()
},
]
+67
View File
@@ -0,0 +1,67 @@
const chess = require('chess')
const { board } = require('@/keyboards')
const { debug, unescapeUser, escapeUser } = require('@/helpers')
const gameClient = chess.create({ PGN: true })
const status = gameClient.getStatus()
module.exports = () => async (ctx) => {
debug(ctx.update)
let user = await ctx.db('users')
.where('id', Number(ctx.from.id))
.first()
if (user) {
user = unescapeUser(user)
if (JSON.stringify(user) !== JSON.stringify(ctx.from)) {
await ctx.db('users')
.where('id', Number(user.id))
.update(escapeUser(ctx.from))
.catch(debug)
}
} else {
const users = await ctx.db('users')
.insert(escapeUser(ctx.from))
.returning(Object.keys(ctx.from))
.catch(debug)
user = unescapeUser(users[0])
}
await ctx.answerInlineQuery([
{
id: 1,
type: 'sticker',
sticker_file_id: 'CAADAgADNAADX5T2DgeepFdKYLnKAg',
input_message_content: {
message_text: `Black (top): ?
White (bottom): ${user.first_name}
Waiting for a black side`,
},
...board(status.board.squares, true, [{
text: 'Join the game',
callback_data: `join::w::${user.id}`,
}]),
},
{
id: 2,
type: 'sticker',
sticker_file_id: 'CAADAgADMwADX5T2DqhR9w5HSpCZAg',
input_message_content: {
message_text: `White (top): ?
Black (bottom): ${user.first_name}
Waiting for a white side`,
},
...board(status.board.squares, false, [{
text: 'Join the game',
callback_data: `join::b::${user.id}`,
}]),
},
], {
is_personal: true,
cache_time: 0,
})
}
+2
View File
@@ -4,10 +4,12 @@ const emodji = require('./emodji')
module.exports = {
debug,
emodji,
unescapeUser: (user) => Object.keys(user).reduce((acc, key) => {
acc[key] = typeof user[key] === 'string' ? unescape(user[key]) : user[key]
return acc
}, {}),
escapeUser: (user) => Object.keys(user).reduce((acc, key) => {
acc[key] = typeof user[key] === 'string' ? escape(user[key]) : user[key]
return acc
+19 -35
View File
@@ -1,14 +1,20 @@
require('dotenv').load()
require('dotenv').config()
require('module-alias/register')
const knex = require('knex')
const Telegraf = require('telegraf')
const Stage = require('telegraf/stage')
const { debug, unescapeUser } = require('@/helpers')
const { gameScene } = require('@/scenes')
const knexConfig = require('@/../knexfile')
const { inlineHandler, loadHandler, joinHandler, newHandler } = require('@/handlers')
const {
// newHandler,
// joinHandler,
// loadHandler,
inlineJoinHandler,
inlineMoveHandler,
inlineQueryHandler,
} = require('@/handlers')
const { session } = Telegraf
const {
@@ -21,40 +27,18 @@ const bot = new Telegraf(BOT_TOKEN, { username: BOT_NAME })
bot.context.db = knex(knexConfig)
bot.use(session())
bot.use(session({
getSessionKey: (ctx) => (ctx.update.callback_query && ctx.update.callback_query.inline_message_id) ||
(ctx.from && ctx.chat && `${ctx.from.id}:${ctx.chat.id}`),
}))
bot.use(stage.middleware())
bot.on(...inlineHandler())
bot.start(...loadHandler())
bot.action(...newHandler())
bot.action(...joinHandler())
// bot.start(...loadHandler())
// bot.action(...newHandler())
// bot.action(...joinHandler())
bot.action(/^join::(\w)::(\d+)/, async (ctx) => {
debug(ctx.update)
const userId = Number(ctx.match[2])
const iAmWhite = () => ctx.match[1] !== 'w'
if (ctx.from.id === userId) {
return ctx.answerCbQuery('You can\'t join yourself!')
}
const enemy = await ctx.db('users').where('id', userId).first()
await ctx.db('games').returning('id').insert({
user_w: !iAmWhite() ? enemy.id : ctx.from.id,
user_b: !iAmWhite() ? ctx.from.id : enemy.id,
inline_id: ctx.update.callback_query.inline_message_id,
}).catch(debug)
await ctx.editMessageText(
!iAmWhite()
? `Black (top): ${ctx.from.first_name}
White (bottom): ${unescapeUser(enemy).first_name}`
: `Black (top): ${unescapeUser(enemy).first_name}
White (bottom): ${ctx.from.first_name}`
)
return ctx.answerCbQuery()
})
bot.action(...inlineJoinHandler())
bot.action(...inlineMoveHandler())
bot.on('inline_query', inlineQueryHandler())
bot.startPolling()
+11 -7
View File
@@ -6,7 +6,8 @@ const { Markup } = Telegraf
module.exports = (board, isWhite, actions) => {
const horizontal = 'abcdefgh'.split('')
const vertical = Array.from({ length: 8 }).map((item, idx) => idx + 1).reverse()
const vertical = Array.from({ length: 8 })
.map((item, idx) => idx + 1).reverse()
const boardMarkup = vertical.map((row) => horizontal.map((col) => {
const square = board
@@ -29,10 +30,13 @@ module.exports = (board, isWhite, actions) => {
: { text: unescape('%u0020'), callback_data: `${col}${row}` }
}))
return Markup.inlineKeyboard([
...(isWhite
? boardMarkup
: boardMarkup.map((row) => row.reverse()).reverse()),
actions,
]).extra()
const keyboard = isWhite
? boardMarkup
: boardMarkup.map((row) => row.reverse()).reverse()
if (actions) {
keyboard.push(actions)
}
return Markup.inlineKeyboard(keyboard).extra()
}