initial code in state marchote

This commit is contained in:
mose 2019-09-12 17:02:55 +08:00
commit efcf4e2bee
15 changed files with 3166 additions and 0 deletions

10
.env.example Normal file
View file

@ -0,0 +1,10 @@
BOT_LOG_LEVEL=debug
BOT_NAME=brocket
BOT_SHELL_USER_NAME=user
BOT_SHELL_ROOM_NAME=shell
ROCKETCHAT_URL=http://localhost:3000
ROCKETCHAT_USER=brocket
ROCKETCHAT_PASSWORD=pass
RESPOND_TO_DM=true
RESPOND_TO_EDITED=true
LISTEN_ON_ALL_PUBLIC=false

64
.gitignore vendored Normal file
View file

@ -0,0 +1,64 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# npm lockfile (using yarn)
package-lock.json

29
Dockerfile Normal file
View file

@ -0,0 +1,29 @@
FROM node:8.11.2-alpine
LABEL maintainer="Rocket.Chat Team <buildmaster@rocket.chat>"
ENV npm_config_loglevel=error
ENV BOT_OWNER "No owner specified"
ENV BOT_DESC "bBot with the Rocket.Chat adapter"
USER root
COPY bin/bbot /home/bbot/bin/
COPY package.json /home/bbot/
COPY index.js /home/bbot/
COPY src/* /home/bbot/src/
RUN apk add --update --no-cache \
git && \
adduser -S bbot && \
addgroup -S bbot && \
touch ~/.bashrc && \
npm install --global npm@latest && \
chown -R bbot:bbot /home/bbot/
WORKDIR /home/bbot/
USER bbot
RUN npm install --no-audit
CMD ["/bin/ash", "/home/bbot/bin/bbot"]

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Amazebot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

50
README.md Normal file
View file

@ -0,0 +1,50 @@
[create-user]: https://rocket.chat/docs/bots/creating-bot-users/
[configure-bot]: https://rocket.chat/docs/bots/configure-bot-environment/
![bRocket | A bBot boilerplate for Rocket.Chat bots](https://github.com/Amazebot/bbot-rocketchat-boilerplate/raw/master/img/banner.png)
## Setup
## 1. 🍴 Fork or clone this repo
- `git clone amazebot/bbot-rocketchat-boilerplate MY_BOT`
- to clone without git history, add `--depth 1` flag
- or once cloned, start a fresh history `rm -rf .git && git init`
## 2. 💻 Setup your project
- `npm install` get dependencies
- `npm run setup` add your details
## 3. ✨ Test in shell
- `npm start -- -m shell`
## 4. 👨‍💻 Start coding
- customise **index.js**
- look at **examples.js**
## 5. 💬 Run in Rocket.Chat
- create user with bot role
- set login credentials in .env
- `npm start` (`rocketchat` is default adapter)
___
You'll need a Rocket.Chat instance to test. See Rocket.Chat's docs on
[Creating Bot Users][create-user] before you begin.
Easy deployment options coming soon.
See [bbot.chat](http://bbot.chat) for get started guides.
## Configure
All **bBot** settings require the `BOT_` prefix on environment variables.
See Rocket.Chat's docs on [Configuring Bot Environments][configure-bot] for
settings specific to the SDK.
Configs can be given from command line. Try `node index.js -h` for options.
They can also be set in **package.json** under the `"bot"` attribute. You should
review all the package details and customise it to your own project details.
## Development
You can run and interact with the bot directly in shell, for quick development.
Run `node index.js -m shell` to override Rocket.Chat as the message adapter.

148
bin/bbot Normal file
View file

@ -0,0 +1,148 @@
#!/bin/bash
# Stop NPM from complaining about useless stuff.
export npm_config_loglevel=error
# Listen for SIGINT/SIGTERM so the container can be killed with CTRL+C
# Also prevents reboot bug where the container freezes for 30-60 seconds
asyncRun() {
"$@" &
pid="$!"
trap "echo 'Stopping PID $pid'; kill -SIGTERM $pid" SIGINT SIGTERM
# Signal emitted while waiting will make the wait command return code > 128
# Wrap it in a loop that doesn't end before the process is indeed stopped
while kill -0 $pid >/dev/null 2>&1; do
wait
done
}
cat <<EOF
888 888888b. 888
888 888 "88b 888
888 888 .88P 888
88888b. 8888888K. .d88b. 888888
888 "88b 888 "Y88b d88""88b 888
888 888 888 888 888 888 888
888 d88P 888 d88P Y88..88P Y88b.
88888P" 8888888P" "Y88P" "Y888
8888888b. 888 888 .d8888b. 888 888
888 Y88b 888 888 d88P Y88b 888 888
888 888 888 888 888 888 888 888
888 d88P .d88b. .d8888b 888 888 .d88b. 888888 888 88888b. 8888b. 888888
8888888P" d88""88b d88P" 888 .88P d8P Y8b 888 888 888 "88b "88b 888
888 T88b 888 888 888 888888K 88888888 888 888 888 888 888 .d888888 888
888 T88b Y88..88P Y88b. 888 "88b Y8b. Y88b. d8b Y88b d88P 888 888 888 888 Y88b.
888 T88b "Y88P" "Y8888P 888 888 "Y8888 "Y888 Y8P "Y8888P" 888 888 "Y888888 "Y888
EOF
echo "Your bBot Rocket.Chat Docker container is now starting. Please wait..."
# Check if the Rocket.Chat URL has been set
if [[ -z "${ROCKETCHAT_URL}" ]]; then
echo "-------------"
echo "----ERROR----"
echo "-------------"
echo "ROCKETCHAT_URL environment variable has not been set."
echo "Set this to your Rocket.Chat Server URL. e.g."
echo "ROCKETCHAT_URL=https://bots.rocket.chat"
echo "-------------"
echo "Exiting...."
exit 1
fi
# Check if the Rocket.Chat User has been set
if [[ -z "${ROCKETCHAT_USER}" ]]; then
echo "-------------"
echo "----ERROR----"
echo "-------------"
echo "ROCKETCHAT_USER environment variable has not been set."
echo "Set this to the bot account username on your Rocket.Chat server. e.g."
echo "ROCKETCHAT_USER=brocket"
echo "-------------"
echo "Exiting...."
exit 1
fi
# Check if the Rocket.Chat password has been set
if [[ -z "${ROCKETCHAT_PASSWORD}" ]]; then
echo "-------------"
echo "----ERROR----"
echo "-------------"
echo "The ROCKETCHAT_PASSWORD Environment Variable has not been set."
echo "Set this to the bot account password on your Rocket.Chat server. e.g."
echo "ROCKETCHAT_PASSWORD=supersecret"
echo "-------------"
echo "Exiting...."
exit 1
fi
# Check there's either rooms defined, or the bot is listening to all
if [[ -z "${ROCKETCHAT_ROOM}" ]]; then
if [[ -z "${LISTEN_ON_ALL_PUBLIC}" ]]; then
echo "-------------"
echo "----ERROR----"
echo "-------------"
echo "Either ROCKETCHAT_ROOM or LISTEN_ON_ALL_PUBLIC need to be defined."
echo "Without rooms to join, if it's not listening on all public rooms,"
echo "the bot won't receive any messages to respond to. e.g."
echo "ROCKETCHAT_ROOM=general,my-private-room"
echo "or"
echo "LISTEN_ON_ALL_PUBLIC=true"
echo "-------------"
echo "Exiting...."
exit 1
fi
fi
# Install any required deps.
cd /home/bbot/
# Allow using custom registry for node modules
if [[ -z "${NPM_REGISTRY}" ]]; then
echo "INFO: The NPM_REGISTRY environment variable has not been set."
echo "INFO: Using npmjs as the default."
else
echo "INFO: The NPM_REGISTRY environment variable is $NPM_REGISTRY."
echo "INFO: NPM will use this registry to pull packages from."
npm set registry $NPM_REGISTRY
fi
# This happens here as well as during the container build process.
# This is insurance that no dependencies were missed.
# If node_modules externally mounted, insures the base deps are there.
echo "INFO: Attempting to install this containers dependancies"
npm install --no-audit
# Report information about optional behviour environment variables
if [[ -z "${RESPOND_TO_DM}" ]]; then
echo "-------------"
echo "INFO: The RESPOND_TO_DM environment variable has not been set."
echo "INFO: Set RESPOND_TO_DM=true to respond to direct messages."
echo "Default: RESPOND_TO_DM=false"
echo "-------------"
fi
if [[ -z "${RESPOND_TO_EDITED}" ]]; then
echo "-------------"
echo "INFO: The RESPOND_TO_EDITED environment varialbe is not set."
echo "INFO: Set RESPOND_TO_EDITED=true to respond to messages after edits."
echo "Default: RESPOND_TO_EDITED=false"
echo "-------------"
fi
if [[ -z "${RESPOND_TO_LIVECHAT}" ]]; then
echo "-------------"
echo "INFO: The RESPOND_TO_LIVECHAT environment varialbe is not set."
echo "INFO: Set RESPOND_TO_LIVECHAT=true to respond to livechat messages."
echo "Default: RESPOND_TO_LIVECHAT=false"
echo "-------------"
fi
set -e
export PATH="node_modules/.bin:node_modules/bbot/node_modules/.bin:$PATH"
# Start bBot using the asyncRun function
asyncRun node index.js "$@"

4
bin/bbot.cmd Normal file
View file

@ -0,0 +1,4 @@
@echo off
call npm install
SETLOCAL
node ../index.js %*

BIN
img/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
img/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

14
index.js Normal file
View file

@ -0,0 +1,14 @@
const bot = require('bbot')
/** This is a workaround for Heroku to confirm web binding. */
// @todo Remove this when bBot includes Express (next release)
const http = require('http')
const handle = (req, res) => res.end('hit')
const server = http.createServer(handle)
server.listen(process.env.PORT || 5000)
/** Add your bot logic here. Removing the imported examples. */
// require('./src/examples')
require('./src/reactions')
bot.start() // 🚀

76
init.js Normal file
View file

@ -0,0 +1,76 @@
const fs = require('fs')
const cpm = require('child_process')
const { promisify } = require('util')
const inquirer = require('./node_modules/inquirer')
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const copyFile = promisify(fs.copyFile)
const exec = promisify(cpm.exec)
async function replaceInFile (file, regex, value) {
const content = await readFile(file, 'utf8')
const update = content.replace(regex, value)
await writeFile(file, update, 'utf8')
}
inquirer.prompt([
{
type: 'input',
name: 'package',
message: 'Name your node package',
default: 'bbot-bot',
validate: (name) => (new RegExp('^[a-z@/-]*$').test(name))
? true
: 'Invalid name for npm package.'
},
{
type: 'input',
name: 'bot',
message: 'Name your new bot',
default: 'bot'
},
{
type: 'input',
name: 'author',
message: 'What is your name?'
},
{
type: 'input',
name: 'email',
message: 'Email (for package author field)'
},
{
type: 'confirm',
name: 'env',
message: 'Create .env with defaults?'
}
]).then(async (answers) => {
if (answers.package) {
await replaceInFile(
'./package.json',
/^(\s\s"name":\s")(.*?)(")/gm,
`$1${answers.package}$3`
)
}
if (answers.bot) {
await replaceInFile(
'./package.json',
/^(\s{2}"bot": {\n\s{4}"name": ")(.*?)(")/gm,
`$1${answers.bot}$3`
)
}
if (answers.author || answers.email) {
let author = answers.author ? answers.author : ''
if (answers.email) author += `<${answers.email}>`
await replaceInFile(
'./package.json',
/^(\s\s"author":\s")(.*?)(")/gm,
`$1${author}$3`
)
}
if (answers.env) {
await copyFile('.env.example', '.env')
await replaceInFile('.env', /brocket/g, answers.bot)
await exec('open .env')
}
})

46
package.json Normal file
View file

@ -0,0 +1,46 @@
{
"name": "bbot-bot",
"version": "1.0.0",
"description": "bRocket is a bBot boilerplate for building Rocket.Chat bots",
"main": "index.js",
"files": [
"index.js",
"src"
],
"repository": "git@github.com:Amazebot/bbot-rocketchat-boilerplate.git",
"author": "mose<mose@mose.com>",
"contributors": [],
"license": "MIT",
"private": false,
"engines": {
"node": "> 8.0.0",
"npm": "> 5.0.0"
},
"keywords": [
"bBot",
"Rocket.Chat",
"rocketchat",
"chatbot",
"chat",
"messaging",
"conversation",
"CUI"
],
"bot": {
"name": "bot",
"message-adapter": "rocketchat",
"avatar": "https://crapaud-fou.org/images/coaaaa.png"
},
"dependencies": {
"bbot": "^1.3.0"
},
"devDependencies": {
"nodemon": "^1.18.3"
},
"scripts": {
"setup": "node init.js",
"start": "node index.js",
"watch": "nodemon index.js",
"debug": "nodemon --inspect index.js"
}
}

201
src/examples.js Normal file
View file

@ -0,0 +1,201 @@
const bot = require('bbot')
/**
* All branch examples start from the "global" conversation scope.
* We are working on features to provide branching within a conversational
* context, but as those methods are changing in the beta, they aren't included
* as examples to follow just yet.
*/
/**
* Branch types are declared from their scope, see all available types here:
* http://bbot.chat/docs/path#builtforbranching
*
* `text` branches take the following arguments:
* - matcher: expression, semantic conditions object, or array of conditions
* - callback: to fire on successful match, given the state object (b)
* - [options]: object with `id` (string) and/or `force` (boolean) attributes
*
* Test with "Hello bots!"
*/
bot.global.text(/(hi|hello).*bots?/, (b) => b.respond('Hello :wave:'), {
id: 'hello-bots'
})
/**
* `direct` branch type requires the bot to be explicitly addressed.
*
* `reply` instead of `respond` prepends messages with user's name.
*
* In Rocket.Chat all messages to a bot in a direct room have the name prepended
* by the Rocket.Chat SDK before it's processed by the bot framework.
*
* Test with "@brocket Hello" or just "Hello" in a DM.
*/
bot.global.direct(/hi|hello/i, (b) => b.reply('Hey there.'), {
id: 'hello-direct'
})
/**
* `respondVia` allows using custom platform methods to dispatch response.
*
* Matcher conditions allow semantic attributes with a string or array of
* optional values to match against, possibly capturing content.
* Accepted atts: is, starts, ends, contains, excludes, after, before, range
*
* Test with "Hello anyone?"
*/
bot.global.text({
contains: ['hi', 'hello']
}, (b) => b.respondVia('react', ':wave:'), {
id: 'hello-react'
})
/**
* Branch callbacks allow asynchronous responding, if they return a promise.
* State (b) includes branch matching attributes, see bbot.chat/docs/thought.
*
* Test with "@brocket ping back in 5 seconds"
*/
bot.global.direct(/ping back in (\d*)/i, async (b) => {
const ms = parseInt(b.match[1]) * 1000
await new Promise((resolve) => setTimeout(resolve, ms))
return b.respond('Ping :ping_pong:')
}, {
id: 'ping-delay'
})
/**
* The `respond` method can accept attachment objects as well as strings.
* Rendering support depends on the message platform and adapter. In shell,
* it will display the fallback text.
*
* Test with "bot attach image"
*/
bot.global.text(/attach image/i, (b) => {
return b.respond({
fallback: `See: https://www.wikiwand.com/en/Three_Laws_of_Robotics`,
image: `https://upload.wikimedia.org/wikipedia/en/8/8e/I_Robot_-_Runaround.jpg`,
title: {
text: `Asimov's Three Laws of Robotics`,
link: `https://www.wikiwand.com/en/Three_Laws_of_Robotics`
}
})
}, { id: 'attach-image' })
/**
* The `envelope` provides helpers for adding rich-message payloads before
* responding. Preparing envelopes before dispatch also allows changing the
* user/room the envelope is addressed to or dispatching multiple envelopes.
*
* Test with "I want a prize"
*/
bot.global.text({
contains: 'prize'
}, (b) => {
b.envelope.write('Choose your fate! 🚪... 🎁 ')
b.envelope.attach({ color: '#f4426e' })
b.envelope.payload
.quickReply({ text: 'Door number 1' })
.quickReply({ text: 'Door number 2' })
.quickReply({ text: 'Door number 3' })
return b.respond().catch((err) => console.error(err))
}, { id: 'door-prize-intro' })
/**
* The `conditions` attribute contains results of semantic condition matching
* and capture groups. Each condition can be given a key for easy reference.
*
* Test with "what's behind door number 2"
*/
bot.global.text({
door: { after: 'door', range: '1-3' }
}, (b) => {
switch (b.conditions.captured.door) {
case '1': return b.respond(`You win nothing 💔`)
case '2': return b.respond(`You win a monkey 🐒`)
case '3': return b.respond(`It's a new car!! 🚗`)
}
}, { id: 'door-prize-award' })
/**
* Branch callbacks can be async functions, to awaiting one or more processes
* before responding. This example uses API requests to fill a dynamic array
* of actions, using the url property to provide link action buttons.
*
* Test with "@brocket plan meeting" in a public or private room.
*/
bot.global.direct({
is: 'plan meeting'
}, async (b) => {
if (bot.adapters.message.name !== 'rocketchat-message-adapter') return
b.envelope.write('Please review time zones in the room...')
const { id, type } = b.message.user.room
let room
const q = { roomId: id }
if (type === 'c') {
room = await bot.adapters.message.api.get('channels.members', q, true)
} else if (type === 'p') {
room = await bot.adapters.message.api.get('groups.members', q, true)
} else {
return b.respond('Sorry, that only works in channels and private groups.')
}
const offsets = room.members
.map((member) => member.utcOffset || undefined)
.filter((offset) => !!offset)
for (let utc of offsets) {
b.envelope.payload.quickReply({
text: `🌐 UTC ${utc}`,
url: `https://www.timeanddate.com/worldclock/timezone/utc${utc}`
})
}
b.respond()
})
/**
* @todo This example requires PR #11811 to be merged. Room names are undefined.
*/
bot.global.text(/where am i/i, (b) => {
const { name, type } = b.message.user.room
switch (type) {
case 'c': return b.respond(`You're in the #${name} public channel.`)
case 'p': return b.respond(`You're in a private group called **${name}**.`)
case 'l': return b.respond(`You're in a livechat channel.`)
case 'd': return b.respond(`You're in a DM with me :hugging:`)
}
}, {
id: 'location-check'
})
/**
* Custom options can be added to the bot, with the full config utility of bBot,
* allowing them to be defined as environment variables, command line args or
* package.json attributes. Extend settings with a yargs option format.
*
* Try any of the following:
* - `node index.js --avatar <YOUR_AVATAR_URL>`
* - BOT_AVATAR=<YOUR_AVATAR_URL> in .env
* - `{ "bot": { "avatar": "<YOUR_AVATAR_URL>" } }` in package.json
*/
bot.settings.extend({
avatar: {
'type': 'string',
'description': 'Set a custom avatar for your bot account profile'
}
})
/**
* The bot can access lower level methods of the Rocket.Chat SDK through the
* message adapter, once it's connected. This example sets an avatar on login.
*
* Try replacing the avatar configured in package.json with your own.
*/
bot.events.on('started', () => {
if (bot.adapters.message.name !== 'rocketchat-message-adapter') return
if (bot.settings.get('avatar')) {
bot.logger.info('Setting bot avatar')
bot.adapters.message.api.post('users.setAvatar', {
avatarUrl: bot.settings.get('avatar')
})
}
})

14
src/reactions.js Normal file
View file

@ -0,0 +1,14 @@
const bot = require('bbot');
bot.global.text({
contains: ['facebook', 'google', 'amazon', 'apple', 'microsoft']
}, (b) => {
b.respondVia('react', ':hear_no_evil:');
}, {
id: 'gafam-react'
});
bot.global.text(/pe?tit? globe/i, (b) => b.respond('![pti globe](https://eye.mose.fr/2019-07-31-21-50_grab.png)'), {
id: 'ptiglobe-direct'
})

2489
yarn.lock Normal file

File diff suppressed because it is too large Load diff