Keybase bot-scripting for Node.js - now written all in TypeScript! Send encrypted data all over this world.
This module is a side-project/work in progress and may change or have crashes, but feel free to play with it. As long as you have a Keybase account, you can use this module to script basic Keybase commands such as sending and reading messages and attachments, managing teams, and even sending Lumens on the Stellar network.
- Install Node.js 8 or above.
- Make sure that you have Keybase installed and running.
- Install
keybase-bot
.npm install keybase-bot # or yarn add keybase-bot
You're ready to make your first Keybase bot!
Let's make a bot that says hello to the Keybase user kbot.
We recommend either creating a dedicated Keybase account for the bot, or if you decide to reuse your own account at the very least create a dedicated paperkey so you can revoke it later if the machines rise up.
const Bot = require('keybase-bot')
const bot = new Bot()
const username = 'your username'
const paperkey = 'your paperkey'
bot
.init(username, paperkey, {verbose: false})
.then(() => {
console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`)
const channel = {name: 'kbot,' + bot.myInfo().username, public: false, topicType: 'chat'}
const message = {
body: `Hello kbot! This is ${bot.myInfo().username} saying hello from my device ${bot.myInfo().devicename}`,
}
bot.chat
.send(channel, message)
.then(() => {
console.log('Message sent!')
bot.deinit()
})
.catch(error => {
console.error(error)
bot.deinit()
})
})
.catch(error => {
console.error(error)
bot.deinit()
})
To run the above bot, you want to save that code into a file and run it with node:
node <my-awesome-file-name>.js
This code is also in demos/hello-world.js
, if you want to take a look in there. There are also some other cool bots in the demos directory, including a bot that tells you how many unread messages you have and a bot that does math for you and your friends. You can write a bot in any language that can compile to JavaScript and run on Node.js. We have some ES7+ (with async/await
) demos in demos/ES7
and even a bot in Iced CoffeeScript! (in demos/iced
)
All the source of this library is now written in TypeScript. If you're working on the library, please use yarn
to install the necessary modules, and then run yarn build
to build the JavaScript library files. Finally, make a test config file in __tests__/
(look at __tests__/test.config.ts
as an example) and run yarn test
. If everything passes, you haven't broken everything horribly.
- Bot
- Bot Types
- Chat
- Chat Types
- Wallet
- Wallet Types
- Team
A Keybase bot.
Initialize your bot by starting an instance of the Keybase service and logging in using oneshot mode.
username
string The username of your bot's Keybase account.paperkey
string The paperkey of your bot's Keybase account.options
InitOptions The initialization options for your bot.
bot.init('username', 'paperkey')
Returns Promise<void>
Initialize your bot by using an existing running service with a logged in user.
homeDir
string The home directory of this currently running service. Leave blank to use the default homeDir for your system.options
InitOptions The initialization options for your bot.
bot.initFromRunningService()
Returns Promise<void>
Get info about your bot!
const info = bot.myInfo()
Returns BotInfo? – Useful information like the username, device, and home directory of your bot. If your bot isn't initialized, you'll get null
.
Deinitializes the bot by logging out, stopping the keybase service, and removing any leftover login files made by the bot. This should be run before your bot ends.
bot.deinit()
Returns Promise<void>
A collection of types used by the bot.
Options for initializing the bot.
Type: {verbose: boolean?, botLite: boolean?, disableTyping: boolean?, autoLogSendOnCrash: boolean?}
Useful information like the username, device, home directory of your bot and configuration options.
Type: {username: string, devicename: string, homeDir: string?, botLite: boolean?, disableTyping: boolean?}
Extends ClientBase
The chat module of your Keybase bot. For more info about the API this module uses, you may want to check out keybase chat api
.
Joins a team conversation.
channel
ChatChannel The team chat channel to join.
bot.chat.listConvsOnName('team_name').then(async teamConversations => {
for (const conversation of teamConversations) {
if (conversation.memberStatus !== 'active') {
await bot.chat.join(conversation.channel)
console.log('Joined team channel', conversation.channel)
}
}
})
Returns Promise<void>
Leaves a team conversation.
channel
ChatChannel The team chat channel to leave.
bot.chat.listConvsOnName('team_name').then(async teamConversations => {
for (const conversation of teamConversations) {
if (conversation.memberStatus === 'active') {
await bot.chat.leave(conversation.channel)
console.log('Left team channel', conversation.channel)
}
}
})
Returns Promise<void>
Gets current unfurling settings
bot.chat.getUnfurlSettings().then(mode => console.log(mode))
Returns Promise<UnfurlMode>
Sets the unfurling mode In Keybase, unfurling means generating previews for links that you're sending in chat messages. If the mode is set to always or the domain in the URL is present on the whitelist, the Keybase service will automatically send a preview to the message recipient in a background chat channel.
mode
UnfurlMode the new unfurl mode
bot.chat
.setUnfurlMode({
mode: 'always',
})
.then(mode => console.log('mode updated!'))
Returns Promise<void>
Loads a flip's details
conversationID
string conversation ID received in API listen.flipConversationID
string flipConvID from the message summary.messageID
number ID of the message in the conversation.gameID
string gameID from the flip message contents.
// check demos/es7/poker-hands.js
Returns Promise<FlipSummary>
Lists your chats, with info on which ones have unread messages.
options
ChatListOptions An object of options that can be passed to the method.
bot.chat.list({unreadOnly: true}).then(chatConversations => console.log(chatConversations))
Returns Promise<Array<ChatConversation>> An array of chat conversations. If there are no conversations, the array is empty.
Lists conversation channels in a team
name
string Name of the teamoptions
ChatListChannelsOptions An object of options that can be passed to the method.
bot.chat.listChannels('team_name').then(chatConversations => console.log(chatConversations))
Returns Promise<Array<ChatConversation>> An array of chat conversations. If there are no conversations, the array is empty.
Reads the messages in a channel. You can read with or without marking as read.
channel
ChatChannel The chat channel to read messages in.options
ChatReadOptions An object of options that can be passed to the method.
alice.chat.read(channel).then(messages => console.log(messages))
Returns Promise<ReadResult> A summary of data about a message, including who send it, when, the content of the message, etc. If there are no messages in your channel, then an error is thrown.
Send a message to a certain channel.
channel
ChatChannel The chat channel to send the message in.message
ChatMessage The chat message to send.options
ChatSendOptions An object of options that can be passed to the method.
const channel = {name: 'kbot,' + bot.myInfo().username, public: false, topicType: 'chat'}
const message = {body: 'Hello kbot!'}
bot.chat.send(channel, message).then(() => console.log('message sent!'))
Returns Promise<SendResult>
Creates a new blank conversation.
channel
ChatChannel The chat channel to create.
bot.chat.createChannel(channel).then(() => console.log('conversation created'))
Returns Promise<void>
Send a file to a channel.
channel
ChatChannel The chat channel to send the message in.filename
string The absolute path of the file to send.options
ChatAttachOptions An object of options that can be passed to the method.
bot.chat.attach(channel, '/Users/nathan/my_picture.png').then(() => console.log('Sent a picture!'))
Returns Promise<SendResult>
Download a file send via Keybase chat.
channel
ChatChannel The chat channel that the desired attacment to download is in.messageId
number The message id of the attached file.output
string The absolute path of where the file should be downloaded to.options
ChatDownloadOptions An object of options that can be passed to the method
bot.chat.download(channel, 325, '/Users/nathan/Downloads/file.png')
Reacts to a given message in a channel. Messages have messageId's associated with
them, which you can learn in bot.chat.read
.
channel
ChatChannel The chat channel to send the message in.messageId
number The id of the message to react to.reaction
string The reaction emoji, in colon form.options
ChatReactOptions An object of options that can be passed to the method.
bot.chat.react(channel, 314, ':+1:').then(() => console.log('Thumbs up!'))
Returns Promise<SendResult>
Deletes a message in a channel. Messages have messageId's associated with
them, which you can learn in bot.chat.read
. Known bug: the GUI has a cache,
and deleting from the CLI may not become apparent immediately.
channel
ChatChannel The chat channel to send the message in.messageId
number The id of the message to delete.options
ChatDeleteOptions An object of options that can be passed to the method.
bot.chat.delete(channel, 314).then(() => console.log('message deleted!'))
Returns Promise<void>
Listens for new chat messages on a specified channel. The onMessage
function is called for every message your bot receives. This is pretty similar to watchAllChannelsForNewMessages
, except it specifically checks one channel. Note that it receives messages your own bot posts, but from other devices. You can filter out your own messages by looking at a message's sender object.
Hides exploding messages by default.
channel
ChatChannel The chat channel to watch.onMessage
OnMessage A callback that is triggered on every message your bot receives.onError
OnError A callback that is triggered on any error that occurs while the method is executing.options
ListenOptions Options for the listen method.
// Reply to all messages between you and `kbot` with 'thanks!'
const channel = {name: 'kbot,' + bot.myInfo().username, public: false, topicType: 'chat'}
const onMessage = message => {
const channel = message.channel
bot.chat.send(channel, {body: 'thanks!!!'})
}
bot.chat.watchChannelForNewMessages(channel, onMessage)
Returns Promise<void>
This function will put your bot into full-read mode, where it reads everything it can and every new message it finds it will pass to you, so you can do what you want with it. For example, if you want to write a Keybase bot that talks shit at anyone who dares approach it, this is the function to use. Note that it receives messages your own bot posts, but from other devices. You can filter out your own messages by looking at a message's sender object. Hides exploding messages by default.
onMessage
OnMessage A callback that is triggered on every message your bot receives.onError
OnError A callback that is triggered on any error that occurs while the method is executing.options
ListenOptions Options for the listen method.
// Reply to incoming traffic on all channels with 'thanks!'
const onMessage = message => {
const channel = message.channel
bot.chat.send(channel, {body: 'thanks!!!'})
}
bot.chat.watchAllChannelsForNewMessages(onMessage)
Returns Promise<void>
A collection of types used by the Chat module.
A Keybase chat channel. This can be a channel in a team, or just an informal channel between two users. name: the name of the team or comma-separated list of participants
Type: {name: string, public: boolean, membersType: MembersType, topicType: TopicType?, topicName: string?}
A chat message. The content goes in the body
property!
Type: {body: string}
body
string
A chat conversation. This is essentially a chat channel plus some additional metadata.
Type: {id: string, channel: ChatChannel, unread: boolean, activeAt: number, activeAtMs: number, memberStatus: string}
Options for the list
method of the chat module.
Type: {failOffline: boolean?, showErrors: boolean?, topicType: TopicType?, unreadOnly: boolean?}
Options for the listChannels
method of the chat module.
Type: {topicType: TopicType?, membersType: MembersType?}
topicType
TopicType?membersType
MembersType?
Options for the read
method of the chat module.
Type: {conversationId: string?, failOffline: boolean?, pagination: Pagination?, peek: boolean?, unreadOnly: boolean?}
conversationId
string?failOffline
boolean?pagination
Pagination?peek
boolean?unreadOnly
boolean?
Options for the send
method of the chat module.
Type: {conversationId: string?, nonblock: boolean?, membersType: MembersType, confirmLumenSend: boolean?}
Options for the react
method of the chat module.
Type: {conversationId: string?}
conversationId
string?
Options for the attach
method of the chat module.
Type: {conversationId: string?, title: string?, preview: string?}
Options for the download
method of the chat module.
Type: {conversationId: string?, preview: string?, noStream: boolean?}
Options for the delete
method of the chat module.
Type: {conversationId: string?}
conversationId
string?
Options for the methods in the chat module that listen for new messages. Local messages are ones sent by your device. Including them in the output is useful for applications such as logging conversations, monitoring own flips and building tools that seamlessly integrate with a running client used by the user.
Type: {hideExploding: boolean, showLocal: boolean}
A function to call when a message is received.
Type: function (message: MessageSummary): (void | Promise<void>)
A function to call when an error occurs.
Type: function (error: Error): (void | Promise<void>)
Extends ClientBase
The wallet module of your Keybase bot. For more info about the API this module uses, you may want to check out keybase wallet api
.
Provides a list of all accounts owned by the current Keybase user.
bot.wallet.balances().then(accounts => console.log(accounts))
Returns Promise<Array<Account>> An array of accounts. If there are no accounts, the array is empty.
Provides a list of all transactions in a single account.
accountId
string The id of an account owned by a Keybase user.
bot.wallet.history('GDUKZH6Q3U5WQD4PDGZXYLJE3P76BDRDWPSALN4OUFEESI2QL5UZHCK').then(transactions => console.log(transactions))
Returns Promise<Array<Transaction>> An array of transactions related to the account.
Get details about a particular transaction
transactionId
string The id of the transaction you would like details about.
bot.wallet.details('e5334601b9dc2a24e031ffeec2fce37bb6a8b4b51fc711d16dec04d3e64976c4').then(details => console.log(details))
Returns Promise<Transaction> An object of details about the transaction specified.
Lookup the primary Stellar account ID of a Keybase user.
name
string The name of the user you want to lookup. This can be either a Keybase username or a username of another account that is supported by Keybase if it is followed by an '@'.
const lookup1 = bot.wallet.lookup('patrick')
// 'patrick' on Keybase is 'patrickxb' on twitter
const lookup2 = bot.wallet.lookup('patrcikxb@twitter')
// Using Lodash's `isEqual` since objects with same values aren't equal in JavaScript
_.isEqual(lookup1, lookup2) // => true
Returns Promise<{accountID: string, username: string}> An object containing the account ID and Keybase username of the found user.
Send lumens (XLM) via Keybase with your bot!
recipient
string Who you're sending your money to. This can be a Keybase user, stellar address, or a username of another account that is supported by Keybase if it is followed by an '@'.amount
string The amount of XLM to send.currency
string Adds a currency value to the amount specified. For example, adding 'USD' would sendmessage
string The message for your payment
bot.wallet.send('nathunsmitty', '3.50') // Send 3.50 XLM to Keybase user `nathunsmitty`
bot.wallet.send('nathunsmitty@github', '3.50') // Send 3.50 XLM to GitHub user `nathunsmitty`
bot.wallet.send('nathunsmitty', '3.50', 'USD') // Send $3.50 worth of lumens to Keybase user `nathunsmitty`
bot.wallet.send('nathunsmitty', '3.50', 'USD', 'Shut up and take my money!') // Send $3.50 worth of lumens to Keybase user `nathunsmitty` with a memo
Returns Promise<Transaction> The trasaction object of the transaction.
Send lumens (XLM) via Keybase to more than one user at once. As opposed to the normal bot.wallet.send command, this can get multiple transactions into the same 5-second Stellar ledger.
batchId
string example, if sending a bunch of batches for an airdrop, you could pass them allairdrop2025
.payments
Array<PaymentBatchItem> an array of objects containing recipients and XLM of the form {"recipient": "someusername", "amount": "1.234", "message", "hi there"}
bot.wallet.batch("airdrop2040",[{"recipient":"a1","amount": "1.414", "message": "hi a1, yes 1"},{"recipient": "a2", "amount": "3.14159", "message": "hi a2, yes 2"},}])
Returns Promise<BatchResult> an object
If you send XLM to a Keybase user who has not established a wallet, you can cancel the payment before the recipient claims it and the XLM will be returned to your account.
transactionId
string The id of the transaction to cancel.
bot.wallet
.cancel('e5334601b9dc2a24e031ffeec2fce37bb6a8b4b51fc711d16dec04d3e64976c4')
.then(() => console.log('Transaction successfully canceled!'))
Returns Promise<void>
A collection of types used by the Wallet module.
An asset.
Type: {type: string, code: string, issuer: string, verifiedDomain: string, issuerName: string}
An exchange rate, which specifies a currency and the rate of exchange between an asset and that currency.
Type: {currency: string, rate: string}
A balance.
Type: {asset: Asset, amount: string, limit: string}
An account, with money inside!
Type: {accountId: string, name: string, isPrimary: boolean, balance: (null | Array<Balance>), exchangeRate: ExchangeRate}
accountId
stringname
stringisPrimary
booleanbalance
(null | Array<Balance>)exchangeRate
ExchangeRate
A transaction, where a user sends money to another user.
Type: {txId: string, time: number, status: PaymentStatus, statusDetail: string, amount: string, asset: Asset, displayAmount: string, displayCurrency: string, fromStellar: string, toStellar: string, fromUsername: string, toUsername: string, note: string, noteErr: string, unread: boolean}
txId
stringtime
numberstatus
PaymentStatusstatusDetail
stringamount
stringasset
AssetdisplayAmount
stringdisplayCurrency
stringfromStellar
stringtoStellar
stringfromUsername
stringtoUsername
stringnote
stringnoteErr
stringunread
boolean
The status of a payment.
Type: ("none"
| "pending"
| "claimable"
| "completed"
| "error"
| "unknown"
| "canceled"
)
A batch send result
Type: {payments: Array<BatchItemResult>, startTime: number, preparedTime: number, allSubmittedTime: number, endTime: number, overallDurationMs: number, prepareDurationMs: number, submitDurationMs: number, waitDurationMs: number, countSuccess: number, countError: number, countPending: number, avgDurationMs: number, avgSuccessDurationMs: number, avgErrorDurationMs: number}
payments
Array<BatchItemResult>startTime
numberpreparedTime
numberallSubmittedTime
numberendTime
numberoverallDurationMs
numberprepareDurationMs
numbersubmitDurationMs
numberwaitDurationMs
numbercountSuccess
numbercountError
numbercountPending
numberavgDurationMs
numberavgSuccessDurationMs
numberavgErrorDurationMs
number
In batch sends, one individual send
Type: {recipient: string, amount: string, message: string?}
Extends ClientBase
The wallet module of your Keybase bot. For more info about the API this module uses, you may want to check out keybase wallet api
.
Add a bunch of people with different privileges to a team
additions
AddMembersParam an array of the users to add, with privs
bot.team
.addMembers({
team: 'phoenix',
emails: [{email: '[email protected]', role: 'writer'}, {email: '[email protected]', role: 'admin'}],
usernames: [{username: 'frank', role: 'reader'}, {username: 'keybaseio@twitter', role: 'writer'}],
})
.then(res => console.log(res))
Returns Promise<AddMembersResult> -
Remove someone from a team
removal
RemoveMemberParam object with theteam
name andusername
bot.team.removeMember({team: 'phoenix', username: 'frank'}).then(res => console.log(res))
Returns Promise<RemoveMemberResult> -
List a team's members
team
ListTeamMembershipsParam an object with theteam
name in it
bot.team.listTeamMemberships({team: 'phoenix'}).then(res => console.log(res))
Returns Promise<ListTeamMembershipsResult> -
Make sure that you have Node, Yarn, and the Keybase application installed. We also use developer tools such as EditorConfig, ESLint, Flow, and Prettier so you'll probably want to make sure that your development is configured to use those tools somewhere in your code writing process.
- Clone this repo.
- Install dependencies with
yarn
. - Build the bot in watch mode with
yarn dev
. - Build the bot for production with
yarn build
. - Build the docs for the bot with
yarn docs
.
That's it. We accept changes via Pull Requests; please make sure that any changes you make build successfully and pass Flow, Prettier, and ESLint checks. We'd also really appreciate it if your PR could follow the Conventional Commit specification. If you're adding a new feature, please add/update tests, demos, documentation, and whatever else makes sense to go with it. If you have any questions about contributing, please feel free to ask a maintainer!
We run tests using Jest. All tests are run against actual Keybase processes that are created and destroyed during testing and ping the actual Keybase server to do things like send messages and XLM. To facilitate this, the tests read a file in __tests__/test.config.ts
that contains usernames, paperkeys, and team names that are used during testing. You'll need three test Keybase accounts, two teams, and some Stellar Lumens to run all tests.
- Copy
__tests__/test.config.example.ts
as__tests__/test.config.ts
. Note that__tests__/test.config.ts
should NOT be version controlled, as it will contain paper keys! - Edit
__tests__/test.config.ts
as it specifies, replacing the placeholder values with actual usernames, paperkeys, and team names. - Run
yarn test
. Everything should pass!
We automatically generate a CHANGELOG and version (using Semantic Versioning) keybase-bot
with standard-version
. To cut a new release:
- Make sure all commits that are to be included in the release are squash-merged into
master
branch. - On your local copy of the bot, checkout
master
and ensure it's up to date withorigin/master
. - Run
standard-version
with the commandyarn release
. - Push the new git tags to
origin
. (git push --follow-tags origin master
) - Publish to npm with
yarn publish
.
BSD-3-Clause