├── .nvmrc ├── .yarnrc ├── __tests__ ├── setup.ts ├── test-utils │ ├── publicPaperkeyLabel.ts │ ├── pollFor.ts │ ├── stopServiceManually.ts │ ├── index.ts │ └── startServiceManually.ts ├── myInfo.test.ts ├── race.conditions.test.ts ├── helpers.test.ts ├── tests.config.example.ts ├── chat.simple.watch.test.ts ├── kvstore.test.ts ├── deinit.test.ts ├── init.test.ts └── team.test.ts ├── .prettierignore ├── .gitattributes ├── lib ├── utils │ ├── keybaseBinaryName.d.ts │ ├── randomTempDir.d.ts │ ├── timeout.d.ts │ ├── safeJSONStringify.d.ts │ ├── rmdirRecursive.d.ts │ ├── options.js │ ├── options.js.map │ ├── whichKeybase.d.ts │ ├── keybaseBinaryName.js.map │ ├── keybaseBinaryName.js │ ├── timeout.js.map │ ├── timeout.js │ ├── safeJSONStringify.js │ ├── options.d.ts │ ├── randomTempDir.js.map │ ├── keybaseExec.d.ts │ ├── safeJSONStringify.js.map │ ├── index.js.map │ ├── pingKeybaseService.js.map │ ├── whichKeybase.js.map │ ├── adminDebugLogger.d.ts │ ├── pingKeybaseService.d.ts │ ├── index.d.ts │ ├── keybaseStatus.js.map │ ├── randomTempDir.js │ ├── keybaseStatus.d.ts │ ├── rmdirRecursive.js.map │ ├── index.js │ ├── formatAPIObject.d.ts │ ├── adminDebugLogger.js.map │ ├── keybaseExec.js.map │ ├── keybaseExec.js │ ├── formatAPIObject.js.map │ ├── whichKeybase.js │ ├── pingKeybaseService.js │ ├── keybaseStatus.js │ ├── rmdirRecursive.js │ └── formatAPIObject.js ├── types │ ├── gregor1 │ │ ├── index.js.map │ │ ├── index.js │ │ └── index.d.ts │ └── stellar1 │ │ ├── index.js.map │ │ └── index.js ├── constants.d.ts ├── constants.js.map ├── constants.js ├── helpers-client │ ├── index.d.ts │ ├── index.js.map │ └── index.js ├── client-base │ ├── index.d.ts │ └── index.js.map ├── service │ ├── index.d.ts │ └── index.js.map ├── team-client │ ├── index.js.map │ └── index.d.ts ├── wallet-client │ ├── index.js.map │ └── index.d.ts ├── kvstore-client │ ├── index.js.map │ └── index.d.ts ├── index.d.ts └── index.js.map ├── .eslintignore ├── .travis.yml ├── .gitignore ├── src ├── utils │ ├── keybaseBinaryName.ts │ ├── safeJSONStringify.ts │ ├── timeout.ts │ ├── randomTempDir.ts │ ├── index.ts │ ├── whichKeybase.ts │ ├── rmdirRecursive.ts │ ├── options.ts │ ├── pingKeybaseService.ts │ ├── keybaseStatus.ts │ ├── adminDebugLogger.ts │ ├── keybaseExec.ts │ ├── formatAPIObject.test.ts │ └── formatAPIObject.ts ├── constants.ts ├── helpers-client │ └── index.ts ├── team-client │ └── index.ts ├── types │ └── gregor1 │ │ └── index.ts └── client-base │ └── index.ts ├── .prettierrc ├── .vscode ├── settings.json └── launch.json ├── .editorconfig ├── jest.config.js ├── tsconfig.demos.json ├── tsconfig.json ├── Makefile ├── demos ├── es7 │ ├── check-for-unreads-es7.js │ ├── hello-world-es7.js │ ├── advertised-echo.js │ ├── team-inviter.js │ ├── math-bot-es7.js │ ├── flippy.js │ ├── poker-hands.js │ └── poker-hands-reverse.js ├── check-for-unreads.js ├── typescript │ ├── hello-world.ts │ └── hunt-for-resets.ts ├── hello-world.js ├── compiled-from-typescript │ ├── hello-world.js │ └── hunt-for-resets.js ├── math-bot.js └── iced │ └── puzzle-bot.iced ├── .eslintrc.js ├── documentation.yml └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.12.0 2 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | save-prefix "" 2 | -------------------------------------------------------------------------------- /__tests__/setup.ts: -------------------------------------------------------------------------------- 1 | jest.setTimeout(60000) 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /index.js.map 3 | flow-typed 4 | /lib/ 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /lib/ linguist-generated=true 2 | /src/types/ linguist-generated=true 3 | -------------------------------------------------------------------------------- /lib/utils/keybaseBinaryName.d.ts: -------------------------------------------------------------------------------- 1 | declare const binaryName: string; 2 | export default binaryName; 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /index.js.map 3 | /lib/ 4 | /src/types/ 5 | /demos/es7 6 | /demos/javascript 7 | -------------------------------------------------------------------------------- /lib/utils/randomTempDir.d.ts: -------------------------------------------------------------------------------- 1 | declare function randomTempDir(): string; 2 | export default randomTempDir; 3 | -------------------------------------------------------------------------------- /lib/utils/timeout.d.ts: -------------------------------------------------------------------------------- 1 | declare function timeout(time: number): Promise; 2 | export default timeout; 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/*' 4 | - 'node' 5 | script: yarn tsc 6 | cache: yarn 7 | -------------------------------------------------------------------------------- /lib/utils/safeJSONStringify.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: (input: object) => string; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /lib/utils/rmdirRecursive.d.ts: -------------------------------------------------------------------------------- 1 | declare function rmdirRecursive(dirName: string): Promise; 2 | export default rmdirRecursive; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.DS_Store 3 | tests.config.js 4 | tests.config.ts 5 | yarn-error.log 6 | tsconfig.tsbuildinfo 7 | debug/ 8 | -------------------------------------------------------------------------------- /lib/utils/options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=options.js.map -------------------------------------------------------------------------------- /lib/utils/options.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/utils/options.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /lib/types/gregor1/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/gregor1/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG"} -------------------------------------------------------------------------------- /src/utils/keybaseBinaryName.ts: -------------------------------------------------------------------------------- 1 | import {platform} from 'os' 2 | 3 | const binaryName = platform() === 'win32' ? 'keybase.exe' : 'keybase' 4 | 5 | export default binaryName 6 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const API_VERSIONS = { 2 | chat: 1, 3 | kvstore: 1, 4 | team: 1, 5 | wallet: 1, 6 | } 7 | 8 | export type API_TYPES = 'chat' | 'kvstore' | 'team' | 'wallet' 9 | -------------------------------------------------------------------------------- /__tests__/test-utils/publicPaperkeyLabel.ts: -------------------------------------------------------------------------------- 1 | export default function publicPaperkeyLabel(paperkey: string): string { 2 | return paperkey 3 | .split(' ') 4 | .slice(0, 2) 5 | .join(' ') 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "printWidth": 140, 4 | "semi": false, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "overrides": [] 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/safeJSONStringify.ts: -------------------------------------------------------------------------------- 1 | export default (input: object): string => 2 | JSON.stringify(input).replace(/[\u007F-\uFFFF]/g, (chr: string): string => '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).substr(-4)) 3 | -------------------------------------------------------------------------------- /src/utils/timeout.ts: -------------------------------------------------------------------------------- 1 | function timeout(time: number): Promise { 2 | return new Promise(resolve => { 3 | setTimeout(() => { 4 | resolve() 5 | }, time) 6 | }) 7 | } 8 | 9 | export default timeout 10 | -------------------------------------------------------------------------------- /lib/constants.d.ts: -------------------------------------------------------------------------------- 1 | export declare const API_VERSIONS: { 2 | chat: number; 3 | kvstore: number; 4 | team: number; 5 | wallet: number; 6 | }; 7 | export declare type API_TYPES = 'chat' | 'kvstore' | 'team' | 'wallet'; 8 | -------------------------------------------------------------------------------- /lib/constants.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;AAAa,QAAA,YAAY,GAAG;IAC1B,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;CACV,CAAA"} -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.API_VERSIONS = { 4 | chat: 1, 5 | kvstore: 1, 6 | team: 1, 7 | wallet: 1, 8 | }; 9 | //# sourceMappingURL=constants.js.map -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // coyne: I dislike having lib/show up in here; keep accidentally editing a .js file 2 | { 3 | "files.exclude": { 4 | "node_modules/": true, 5 | "lib/": true 6 | }, 7 | "typescript.tsdk": "node_modules/typescript/lib" 8 | } 9 | -------------------------------------------------------------------------------- /lib/utils/whichKeybase.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the full path to the keybase binary or throws an error 3 | * @ignore 4 | * @example 5 | * whichKeybase().then((path) => console.log(path)) 6 | */ 7 | declare function whichKeybase(): Promise; 8 | export default whichKeybase; 9 | -------------------------------------------------------------------------------- /lib/utils/keybaseBinaryName.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"keybaseBinaryName.js","sourceRoot":"","sources":["../../src/utils/keybaseBinaryName.ts"],"names":[],"mappings":";;AAAA,yBAA2B;AAE3B,IAAM,UAAU,GAAG,aAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAA;AAErE,kBAAe,UAAU,CAAA"} -------------------------------------------------------------------------------- /lib/utils/keybaseBinaryName.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var os_1 = require("os"); 4 | var binaryName = os_1.platform() === 'win32' ? 'keybase.exe' : 'keybase'; 5 | exports.default = binaryName; 6 | //# sourceMappingURL=keybaseBinaryName.js.map -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | 7 | [**.iced] 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [**.js] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [**.ts] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /lib/utils/timeout.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/utils/timeout.ts"],"names":[],"mappings":";;AAAA,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,OAAO,CAAC,UAAA,OAAO;QACxB,UAAU,CAAC;YACT,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,IAAI,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,kBAAe,OAAO,CAAA"} -------------------------------------------------------------------------------- /src/utils/randomTempDir.ts: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | import path from 'path' 3 | import crypto from 'crypto' 4 | 5 | function randomTempDir(): string { 6 | const name: string = crypto.randomBytes(16).toString('hex') 7 | return path.join(os.tmpdir(), `keybase_bot_${name}`) 8 | } 9 | 10 | export default randomTempDir 11 | -------------------------------------------------------------------------------- /lib/utils/timeout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function timeout(time) { 4 | return new Promise(function (resolve) { 5 | setTimeout(function () { 6 | resolve(); 7 | }, time); 8 | }); 9 | } 10 | exports.default = timeout; 11 | //# sourceMappingURL=timeout.js.map -------------------------------------------------------------------------------- /lib/utils/safeJSONStringify.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.default = (function (input) { 4 | return JSON.stringify(input).replace(/[\u007F-\uFFFF]/g, function (chr) { return '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).substr(-4); }); 5 | }); 6 | //# sourceMappingURL=safeJSONStringify.js.map -------------------------------------------------------------------------------- /lib/utils/options.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Options for initializing the bot. 3 | */ 4 | export interface InitOptions { 5 | verbose?: boolean; 6 | botLite?: boolean; 7 | disableTyping?: boolean; 8 | autoLogSendOnCrash?: boolean; 9 | adminDebugDirectory?: string; 10 | keybaseBinaryLocation?: string; 11 | useDetachedService?: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /lib/utils/randomTempDir.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"randomTempDir.js","sourceRoot":"","sources":["../../src/utils/randomTempDir.ts"],"names":[],"mappings":";;;;;AAAA,0CAAmB;AACnB,8CAAuB;AACvB,kDAA2B;AAE3B,SAAS,aAAa;IACpB,IAAM,IAAI,GAAW,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3D,OAAO,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,MAAM,EAAE,EAAE,iBAAe,IAAM,CAAC,CAAA;AACtD,CAAC;AAED,kBAAe,aAAa,CAAA"} -------------------------------------------------------------------------------- /lib/utils/keybaseExec.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare type ExecOptions = { 3 | stdinBuffer?: Buffer | string; 4 | onStdOut?: (line: string) => void; 5 | json?: boolean; 6 | timeout?: number; 7 | }; 8 | declare const keybaseExec: (workingDir: string, homeDir: string | void, args: string[], options?: ExecOptions) => Promise; 9 | export default keybaseExec; 10 | -------------------------------------------------------------------------------- /lib/utils/safeJSONStringify.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"safeJSONStringify.js","sourceRoot":"","sources":["../../src/utils/safeJSONStringify.ts"],"names":[],"mappings":";;AAAA,mBAAe,UAAC,KAAa;IAC3B,OAAA,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,UAAC,GAAW,IAAa,OAAA,KAAK,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAA5D,CAA4D,CAAC;AAAxI,CAAwI,EAAA"} -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | setupFilesAfterEnv: ['/__tests__/setup.ts'], 5 | 6 | // The glob patterns Jest uses to detect test files 7 | // I've changed this slightly so we can have some config 8 | // JS files in our test directory without an issue 9 | testMatch: ['**/__tests__/**/*.(spec|test).ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.demos.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es2019", 6 | "noImplicitAny": true, 7 | "moduleResolution": "node", 8 | "rootDir": "demos/typescript", 9 | "outDir": "demos/compiled-from-typescript", 10 | "lib": ["es2019"] 11 | }, 12 | "include": ["demos/typescript"], 13 | "exclude": ["demos/compiled-from-typescript"] 14 | } 15 | -------------------------------------------------------------------------------- /lib/utils/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;AAAA,qDAA6E;AAArE,iDAAA,oBAAoB,CAAA;AAAE,kDAAA,qBAAqB,CAAA;AACnD,6CAAoD;AAA5C,oCAAA,OAAO,CAAe;AAC9B,iDAAwD;AAAhD,wCAAA,OAAO,CAAiB;AAChC,mDAA0D;AAAlD,0CAAA,OAAO,CAAkB;AACjC,iDAAwD;AAAhD,wCAAA,OAAO,CAAiB;AAChC,2DAAkE;AAA1D,kDAAA,OAAO,CAAsB;AACrC,+CAAsD;AAA9C,sCAAA,OAAO,CAAgB;AAC/B,qCAA4C;AAApC,4BAAA,OAAO,CAAW;AAC1B,yDAAgE;AAAxD,gDAAA,OAAO,CAAqB"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "target": "es5", 7 | "noImplicitAny": true, 8 | "moduleResolution": "node", 9 | "sourceMap": true, 10 | //"incremental": true, 11 | "rootDir": "src/", 12 | "outDir": "lib", 13 | "declaration": true, 14 | "lib": ["es6"] 15 | }, 16 | "include": ["src/*"], 17 | "exclude": ["__tests__", "demos"] 18 | } 19 | -------------------------------------------------------------------------------- /lib/utils/pingKeybaseService.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pingKeybaseService.js","sourceRoot":"","sources":["../../src/utils/pingKeybaseService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qEAA8C;AAE9C;;;;;;;GAOG;AACH,SAAe,kBAAkB,CAAC,UAAkB,EAAE,OAAsB;;;;;;;oBAGxE,qBAAM,qBAAW,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC,EAAA;;oBAA5F,SAA4F,CAAA;oBAC5F,sBAAO,IAAI,EAAA;;;oBAEX,sBAAO,KAAK,EAAA;;;;;CAEf;AAED,kBAAe,kBAAkB,CAAA"} -------------------------------------------------------------------------------- /lib/utils/whichKeybase.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"whichKeybase.js","sourceRoot":"","sources":["../../src/utils/whichKeybase.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAyB;AACzB,6BAA8B;AAC9B,0EAAmD;AAEnD,IAAM,MAAM,GAAG,gBAAS,CAAC,eAAK,CAAC,CAAA;AAE/B;;;;;GAKG;AACH,SAAe,YAAY;;;;;wBACZ,qBAAM,MAAM,CAAC,2BAAiB,CAAC,EAAA;;oBAAtC,IAAI,GAAG,SAA+B;oBAC5C,IAAI,CAAC,IAAI,EAAE;wBACT,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;qBACjD;oBACD,sBAAO,IAAI,EAAA;;;;CACZ;AAED,kBAAe,YAAY,CAAA"} -------------------------------------------------------------------------------- /lib/utils/adminDebugLogger.d.ts: -------------------------------------------------------------------------------- 1 | export declare class AdminDebugLogger { 2 | private _logDir?; 3 | private _botId; 4 | private _botServiceLogPath?; 5 | private _deinitYet; 6 | get directory(): string | null; 7 | get filename(): string | null; 8 | constructor(botId: string); 9 | init(logDir: string, botServiceLogPath: string): Promise; 10 | deinit(): void; 11 | info(text: string): Promise; 12 | error(text: string): Promise; 13 | private _logIt; 14 | private _copyLoop; 15 | } 16 | -------------------------------------------------------------------------------- /lib/utils/pingKeybaseService.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks whether the keybase service is running by calling `keybase status --json`. 3 | * @ignore 4 | * @param workingDir - the directory containing the binary, according to top level Bot 5 | * @param homeDir - The home directory of the service you want to fetch the status from. 6 | * @example 7 | * pingKeybaseService('/my/dir').then(status => console.log("service running", status)) 8 | */ 9 | declare function pingKeybaseService(workingDir: string, homeDir: void | string): Promise; 10 | export default pingKeybaseService; 11 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export {formatAPIObjectInput, formatAPIObjectOutput} from './formatAPIObject' 2 | export {default as keybaseExec} from './keybaseExec' 3 | export {default as randomTempDir} from './randomTempDir' 4 | export {default as rmdirRecursive} from './rmdirRecursive' 5 | export {default as keybaseStatus} from './keybaseStatus' 6 | export {default as pingKeybaseService} from './pingKeybaseService' 7 | export {default as whichKeybase} from './whichKeybase' 8 | export {default as timeout} from './timeout' 9 | export {default as keybaseBinaryName} from './keybaseBinaryName' 10 | -------------------------------------------------------------------------------- /lib/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | export { formatAPIObjectInput, formatAPIObjectOutput } from './formatAPIObject'; 2 | export { default as keybaseExec } from './keybaseExec'; 3 | export { default as randomTempDir } from './randomTempDir'; 4 | export { default as rmdirRecursive } from './rmdirRecursive'; 5 | export { default as keybaseStatus } from './keybaseStatus'; 6 | export { default as pingKeybaseService } from './pingKeybaseService'; 7 | export { default as whichKeybase } from './whichKeybase'; 8 | export { default as timeout } from './timeout'; 9 | export { default as keybaseBinaryName } from './keybaseBinaryName'; 10 | -------------------------------------------------------------------------------- /src/utils/whichKeybase.ts: -------------------------------------------------------------------------------- 1 | import which from 'which' 2 | import {promisify} from 'util' 3 | import keybaseBinaryName from './keybaseBinaryName' 4 | 5 | const aWhich = promisify(which) 6 | 7 | /** 8 | * Returns the full path to the keybase binary or throws an error 9 | * @ignore 10 | * @example 11 | * whichKeybase().then((path) => console.log(path)) 12 | */ 13 | async function whichKeybase(): Promise { 14 | const path = await aWhich(keybaseBinaryName) 15 | if (!path) { 16 | throw new Error('Could not find keybase binary') 17 | } 18 | return path 19 | } 20 | 21 | export default whichKeybase 22 | -------------------------------------------------------------------------------- /lib/utils/keybaseStatus.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"keybaseStatus.js","sourceRoot":"","sources":["../../src/utils/keybaseStatus.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qEAA8C;AAe9C;;;;;;;GAOG;AACH,SAAe,aAAa,CAAC,UAAkB,EAAE,OAAgB;;;;;wBAChD,qBAAM,qBAAW,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC,EAAA;;oBAAnF,MAAM,GAAG,SAA0E;oBACzF,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;wBACpE,sBAAO;gCACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;gCACzB,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI;gCAC9B,OAAO,SAAA;6BACR,EAAA;qBACF;yBAAM;wBACL,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;qBACnE;;;;;CACF;AAED,kBAAe,aAAa,CAAA"} -------------------------------------------------------------------------------- /lib/utils/randomTempDir.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | var os_1 = __importDefault(require("os")); 7 | var path_1 = __importDefault(require("path")); 8 | var crypto_1 = __importDefault(require("crypto")); 9 | function randomTempDir() { 10 | var name = crypto_1.default.randomBytes(16).toString('hex'); 11 | return path_1.default.join(os_1.default.tmpdir(), "keybase_bot_" + name); 12 | } 13 | exports.default = randomTempDir; 14 | //# sourceMappingURL=randomTempDir.js.map -------------------------------------------------------------------------------- /__tests__/test-utils/pollFor.ts: -------------------------------------------------------------------------------- 1 | import {timeout} from '../../lib/utils' 2 | 3 | // convenient promise function that returns when the non-promise function returns a truthy 4 | // value, or throws an error if it times out. This is so we don't have to have so many timeout(3000) type things in our tests 5 | async function pollFor(fnToReturnTruthy: Function, maxMilliseconds?: number): Promise { 6 | const endTime = Date.now() + (maxMilliseconds || 30000) 7 | while (Date.now() < endTime) { 8 | if (fnToReturnTruthy()) { 9 | return true 10 | } 11 | await timeout(5) 12 | } 13 | throw new Error('timeout in pollFor') 14 | } 15 | 16 | export default pollFor 17 | -------------------------------------------------------------------------------- /lib/types/gregor1/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | * gregor.1 4 | * 5 | * Auto-generated to TypeScript types by avdl-compiler v1.4.8 (https://github.com/keybase/node-avdl-compiler) 6 | * Input files: 7 | * - ../client/protocol/avdl/gregor1/auth.avdl 8 | * - ../client/protocol/avdl/gregor1/auth_internal.avdl 9 | * - ../client/protocol/avdl/gregor1/auth_update.avdl 10 | * - ../client/protocol/avdl/gregor1/common.avdl 11 | * - ../client/protocol/avdl/gregor1/incoming.avdl 12 | * - ../client/protocol/avdl/gregor1/outgoing.avdl 13 | * - ../client/protocol/avdl/gregor1/remind.avdl 14 | */ 15 | Object.defineProperty(exports, "__esModule", { value: true }); 16 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROTOCOL_PATH=$(GOPATH)/src/github.com/keybase/client/protocol 2 | AVDLC=$(PROTOCOL_PATH)/node_modules/.bin/avdlc 3 | PRETTIER=./node_modules/.bin/prettier 4 | 5 | .DEFAULT_GOAL := types 6 | 7 | types: 8 | @mkdir -p src/types/{keybase1,gregor1,chat1,stellar1}/ 9 | $(AVDLC) -b -l typescript -t -o src/types/keybase1 $(PROTOCOL_PATH)/avdl/keybase1/*.avdl 10 | $(AVDLC) -b -l typescript -t -o src/types/gregor1 $(PROTOCOL_PATH)/avdl/gregor1/*.avdl 11 | $(AVDLC) -b -l typescript -t -o src/types/chat1 $(PROTOCOL_PATH)/avdl/chat1/*.avdl 12 | $(AVDLC) -b -l typescript -t -o src/types/stellar1 $(PROTOCOL_PATH)/avdl/stellar1/*.avdl 13 | $(PRETTIER) --write src/types/**/*.ts 14 | 15 | clean: 16 | rm -rf src/types/* 17 | -------------------------------------------------------------------------------- /src/utils/rmdirRecursive.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import {promisify} from 'util' 4 | 5 | async function rmdirRecursive(dirName: string): Promise { 6 | const fsLstat = promisify(fs.lstat) 7 | const fsUnlink = promisify(fs.unlink) 8 | const fsRmdir = promisify(fs.rmdir) 9 | const fsReaddir = promisify(fs.readdir) 10 | const dirStat = await fsLstat(dirName) 11 | if (dirStat) { 12 | for (const entry of await fsReaddir(dirName)) { 13 | const entryPath = path.join(dirName, entry) 14 | const stat = await fsLstat(entryPath) 15 | if (stat.isDirectory()) { 16 | await rmdirRecursive(entryPath) 17 | } else { 18 | await fsUnlink(entryPath) 19 | } 20 | } 21 | await fsRmdir(dirName) 22 | } 23 | } 24 | 25 | export default rmdirRecursive 26 | -------------------------------------------------------------------------------- /demos/es7/check-for-unreads-es7.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | 4 | async function main() { 5 | const bot = new Bot() 6 | 7 | try { 8 | const username = process.env.KB_USERNAME 9 | const paperkey = process.env.KB_PAPERKEY 10 | await bot.init(username, paperkey) 11 | const conversations = await bot.chat.list({ 12 | showErrors: true, 13 | }) 14 | if (conversations.length) { 15 | const unreadCount = conversations.filter(c => c.unread).length 16 | console.log(`You have ${unreadCount} unread conversations out of ${conversations.length} total`) 17 | } else { 18 | console.log('You have no messages. Go chat with someone!') 19 | } 20 | } catch (error) { 21 | console.error(error) 22 | } finally { 23 | await bot.deinit() 24 | } 25 | } 26 | 27 | main() 28 | -------------------------------------------------------------------------------- /src/utils/options.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Options for initializing the bot. 3 | */ 4 | export interface InitOptions { 5 | verbose?: boolean 6 | // Disables non-critical background services to improve bot performance. 7 | botLite?: boolean 8 | // Disable sending/receiving typing notifications to reduce bot bandwidth 9 | disableTyping?: boolean 10 | // Automatically send logs on crash 11 | autoLogSendOnCrash?: boolean 12 | // Copies the service log to a dedicated directory so it survives after bot shutdown 13 | // and also records its own actions 14 | adminDebugDirectory?: string 15 | // Overrides the path to the keybase binary 16 | keybaseBinaryLocation?: string 17 | // With this turned on, a SIGINT won't kill your Keybase service, 18 | // but you must remember to call deinit() or it will survive 19 | useDetachedService?: boolean 20 | } 21 | -------------------------------------------------------------------------------- /lib/helpers-client/index.d.ts: -------------------------------------------------------------------------------- 1 | import ClientBase from '../client-base'; 2 | interface ApiCallArg { 3 | endpoint: string; 4 | arg?: any; 5 | } 6 | declare class Helpers extends ClientBase { 7 | /** 8 | * Make a call to a Keybase URL (TODO: add support for POST params) 9 | * @memberof Helpers 10 | * @param url - a full URL to hit 11 | * @returns - 12 | * @example 13 | * bot.helpers.rawApiCall({endpoint:"/me"}).then(res => console.log(res)) 14 | */ 15 | rawApiCall(arg: ApiCallArg): Promise; 16 | /** 17 | * Ping keybase server. The returned promise resolves the keybase daemon is 18 | * up and server is reachable. 19 | * @memberof Helpers 20 | * @returns - 21 | * @example 22 | * bot.helpers.ping() 23 | */ 24 | ping(): Promise; 25 | } 26 | export default Helpers; 27 | -------------------------------------------------------------------------------- /demos/check-for-unreads.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../lib/index.js') 3 | 4 | function main() { 5 | const bot = new Bot() 6 | const username = process.env.KB_USERNAME 7 | const paperkey = process.env.KB_PAPERKEY 8 | bot 9 | .init(username, paperkey) 10 | .then(() => { 11 | bot.chat.list({showErrors: true}).then(conversations => { 12 | if (conversations.length) { 13 | const unreadCount = conversations.filter(c => c.unread).length 14 | console.log(`You have ${unreadCount} unread conversations out of ${conversations.length} total`) 15 | } else { 16 | console.log('You have no messages. Go chat with someone!') 17 | } 18 | bot.deinit() 19 | }) 20 | }) 21 | .catch(error => { 22 | console.error(error) 23 | bot.deinit() 24 | }) 25 | } 26 | 27 | main() 28 | -------------------------------------------------------------------------------- /lib/utils/keybaseStatus.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Useful information like the username, device, home directory of your bot and 3 | * configuration options. 4 | */ 5 | export interface BotInfo { 6 | username: string; 7 | devicename: string; 8 | homeDir?: string; 9 | botLite?: boolean; 10 | disableTyping?: boolean; 11 | debugLogging?: boolean; 12 | } 13 | /** 14 | * Returns { username, devicename, homeDir } from `keybase status --json`. 15 | * @ignore 16 | * @param workingDir - the directory containing the binary, according to top level Bot 17 | * @param homeDir - The home directory of the service you want to fetch the status from. 18 | * @example 19 | * keybaseStatus('/my/dir').then(status => console.log(status.username)) 20 | */ 21 | declare function keybaseStatus(workingDir: string, homeDir?: string): Promise; 22 | export default keybaseStatus; 23 | -------------------------------------------------------------------------------- /src/utils/pingKeybaseService.ts: -------------------------------------------------------------------------------- 1 | import keybaseExec from '../utils/keybaseExec' 2 | 3 | /** 4 | * Checks whether the keybase service is running by calling `keybase status --json`. 5 | * @ignore 6 | * @param workingDir - the directory containing the binary, according to top level Bot 7 | * @param homeDir - The home directory of the service you want to fetch the status from. 8 | * @example 9 | * pingKeybaseService('/my/dir').then(status => console.log("service running", status)) 10 | */ 11 | async function pingKeybaseService(workingDir: string, homeDir: void | string): Promise { 12 | // TODO: use a faster technique when core releases one 13 | try { 14 | await keybaseExec(workingDir, homeDir, ['--no-auto-fork', 'status', '--json'], {json: true}) 15 | return true 16 | } catch (err) { 17 | return false 18 | } 19 | } 20 | 21 | export default pingKeybaseService 22 | -------------------------------------------------------------------------------- /demos/es7/hello-world-es7.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | 4 | async function main() { 5 | const bot = new Bot() 6 | try { 7 | const username = process.env.KB_USERNAME 8 | const paperkey = process.env.KB_PAPERKEY 9 | await bot.init(username, paperkey, {verbose: false}) 10 | console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`) 11 | const channel = {name: 'kbot,' + bot.myInfo().username, public: false, topicType: 'chat'} 12 | const message = { 13 | body: `Hello kbot! This is ${bot.myInfo().username} saying hello from my device ${ 14 | bot.myInfo().devicename 15 | }`, 16 | } 17 | await bot.chat.send(channel, message) 18 | console.log('Message sent!') 19 | } catch (error) { 20 | console.error(error) 21 | } finally { 22 | await bot.deinit() 23 | } 24 | } 25 | 26 | main() 27 | -------------------------------------------------------------------------------- /demos/typescript/hello-world.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import Bot from '../../lib/index.js' 3 | 4 | async function main(): Promise { 5 | const bot = new Bot() 6 | try { 7 | const username = process.env.KB_USERNAME 8 | const paperkey = process.env.KB_PAPERKEY 9 | const friend = process.env.KB_FRIEND 10 | await bot.init(username, paperkey, {verbose: false}) 11 | console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`) 12 | const channel = {name: friend, public: false, topicType: 'chat'} 13 | const message = { 14 | body: `Hello ${friend}! This is ${bot.myInfo().username} saying hello from my device ${bot.myInfo().devicename}`, 15 | } 16 | await bot.chat.send(channel, message) 17 | console.log('Message sent!') 18 | } catch (error) { 19 | console.error(error) 20 | } finally { 21 | await bot.deinit() 22 | } 23 | } 24 | 25 | main() 26 | -------------------------------------------------------------------------------- /__tests__/myInfo.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | import {publicPaperkeyLabel} from './test-utils' 4 | 5 | describe('bot.myInfo()', () => { 6 | it('returns a username, devicename, homeDir, and service config options when the bot is initialized', async () => { 7 | const alice = new Bot() 8 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 9 | const aliceInfo = alice.myInfo()! 10 | expect(aliceInfo).not.toBeNull() 11 | expect(aliceInfo.username).toBe(config.bots.alice1.username) 12 | expect(aliceInfo.devicename).toBe(publicPaperkeyLabel(config.bots.alice1.paperkey)) 13 | expect(aliceInfo).toHaveProperty('homeDir') 14 | expect(aliceInfo.botLite).toBe(true) 15 | expect(aliceInfo.disableTyping).toBe(true) 16 | await alice.deinit() 17 | }) 18 | 19 | it('returns null when the bot is not initialized', async () => { 20 | const alice = new Bot() 21 | expect(alice.myInfo()).toBeNull() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /__tests__/race.conditions.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | 4 | describe('init/chat/deinit checking', (): void => { 5 | const loop = 10 6 | it(`can init, send ${loop}, deinit`, async (): Promise => { 7 | const code = Date.now() 8 | const alice = new Bot() 9 | const bob = new Bot() 10 | const channel = {name: `${config.bots.alice1.username},${config.bots.bob1.username}`} 11 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 12 | for (let i = 1; i <= loop; i++) { 13 | await alice.chat.send(channel, {body: `race-${i}-${code}`}) 14 | } 15 | await alice.deinit() 16 | await bob.init(config.bots.bob1.username, config.bots.bob1.paperkey) 17 | await bob.chat.read({name: config.bots.alice1.username}) 18 | const read1 = await bob.chat.read(channel, {pagination: {num: 1}}) 19 | expect(read1.messages[0].content?.text?.body).toContain(`race-${loop}-${code}`) 20 | await bob.deinit() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /lib/utils/rmdirRecursive.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"rmdirRecursive.js","sourceRoot":"","sources":["../../src/utils/rmdirRecursive.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0CAAmB;AACnB,8CAAuB;AACvB,6BAA8B;AAE9B,SAAe,cAAc,CAAC,OAAe;;;;;;oBACrC,OAAO,GAAG,gBAAS,CAAC,YAAE,CAAC,KAAK,CAAC,CAAA;oBAC7B,QAAQ,GAAG,gBAAS,CAAC,YAAE,CAAC,MAAM,CAAC,CAAA;oBAC/B,OAAO,GAAG,gBAAS,CAAC,YAAE,CAAC,KAAK,CAAC,CAAA;oBAC7B,SAAS,GAAG,gBAAS,CAAC,YAAE,CAAC,OAAO,CAAC,CAAA;oBACvB,qBAAM,OAAO,CAAC,OAAO,CAAC,EAAA;;oBAAhC,OAAO,GAAG,SAAsB;yBAClC,OAAO,EAAP,yBAAO;0BACmC;oBAAxB,qBAAM,SAAS,CAAC,OAAO,CAAC,EAAA;;oBAAxB,KAAA,SAAwB;;;yBAAxB,CAAA,cAAwB,CAAA;oBAAjC,KAAK;oBACR,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;oBAC9B,qBAAM,OAAO,CAAC,SAAS,CAAC,EAAA;;oBAA/B,IAAI,GAAG,SAAwB;yBACjC,IAAI,CAAC,WAAW,EAAE,EAAlB,wBAAkB;oBACpB,qBAAM,cAAc,CAAC,SAAS,CAAC,EAAA;;oBAA/B,SAA+B,CAAA;;wBAE/B,qBAAM,QAAQ,CAAC,SAAS,CAAC,EAAA;;oBAAzB,SAAyB,CAAA;;;oBANT,IAAwB,CAAA;;wBAS5C,qBAAM,OAAO,CAAC,OAAO,CAAC,EAAA;;oBAAtB,SAAsB,CAAA;;;;;;CAEzB;AAED,kBAAe,cAAc,CAAA"} -------------------------------------------------------------------------------- /__tests__/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | 4 | describe('Chat Methods', (): void => { 5 | const alice = new Bot() 6 | 7 | beforeAll( 8 | async (): Promise => { 9 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 10 | } 11 | ) 12 | afterAll( 13 | async (): Promise => { 14 | await alice.deinit() 15 | } 16 | ) 17 | 18 | it('works', async (): Promise => { 19 | // check that sessions work 20 | const meRes = await alice.helpers.rawApiCall({endpoint: 'me'}) 21 | expect(meRes.me.basics.username).toBe(alice.myInfo()?.username) 22 | 23 | // check a GET request 24 | const searchRes = await alice.helpers.rawApiCall({ 25 | endpoint: 'user/user_search', 26 | arg: { 27 | q: 'chris', 28 | // eslint-disable-next-line @typescript-eslint/camelcase 29 | num_wanted: 1, 30 | }, 31 | }) 32 | expect(searchRes.list[0].keybase.username).toBe('chris') 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /lib/helpers-client/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers-client/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+DAAuC;AACvC,kCAAoC;AAUpC;GACG;AACH;IAAsB,2BAAU;IAAhC;;IAkCA,CAAC;IAjCC;;;;;;;OAOG;IACU,4BAAU,GAAvB,UAAwB,GAAe;;;;;4BACrC,qBAAM,IAAI,CAAC,iBAAiB,EAAE,EAAA;;wBAA9B,SAA8B,CAAA;wBACxB,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;wBAC3B,IAAI,GAAG,CAAC,GAAG,EAAE;4BACX,KAAW,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE;gCACvB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAK,CAAC,SAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAG,CAAC,CAAA;6BACzC;yBACF;wBACD,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;wBACR,qBAAM,mBAAW,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC,EAAA;;wBAA9E,MAAM,GAAG,SAAqE;wBACpF,sBAAO,MAAM,EAAA;;;;KACd;IAED;;;;;;;OAOG;IACU,sBAAI,GAAjB;;;;4BACE,qBAAM,IAAI,CAAC,iBAAiB,EAAE,EAAA;;wBAA9B,SAA8B,CAAA;wBACvB,qBAAM,mBAAW,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,EAAA;4BAAlE,sBAAO,SAA2D,EAAA;;;;KACnE;IACH,cAAC;AAAD,CAAC,AAlCD,CAAsB,qBAAU,GAkC/B;AAED,kBAAe,OAAO,CAAA"} -------------------------------------------------------------------------------- /demos/hello-world.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../lib/index.js') 3 | 4 | function main() { 5 | const bot = new Bot() 6 | const username = process.env.KB_USERNAME 7 | const paperkey = process.env.KB_PAPERKEY 8 | bot 9 | .init(username, paperkey, {verbose: false}) 10 | .then(() => { 11 | console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`) 12 | const channel = {name: 'kbot,' + bot.myInfo().username, public: false, topicType: 'chat'} 13 | const message = { 14 | body: `Hello kbot! This is ${bot.myInfo().username} saying hello from my device ${ 15 | bot.myInfo().devicename 16 | }`, 17 | } 18 | bot.chat 19 | .send(channel, message) 20 | .then(() => { 21 | console.log('Message sent!') 22 | bot.deinit() 23 | }) 24 | .catch(error => { 25 | console.error(error) 26 | bot.deinit() 27 | }) 28 | }) 29 | .catch(error => { 30 | console.error(error) 31 | bot.deinit() 32 | }) 33 | } 34 | 35 | main() 36 | -------------------------------------------------------------------------------- /__tests__/test-utils/stopServiceManually.ts: -------------------------------------------------------------------------------- 1 | import {keybaseExec, rmdirRecursive, timeout, whichKeybase} from '../../lib/utils' 2 | import path from 'path' 3 | import {promisify} from 'util' 4 | import fs from 'fs' 5 | 6 | async function stopServiceManually(homeDir: string) { 7 | const workingDir = path.parse(await whichKeybase()).dir 8 | await keybaseExec(workingDir, homeDir, ['logout']) 9 | await keybaseExec(workingDir, homeDir, ['ctl', 'stop', '--shutdown']) 10 | 11 | // Wait until the pid file disappears as a workaround for the lack of process tracking 12 | const fileToWatchFor = path.join(homeDir, '.config', 'keybase', 'keybase.pid') 13 | let i = 0 14 | while (true) { 15 | await timeout(10) 16 | try { 17 | await promisify(fs.stat)(fileToWatchFor) 18 | if (++i >= 1000) { 19 | throw new Error('Process ID file still exists after 10 seconds') 20 | } 21 | } catch (e) { 22 | if (e.code !== 'ENOENT') { 23 | throw e 24 | } 25 | 26 | // We're expecting ENOENT here 27 | break 28 | } 29 | } 30 | 31 | await rmdirRecursive(homeDir) 32 | } 33 | 34 | export default stopServiceManually 35 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug tests", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand"], 12 | "console": "integratedTerminal", 13 | "internalConsoleOptions": "neverOpen", 14 | "port": 9229 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Launch Hello World", 20 | "program": "${workspaceFolder}/demos/hello_world.js" 21 | }, 22 | { 23 | "type": "node", 24 | "request": "launch", 25 | "name": "Launch Check For Unreads", 26 | "program": "${workspaceFolder}/demos/check_for_unreads.js" 27 | }, 28 | { 29 | "type": "node", 30 | "request": "launch", 31 | "name": "Launch Math Bot", 32 | "program": "${workspaceFolder}/demos/math_bot.js" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var formatAPIObject_1 = require("./formatAPIObject"); 4 | exports.formatAPIObjectInput = formatAPIObject_1.formatAPIObjectInput; 5 | exports.formatAPIObjectOutput = formatAPIObject_1.formatAPIObjectOutput; 6 | var keybaseExec_1 = require("./keybaseExec"); 7 | exports.keybaseExec = keybaseExec_1.default; 8 | var randomTempDir_1 = require("./randomTempDir"); 9 | exports.randomTempDir = randomTempDir_1.default; 10 | var rmdirRecursive_1 = require("./rmdirRecursive"); 11 | exports.rmdirRecursive = rmdirRecursive_1.default; 12 | var keybaseStatus_1 = require("./keybaseStatus"); 13 | exports.keybaseStatus = keybaseStatus_1.default; 14 | var pingKeybaseService_1 = require("./pingKeybaseService"); 15 | exports.pingKeybaseService = pingKeybaseService_1.default; 16 | var whichKeybase_1 = require("./whichKeybase"); 17 | exports.whichKeybase = whichKeybase_1.default; 18 | var timeout_1 = require("./timeout"); 19 | exports.timeout = timeout_1.default; 20 | var keybaseBinaryName_1 = require("./keybaseBinaryName"); 21 | exports.keybaseBinaryName = keybaseBinaryName_1.default; 22 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /__tests__/test-utils/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import {exec} from 'child_process' 3 | import {promisify} from 'util' 4 | 5 | export {default as publicPaperkeyLabel} from './publicPaperkeyLabel' 6 | export {default as startServiceManually} from './startServiceManually' 7 | export {default as stopServiceManually} from './stopServiceManually' 8 | export {default as pollFor} from './pollFor' 9 | 10 | // 11 | // Coyne: I really don't think we need separate files for every small test util function, 12 | // so I'm going to start putting these in here. 13 | // 14 | 15 | export async function doesFileOrDirectoryExist(fpath: string): Promise { 16 | try { 17 | await promisify(fs.lstat)(fpath) 18 | return true 19 | } catch (err) { 20 | return false 21 | } 22 | } 23 | 24 | export async function countProcessesMentioning(substr: string): Promise { 25 | expect(substr).toMatch(/^[0-9a-z_\- /]+$/i) 26 | const aexec = promisify(exec) 27 | try { 28 | const execRes = await aexec(`ps ax | grep -v 'grep' | grep "${substr}"`) 29 | return execRes.stdout.split('\n').length - 1 30 | } catch (e) { 31 | if (e.code === 1) { 32 | return 0 33 | } else { 34 | throw new Error('Error looking for processes') 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demos/compiled-from-typescript/hello-world.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var __importDefault = (this && this.__importDefault) || function (mod) { 4 | return (mod && mod.__esModule) ? mod : { "default": mod }; 5 | }; 6 | Object.defineProperty(exports, "__esModule", { value: true }); 7 | const index_js_1 = __importDefault(require("../../lib/index.js")); 8 | async function main() { 9 | const bot = new index_js_1.default(); 10 | try { 11 | const username = process.env.KB_USERNAME; 12 | const paperkey = process.env.KB_PAPERKEY; 13 | const friend = process.env.KB_FRIEND; 14 | await bot.init(username, paperkey, { verbose: false }); 15 | console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`); 16 | const channel = { name: friend, public: false, topicType: 'chat' }; 17 | const message = { 18 | body: `Hello ${friend}! This is ${bot.myInfo().username} saying hello from my device ${bot.myInfo().devicename}`, 19 | }; 20 | await bot.chat.send(channel, message); 21 | console.log('Message sent!'); 22 | } 23 | catch (error) { 24 | console.error(error); 25 | } 26 | finally { 27 | await bot.deinit(); 28 | } 29 | } 30 | main(); 31 | -------------------------------------------------------------------------------- /lib/client-base/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { ChildProcess } from 'child_process'; 3 | import { API_TYPES } from '../constants'; 4 | import { AdminDebugLogger } from '../utils/adminDebugLogger'; 5 | export interface ApiCommandArg { 6 | apiName: API_TYPES; 7 | method: string; 8 | options?: object; 9 | timeout?: number; 10 | } 11 | export declare class ErrorWithCode extends Error { 12 | code: number; 13 | constructor(code: number, message: string); 14 | } 15 | /** 16 | * A Client base. 17 | * @ignore 18 | */ 19 | declare class ClientBase { 20 | initialized: boolean; 21 | username?: string; 22 | devicename?: string; 23 | homeDir?: string; 24 | verbose: boolean; 25 | protected _spawnedProcesses: ChildProcess[]; 26 | protected _workingDir: string; 27 | protected _deinitializing: boolean; 28 | protected _adminDebugLogger: AdminDebugLogger; 29 | constructor(workingDir: string, adminDebugLogger: AdminDebugLogger); 30 | _init(homeDir?: string): Promise; 31 | _deinit(): Promise; 32 | protected _runApiCommand(arg: ApiCommandArg): Promise; 33 | protected _guardInitialized(): Promise; 34 | protected _pathToKeybaseBinary(): string; 35 | } 36 | export default ClientBase; 37 | -------------------------------------------------------------------------------- /src/utils/keybaseStatus.ts: -------------------------------------------------------------------------------- 1 | import keybaseExec from '../utils/keybaseExec' 2 | 3 | /** 4 | * Useful information like the username, device, home directory of your bot and 5 | * configuration options. 6 | */ 7 | export interface BotInfo { 8 | username: string 9 | devicename: string 10 | homeDir?: string 11 | botLite?: boolean 12 | disableTyping?: boolean 13 | debugLogging?: boolean 14 | } 15 | 16 | /** 17 | * Returns { username, devicename, homeDir } from `keybase status --json`. 18 | * @ignore 19 | * @param workingDir - the directory containing the binary, according to top level Bot 20 | * @param homeDir - The home directory of the service you want to fetch the status from. 21 | * @example 22 | * keybaseStatus('/my/dir').then(status => console.log(status.username)) 23 | */ 24 | async function keybaseStatus(workingDir: string, homeDir?: string): Promise { 25 | const status = await keybaseExec(workingDir, homeDir, ['status', '--json'], {json: true}) 26 | if (status && status.Username && status.Device && status.Device.name) { 27 | return { 28 | username: status.Username, 29 | devicename: status.Device.name, 30 | homeDir, 31 | } 32 | } else { 33 | throw new Error('Failed to get current username and device name.') 34 | } 35 | } 36 | 37 | export default keybaseStatus 38 | -------------------------------------------------------------------------------- /__tests__/tests.config.example.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * if you'd like to run any of the tests in this directory, 3 | * save this file as `tests.config.ts` and put in real usernames and paperkeys 4 | * for each of the participants here. 5 | */ 6 | 7 | export = { 8 | bots: { 9 | alice1: { 10 | // Alice should have an active Stellar account with a little bit of XLM in it 11 | username: 'alice', 12 | paperkey: 'foo bar car...', 13 | }, 14 | alice2: { 15 | username: 'alice' /* should be the same username as alice1, but... */, 16 | paperkey: 'yo there paperkey...' /* ...this should be a DIFFERENT paperkey than the one for alice1 */, 17 | }, 18 | bob1: { 19 | // Bob should have an active Stellar account with a little bit of XLM in it 20 | username: 'bob', 21 | paperkey: 'one two three four...', 22 | }, 23 | charlie1: { 24 | // Charlie should be an account that does not have access to Stellar features 25 | username: 'charlie', 26 | paperkey: 'get this bread...', 27 | }, 28 | }, 29 | teams: { 30 | acme: { 31 | teamname: 'someteam' /* a real team that you add your alice1, alice2, and bob1 all into */, 32 | }, 33 | alicesPlayground: { 34 | teamname: 'someteam2' /* a team with alice in it AS ADMIN, but bob or charlie not in team */, 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 6 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module', // Allows for the use of imports 11 | files: ['*.tsx', '**/*.ts', '*.d.ts'], 12 | exclude: ['lib/'], 13 | }, 14 | rules: { 15 | '@typescript-eslint/no-non-null-assertion': 'off', 16 | '@typescript-eslint/ban-ts-ignore': 'off', // we use these in our .ts tests to try to do bad things 17 | 'no-var': 'error', 18 | 'prefer-const': 'error', 19 | 'no-duplicate-imports': 0, 20 | 'comma-dangle': [2, 'always-multiline'], 21 | strict: [2, 'global'], 22 | }, 23 | settings: {}, 24 | overrides: [ 25 | { 26 | files: ['src/**/*.ts', '__tests__/*.ts'], 27 | env: { 28 | jest: true, 29 | }, 30 | }, 31 | ], 32 | } 33 | -------------------------------------------------------------------------------- /__tests__/test-utils/startServiceManually.ts: -------------------------------------------------------------------------------- 1 | import {spawn} from 'child_process' 2 | import readline from 'readline' 3 | import {keybaseExec, whichKeybase} from '../../lib/utils' 4 | import path from 'path' 5 | 6 | function keybaseServiceStartup(homeDir: string): Promise { 7 | const child = spawn('keybase', ['--home', homeDir, '--enable-bot-lite-mode', 'service']) 8 | const lineReader = readline.createInterface({input: child.stderr}) 9 | return new Promise((resolve, reject) => { 10 | child.on('close', code => { 11 | // any code here including 0 is bad here, if it happens before resolve 12 | //, since this service should stay running 13 | reject(new Error(`keybase service exited with code ${code}:`)) 14 | }) 15 | lineReader.on('line', (line: string) => { 16 | if (line.indexOf('net.Listen on unix:') !== -1) { 17 | resolve() 18 | } 19 | }) 20 | }); 21 | } 22 | 23 | async function startServiceManually(homeDir: string, username: string, paperkey: string) { 24 | const workingDir = path.parse(await whichKeybase()).dir 25 | await keybaseServiceStartup(homeDir) 26 | // Ideally, this should use `login` instead of `oneshot` but `login` doesn't provide a programmatic way to input a password. 27 | await keybaseExec(workingDir, homeDir, ['oneshot', '--username', username], { 28 | stdinBuffer: paperkey, 29 | }) 30 | } 31 | 32 | export default startServiceManually 33 | -------------------------------------------------------------------------------- /lib/service/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BotInfo } from '../utils/keybaseStatus'; 2 | import { InitOptions } from '../utils/options'; 3 | import { AdminDebugLogger } from '../utils/adminDebugLogger'; 4 | declare class Service { 5 | initialized: false | 'paperkey' | 'runningService'; 6 | running: boolean; 7 | username: undefined | string; 8 | devicename: undefined | string; 9 | homeDir: undefined | string; 10 | verbose: boolean; 11 | botLite: boolean; 12 | disableTyping: boolean; 13 | serviceLogFile: undefined | string; 14 | workingDir: string; 15 | autoLogSendOnCrash: boolean; 16 | private _paperkey; 17 | private _useDetachedService; 18 | private _debugLogging; 19 | protected _adminDebugLogger: AdminDebugLogger; 20 | constructor(workingDir: string, adminDebugLogger: AdminDebugLogger, debugLogging: boolean); 21 | init(username: string, paperkey: string, options?: InitOptions): Promise; 22 | initFromRunningService(homeDir?: string, options?: InitOptions): Promise; 23 | private _killCustomService; 24 | deinit(): Promise; 25 | myInfo(): BotInfo | undefined; 26 | /** 27 | * 28 | * @ignore 29 | * This is a bit different from normal keybaseExecs and is unique to the service 30 | * starting up 31 | * @example 32 | * service.startupService() 33 | */ 34 | startupService(): Promise; 35 | logSend(): Promise; 36 | } 37 | export default Service; 38 | -------------------------------------------------------------------------------- /demos/typescript/hunt-for-resets.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import Bot from '../../lib/index.js' 3 | import {ResetConvMemberAPI} from '../../lib/types/chat1/index.js' 4 | // 5 | // have you ever had a direct conversation with someone, and then they "reset" 6 | // their account? This locks them out, and you're supposed to check their proofs 7 | // and let them back in, only if you're satisfied. Well...bots are always satisfied 8 | // so your bot can just let them back in...if you want. 9 | // 10 | // Note this bot demo isn't using a username + paperkey because it's also demo'ing running 11 | // against your own logged in user. 12 | // 13 | 14 | async function letThemIn(bot: Bot, p: ResetConvMemberAPI): Promise { 15 | try { 16 | await bot.chat.addResetConvMember(p) 17 | console.log(`SUCCEESS - let in ${p.username} to ${p.conversationId}`) 18 | } catch (err) { 19 | console.error(`ERROR - to let in ${p.username} to ${p.conversationId}`, err) 20 | } 21 | } 22 | 23 | async function main(): Promise { 24 | const bot = new Bot() 25 | try { 26 | await bot.initFromRunningService() 27 | console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`) 28 | const problemChats = await bot.chat.getResetConvMembers() 29 | for (const p of problemChats.members) { 30 | await letThemIn(bot, p) 31 | } 32 | } catch (error) { 33 | console.error(error) 34 | } finally { 35 | await bot.deinit() 36 | } 37 | } 38 | 39 | main() 40 | -------------------------------------------------------------------------------- /lib/utils/formatAPIObject.d.ts: -------------------------------------------------------------------------------- 1 | import { API_TYPES } from '../constants'; 2 | /** 3 | Takes a Keybase API input JavaScript object and recursively formats it into snake_case or kebab-case instead of camelCase for the service. 4 | * @ignore 5 | * @param obj - The object to be formatted. 6 | * @param apiType - The type of api the the input is being served to. Currently Keybase has chat, team, and wallet apis. 7 | * @returns - The new, formatted object. 8 | * @example 9 | * const inputOptions = formatAPIObject({unreadOnly: true}) 10 | * console.log(inputOptions) // {unread_only: true} 11 | */ 12 | export declare function formatAPIObjectInput(obj: any, apiType: API_TYPES): any; 13 | /** 14 | * Context of the object formatting process. 15 | * @ignore 16 | */ 17 | export declare type FormatAPIObjectOutputContext = { 18 | apiName: API_TYPES; 19 | method: string; 20 | parent?: any[]; 21 | }; 22 | /** 23 | Takes a Keybase output object and formats it in a more digestable JavaScript style by using camelCase instead of snake_case. 24 | * @ignore 25 | * @param obj - The object to be formatted. 26 | * @param context - An optional context with information about the called method required to perform blacklist lookups. 27 | * @returns - The new, formatted object. 28 | * @example 29 | * const outputRes = formatAPIObject({unread_only: true}) 30 | * console.log(outputRes) // {unreadOnly: true} 31 | */ 32 | export declare function formatAPIObjectOutput(obj: any, context: FormatAPIObjectOutputContext | null): any; 33 | -------------------------------------------------------------------------------- /src/helpers-client/index.ts: -------------------------------------------------------------------------------- 1 | import ClientBase from '../client-base' 2 | import {keybaseExec} from '../utils' 3 | 4 | // TODO: API calls can support other stuff such as POSTS 5 | // and we should add those. Just starting simple. 6 | // see `keybase apicall --help` 7 | interface ApiCallArg { 8 | endpoint: string 9 | arg?: any 10 | } 11 | 12 | /* A module of various helper functions for your bot 13 | */ 14 | class Helpers extends ClientBase { 15 | /** 16 | * Make a call to a Keybase URL (TODO: add support for POST params) 17 | * @memberof Helpers 18 | * @param url - a full URL to hit 19 | * @returns - 20 | * @example 21 | * bot.helpers.rawApiCall({endpoint:"/me"}).then(res => console.log(res)) 22 | */ 23 | public async rawApiCall(arg: ApiCallArg): Promise { 24 | await this._guardInitialized() 25 | const args = [arg.endpoint] 26 | if (arg.arg) { 27 | for (const k in arg.arg) { 28 | args.unshift('-a', `${k}=${arg.arg[k]}`) 29 | } 30 | } 31 | args.unshift('apicall') 32 | const output = await keybaseExec(this._workingDir, this.homeDir, args, {json: true}) 33 | return output 34 | } 35 | 36 | /** 37 | * Ping keybase server. The returned promise resolves the keybase daemon is 38 | * up and server is reachable. 39 | * @memberof Helpers 40 | * @returns - 41 | * @example 42 | * bot.helpers.ping() 43 | */ 44 | public async ping(): Promise { 45 | await this._guardInitialized() 46 | return await keybaseExec(this._workingDir, this.homeDir, ['ping']) 47 | } 48 | } 49 | 50 | export default Helpers 51 | -------------------------------------------------------------------------------- /lib/team-client/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/team-client/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+DAAuC;AAsBvC,uIAAuI;AACvI;IAAmB,wBAAU;IAA7B;;IAkEA,CAAC;IAjEC;;;;;;;OAOG;IACU,qBAAM,GAAnB,UAAoB,QAAyB;;;;;4BAC3C,qBAAM,IAAI,CAAC,iBAAiB,EAAE,EAAA;;wBAA9B,SAA8B,CAAA;wBACxB,OAAO,GAAG,QAAQ,CAAA;wBACZ,qBAAM,IAAI,CAAC,cAAc,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,SAAA,EAAC,CAAC,EAAA;;wBAAlF,GAAG,GAAG,SAA4E;wBACxF,IAAI,CAAC,GAAG,EAAE;4BACR,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAA;yBAC1B;wBACD,sBAAO,GAAG,EAAA;;;;KACX;IACD;;;;;;;OAOG;IACU,yBAAU,GAAvB,UAAwB,SAA0B;;;;;4BAChD,qBAAM,IAAI,CAAC,iBAAiB,EAAE,EAAA;;wBAA9B,SAA8B,CAAA;wBACxB,OAAO,GAAG,SAAS,CAAA;wBACb,qBAAM,IAAI,CAAC,cAAc,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,SAAA,EAAC,CAAC,EAAA;;wBAAlF,GAAG,GAAG,SAA4E;wBACxF,IAAI,CAAC,GAAG,EAAE;4BACR,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,CAAA;yBAC9B;wBACD,sBAAO,GAAG,EAAA;;;;KACX;IAED;;;;;;OAMG;IACU,2BAAY,GAAzB,UAA0B,OAA0B;;;;;4BAClD,qBAAM,IAAI,CAAC,iBAAiB,EAAE,EAAA;;wBAA9B,SAA8B,CAAA;wBACxB,OAAO,GAAG,OAAO,CAAA;wBACvB,qBAAM,IAAI,CAAC,cAAc,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,SAAA,EAAC,CAAC,EAAA;;wBAA9E,SAA8E,CAAA;;;;;KAC/E;IAED;;;;;;;OAOG;IACU,kCAAmB,GAAhC,UAAiC,IAA8B;;;;;4BAC7D,qBAAM,IAAI,CAAC,iBAAiB,EAAE,EAAA;;wBAA9B,SAA8B,CAAA;wBACxB,OAAO,GAAG,IAAI,CAAA;wBACR,qBAAM,IAAI,CAAC,cAAc,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,EAAE,OAAO,SAAA,EAAC,CAAC,EAAA;;wBAA5F,GAAG,GAAG,SAAsF;wBAClG,IAAI,CAAC,GAAG,EAAE;4BACR,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;yBACvC;wBACD,sBAAO,GAAG,EAAA;;;;KACX;IACH,WAAC;AAAD,CAAC,AAlED,CAAmB,qBAAU,GAkE5B;AAED,kBAAe,IAAI,CAAA"} -------------------------------------------------------------------------------- /demos/es7/advertised-echo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | 4 | const bot = new Bot() 5 | 6 | async function main() { 7 | try { 8 | const username = process.env.KB_USERNAME 9 | const paperkey = process.env.KB_PAPERKEY 10 | await bot.init(username, paperkey) 11 | const info = bot.myInfo() 12 | console.log(`Echo bot initialized with username ${info.username}.`) 13 | 14 | await bot.chat.clearCommands() 15 | await bot.chat.advertiseCommands({ 16 | advertisements: [ 17 | { 18 | type: 'public', 19 | commands: [ 20 | { 21 | name: 'echo', 22 | description: 'Sends out your message to the current channel.', 23 | usage: '[your text]', 24 | }, 25 | ], 26 | }, 27 | ], 28 | }) 29 | 30 | const onMessage = async message => { 31 | if (message.content.type !== 'text') { 32 | return 33 | } 34 | 35 | if (!message.content.text.body.startsWith('!echo ')) { 36 | return 37 | } 38 | 39 | bot.chat.send(message.conversationId, { 40 | body: message.content.text.body.substr(6), 41 | }) 42 | } 43 | 44 | const onError = e => console.error(e) 45 | console.log(`Listening for messages...`) 46 | await bot.chat.watchAllChannelsForNewMessages(onMessage, onError) 47 | } catch (error) { 48 | console.error(error) 49 | } 50 | } 51 | 52 | async function shutDown() { 53 | await bot.deinit() 54 | process.exit() 55 | } 56 | 57 | process.on('SIGINT', shutDown) 58 | process.on('SIGTERM', shutDown) 59 | 60 | main() 61 | -------------------------------------------------------------------------------- /demos/compiled-from-typescript/hunt-for-resets.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var __importDefault = (this && this.__importDefault) || function (mod) { 4 | return (mod && mod.__esModule) ? mod : { "default": mod }; 5 | }; 6 | Object.defineProperty(exports, "__esModule", { value: true }); 7 | const index_js_1 = __importDefault(require("../../lib/index.js")); 8 | // 9 | // have you ever had a direct conversation with someone, and then they "reset" 10 | // their account? This locks them out, and you're supposed to check their proofs 11 | // and let them back in, only if you're satisfied. Well...bots are always satisfied 12 | // so your bot can just let them back in...if you want. 13 | // 14 | // Note this bot demo isn't using a username + paperkey because it's also demo'ing running 15 | // against your own logged in user. 16 | // 17 | async function letThemIn(bot, p) { 18 | try { 19 | await bot.chat.addResetConvMember(p); 20 | console.log(`SUCCEESS - let in ${p.username} to ${p.conversationId}`); 21 | } 22 | catch (err) { 23 | console.error(`ERROR - to let in ${p.username} to ${p.conversationId}`, err); 24 | } 25 | } 26 | async function main() { 27 | const bot = new index_js_1.default(); 28 | try { 29 | await bot.initFromRunningService(); 30 | console.log(`Your bot is initialized. It is logged in as ${bot.myInfo().username}`); 31 | const problemChats = await bot.chat.getResetConvMembers(); 32 | for (const p of problemChats.members) { 33 | await letThemIn(bot, p); 34 | } 35 | } 36 | catch (error) { 37 | console.error(error); 38 | } 39 | finally { 40 | await bot.deinit(); 41 | } 42 | } 43 | main(); 44 | -------------------------------------------------------------------------------- /documentation.yml: -------------------------------------------------------------------------------- 1 | # Configuration for documentation.js 2 | # Specifies the table of contents order and child docs 3 | # CONFIG: https://github.com/documentationjs/documentation/blob/master/docs/CONFIG.md 4 | toc: 5 | - Bot 6 | - name: Bot Types 7 | description: | 8 | A collection of types used by the bot. 9 | children: 10 | - InitOptions 11 | - BotInfo 12 | - Chat 13 | - name: Chat Types 14 | description: | 15 | A collection of types used by the Chat module. 16 | children: 17 | - ChatChannel 18 | - ChatMessage 19 | - Thread 20 | - MsgSummary 21 | - MsgContent 22 | - ConvSummary 23 | - SendRes 24 | - UICoinFlipStatus 25 | - UnfurlSettings 26 | - UserBotCommandOutput 27 | - ChatAttachOptions 28 | - ChatDownloadOptions 29 | - ChatReactOptions 30 | - ChatDeleteOptions 31 | - Advertisement 32 | - AdvertisementsLookup 33 | - OnMessage 34 | - OnError 35 | - ListenOptions 36 | - KVStore 37 | - name: KVStore Types 38 | description: | 39 | A collection of types used by the KVStore module. 40 | children: 41 | - KVListNamespaceResult 42 | - KVListEntryResult 43 | - KVGetResult 44 | - KVPutResult 45 | - KVDeleteEntryResult 46 | - Team 47 | - name: Team Types 48 | description: | 49 | A collection of types used by the Team module. 50 | children: 51 | - AddMembersParam 52 | - RemoveMemberParam 53 | - ListTeamMembershipParam 54 | - TeamAddMemberResult 55 | - TeamDetails 56 | - Wallet 57 | - name: Wallet Types 58 | description: | 59 | A collection of types used by the Wallet module. 60 | children: 61 | - AccountID 62 | - TransactionID 63 | - OwnAccountCLILocal 64 | - PaymentCLILocal 65 | - BatchPaymentArg 66 | - BatchResultLocal 67 | -------------------------------------------------------------------------------- /__tests__/chat.simple.watch.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | import {pollFor} from './test-utils' 4 | import {ChatChannel, MsgSummary} from '../lib/types/chat1' 5 | 6 | // 7 | // Coyne: I created this test specifically as a standalone because we have been hacing an issue 8 | // where a freshly initialized bot isn't outputting on calls to watchAllChannelsForNewMessages() 9 | // this should pass when that is fixed. It's also in chat.test.ts, but that's full of tests. We 10 | // can delete this file once all tests are passing. 11 | 12 | describe('Chat Methods', (): void => { 13 | const alice = new Bot() 14 | const bob = new Bot() 15 | 16 | beforeAll( 17 | async (): Promise => { 18 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey, {adminDebugDirectory: `${__dirname}/../debug/alice`}) 19 | await bob.init(config.bots.bob1.username, config.bots.bob1.paperkey, {adminDebugDirectory: `${__dirname}/../debug/bob`}) 20 | } 21 | ) 22 | afterAll( 23 | async (): Promise => { 24 | await alice.deinit() 25 | await bob.deinit() 26 | } 27 | ) 28 | 29 | it('works', async (): Promise => { 30 | let done = false 31 | const directChannel: ChatChannel = {name: `${bob.myInfo()?.username},${alice.myInfo()?.username}`} 32 | bob.chat.watchAllChannelsForNewMessages((message: MsgSummary): void => { 33 | bob.adminDebugLogInfo(`I Got a message ${JSON.stringify(message)}`) 34 | done = true 35 | }) 36 | alice.chat.watchAllChannelsForNewMessages((message: MsgSummary): void => { 37 | alice.adminDebugLogInfo(`I Got a message ${JSON.stringify(message)}`) 38 | }) 39 | alice.chat.send(directChannel, {body: `HI THERE ALICE, THIS IS BOB AND THE TIME IS ${new Date().toISOString()}`}) 40 | await pollFor((): boolean => done) 41 | 42 | expect(done).toBe(true) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /lib/utils/adminDebugLogger.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"adminDebugLogger.js","sourceRoot":"","sources":["../../src/utils/adminDebugLogger.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAA2B;AAC3B,6BAA8B;AAC9B,yBAAuC;AACvC,0CAAmB;AACnB,8CAAuB;AACvB,iCAA+B;AAE/B;IAgBE,0BAAmB,KAAa;QAC9B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;IACzB,CAAC;IAbD,sBAAW,uCAAS;aAApB;YACE,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAA;QAC7B,CAAC;;;OAAA;IACD,sBAAW,sCAAQ;aAAnB;YACE,IAAI,IAAI,CAAC,SAAS,EAAE;gBAClB,OAAO,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAe,IAAI,CAAC,MAAM,aAAU,CAAC,CAAA;aACvE;iBAAM;gBACL,OAAO,IAAI,CAAA;aACZ;QACH,CAAC;;;OAAA;IAKY,+BAAI,GAAjB,UAAkB,MAAc,EAAE,iBAAyB;;;;;wBACzD,IAAI,CAAC,kBAAkB,GAAG,iBAAiB,CAAA;wBAC3C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;6BACjB,IAAI,CAAC,SAAS,EAAd,wBAAc;wBAAE,qBAAM,gBAAS,CAAC,gBAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,EAAA;;wBAAvC,SAAuC,CAAA;;;wBAC3D,IAAI,CAAC,SAAS,EAAE,CAAA;;;;;KACjB;IACM,iCAAM,GAAb;QACE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;IACxB,CAAC;IACY,+BAAI,GAAjB,UAAkB,IAAY;;;;4BAC5B,qBAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,EAAA;;wBAA5B,SAA4B,CAAA;;;;;KAC7B;IACY,gCAAK,GAAlB,UAAmB,IAAY;;;;4BAC7B,qBAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,EAAA;;wBAA5B,SAA4B,CAAA;;;;;KAC7B;IACa,iCAAM,GAApB,UAAqB,IAAY,EAAE,IAAe;;;;;;6BAC5C,CAAA,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAA,EAA/B,wBAA+B;wBAC3B,IAAI,GAAM,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,UAAK,IAAI,UAAK,IAAI,GAAG,YAAE,CAAC,GAAK,CAAA;wBACrE,qBAAM,gBAAS,CAAC,eAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,EAAA;;wBAAzD,SAAyD,CAAA;;;;;;KAE5D;IACa,oCAAS,GAAvB;;;;;;6BAGS,CAAC,IAAI,CAAC,UAAU;;;;6BAEf,CAAA,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,kBAAkB,CAAA,EAAzC,wBAAyC;wBACrC,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAe,IAAI,CAAC,MAAM,iBAAc,CAAC,CAAA;wBACvF,qBAAM,gBAAS,CAAC,aAAQ,CAAC,CAAC,IAAI,CAAC,kBAAkB,EAAE,WAAW,CAAC,EAAA;;wBAA/D,SAA+D,CAAA;;;;;wBAGjE,IAAI,CAAC,KAAK,CAAC,gCAA8B,GAAC,CAAC,QAAQ,EAAI,CAAC,CAAA;;4BAE1D,qBAAM,eAAO,CAAC,GAAG,CAAC,EAAA;;wBAAlB,SAAkB,CAAA;;;;;;KAErB;IACH,uBAAC;AAAD,CAAC,AAxDD,IAwDC;AAxDY,4CAAgB"} -------------------------------------------------------------------------------- /demos/es7/team-inviter.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | 4 | const bot = new Bot() 5 | 6 | const teamName = process.env.KB_TEAM 7 | 8 | async function main() { 9 | try { 10 | const username = process.env.KB_USERNAME 11 | const paperkey = process.env.KB_PAPERKEY 12 | await bot.init(username, paperkey) 13 | const info = bot.myInfo() 14 | console.log(`Bot initialized with username ${info.username}.`) 15 | 16 | console.log(`Listening for all messages...`) 17 | await bot.chat.watchAllChannelsForNewMessages( 18 | async msg => { 19 | try { 20 | console.log(msg) 21 | 22 | if (msg.channel.membersType !== 'impteamnative' && msg.channel.membersType !== 'impteamupgrade') { 23 | // Only react to direct messages. 24 | console.log(`Invalid type - ${msg.channel.membersType}`) 25 | return 26 | } 27 | 28 | if (msg.content.type === 'text' && msg.content.text.body.toLowerCase().includes('invite me')) { 29 | await bot.team.addMembers({ 30 | team: teamName, 31 | usernames: [{username: msg.sender.username, role: 'reader'}], 32 | }) 33 | await bot.chat.send(msg.conversationId, { 34 | body: `There you go! Welcome to ${teamName}!`, 35 | }) 36 | return 37 | } 38 | 39 | await bot.chat.send(msg.conversationId, { 40 | body: `Hi ${msg.sender.username}! I'm a proof of concept of a bot that invites users into an open team - in this case ${teamName}. Reply with "invite me" and I'll add you there!`, 41 | }) 42 | } catch (err) { 43 | console.error(err) 44 | } 45 | }, 46 | e => console.error(e) 47 | ) 48 | } catch (error) { 49 | console.error(error) 50 | } 51 | } 52 | 53 | async function shutDown() { 54 | await bot.deinit() 55 | process.exit() 56 | } 57 | 58 | process.on('SIGINT', shutDown) 59 | process.on('SIGTERM', shutDown) 60 | 61 | main() 62 | -------------------------------------------------------------------------------- /lib/utils/keybaseExec.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"keybaseExec.js","sourceRoot":"","sources":["../../src/utils/keybaseExec.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /demos/es7/math-bot-es7.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | const mathjs = require('mathjs') 4 | 5 | // 6 | // This bot replies to any message from any user, 7 | // starting with `/math` (in any channel) 8 | // by actually trying to do the math. For example 9 | // send it : 10 | // 11 | // /math sqrt(pi/2) * 3!` 12 | // 13 | 14 | const bot = new Bot() 15 | 16 | const msgReply = s => { 17 | let a1, a2, ans, b1, b2, eqn 18 | try { 19 | ans = '= ' + mathjs['eval'](s).toString() 20 | } catch (e) { 21 | a1 = Math.floor(Math.random() * 10) 22 | b1 = Math.floor(Math.random() * 10) 23 | a2 = Math.floor(Math.random() * 10) 24 | b2 = Math.floor(Math.random() * 10) 25 | eqn = '(' + a1 + ' + ' + b1 + 'i) * (' + a2 + ' + ' + b2 + 'i)' 26 | ans = "Sorry, I can't do that math. Did you know " + eqn + ' = ' + mathjs['eval'](eqn).toString() + '? True.' 27 | } 28 | return ans 29 | } 30 | 31 | async function main() { 32 | try { 33 | const username = process.env.KB_USERNAME 34 | const paperkey = process.env.KB_PAPERKEY 35 | await bot.init(username, paperkey) 36 | console.log('I am me!', bot.myInfo().username, bot.myInfo().devicename) 37 | console.log('Beginning watch for new messages.') 38 | console.log(`Tell anyone to send a message to ${bot.myInfo().username} starting with '/math '`) 39 | 40 | const onMessage = async message => { 41 | if (message.content.type === 'text') { 42 | const prefix = message.content.text.body.slice(0, 6) 43 | if (prefix === '/math ') { 44 | const reply = {body: msgReply(message.content.text.body.slice(6))} 45 | await bot.chat.send(message.conversationId, reply) 46 | } 47 | } 48 | } 49 | const onError = e => console.error(e) 50 | bot.chat.watchAllChannelsForNewMessages(onMessage, onError) 51 | } catch (error) { 52 | console.error(error.message) 53 | } 54 | } 55 | 56 | async function shutDown() { 57 | await bot.deinit() 58 | process.exit() 59 | } 60 | 61 | process.on('SIGINT', shutDown) 62 | process.on('SIGTERM', shutDown) 63 | 64 | main() 65 | -------------------------------------------------------------------------------- /demos/math-bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../lib/index.js') 3 | const mathjs = require('mathjs') 4 | 5 | // 6 | // This bot replies to any message from any user, 7 | // starting with `/math` (in any channel) 8 | // by actually trying to do the math. For example 9 | // send it : 10 | // 11 | // /math sqrt(pi/2) * 3!` 12 | // 13 | 14 | const bot = new Bot() 15 | 16 | const msgReply = s => { 17 | let a1, a2, ans, b1, b2, eqn 18 | try { 19 | ans = '= ' + mathjs['eval'](s).toString() 20 | } catch (e) { 21 | a1 = Math.floor(Math.random() * 10) 22 | b1 = Math.floor(Math.random() * 10) 23 | a2 = Math.floor(Math.random() * 10) 24 | b2 = Math.floor(Math.random() * 10) 25 | eqn = '(' + a1 + ' + ' + b1 + 'i) * (' + a2 + ' + ' + b2 + 'i)' 26 | ans = "Sorry, I can't do that math. Did you know " + eqn + ' = ' + mathjs['eval'](eqn).toString() + '? True.' 27 | } 28 | return ans 29 | } 30 | 31 | function main() { 32 | const username = process.env.KB_USERNAME 33 | const paperkey = process.env.KB_PAPERKEY 34 | bot 35 | .init(username, paperkey) 36 | .then(() => { 37 | console.log('I am me!', bot.myInfo().username, bot.myInfo().devicename) 38 | console.log('Beginning watch for new messages.') 39 | console.log(`Tell anyone to send a message to ${bot.myInfo().username} starting with '/math '`) 40 | const onMessage = message => { 41 | if (message.content.type === 'text') { 42 | const prefix = message.content.text.body.slice(0, 6) 43 | if (prefix === '/math ') { 44 | const reply = {body: msgReply(message.content.text.body.slice(6))} 45 | bot.chat.send(message.conversationId, reply) 46 | } 47 | } 48 | } 49 | const onError = e => console.error(e) 50 | bot.chat.watchAllChannelsForNewMessages(onMessage, onError) 51 | }) 52 | .catch(error => { 53 | console.error(error) 54 | shutDown() 55 | }) 56 | } 57 | 58 | function shutDown() { 59 | bot.deinit().then(() => process.exit()) 60 | } 61 | 62 | process.on('SIGINT', shutDown) 63 | process.on('SIGTERM', shutDown) 64 | 65 | main() 66 | -------------------------------------------------------------------------------- /lib/types/stellar1/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/stellar1/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /demos/es7/flippy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | 4 | /* 5 | * Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values_inclusive 6 | */ 7 | function getRandomIntInclusive(min, max) { 8 | min = Math.ceil(min) 9 | max = Math.floor(max) 10 | return Math.floor(Math.random() * (max - min + 1)) + min 11 | } 12 | 13 | const bot = new Bot() 14 | 15 | async function main() { 16 | const channel = { 17 | name: process.env.KB_TEAM, 18 | public: false, 19 | topicType: 'chat', 20 | membersType: 'team', 21 | topicName: 'general', 22 | } 23 | 24 | try { 25 | const username = process.env.KB_USERNAME 26 | const paperkey = process.env.KB_PAPERKEY 27 | await bot.init(username, paperkey) 28 | const info = bot.myInfo() 29 | console.log(`Flippy initialized with username ${info.username}.`) 30 | 31 | const onMessage = async message => { 32 | if (message.content.type === 'text') { 33 | // TODO: could probably normalize the body a little more before comparisons 34 | const messageBody = message.content.text.body.toLowerCase() 35 | switch (messageBody) { 36 | case 'new flip': 37 | console.log('Starting a new flip!') 38 | const messageToSend = {body: 'Starting a new flip...'} 39 | await bot.chat.send(channel, messageToSend) 40 | break 41 | case 'coin flip': 42 | case 'coyne flip': 43 | const flip = getRandomIntInclusive(0, 1) ? ':+1:' : ':-1:' 44 | console.log('Making a flip:', flip) 45 | await bot.chat.react(channel, message.id, flip) 46 | break 47 | default: 48 | break 49 | } 50 | } 51 | } 52 | 53 | const onError = e => console.error(e) 54 | console.log(`Listening in the general channel of ${channel.name}...`) 55 | await bot.chat.watchChannelForNewMessages(channel, onMessage, onError) 56 | } catch (error) { 57 | console.error(error) 58 | } 59 | } 60 | 61 | async function shutDown() { 62 | await bot.deinit() 63 | process.exit() 64 | } 65 | 66 | process.on('SIGINT', shutDown) 67 | process.on('SIGTERM', shutDown) 68 | 69 | main() 70 | -------------------------------------------------------------------------------- /src/utils/adminDebugLogger.ts: -------------------------------------------------------------------------------- 1 | import mkdirp from 'mkdirp' 2 | import {promisify} from 'util' 3 | import {appendFile, copyFile} from 'fs' 4 | import os from 'os' 5 | import path from 'path' 6 | import {timeout} from './index' 7 | 8 | export class AdminDebugLogger { 9 | private _logDir?: string 10 | private _botId: string 11 | private _botServiceLogPath?: string 12 | private _deinitYet: boolean 13 | 14 | public get directory(): string | null { 15 | return this._logDir || null 16 | } 17 | public get filename(): string | null { 18 | if (this.directory) { 19 | return path.join(this.directory, `keybase_bot_${this._botId}.bot.log`) 20 | } else { 21 | return null 22 | } 23 | } 24 | public constructor(botId: string) { 25 | this._botId = botId 26 | this._deinitYet = false 27 | } 28 | public async init(logDir: string, botServiceLogPath: string): Promise { 29 | this._botServiceLogPath = botServiceLogPath 30 | this._logDir = logDir 31 | if (this.directory) await promisify(mkdirp)(this.directory) 32 | this._copyLoop() 33 | } 34 | public deinit(): void { 35 | this._deinitYet = true 36 | } 37 | public async info(text: string): Promise { 38 | await this._logIt(text, 'E') 39 | } 40 | public async error(text: string): Promise { 41 | await this._logIt(text, 'I') 42 | } 43 | private async _logIt(text: string, code: 'E' | 'I'): Promise { 44 | if (this.directory && this.filename) { 45 | const line = `${new Date().toISOString()} [${code}] ${text}${os.EOL}` 46 | await promisify(appendFile)(this.filename, line, 'utf-8') 47 | } 48 | } 49 | private async _copyLoop(): Promise { 50 | // should do something with streams here, but for now when running in admindebug mode, 51 | // I'll just copy the logs over, taking a break after each copy. 52 | while (!this._deinitYet) { 53 | try { 54 | if (this.directory && this._botServiceLogPath) { 55 | const destination = path.join(this.directory, `keybase_bot_${this._botId}.service.log`) 56 | await promisify(copyFile)(this._botServiceLogPath, destination) 57 | } 58 | } catch (e) { 59 | this.error(`Couldn't copy service log. ${e.toString()}`) 60 | } 61 | await timeout(900) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/client-base/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client-base/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/utils/keybaseExec.ts: -------------------------------------------------------------------------------- 1 | import {spawn} from 'child_process' 2 | import readline from 'readline' 3 | import path from 'path' 4 | import keybaseBinaryName from './keybaseBinaryName' 5 | 6 | export type ExecOptions = { 7 | stdinBuffer?: Buffer | string 8 | onStdOut?: (line: string) => void 9 | json?: boolean 10 | timeout?: number 11 | } 12 | 13 | const keybaseExec = ( 14 | workingDir: string, 15 | homeDir: void | string, 16 | args: string[], 17 | options: ExecOptions = {stdinBuffer: undefined, onStdOut: undefined, timeout: undefined} 18 | ): Promise => { 19 | const runArgs: string[] = [...args] 20 | if (homeDir) { 21 | runArgs.unshift('--home', homeDir) 22 | } 23 | const keybasePath = path.join(workingDir, keybaseBinaryName) 24 | const child = spawn(keybasePath, runArgs) 25 | const stdOutBuffer: Buffer[] = [] 26 | const stdErrBuffer: Buffer[] = [] 27 | 28 | if (options.stdinBuffer) { 29 | child.stdin.write(options.stdinBuffer) 30 | } 31 | child.stdin.end() 32 | 33 | const lineReaderStdout = readline.createInterface({input: child.stdout}) 34 | 35 | // Use readline interface to parse each line (\n separated) when provided 36 | // with onStdOut callback 37 | if (options.onStdOut) { 38 | lineReaderStdout.on('line', options.onStdOut) 39 | } else { 40 | child.stdout.on('data', chunk => { 41 | stdOutBuffer.push(chunk) 42 | }) 43 | } 44 | // Capture STDERR and use as error message if needed 45 | child.stderr.on('data', chunk => { 46 | stdErrBuffer.push(chunk) 47 | }) 48 | 49 | let done = false 50 | if (options.timeout) { 51 | setTimeout(() => { 52 | if (!done) { 53 | child.kill() 54 | } 55 | }, options.timeout) 56 | } 57 | 58 | return new Promise((resolve, reject) => { 59 | child.on('close', code => { 60 | done = true 61 | 62 | let finalStdOut: string | null = null 63 | // Pass back 64 | if (code) { 65 | const errorMessage = Buffer.concat(stdErrBuffer).toString('utf8') 66 | return reject(new Error(errorMessage)) 67 | } else { 68 | const stdout = Buffer.concat(stdOutBuffer).toString('utf8') 69 | 70 | try { 71 | finalStdOut = options.json ? JSON.parse(stdout) : stdout 72 | } catch (e) { 73 | return reject(e) 74 | } 75 | } 76 | return resolve(finalStdOut) 77 | }) 78 | }) 79 | } 80 | 81 | export default keybaseExec 82 | -------------------------------------------------------------------------------- /lib/team-client/index.d.ts: -------------------------------------------------------------------------------- 1 | import ClientBase from '../client-base'; 2 | import * as keybase1 from '../types/keybase1'; 3 | export interface CreateTeamParam { 4 | team: string; 5 | } 6 | export interface AddMembersParam { 7 | team: string; 8 | emails?: keybase1.MemberEmail[]; 9 | usernames?: keybase1.MemberUsername[]; 10 | } 11 | export interface RemoveMemberParam { 12 | team: string; 13 | username: string; 14 | } 15 | export interface ListTeamMembershipsParam { 16 | team: string; 17 | } 18 | /** The team module of your Keybase bot. For more info about the API this module uses, you may want to check out `keybase team api`. */ 19 | declare class Team extends ClientBase { 20 | /** 21 | * Create a new Keybase team or subteam 22 | * @memberof Team 23 | * @param creation - the name of the team to create 24 | * @returns - 25 | * @example 26 | * bot.team.create({"team": "phoenix"}).then(res => console.log(res)) 27 | */ 28 | create(creation: CreateTeamParam): Promise; 29 | /** 30 | * Add a bunch of people with different privileges to a team 31 | * @memberof Team 32 | * @param additions - an array of the users to add, with privs 33 | * @returns - A result object of adding these members to the team. 34 | * @example 35 | * bot.team.addMembers({"team": "phoenix", "emails": [{"email": "alice@keybase.io", "role": "writer"}, {"email": "cleo@keybase.io", "role": "admin"}], "usernames": [{"username": "frank", "role": "reader"}, {"username": "keybaseio@twitter", "role": "writer"}]}).then(res => console.log(res)) 36 | */ 37 | addMembers(additions: AddMembersParam): Promise; 38 | /** 39 | * Remove someone from a team. 40 | * @memberof Team 41 | * @param removal - object with the `team` name and `username` 42 | * @example 43 | * bot.team.removeMember({"team": "phoenix", "username": "frank"}).then(res => console.log(res)) 44 | */ 45 | removeMember(removal: RemoveMemberParam): Promise; 46 | /** 47 | * List a team's members. 48 | * @memberof Team 49 | * @param team - an object with the `team` name in it. 50 | * @returns - Details about the team. 51 | * @example 52 | * bot.team.listTeamMemberships({"team": "phoenix"}).then(res => console.log(res)) 53 | */ 54 | listTeamMemberships(team: ListTeamMembershipsParam): Promise; 55 | } 56 | export default Team; 57 | -------------------------------------------------------------------------------- /src/utils/formatAPIObject.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/camelcase */ 2 | import {formatAPIObjectInput, formatAPIObjectOutput} from './formatAPIObject' 3 | 4 | describe('formatAPIObjectInput', () => { 5 | it('works', () => { 6 | const input = { 7 | conversationId: 'blah', 8 | nonblock: false, 9 | members: { 10 | memberType: 'user', 11 | memberIds: [1, 2, 3, 4, 5], 12 | }, 13 | } 14 | let formattedInput = formatAPIObjectInput(input, 'chat') 15 | expect(formattedInput).toEqual({ 16 | conversation_id: 'blah', 17 | nonblock: false, 18 | members: { 19 | member_type: 'user', 20 | member_ids: [1, 2, 3, 4, 5], 21 | }, 22 | }) 23 | formattedInput = formatAPIObjectInput(input, 'wallet') 24 | expect(formattedInput).toEqual({ 25 | 'conversation-id': 'blah', 26 | nonblock: false, 27 | members: { 28 | 'member-type': 'user', 29 | 'member-ids': [1, 2, 3, 4, 5], 30 | }, 31 | }) 32 | }) 33 | }) 34 | 35 | describe('formatAPIObjectOutput', () => { 36 | it('works', () => { 37 | const output = { 38 | conversation_id: 'blah', 39 | nonblock: false, 40 | members: { 41 | member_type: 'user', 42 | member_ids: [1, 2, 3, 4, 5], 43 | }, 44 | } 45 | const formattedOutput = formatAPIObjectOutput(output, null) 46 | expect(formattedOutput).toEqual({ 47 | conversationId: 'blah', 48 | nonblock: false, 49 | members: { 50 | memberType: 'user', 51 | memberIds: [1, 2, 3, 4, 5], 52 | }, 53 | }) 54 | }) 55 | 56 | it('works with a blacklisted field', () => { 57 | const output = { 58 | messages: [ 59 | { 60 | msg: { 61 | message_id: 'blah', 62 | reactions: { 63 | reactions: { 64 | ':poop:': {the_bob: {some_field: 'cool!'}}, 65 | }, 66 | }, 67 | }, 68 | }, 69 | ], 70 | } 71 | const formattedOutput = formatAPIObjectOutput(output, { 72 | apiName: 'chat', 73 | method: 'read', 74 | }) 75 | expect(formattedOutput).toEqual({ 76 | messages: [ 77 | { 78 | msg: { 79 | messageId: 'blah', 80 | reactions: { 81 | reactions: { 82 | poop: {the_bob: {someField: 'cool!'}}, 83 | }, 84 | }, 85 | }, 86 | }, 87 | ], 88 | }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /demos/es7/poker-hands.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | const {Hand} = require('pokersolver') 4 | 5 | const bot = new Bot() 6 | 7 | const deck = 8 | '2s,3s,4s,5s,6s,7s,8s,9s,Ts,Js,Qs,Ks,As,2c,3c,4c,5c,6c,7c,8c,9c,Tc,Jc,Qc,Kc,Ac,2d,3d,4d,5d,6d,7d,8d,9d,Td,Jd,Qd,Kd,Ad,2h,3h,4h,5h,6h,7h,8h,9h,Th,Jh,Qh,Kh,Ah' 9 | const deckI2S = deck.split(',').reduce((prev, curr, ind) => { 10 | prev[ind] = curr 11 | return prev 12 | }, {}) 13 | 14 | // Tells the cards in user's poker flip 15 | async function main() { 16 | try { 17 | const username = process.env.KB_USERNAME 18 | const paperkey = process.env.KB_PAPERKEY 19 | await bot.init(username, paperkey) 20 | const info = bot.myInfo() 21 | console.log(`Bot initialized with username ${info.username}.`) 22 | 23 | console.log(`Listening for all messages...`) 24 | await bot.chat.watchAllChannelsForNewMessages( 25 | async msg => { 26 | try { 27 | console.log(msg) 28 | 29 | if (msg.channel.membersType !== 'impteamnative') { 30 | // Only react to direct messages. 31 | console.log(`Invalid type - ${msg.channel.membersType}`) 32 | return 33 | } 34 | 35 | if (msg.content.type === 'flip') { 36 | let resolved 37 | while (!resolved) { 38 | try { 39 | const flip = await bot.chat.loadFlip(msg.conversationId, msg.content.flip.flipConvId, msg.id, msg.content.flip.gameId) 40 | if (flip.phase === 2) { 41 | resolved = true 42 | 43 | if (flip.resultInfo.typ !== 3 || !flip.resultInfo.hands.find(x => x.target === 'me')) { 44 | await bot.chat.send(msg.conversationId, { 45 | body: `Invalid flip type! Please do a /flip cards 5 me instead.`, 46 | }) 47 | return 48 | } 49 | 50 | const myHand = flip.resultInfo.hands.find(x => x.target === 'me') 51 | const hand = Hand.solve(myHand.hand.map(card => deckI2S[card])) 52 | await bot.chat.send(msg.conversationId, { 53 | body: `You got ${hand.descr}.`, 54 | }) 55 | } 56 | } catch (err) { 57 | console.log('flip fetch error', err) 58 | } 59 | } 60 | return 61 | } 62 | 63 | await bot.chat.send(msg.conversationId, { 64 | body: `Hi ${msg.sender.username}! Do a /flip cards 5 me and I'll tell what's in your poker hand!`, 65 | }) 66 | } catch (err) { 67 | console.error(err) 68 | } 69 | }, 70 | e => console.error(e) 71 | ) 72 | } catch (error) { 73 | console.error(error) 74 | } 75 | } 76 | 77 | async function shutDown() { 78 | await bot.deinit() 79 | process.exit() 80 | } 81 | 82 | process.on('SIGINT', shutDown) 83 | process.on('SIGTERM', shutDown) 84 | 85 | main() 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keybase-bot", 3 | "version": "4.0.0", 4 | "description": "Script Keybase in Node.js!", 5 | "keywords": [ 6 | "keybase", 7 | "kbfs", 8 | "bot", 9 | "chatbot", 10 | "encryption", 11 | "crypto", 12 | "pgp", 13 | "gpg" 14 | ], 15 | "main": "lib/index.js", 16 | "types": "lib/index.d.ts", 17 | "homepage": "https://github.com/keybase/keybase-bot", 18 | "repository": "git@github.com:keybase/keybase-bot.git", 19 | "bugs": { 20 | "url": "https://github.com/keybase/keybase-bot/issues" 21 | }, 22 | "author": "Chris Coyne ", 23 | "license": "BSD-3-Clause", 24 | "engines": { 25 | "node": ">=8.0.0" 26 | }, 27 | "files": [ 28 | "lib/*" 29 | ], 30 | "husky": { 31 | "hooks": { 32 | "pre-commit": "lint-staged" 33 | } 34 | }, 35 | "lint-staged": { 36 | "*.ts": [ 37 | "eslint --fix" 38 | ], 39 | "*.{json,md,ts}": [ 40 | "prettier --write", 41 | "git add" 42 | ] 43 | }, 44 | "scripts": { 45 | "modules": "yarn --frozen-lockfile --prefer-offline", 46 | "modules-prod": "yarn --frozen-lockfile --prefer-offline --prod", 47 | "docs": "documentation readme src/{{chat,wallet,team,kvstore}-client,index.ts} --config documentation.yml --require-extension=.ts --parse-extension=.ts --babel=./.babelrc.js --section=API --shallow --github && prettier README.md --write", 48 | "dev": "tsc --watch", 49 | "test": "jest --runInBand", 50 | "release": "standard-version", 51 | "build": "rm -rf lib/ && tsc", 52 | "build-demos": "rm -rf demos/javascript && tsc -p ./tsconfig.demos.json", 53 | "clean": "rm -rf lib/", 54 | "send": "node ./lib/send.js", 55 | "typegen": "make types" 56 | }, 57 | "dependencies": { 58 | "isexe": "2.0.0", 59 | "lodash.camelcase": "4.3.0", 60 | "lodash.kebabcase": "4.1.1", 61 | "lodash.snakecase": "4.1.1", 62 | "mkdirp": "0.5.1", 63 | "which": "1.3.1" 64 | }, 65 | "devDependencies": { 66 | "@types/jest": "24.0.15", 67 | "@types/lodash.camelcase": "4.3.6", 68 | "@types/lodash.kebabcase": "4.1.6", 69 | "@types/lodash.snakecase": "4.1.6", 70 | "@types/mkdirp": "0.5.2", 71 | "@types/which": "1.3.1", 72 | "@typescript-eslint/eslint-plugin": "2.8.0", 73 | "@typescript-eslint/parser": "2.0.0", 74 | "documentation": "12.1.2", 75 | "eslint": "6.2.2", 76 | "eslint-config-prettier": "6.7.0", 77 | "eslint-config-standard": "14.1.0", 78 | "eslint-plugin-filenames": "1.3.2", 79 | "eslint-plugin-import": "2.18.2", 80 | "eslint-plugin-jsdoc": "15.8.3", 81 | "eslint-plugin-node": "9.1.0", 82 | "eslint-plugin-prettier": "3.1.1", 83 | "eslint-plugin-promise": "4.2.1", 84 | "eslint-plugin-standard": "4.0.1", 85 | "husky": "3.0.0", 86 | "jest": "25.1.0", 87 | "lint-staged": "9.2.0", 88 | "mathjs": "6.0.3", 89 | "pokersolver": "2.1.3", 90 | "prettier": "1.19.1", 91 | "regenerator-runtime": "0.13.2", 92 | "standard-version": "7.1.0", 93 | "ts-jest": "25.0.0", 94 | "typescript": "3.7.2" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/types/gregor1/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare type DurationMsec = number; 3 | export declare type DurationSec = number; 4 | export declare type Category = string; 5 | export declare type System = string; 6 | export declare type UID = Buffer; 7 | export declare type MsgID = Buffer; 8 | export declare type DeviceID = Buffer; 9 | export declare type Body = Buffer; 10 | export declare type Time = number; 11 | export declare type SessionID = string; 12 | export declare type SessionToken = string; 13 | export declare type AuthResult = { 14 | uid: UID; 15 | username: string; 16 | sid: SessionID; 17 | isAdmin: boolean; 18 | }; 19 | export declare type TimeOrOffset = { 20 | time: Time; 21 | offset: DurationMsec; 22 | }; 23 | export declare type Metadata = { 24 | uid: UID; 25 | msgId: MsgID; 26 | ctime: Time; 27 | deviceId: DeviceID; 28 | inBandMsgType: number; 29 | }; 30 | export declare type ReminderID = { 31 | uid: UID; 32 | msgId: MsgID; 33 | seqno: number; 34 | }; 35 | export declare type OutOfBandMessage = { 36 | uid: UID; 37 | system: System; 38 | body: Body; 39 | }; 40 | /** 41 | * DescribeConnectedUsers will take a list of users, and return the list of users 42 | * which are connected to any Gregor in the cluster, and what devices (and device type) 43 | * those users are connected with. 44 | */ 45 | export declare type ConnectedDevice = { 46 | deviceId: DeviceID; 47 | deviceType: string; 48 | devicePlatform: string; 49 | userAgent: string; 50 | }; 51 | export declare type StateSyncMessage = { 52 | md: Metadata; 53 | }; 54 | export declare type MsgRange = { 55 | endTime: TimeOrOffset; 56 | category: Category; 57 | skipMsgIDs: MsgID[] | null; 58 | }; 59 | export declare type Item = { 60 | category: Category; 61 | dtime: TimeOrOffset; 62 | remindTimes: TimeOrOffset[] | null; 63 | body: Body; 64 | }; 65 | export declare type ConnectedUser = { 66 | uid: UID; 67 | devices: ConnectedDevice[] | null; 68 | }; 69 | export declare type Dismissal = { 70 | msgIDs: MsgID[] | null; 71 | ranges: MsgRange[] | null; 72 | }; 73 | export declare type ItemAndMetadata = { 74 | md?: Metadata; 75 | item?: Item; 76 | }; 77 | export declare type State = { 78 | items: ItemAndMetadata[] | null; 79 | }; 80 | export declare type StateUpdateMessage = { 81 | md: Metadata; 82 | creation?: Item; 83 | dismissal?: Dismissal; 84 | }; 85 | export declare type Reminder = { 86 | item: ItemAndMetadata; 87 | seqno: number; 88 | remindTime: Time; 89 | }; 90 | export declare type InBandMessage = { 91 | stateUpdate?: StateUpdateMessage; 92 | stateSync?: StateSyncMessage; 93 | }; 94 | export declare type ReminderSet = { 95 | reminders: Reminder[] | null; 96 | moreRemindersReady: boolean; 97 | }; 98 | export declare type Message = { 99 | oobm?: OutOfBandMessage; 100 | ibm?: InBandMessage; 101 | }; 102 | export declare type SyncResult = { 103 | msgs: InBandMessage[] | null; 104 | hash: Buffer; 105 | }; 106 | -------------------------------------------------------------------------------- /lib/utils/keybaseExec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __spreadArrays = (this && this.__spreadArrays) || function () { 3 | for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; 4 | for (var r = Array(s), k = 0, i = 0; i < il; i++) 5 | for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) 6 | r[k] = a[j]; 7 | return r; 8 | }; 9 | var __importDefault = (this && this.__importDefault) || function (mod) { 10 | return (mod && mod.__esModule) ? mod : { "default": mod }; 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | var child_process_1 = require("child_process"); 14 | var readline_1 = __importDefault(require("readline")); 15 | var path_1 = __importDefault(require("path")); 16 | var keybaseBinaryName_1 = __importDefault(require("./keybaseBinaryName")); 17 | var keybaseExec = function (workingDir, homeDir, args, options) { 18 | if (options === void 0) { options = { stdinBuffer: undefined, onStdOut: undefined, timeout: undefined }; } 19 | var runArgs = __spreadArrays(args); 20 | if (homeDir) { 21 | runArgs.unshift('--home', homeDir); 22 | } 23 | var keybasePath = path_1.default.join(workingDir, keybaseBinaryName_1.default); 24 | var child = child_process_1.spawn(keybasePath, runArgs); 25 | var stdOutBuffer = []; 26 | var stdErrBuffer = []; 27 | if (options.stdinBuffer) { 28 | child.stdin.write(options.stdinBuffer); 29 | } 30 | child.stdin.end(); 31 | var lineReaderStdout = readline_1.default.createInterface({ input: child.stdout }); 32 | // Use readline interface to parse each line (\n separated) when provided 33 | // with onStdOut callback 34 | if (options.onStdOut) { 35 | lineReaderStdout.on('line', options.onStdOut); 36 | } 37 | else { 38 | child.stdout.on('data', function (chunk) { 39 | stdOutBuffer.push(chunk); 40 | }); 41 | } 42 | // Capture STDERR and use as error message if needed 43 | child.stderr.on('data', function (chunk) { 44 | stdErrBuffer.push(chunk); 45 | }); 46 | var done = false; 47 | if (options.timeout) { 48 | setTimeout(function () { 49 | if (!done) { 50 | child.kill(); 51 | } 52 | }, options.timeout); 53 | } 54 | return new Promise(function (resolve, reject) { 55 | child.on('close', function (code) { 56 | done = true; 57 | var finalStdOut = null; 58 | // Pass back 59 | if (code) { 60 | var errorMessage = Buffer.concat(stdErrBuffer).toString('utf8'); 61 | return reject(new Error(errorMessage)); 62 | } 63 | else { 64 | var stdout = Buffer.concat(stdOutBuffer).toString('utf8'); 65 | try { 66 | finalStdOut = options.json ? JSON.parse(stdout) : stdout; 67 | } 68 | catch (e) { 69 | return reject(e); 70 | } 71 | } 72 | return resolve(finalStdOut); 73 | }); 74 | }); 75 | }; 76 | exports.default = keybaseExec; 77 | //# sourceMappingURL=keybaseExec.js.map -------------------------------------------------------------------------------- /lib/wallet-client/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/wallet-client/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/utils/formatAPIObject.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"formatAPIObject.js","sourceRoot":"","sources":["../../src/utils/formatAPIObject.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/team-client/index.ts: -------------------------------------------------------------------------------- 1 | import ClientBase from '../client-base' 2 | import * as keybase1 from '../types/keybase1' 3 | 4 | export interface CreateTeamParam { 5 | team: string 6 | } 7 | 8 | export interface AddMembersParam { 9 | team: string 10 | emails?: keybase1.MemberEmail[] 11 | usernames?: keybase1.MemberUsername[] 12 | } 13 | 14 | export interface RemoveMemberParam { 15 | team: string 16 | username: string 17 | } 18 | 19 | export interface ListTeamMembershipsParam { 20 | team: string 21 | } 22 | 23 | /** The team module of your Keybase bot. For more info about the API this module uses, you may want to check out `keybase team api`. */ 24 | class Team extends ClientBase { 25 | /** 26 | * Create a new Keybase team or subteam 27 | * @memberof Team 28 | * @param creation - the name of the team to create 29 | * @returns - 30 | * @example 31 | * bot.team.create({"team": "phoenix"}).then(res => console.log(res)) 32 | */ 33 | public async create(creation: CreateTeamParam): Promise { 34 | await this._guardInitialized() 35 | const options = creation 36 | const res = await this._runApiCommand({apiName: 'team', method: 'create-team', options}) 37 | if (!res) { 38 | throw new Error('create') 39 | } 40 | return res 41 | } 42 | /** 43 | * Add a bunch of people with different privileges to a team 44 | * @memberof Team 45 | * @param additions - an array of the users to add, with privs 46 | * @returns - A result object of adding these members to the team. 47 | * @example 48 | * bot.team.addMembers({"team": "phoenix", "emails": [{"email": "alice@keybase.io", "role": "writer"}, {"email": "cleo@keybase.io", "role": "admin"}], "usernames": [{"username": "frank", "role": "reader"}, {"username": "keybaseio@twitter", "role": "writer"}]}).then(res => console.log(res)) 49 | */ 50 | public async addMembers(additions: AddMembersParam): Promise { 51 | await this._guardInitialized() 52 | const options = additions 53 | const res = await this._runApiCommand({apiName: 'team', method: 'add-members', options}) 54 | if (!res) { 55 | throw new Error('addMembers') 56 | } 57 | return res 58 | } 59 | 60 | /** 61 | * Remove someone from a team. 62 | * @memberof Team 63 | * @param removal - object with the `team` name and `username` 64 | * @example 65 | * bot.team.removeMember({"team": "phoenix", "username": "frank"}).then(res => console.log(res)) 66 | */ 67 | public async removeMember(removal: RemoveMemberParam): Promise { 68 | await this._guardInitialized() 69 | const options = removal 70 | await this._runApiCommand({apiName: 'team', method: 'remove-member', options}) 71 | } 72 | 73 | /** 74 | * List a team's members. 75 | * @memberof Team 76 | * @param team - an object with the `team` name in it. 77 | * @returns - Details about the team. 78 | * @example 79 | * bot.team.listTeamMemberships({"team": "phoenix"}).then(res => console.log(res)) 80 | */ 81 | public async listTeamMemberships(team: ListTeamMembershipsParam): Promise { 82 | await this._guardInitialized() 83 | const options = team 84 | const res = await this._runApiCommand({apiName: 'team', method: 'list-team-memberships', options}) 85 | if (!res) { 86 | throw new Error('listTeamMemberships') 87 | } 88 | return res 89 | } 90 | } 91 | 92 | export default Team 93 | -------------------------------------------------------------------------------- /src/types/gregor1/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * gregor.1 3 | * 4 | * Auto-generated to TypeScript types by avdl-compiler v1.4.8 (https://github.com/keybase/node-avdl-compiler) 5 | * Input files: 6 | * - ../client/protocol/avdl/gregor1/auth.avdl 7 | * - ../client/protocol/avdl/gregor1/auth_internal.avdl 8 | * - ../client/protocol/avdl/gregor1/auth_update.avdl 9 | * - ../client/protocol/avdl/gregor1/common.avdl 10 | * - ../client/protocol/avdl/gregor1/incoming.avdl 11 | * - ../client/protocol/avdl/gregor1/outgoing.avdl 12 | * - ../client/protocol/avdl/gregor1/remind.avdl 13 | */ 14 | 15 | export type DurationMsec = number 16 | 17 | export type DurationSec = number 18 | 19 | export type Category = string 20 | 21 | export type System = string 22 | 23 | export type UID = Buffer 24 | 25 | export type MsgID = Buffer 26 | 27 | export type DeviceID = Buffer 28 | 29 | export type Body = Buffer 30 | 31 | export type Time = number 32 | 33 | export type SessionID = string 34 | 35 | export type SessionToken = string 36 | 37 | export type AuthResult = { 38 | uid: UID 39 | username: string 40 | sid: SessionID 41 | isAdmin: boolean 42 | } 43 | 44 | export type TimeOrOffset = { 45 | time: Time 46 | offset: DurationMsec 47 | } 48 | 49 | export type Metadata = { 50 | uid: UID 51 | msgId: MsgID 52 | ctime: Time 53 | deviceId: DeviceID 54 | inBandMsgType: number 55 | } 56 | 57 | export type ReminderID = { 58 | uid: UID 59 | msgId: MsgID 60 | seqno: number 61 | } 62 | 63 | export type OutOfBandMessage = { 64 | uid: UID 65 | system: System 66 | body: Body 67 | } 68 | 69 | /** 70 | * DescribeConnectedUsers will take a list of users, and return the list of users 71 | * which are connected to any Gregor in the cluster, and what devices (and device type) 72 | * those users are connected with. 73 | */ 74 | export type ConnectedDevice = { 75 | deviceId: DeviceID 76 | deviceType: string 77 | devicePlatform: string 78 | userAgent: string 79 | } 80 | 81 | export type StateSyncMessage = { 82 | md: Metadata 83 | } 84 | 85 | export type MsgRange = { 86 | endTime: TimeOrOffset 87 | category: Category 88 | skipMsgIDs: MsgID[] | null 89 | } 90 | 91 | export type Item = { 92 | category: Category 93 | dtime: TimeOrOffset 94 | remindTimes: TimeOrOffset[] | null 95 | body: Body 96 | } 97 | 98 | export type ConnectedUser = { 99 | uid: UID 100 | devices: ConnectedDevice[] | null 101 | } 102 | 103 | export type Dismissal = { 104 | msgIDs: MsgID[] | null 105 | ranges: MsgRange[] | null 106 | } 107 | 108 | export type ItemAndMetadata = { 109 | md?: Metadata 110 | item?: Item 111 | } 112 | 113 | export type State = { 114 | items: ItemAndMetadata[] | null 115 | } 116 | 117 | export type StateUpdateMessage = { 118 | md: Metadata 119 | creation?: Item 120 | dismissal?: Dismissal 121 | } 122 | 123 | export type Reminder = { 124 | item: ItemAndMetadata 125 | seqno: number 126 | remindTime: Time 127 | } 128 | 129 | export type InBandMessage = { 130 | stateUpdate?: StateUpdateMessage 131 | stateSync?: StateSyncMessage 132 | } 133 | 134 | export type ReminderSet = { 135 | reminders: Reminder[] | null 136 | moreRemindersReady: boolean 137 | } 138 | 139 | export type Message = { 140 | oobm?: OutOfBandMessage 141 | ibm?: InBandMessage 142 | } 143 | 144 | export type SyncResult = { 145 | msgs: InBandMessage[] | null 146 | hash: Buffer 147 | } 148 | -------------------------------------------------------------------------------- /src/client-base/index.ts: -------------------------------------------------------------------------------- 1 | import {ChildProcess} from 'child_process' 2 | import {formatAPIObjectInput, formatAPIObjectOutput, keybaseExec, keybaseStatus} from '../utils' 3 | import safeJSONStringify from '../utils/safeJSONStringify' 4 | import keybaseBinaryName from '../utils/keybaseBinaryName' 5 | import {API_VERSIONS, API_TYPES} from '../constants' 6 | import path from 'path' 7 | import {InitOptions} from '../utils/options' 8 | import {AdminDebugLogger} from '../utils/adminDebugLogger' 9 | 10 | export interface ApiCommandArg { 11 | apiName: API_TYPES 12 | method: string 13 | options?: object 14 | timeout?: number 15 | } 16 | 17 | export class ErrorWithCode extends Error { 18 | code: number 19 | constructor(code: number, message: string) { 20 | super(message) 21 | this.code = code 22 | } 23 | } 24 | 25 | /** 26 | * A Client base. 27 | * @ignore 28 | */ 29 | class ClientBase { 30 | public initialized: boolean 31 | public username?: string 32 | public devicename?: string 33 | public homeDir?: string 34 | public verbose: boolean 35 | protected _spawnedProcesses: ChildProcess[] 36 | protected _workingDir: string 37 | protected _deinitializing: boolean 38 | protected _adminDebugLogger: AdminDebugLogger 39 | 40 | public constructor(workingDir: string, adminDebugLogger: AdminDebugLogger) { 41 | this._adminDebugLogger = adminDebugLogger 42 | this._workingDir = workingDir 43 | this.initialized = false 44 | this.verbose = false 45 | this._spawnedProcesses = [] 46 | this._deinitializing = false 47 | } 48 | 49 | public async _init(homeDir?: string): Promise { 50 | const initBotInfo = await keybaseStatus(this._workingDir, homeDir) 51 | this._adminDebugLogger.info(`My workingDir=${this._workingDir} and my homeDir=${this.homeDir}`) 52 | this.homeDir = homeDir 53 | this.username = initBotInfo.username 54 | this.devicename = initBotInfo.devicename 55 | this.initialized = true 56 | } 57 | 58 | public async _deinit(): Promise { 59 | this._deinitializing = true 60 | for (const child of this._spawnedProcesses) { 61 | child.kill() 62 | } 63 | } 64 | 65 | protected async _runApiCommand(arg: ApiCommandArg): Promise { 66 | const options = arg.options ? formatAPIObjectInput(arg.options, arg.apiName) : undefined 67 | const input = { 68 | method: arg.method, 69 | params: { 70 | version: API_VERSIONS[arg.apiName], 71 | options, 72 | }, 73 | } 74 | const inputString = safeJSONStringify(input) 75 | const size = inputString.length 76 | const output = await keybaseExec(this._workingDir, this.homeDir, [arg.apiName, 'api'], { 77 | stdinBuffer: Buffer.alloc(size, inputString, 'utf8'), 78 | json: true, 79 | timeout: arg.timeout, 80 | }) 81 | if (output.hasOwnProperty('error')) { 82 | throw new ErrorWithCode(output.error.code, output.error.message) 83 | } 84 | const res = formatAPIObjectOutput(output.result, { 85 | apiName: arg.apiName, 86 | method: arg.method, 87 | }) 88 | return res 89 | } 90 | 91 | protected async _guardInitialized(): Promise { 92 | if (!this.initialized) { 93 | throw new Error('The client is not yet initialized.') 94 | } 95 | } 96 | protected _pathToKeybaseBinary(): string { 97 | return path.join(this._workingDir, keybaseBinaryName) 98 | } 99 | } 100 | 101 | export default ClientBase 102 | -------------------------------------------------------------------------------- /lib/kvstore-client/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/kvstore-client/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /demos/iced/puzzle-bot.iced: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env iced 2 | 3 | # USAGE: 4 | # > KB_USERNAME=foo KB_PAPERKEY='whatever' ./puzzle-bot.iced 5 | # 6 | # demonstration of writing a Keybase bot in iced-coffee-script. Note 7 | # that our bot library now deals in Promises, so we'll need to callbackify 8 | # any functions. 9 | 10 | Bot = require '../../lib/index.js' 11 | bot = new Bot() 12 | 13 | # -------------------------------------------------------------------------------- 14 | 15 | WINNERS = {} 16 | PREV_OUTS = {} 17 | GUESS_COUNT = 0 18 | 19 | # -------------------------------------------------------------------------------- 20 | 21 | finalCheck = (inputStr, numInt) -> 22 | # assuming it's a valid positive int. 23 | digits = "#{numInt}".split '' 24 | counts = {} 25 | for d in digits 26 | counts[d] or= 0 27 | counts[d]++ 28 | countKeys = Object.keys(counts).sort (a,b) -> a - b 29 | output = "" 30 | for k in countKeys 31 | output += counts[k] + k 32 | return {output, success: (output is inputStr)} 33 | 34 | # -------------------------------------------------------------------------------- 35 | 36 | msgCheckAndReply = (s, senderName) => 37 | GUESS_COUNT++ 38 | isValidInt = (numInt = parseInt(s))? and not isNaN(numInt) and ("#{numInt}".indexOf('.') is -1) and (numInt.toString() is s) 39 | isValidFloat = (numFloat = parseFloat(s))? and not isNaN(numFloat) and (numFloat.toString() is s) 40 | numWinners = Object.keys(WINNERS).length 41 | numPlayers = Object.keys(PREV_OUTS).length 42 | prompt = "Tell me a number that generates itself." 43 | if isValidFloat and not isValidInt 44 | ans = "We all float down here, #{senderName}. #{prompt}" 45 | else if not isValidInt 46 | ans = prompt 47 | else if numInt is 0 48 | ans = "Quit joking. #{prompt}" 49 | else if numInt <= 0 50 | ans = "Negative numbers aren't real. #{prompt}" 51 | else if numInt > Number.MAX_SAFE_INTEGER - 1 52 | ans = "No, no, no. You're thinking too big. #{prompt}" 53 | else if numInt is 22 54 | ans = "The only thing 22 generates is shame. Tell me a _big_ number that generates itself." 55 | else if numInt <= 100 56 | ans = "I'm sorry, think bigger. #{prompt}" 57 | else 58 | {output, success} = finalCheck s, numInt 59 | if success 60 | WINNERS[senderName] = true 61 | winner_text = ("@#{k}" for k,v of WINNERS).join ", " 62 | ct = (num) -> (":christmas_tree:" for i in [0...num]).join "" 63 | ans = "**#{output}**. :heart: You HAVE found a number that generates itself. Happy holidays to recent solvers:" 64 | ans += "\n\n:#{ct 10}\n#{winner_text}\n#{ct 10}" 65 | console.log " - #{senderName} wins with #{numInt}" 66 | else if PREV_OUTS[senderName]?[s] 67 | ans = "I do not allow mindless strategies. #{prompt}" 68 | else 69 | PREV_OUTS[senderName] or= {} 70 | PREV_OUTS[senderName][output] = true 71 | ans = "**#{output}**. #{prompt} _#{GUESS_COUNT} attempts, #{numWinners} winners_" 72 | return ans 73 | 74 | # -------------------------------------------------------------------------------- 75 | 76 | main = -> 77 | bot.init(process.env.KB_USERNAME, process.env.KB_PAPERKEY).then -> 78 | console.log "I am me! #{bot.myInfo().username}. My homeDir is #{bot.myInfo().homeDir}" 79 | bot.chat.watchAllChannelsForNewMessages (message) -> 80 | if (message.content.type is 'text') 81 | body = message.content.text.body 82 | senderName = message.sender.username 83 | reply = {body: msgCheckAndReply(body, senderName)} 84 | console.log "#{senderName} guessed `#{body}`. Reply = `#{reply.body}`" 85 | bot.chat.send message.conversationId, reply 86 | 87 | shutDown = -> 88 | bot.deinit().then -> 89 | process.exit() 90 | 91 | process.on 'SIGINT', shutDown 92 | process.on 'SIGTERM', shutDown 93 | 94 | main() 95 | -------------------------------------------------------------------------------- /lib/utils/whichKeybase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var which_1 = __importDefault(require("which")); 43 | var util_1 = require("util"); 44 | var keybaseBinaryName_1 = __importDefault(require("./keybaseBinaryName")); 45 | var aWhich = util_1.promisify(which_1.default); 46 | /** 47 | * Returns the full path to the keybase binary or throws an error 48 | * @ignore 49 | * @example 50 | * whichKeybase().then((path) => console.log(path)) 51 | */ 52 | function whichKeybase() { 53 | return __awaiter(this, void 0, void 0, function () { 54 | var path; 55 | return __generator(this, function (_a) { 56 | switch (_a.label) { 57 | case 0: return [4 /*yield*/, aWhich(keybaseBinaryName_1.default)]; 58 | case 1: 59 | path = _a.sent(); 60 | if (!path) { 61 | throw new Error('Could not find keybase binary'); 62 | } 63 | return [2 /*return*/, path]; 64 | } 65 | }); 66 | }); 67 | } 68 | exports.default = whichKeybase; 69 | //# sourceMappingURL=whichKeybase.js.map -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import ChatClient from './chat-client'; 2 | import WalletClient from './wallet-client'; 3 | import TeamClient from './team-client'; 4 | import HelpersClient from './helpers-client'; 5 | import KVStoreClient from './kvstore-client'; 6 | import { BotInfo } from './utils/keybaseStatus'; 7 | import { InitOptions } from './utils/options'; 8 | interface BotConstructorOpts { 9 | debugLogging?: boolean; 10 | } 11 | /** A Keybase bot. */ 12 | declare class Bot { 13 | chat: ChatClient; 14 | wallet: WalletClient; 15 | team: TeamClient; 16 | helpers: HelpersClient; 17 | kvstore: KVStoreClient; 18 | private _workingDir; 19 | private _service; 20 | private _botId; 21 | private _initStatus; 22 | private _adminDebugLogger?; 23 | /** 24 | * Create a bot. Note you can't do much too exciting with your bot after you instantiate it; you have to initialize it first. 25 | * @memberof Bot 26 | * @example 27 | * const bot = new Bot() 28 | */ 29 | constructor(opts?: BotConstructorOpts); 30 | /** 31 | * Initialize your bot by starting an instance of the Keybase service and logging in using oneshot mode. 32 | * @memberof Bot 33 | * @param username - The username of your bot's Keybase account. 34 | * @param paperkey - The paperkey of your bot's Keybase account. 35 | * @param options - The initialization options for your bot. 36 | * @example 37 | * bot.init('username', 'paperkey') 38 | */ 39 | init(username: string, paperkey: string, options?: InitOptions): Promise; 40 | /** 41 | * Initialize your bot by using an existing running service with a logged in user. 42 | * @memberof Bot 43 | * @param homeDir - The home directory of this currently running service. Leave blank to use the default homeDir for your system. 44 | * @param options - The initialization options for your bot. 45 | * @example 46 | * bot.initFromRunningService() 47 | */ 48 | initFromRunningService(homeDir?: string, options?: InitOptions): Promise; 49 | private _beginInitState; 50 | /** 51 | * Get info about your bot! 52 | * @memberof Bot 53 | * @returns – Useful information like the username, device, and home directory of your bot. If your bot isn't initialized, you'll get `null`. 54 | * @example 55 | * const info = bot.myInfo() 56 | */ 57 | myInfo(): BotInfo | null; 58 | /** 59 | * 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. 60 | * @memberof Bot 61 | * @example 62 | * bot.deinit() 63 | */ 64 | deinit(): Promise; 65 | /** 66 | * If bot is initialized with an optional directory `adminDebugDirectory`, this will let you write info text into it. 67 | * @memberof Bot 68 | * @example 69 | * bot.adminDebugLogInfo('My bot is ready to go.') 70 | */ 71 | adminDebugLogInfo(text: string): Promise; 72 | /** 73 | * If bot is initialized with an optional directory `adminDebugDirectory`, this will let you write error text into it. 74 | * @memberof Bot 75 | * @example 76 | * bot.adminDebugLogInfo("My bot is ready to go.") 77 | */ 78 | adminDebugLogError(text: string): Promise; 79 | /** 80 | * Send Keybase service daemon logs to Keybase. 81 | */ 82 | logSend(): Promise; 83 | /** 84 | * Run pprof on the Keybase service daemon and store the result in temporary 85 | * file. After it's done, the promise resolves with the path to the temporary 86 | * file. 87 | */ 88 | pprof(pprofType: 'trace' | 'cpu' | 'heap', duration?: number): Promise; 89 | get botId(): string; 90 | get serviceLogLocation(): string; 91 | private _prepWorkingDir; 92 | private _initSubBots; 93 | } 94 | export = Bot; 95 | -------------------------------------------------------------------------------- /lib/utils/pingKeybaseService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var keybaseExec_1 = __importDefault(require("../utils/keybaseExec")); 43 | /** 44 | * Checks whether the keybase service is running by calling `keybase status --json`. 45 | * @ignore 46 | * @param workingDir - the directory containing the binary, according to top level Bot 47 | * @param homeDir - The home directory of the service you want to fetch the status from. 48 | * @example 49 | * pingKeybaseService('/my/dir').then(status => console.log("service running", status)) 50 | */ 51 | function pingKeybaseService(workingDir, homeDir) { 52 | return __awaiter(this, void 0, void 0, function () { 53 | var err_1; 54 | return __generator(this, function (_a) { 55 | switch (_a.label) { 56 | case 0: 57 | _a.trys.push([0, 2, , 3]); 58 | return [4 /*yield*/, keybaseExec_1.default(workingDir, homeDir, ['--no-auto-fork', 'status', '--json'], { json: true })]; 59 | case 1: 60 | _a.sent(); 61 | return [2 /*return*/, true]; 62 | case 2: 63 | err_1 = _a.sent(); 64 | return [2 /*return*/, false]; 65 | case 3: return [2 /*return*/]; 66 | } 67 | }); 68 | }); 69 | } 70 | exports.default = pingKeybaseService; 71 | //# sourceMappingURL=pingKeybaseService.js.map -------------------------------------------------------------------------------- /demos/es7/poker-hands-reverse.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Bot = require('../../lib/index.js') 3 | const {Hand} = require('pokersolver') 4 | 5 | const bot = new Bot() 6 | 7 | // Utilities for deck parsing 8 | const deck = 9 | '2s,3s,4s,5s,6s,7s,8s,9s,Ts,Js,Qs,Ks,As,2c,3c,4c,5c,6c,7c,8c,9c,Tc,Jc,Qc,Kc,Ac,2d,3d,4d,5d,6d,7d,8d,9d,Td,Jd,Qd,Kd,Ad,2h,3h,4h,5h,6h,7h,8h,9h,Th,Jh,Qh,Kh,Ah' 10 | const deckI2S = deck.split(',').reduce((prev, curr, ind) => { 11 | prev[ind] = curr 12 | return prev 13 | }, {}) 14 | 15 | // A very basic cache to prevent duplicate games. The key is the channel name, 16 | // the value is the last game ID. 17 | const gameCache = {} 18 | 19 | // Flips and tells the cards 20 | async function main() { 21 | try { 22 | const username = process.env.KB_USERNAME 23 | const paperkey = process.env.KB_PAPERKEY 24 | await bot.init(username, paperkey) 25 | const info = bot.myInfo() 26 | console.log(`Bot initialized with username ${info.username}.`) 27 | 28 | console.log(`Listening for all messages...`) 29 | await bot.chat.watchAllChannelsForNewMessages( 30 | async msg => { 31 | try { 32 | console.log(msg) 33 | 34 | if (msg.channel.membersType !== 'impteamnative') { 35 | // Only react to direct messages. 36 | console.log(`Invalid type - ${msg.channel.membersType}`) 37 | return 38 | } 39 | 40 | if (msg.sender.username === info.username && msg.content.type !== 'flip') { 41 | // Our own message 42 | return 43 | } 44 | 45 | if (msg.sender.username === info.username && msg.content.type === 'flip') { 46 | if (gameCache[msg.channel.name] === msg.content.flip.gameId) { 47 | // We've already resolved this one. Flips executed locally can trigger 48 | // multiple 'flip' notifications. 49 | return 50 | } else { 51 | gameCache[msg.channel.name] = msg.content.flip.gameId 52 | } 53 | 54 | // Our flip, when flipping from the same machine expect the flip to 55 | let resolved 56 | while (!resolved) { 57 | try { 58 | const flip = await bot.chat.loadFlip(msg.conversationId, msg.content.flip.flipConvId, msg.id, msg.content.flip.gameId) 59 | if (flip.phase === 2) { 60 | resolved = true 61 | 62 | if (flip.resultInfo.typ !== 3 || !flip.resultInfo.hands.find(x => x.target === 'hand')) { 63 | await bot.chat.send(msg.conversationId, { 64 | body: `Invalid flip type! Expected /flip cards 5 hand.`, 65 | }) 66 | return 67 | } 68 | 69 | const myHand = flip.resultInfo.hands.find(x => x.target === 'hand') 70 | const hand = Hand.solve(myHand.hand.map(card => deckI2S[card])) 71 | await bot.chat.send(msg.conversationId, { 72 | body: `You got ${hand.descr}!`, 73 | }) 74 | return 75 | } 76 | } catch (err) { 77 | console.log('flip fetch error', err) 78 | } 79 | } 80 | return 81 | } 82 | 83 | if (msg.content.type === 'text' && msg.content.text.body.toLowerCase().includes('hand')) { 84 | // Perform the flip 85 | await bot.chat.send(msg.conversationId, { 86 | body: `/flip cards 5 hand`, 87 | }) 88 | return 89 | } 90 | 91 | await bot.chat.send(msg.conversationId, { 92 | body: `Hi ${msg.sender.username}! Send "hand" to me and I'll flip and tell you what's in your hand!`, 93 | }) 94 | } catch (err) { 95 | console.error(err) 96 | } 97 | }, 98 | e => console.error(e), 99 | { 100 | showLocal: true, 101 | } 102 | ) 103 | } catch (error) { 104 | console.error(error) 105 | } 106 | } 107 | 108 | async function shutDown() { 109 | await bot.deinit() 110 | process.exit() 111 | } 112 | 113 | process.on('SIGINT', shutDown) 114 | process.on('SIGTERM', shutDown) 115 | 116 | main() 117 | -------------------------------------------------------------------------------- /lib/utils/keybaseStatus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var keybaseExec_1 = __importDefault(require("../utils/keybaseExec")); 43 | /** 44 | * Returns { username, devicename, homeDir } from `keybase status --json`. 45 | * @ignore 46 | * @param workingDir - the directory containing the binary, according to top level Bot 47 | * @param homeDir - The home directory of the service you want to fetch the status from. 48 | * @example 49 | * keybaseStatus('/my/dir').then(status => console.log(status.username)) 50 | */ 51 | function keybaseStatus(workingDir, homeDir) { 52 | return __awaiter(this, void 0, void 0, function () { 53 | var status; 54 | return __generator(this, function (_a) { 55 | switch (_a.label) { 56 | case 0: return [4 /*yield*/, keybaseExec_1.default(workingDir, homeDir, ['status', '--json'], { json: true })]; 57 | case 1: 58 | status = _a.sent(); 59 | if (status && status.Username && status.Device && status.Device.name) { 60 | return [2 /*return*/, { 61 | username: status.Username, 62 | devicename: status.Device.name, 63 | homeDir: homeDir, 64 | }]; 65 | } 66 | else { 67 | throw new Error('Failed to get current username and device name.'); 68 | } 69 | return [2 /*return*/]; 70 | } 71 | }); 72 | }); 73 | } 74 | exports.default = keybaseStatus; 75 | //# sourceMappingURL=keybaseStatus.js.map -------------------------------------------------------------------------------- /__tests__/kvstore.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | import * as KVStoreClient from '../lib/kvstore-client' 4 | import {ErrorWithCode} from '../lib/client-base' 5 | 6 | test('KVStore methods with an uninitialized bot', (): void => { 7 | const alice1 = new Bot() 8 | const team = config.teams.acme.teamname 9 | expect(alice1.kvstore.listNamespaces(team)).rejects.toThrowError() 10 | }) 11 | 12 | const expectThrowCode = (promise: Promise, checker: (error: ErrorWithCode) => boolean): Promise => 13 | promise 14 | .then(() => expect(true).toBe(false)) 15 | .catch(e => { 16 | expect(checker(e)).toBe(true) 17 | }) 18 | 19 | describe('KVStore Methods', (): void => { 20 | const alice1 = new Bot() 21 | const firstRando = Math.floor(Math.random() * 1000).toString() 22 | const secondRando = Math.floor(Math.random() * 1000).toString() 23 | const namespace = '_test_namespace' + firstRando 24 | const entryKey = '_test_key' + secondRando 25 | const team = config.teams.acme.teamname 26 | let rev: number | null = null 27 | 28 | const getResultMatcher = expect.objectContaining({ 29 | entryKey: expect.any(String), 30 | entryValue: expect.any(String), 31 | namespace: expect.any(String), 32 | revision: expect.any(Number), 33 | teamName: expect.any(String), 34 | }) 35 | 36 | beforeAll( 37 | async (): Promise => { 38 | await alice1.init(config.bots.alice1.username, config.bots.alice1.paperkey) 39 | } 40 | ) 41 | 42 | afterAll( 43 | async (): Promise => { 44 | await alice1.deinit() 45 | } 46 | ) 47 | 48 | it('Getting an unset value', async (): Promise => { 49 | const res = await alice1.kvstore.get(team, namespace, entryKey) 50 | expect(res.entryValue).toEqual(null) 51 | expect(res.revision).toEqual(0) 52 | expect(alice1.kvstore.isPresent(res)).toBe(false) 53 | expect(alice1.kvstore.isDeleted(res)).toBe(false) 54 | }) 55 | 56 | it('Can put with a default revision, but not a stale one', async (): Promise => { 57 | const res = await alice1.kvstore.put(team, namespace, entryKey, 'value1') 58 | rev = res.revision 59 | expect(rev).toBeGreaterThan(0) 60 | return expectThrowCode(alice1.kvstore.put(team, namespace, entryKey, 'value2', rev), KVStoreClient.ErrorIsWrongRevision) 61 | }) 62 | 63 | it('Lists namespaces', async (): Promise => { 64 | const res = await alice1.kvstore.listNamespaces(team) 65 | expect(res?.namespaces?.length).toBeGreaterThan(0) 66 | }) 67 | 68 | it('Lists entryKeys', async (): Promise => { 69 | const res = await alice1.kvstore.listEntryKeys(team, namespace) 70 | expect(res?.entryKeys?.length).toBeGreaterThan(0) 71 | }) 72 | 73 | it('Gets values', async (): Promise => { 74 | const res = await alice1.kvstore.get(team, namespace, entryKey) 75 | expect(res).toEqual(getResultMatcher) 76 | expect(res.entryValue).toEqual('value1') 77 | expect(res.revision).toEqual(rev) 78 | expect(alice1.kvstore.isPresent(res)).toBe(true) 79 | expect(alice1.kvstore.isDeleted(res)).toBe(false) 80 | }) 81 | 82 | it('Cannot delete with a future revision', (): Promise => { 83 | // Increment rev so that later tests always have a usable rev value prepared. 84 | if (rev === null) throw new Error('rev should be a number by now') 85 | rev += 1 86 | return expectThrowCode(alice1.kvstore.delete(team, namespace, entryKey, rev + 1), KVStoreClient.ErrorIsWrongRevision) 87 | }) 88 | 89 | it('Deletes at the correct revision', async (): Promise => { 90 | if (rev === null) throw new Error('rev should be a number by now') 91 | const res = await alice1.kvstore.delete(team, namespace, entryKey, rev) 92 | expect(res.revision).toEqual(rev) 93 | }) 94 | 95 | it('Cannot delete twice', (): Promise => { 96 | return expectThrowCode(alice1.kvstore.delete(team, namespace, entryKey), KVStoreClient.ErrorIsNotFound) 97 | }) 98 | 99 | it('Getting a deleted value', async (): Promise => { 100 | const res = await alice1.kvstore.get(team, namespace, entryKey) 101 | expect(res.entryValue).toEqual(null) 102 | expect(res.revision).toEqual(rev) 103 | expect(alice1.kvstore.isPresent(res)).toBe(false) 104 | expect(alice1.kvstore.isDeleted(res)).toBe(true) 105 | }) 106 | 107 | it('optional teamname', async (): Promise => { 108 | const v = 'optional teamname' 109 | try { 110 | await alice1.kvstore.delete(config.bots.alice1.username, namespace, entryKey) 111 | } catch {} 112 | await alice1.kvstore.put(undefined, namespace, entryKey, v) 113 | const res = await alice1.kvstore.get(`${config.bots.alice1.username},${config.bots.alice1.username}`, namespace, entryKey) 114 | expect(res.entryValue).toEqual(v) 115 | }) 116 | }) 117 | -------------------------------------------------------------------------------- /__tests__/deinit.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | import {startServiceManually, stopServiceManually, doesFileOrDirectoryExist, countProcessesMentioning} from './test-utils' 4 | import {randomTempDir, timeout} from '../lib/utils' 5 | import {MsgSummary} from '../lib/types/chat1' 6 | import {InitOptions} from '../lib/utils/options' 7 | 8 | describe('Keybase bot deinitialization', (): void => { 9 | it('kills all spawned processes it creates', async (): Promise => { 10 | const alice = new Bot() 11 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 12 | const aliceHomeDir = alice.myInfo()!.homeDir || '' 13 | 14 | // make sure our bot can return a home directory 15 | expect(aliceHomeDir.indexOf('keybase_bot_')).toBeGreaterThanOrEqual(0) 16 | // make sure that homeDir exists 17 | expect(await doesFileOrDirectoryExist(aliceHomeDir)).toBe(true) 18 | // make sure we see a running server processes 19 | expect(await countProcessesMentioning(aliceHomeDir)).toBe(1) 20 | 21 | const bobHomeDir = randomTempDir() 22 | const bob = new Bot() 23 | await startServiceManually(bobHomeDir, config.bots.bob1.username, config.bots.bob1.paperkey) 24 | await bob.initFromRunningService(bobHomeDir) 25 | 26 | expect(bobHomeDir.indexOf('keybase_bot_')).toBeGreaterThanOrEqual(0) 27 | expect(await doesFileOrDirectoryExist(bobHomeDir)).toBe(true) 28 | expect(await countProcessesMentioning(bobHomeDir)).toBe(1) 29 | 30 | // get a couple listen processes going 31 | alice.chat.watchAllChannelsForNewMessages((msg: MsgSummary): void => console.log(msg)) 32 | alice.chat.watchAllChannelsForNewMessages((msg: MsgSummary): void => console.log(msg)) 33 | bob.chat.watchAllChannelsForNewMessages((msg: MsgSummary): void => console.log(msg)) 34 | bob.chat.watchAllChannelsForNewMessages((msg: MsgSummary): void => console.log(msg)) 35 | bob.chat.watchAllChannelsForNewMessages((msg: MsgSummary): void => console.log(msg)) 36 | 37 | // Give just a couple seconds for the processes to get going 38 | await timeout(3000) 39 | expect(await countProcessesMentioning(aliceHomeDir)).toBe(3) 40 | expect(await countProcessesMentioning(bobHomeDir)).toBe(4) 41 | 42 | await alice.deinit() 43 | await bob.deinit() 44 | 45 | // Also give a couple seconds for the processes to fully deinit 46 | await timeout(3000) 47 | 48 | // All processes should be shut down for alice 49 | expect(await countProcessesMentioning(aliceHomeDir)).toBe(0) 50 | // The service should still be running for bob, as that was started before he was initialized 51 | expect(await countProcessesMentioning(bobHomeDir)).toBe(1) 52 | await stopServiceManually(bobHomeDir) 53 | await expect(await countProcessesMentioning(bobHomeDir)).toBe(0) 54 | }) 55 | 56 | it('handles shutting down detached services', async (): Promise => { 57 | const initOptions: InitOptions = {useDetachedService: true} 58 | const alice = new Bot() 59 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey, initOptions) 60 | const aliceHomeDir = alice.myInfo()!.homeDir ?? '' 61 | expect(aliceHomeDir.indexOf('keybase_bot_')).toBeGreaterThanOrEqual(0) 62 | expect(await doesFileOrDirectoryExist(aliceHomeDir)).toBe(true) 63 | expect(await countProcessesMentioning(aliceHomeDir)).toBe(1) 64 | await alice.deinit() 65 | await timeout(3000) 66 | expect(await countProcessesMentioning(aliceHomeDir)).toBe(0) 67 | }) 68 | 69 | it('removes its home directory if initialized with a paperkey', async (): Promise => { 70 | const alice = new Bot() 71 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 72 | const homeDir = alice.myInfo()!.homeDir ?? '--nonsense-dir-' 73 | expect(await doesFileOrDirectoryExist(homeDir)).toBe(true) 74 | await alice.deinit() 75 | expect(await doesFileOrDirectoryExist(homeDir)).toBe(false) 76 | }) 77 | 78 | it('handles double deinits gracefully', async (): Promise => { 79 | const alice = new Bot() 80 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 81 | const homeDir = alice.myInfo()!.homeDir ?? '--nonsense-dir-' 82 | await alice.deinit() 83 | await alice.deinit() 84 | expect(await doesFileOrDirectoryExist(homeDir)).toBe(false) 85 | }) 86 | 87 | it('does not remove its home directory if initialized from a running service', async (): Promise => { 88 | const homeDir = randomTempDir() 89 | await startServiceManually(homeDir, config.bots.alice1.username, config.bots.alice1.paperkey) 90 | const alice = new Bot() 91 | await alice.initFromRunningService(homeDir) 92 | 93 | expect(await doesFileOrDirectoryExist(homeDir)).toBe(true) 94 | await alice.deinit() 95 | expect(await doesFileOrDirectoryExist(homeDir)).toBe(true) 96 | await stopServiceManually(homeDir) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /lib/utils/rmdirRecursive.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | var __importDefault = (this && this.__importDefault) || function (mod) { 39 | return (mod && mod.__esModule) ? mod : { "default": mod }; 40 | }; 41 | Object.defineProperty(exports, "__esModule", { value: true }); 42 | var fs_1 = __importDefault(require("fs")); 43 | var path_1 = __importDefault(require("path")); 44 | var util_1 = require("util"); 45 | function rmdirRecursive(dirName) { 46 | return __awaiter(this, void 0, void 0, function () { 47 | var fsLstat, fsUnlink, fsRmdir, fsReaddir, dirStat, _i, _a, entry, entryPath, stat; 48 | return __generator(this, function (_b) { 49 | switch (_b.label) { 50 | case 0: 51 | fsLstat = util_1.promisify(fs_1.default.lstat); 52 | fsUnlink = util_1.promisify(fs_1.default.unlink); 53 | fsRmdir = util_1.promisify(fs_1.default.rmdir); 54 | fsReaddir = util_1.promisify(fs_1.default.readdir); 55 | return [4 /*yield*/, fsLstat(dirName)]; 56 | case 1: 57 | dirStat = _b.sent(); 58 | if (!dirStat) return [3 /*break*/, 11]; 59 | _i = 0; 60 | return [4 /*yield*/, fsReaddir(dirName)]; 61 | case 2: 62 | _a = _b.sent(); 63 | _b.label = 3; 64 | case 3: 65 | if (!(_i < _a.length)) return [3 /*break*/, 9]; 66 | entry = _a[_i]; 67 | entryPath = path_1.default.join(dirName, entry); 68 | return [4 /*yield*/, fsLstat(entryPath)]; 69 | case 4: 70 | stat = _b.sent(); 71 | if (!stat.isDirectory()) return [3 /*break*/, 6]; 72 | return [4 /*yield*/, rmdirRecursive(entryPath)]; 73 | case 5: 74 | _b.sent(); 75 | return [3 /*break*/, 8]; 76 | case 6: return [4 /*yield*/, fsUnlink(entryPath)]; 77 | case 7: 78 | _b.sent(); 79 | _b.label = 8; 80 | case 8: 81 | _i++; 82 | return [3 /*break*/, 3]; 83 | case 9: return [4 /*yield*/, fsRmdir(dirName)]; 84 | case 10: 85 | _b.sent(); 86 | _b.label = 11; 87 | case 11: return [2 /*return*/]; 88 | } 89 | }); 90 | }); 91 | } 92 | exports.default = rmdirRecursive; 93 | //# sourceMappingURL=rmdirRecursive.js.map -------------------------------------------------------------------------------- /lib/wallet-client/index.d.ts: -------------------------------------------------------------------------------- 1 | import ClientBase from '../client-base'; 2 | import * as stellar1 from '../types/stellar1'; 3 | /** 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`. */ 4 | declare class Wallet extends ClientBase { 5 | /** 6 | * Provides a list of all accounts owned by the current Keybase user. 7 | * @memberof Wallet 8 | * @returns - An array of accounts. If there are no accounts, the array is empty. 9 | * @example 10 | * bot.wallet.balances().then(accounts => console.log(accounts)) 11 | */ 12 | balances(): Promise; 13 | /** 14 | * Provides a list of all transactions in a single account. 15 | * @memberof Wallet 16 | * @param accountId - The id of an account owned by a Keybase user. 17 | * @returns - An array of transactions related to the account. 18 | * @example 19 | * bot.wallet.history('GDUKZH6Q3U5WQD4PDGZXYLJE3P76BDRDWPSALN4OUFEESI2QL5UZHCK').then(transactions => console.log(transactions)) 20 | */ 21 | history(accountId: stellar1.AccountID): Promise; 22 | /** 23 | * Get details about a particular transaction 24 | * @memberof Wallet 25 | * @param transactionId - The id of the transaction you would like details about. 26 | * @returns - An object of details about the transaction specified. 27 | * @example 28 | * bot.wallet.details('e5334601b9dc2a24e031ffeec2fce37bb6a8b4b51fc711d16dec04d3e64976c4').then(details => console.log(details)) 29 | */ 30 | details(transactionId: stellar1.TransactionID): Promise; 31 | /** 32 | * Lookup the primary Stellar account ID of a Keybase user. 33 | * @memberof Wallet 34 | * @param name - 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 '@'. 35 | * @returns - An object containing the account ID and Keybase username of the found user. 36 | * @example 37 | * const lookup1 = bot.wallet.lookup('patrick') 38 | * // 'patrick' on Keybase is 'patrickxb' on twitter 39 | * const lookup2 = bot.wallet.lookup('patrcikxb@twitter') 40 | * // Using Lodash's `isEqual` since objects with same values aren't equal in JavaScript 41 | * _.isEqual(lookup1, lookup2) // => true 42 | */ 43 | lookup(name: string): Promise<{ 44 | accountId: stellar1.AccountID; 45 | username: string; 46 | }>; 47 | /** 48 | * Send lumens (XLM) via Keybase with your bot! 49 | * @memberof Wallet 50 | * @param recipient - 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 '@'. 51 | * @param amount - The amount of XLM to send. 52 | * @param [currency] - Adds a currency value to the amount specified. For example, adding 'USD' would send 53 | * @param [message] - The message for your payment 54 | * @returns - The transaction object of the transaction. 55 | * @example 56 | * bot.wallet.send('nathunsmitty', '3.50') // Send 3.50 XLM to Keybase user `nathunsmitty` 57 | * bot.wallet.send('nathunsmitty@github', '3.50') // Send 3.50 XLM to GitHub user `nathunsmitty` 58 | * bot.wallet.send('nathunsmitty', '3.50', 'USD') // Send $3.50 worth of lumens to Keybase user `nathunsmitty` 59 | * 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 60 | */ 61 | send(recipient: string, amount: string, currency?: string, message?: string): Promise; 62 | /** 63 | * Send lumens (XLM) via Keybase to more than one user at once. As opposed to the normal bot.wallet.send 64 | * command, this can get multiple transactions into the same 5-second Stellar ledger. 65 | * @memberof Wallet 66 | * @param batchId - example, if sending a bunch of batches for an airdrop, you could pass them all `airdrop2025`. 67 | * @param payments - an array of objects containing recipients and XLM of the form {"recipient": "someusername", "amount": "1.234", "message", "hi there"} 68 | * @returns - an object 69 | * @example 70 | * bot.wallet.batch("airdrop2040", [{"recipient":"a1","amount": "1.414", "message": "hi a1, yes 1"},{"recipient": "a2", "amount": "3.14159", "message": "hi a2, yes 2"}]) 71 | */ 72 | batch(batchId: string, payments: stellar1.BatchPaymentArg[], timeoutSec?: number): Promise; 73 | /** 74 | * 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. 75 | * @memberof Wallet 76 | * @param transactionId - The id of the transaction to cancel. 77 | * @example 78 | * bot.wallet.cancel('e5334601b9dc2a24e031ffeec2fce37bb6a8b4b51fc711d16dec04d3e64976c4').then(() => console.log('Transaction successfully canceled!')) 79 | */ 80 | cancel(transactionId: stellar1.TransactionID): Promise; 81 | } 82 | export default Wallet; 83 | -------------------------------------------------------------------------------- /__tests__/init.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | import {startServiceManually, stopServiceManually} from './test-utils' 4 | import {randomTempDir} from '../lib/utils' 5 | import os from 'os' 6 | import {InitOptions} from '../lib/utils/options' 7 | 8 | describe('Keybase bot initialization', (): void => { 9 | it('can init with a username and paperkey', async (): Promise => { 10 | const alice = new Bot() 11 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 12 | expect(alice.myInfo()!.username).toBe(config.bots.alice1.username) 13 | // also check a double-init causes error 14 | await expect(alice.init(config.bots.alice1.username, config.bots.alice1.paperkey)).rejects.toThrowError() 15 | await alice.deinit() 16 | }) 17 | 18 | it('throws an error if not given a username', async (): Promise => { 19 | const alice = new Bot() 20 | await expect(alice.init('', config.bots.alice1.paperkey)).rejects.toThrowError() 21 | }) 22 | 23 | it('throws an error if not given a paperkey', async (): Promise => { 24 | const alice = new Bot() 25 | await expect(alice.init(config.bots.alice1.username, '')).rejects.toThrowError() 26 | }) 27 | 28 | it('throws an error if given an invalid paperkey', async (): Promise => { 29 | const alice = new Bot() 30 | await expect(alice.init(config.bots.alice1.username, 'not a real paperkey')).rejects.toThrowError() 31 | }) 32 | 33 | it('can init if a service with a logged in user is currently running', async (): Promise => { 34 | const homeDir = randomTempDir() 35 | await startServiceManually(homeDir, config.bots.alice1.username, config.bots.alice1.paperkey) 36 | const alice = new Bot() 37 | await alice.initFromRunningService(homeDir) 38 | expect(alice.myInfo()!.username).toBe(config.bots.alice1.username) 39 | await alice.deinit() 40 | await stopServiceManually(homeDir) 41 | }) 42 | 43 | it('throws an error if an invalid home directory is given as the location of a currently running service', async (): Promise => { 44 | const homeDir = '/blah/fake-dir-that-does-not-exist' 45 | const alice = new Bot() 46 | await expect(alice.initFromRunningService(homeDir)).rejects.toThrowError() 47 | }) 48 | 49 | it('throws an error if it is already initialized', async (): Promise => { 50 | const alice = new Bot() 51 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 52 | await expect(alice.init(config.bots.alice1.username, config.bots.alice1.paperkey)).rejects.toThrowError() 53 | await alice.deinit() 54 | const homeDir = randomTempDir() 55 | await startServiceManually(homeDir, config.bots.bob1.username, config.bots.bob1.paperkey) 56 | await expect(alice.initFromRunningService(homeDir)).rejects.toThrowError() 57 | await alice.deinit() 58 | await stopServiceManually(homeDir) 59 | }) 60 | 61 | it('can init with botLite/disableTyping enabled by default', async (): Promise => { 62 | const alice = new Bot() 63 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey) 64 | expect(alice.myInfo()!.botLite).toBe(true) 65 | expect(alice.myInfo()!.disableTyping).toBe(true) 66 | await alice.deinit() 67 | }) 68 | 69 | it('can respect disabling botLite and disableTyping', async (): Promise => { 70 | const alice = new Bot() 71 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey, { 72 | botLite: false, 73 | disableTyping: false, 74 | }) 75 | expect(alice.myInfo()!.botLite).toBe(false) 76 | expect(alice.myInfo()!.disableTyping).toBe(false) 77 | await alice.deinit() 78 | }) 79 | 80 | it('can config without botLite or disableTyping', async (): Promise => { 81 | const alice = new Bot() 82 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey, {verbose: true}) 83 | expect(alice.myInfo()!.botLite).toBe(true) 84 | expect(alice.myInfo()!.disableTyping).toBe(true) 85 | await alice.deinit() 86 | }) 87 | 88 | it('can instantiate with debugLogging options', async (): Promise => { 89 | const alice1 = new Bot({debugLogging: true}) 90 | await alice1.init(config.bots.alice1.username, config.bots.alice1.paperkey) 91 | expect(alice1.myInfo()!.debugLogging).toBe(true) 92 | await alice1.deinit() 93 | 94 | const alice2 = new Bot({debugLogging: false}) 95 | await alice2.init(config.bots.alice1.username, config.bots.alice1.paperkey) 96 | expect(alice2.myInfo()!.debugLogging).toBe(false) 97 | await alice2.deinit() 98 | 99 | const alice3 = new Bot({}) 100 | await alice3.init(config.bots.alice1.username, config.bots.alice1.paperkey) 101 | expect(alice3.myInfo()!.debugLogging).toBe(false) 102 | await alice3.deinit() 103 | }) 104 | 105 | it('can config with admin debug mode', async (): Promise => { 106 | const tmpDir = os.tmpdir() 107 | const alice = new Bot() 108 | const initOptions: InitOptions = { 109 | adminDebugDirectory: tmpDir, 110 | } 111 | await alice.init(config.bots.alice1.username, config.bots.alice1.paperkey, initOptions) 112 | await alice.deinit() 113 | }) 114 | }) 115 | -------------------------------------------------------------------------------- /lib/kvstore-client/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ErrorWithCode, default as ClientBase } from '../client-base'; 2 | import * as keybase1 from '../types/keybase1'; 3 | export declare enum KVStoreErrorType { 4 | Other = 0, 5 | WrongRevision = 2760, 6 | BadGeneration = 2761, 7 | NotFound = 2762 8 | } 9 | export declare const ErrorIsWrongRevision: (error: ErrorWithCode) => boolean; 10 | export declare const ErrorIsBadGeneration: (error: ErrorWithCode) => boolean; 11 | export declare const ErrorIsNotFound: (error: ErrorWithCode) => boolean; 12 | /** The kvstore module of your Keybase bot. For more info about the API this module uses, you may want to check out `keybase kvstore help api`. */ 13 | declare class KVStore extends ClientBase { 14 | private normalizeTeam; 15 | /** 16 | * List all of a team's key-value namespaces with a non-deleted entryKey. 17 | * @memberof KVStore 18 | * @param team - The teamname to list namespaces for. Default to the bot's self implicit team if empty. 19 | * @returns - An array of namespaces. 20 | * @example 21 | * bot.kvstore.listNamespaces('phoenix').then(namespaces => console.log(namespaces)) 22 | */ 23 | listNamespaces(team: undefined | string): Promise; 24 | /** 25 | * List all of the non-deleted entryKeys in a namespace. 26 | * @memberof KVStore 27 | * @param team - The teamname to list entryKeys for. Default to the bot's self implicit team if empty. 28 | * @param namespace - The namespace to list entryKeys for. 29 | * @returns - An array of entryKeys and their revisions. 30 | * @example 31 | * bot.kvstore.listEntryKeys('phoenix', 'pw-manager').then(entryKeys => console.log(entryKeys)) 32 | */ 33 | listEntryKeys(team: undefined | string, namespace: string): Promise; 34 | /** 35 | * Get a key-value store entry. 36 | * @memberof KVStore 37 | * @param team - The teamname to get a value from. Default to the bot's self implicit team if empty. 38 | * @param namespace - The namespace to get a value from. 39 | * @param entryKey - The entryKey to get the value for. 40 | * @returns - An entryKey with its value. If this key does not exist, the revision will be 0. 41 | * @example 42 | * bot.kvstore.get('phoenix', 'pw-manager', 'geocities').then(({revision, entryValue}) => console.log({revision, entryValue})) 43 | */ 44 | get(team: undefined | string, namespace: string, entryKey: string): Promise; 45 | /** 46 | * Put a key-value store entry. Specifying a non-zero revision enables custom concurrency behavior, e.g. 1 will throw an error if the entry already exists. 47 | * @memberof KVStore 48 | * @param team - The teamname to set an entryKey in. Default to the bot's self implicit team if empty. 49 | * @param namespace - The namespace to set an entryKey in. 50 | * @param entryKey - The entryKey to set the value for. 51 | * @param entryValue - The value to set. 52 | * @param revision - A revision number (call `get()` to find out the latest) for enforcing safe concurrency. 53 | * @returns - An entryKey with its value. If this key does not exist, the revision will be 0. 54 | * @example 55 | * bot.kvstore.put('phoenix', 'pw-manager', 'geocities', 'hunter2').then(({entryKey, revision}) => console.log({entryKey, revision})) 56 | */ 57 | put(team: undefined | string, namespace: string, entryKey: string, entryValue: string, revision?: number): Promise; 58 | /** 59 | * Delete a key-value store entry. If you specify a revision number, deletion will fail if that revision is not the latest. 60 | * @memberof KVStore 61 | * @param team - The teamname to list entryKeys for. Default to the bot's self implicit team if empty. 62 | * @param namespace - The namespace to list entryKeys for. 63 | * @param entryKey - The entryKey to delete the value for. 64 | * @param revision - A revision number (call `get()` to find out the latest) for enforcing safe concurrency. 65 | * @returns - The deleted entryKey and its revision. 66 | * @example 67 | * bot.kvstore.delete('phoenix', 'pw-manager', 'geocities').then(({entryKey, revision}) => console.log({entryKey, revision})) 68 | */ 69 | delete(team: undefined | string, namespace: string, entryKey: string, revision?: number): Promise; 70 | /** 71 | * Determine whether the result of a `get()` call describes a deleted key-value store entry. 72 | * @memberof KVStore 73 | * @param res - The `get()` result to determine the status of. 74 | * @returns - Whether this key's value is deleted. 75 | * @example 76 | * bot.kvstore.isDeleted(res).then(isDeleted => console.log({isDeleted})) 77 | */ 78 | isDeleted(res: keybase1.KVGetResult): boolean; 79 | /** 80 | * Determine whether the result of a `get()` call describes an entryKey that has an existing value. 81 | * @memberof KVStore 82 | * @param res - The `get()` result to determine the status of. 83 | * @returns - Whether this key's value is present. 84 | * @example 85 | * bot.kvstore.isPresent(res).then(isPresent => console.log({isPresent})) 86 | */ 87 | isPresent(res: keybase1.KVGetResult): boolean; 88 | } 89 | export default KVStore; 90 | -------------------------------------------------------------------------------- /__tests__/team.test.ts: -------------------------------------------------------------------------------- 1 | import Bot from '../lib' 2 | import config from './tests.config' 3 | import {TeamRole, TeamDetails} from '../lib/types/keybase1' 4 | 5 | test('Team methods with an uninitialized bot', (): void => { 6 | const alice1 = new Bot() 7 | const options = {team: config.teams.alicesPlayground.teamname} 8 | expect(alice1.team.listTeamMemberships(options)).rejects.toThrowError() 9 | }) 10 | 11 | function pluralizeRole(r: TeamRole): 'owners' | 'admins' | 'readers' | 'writers' { 12 | switch (r) { 13 | case TeamRole.OWNER: 14 | return 'owners' 15 | case TeamRole.ADMIN: 16 | return 'admins' 17 | case TeamRole.READER: 18 | return 'readers' 19 | case TeamRole.WRITER: 20 | return 'writers' 21 | case TeamRole.NONE: 22 | case TeamRole.BOT: 23 | case TeamRole.RESTRICTEDBOT: 24 | throw new Error('role not expected in tests: ' + r.toString()) 25 | } 26 | } 27 | 28 | function checkMembershipLevel(username: string, teamListResult: TeamDetails): TeamRole | null { 29 | const possibleRoles: TeamRole[] = [TeamRole.OWNER, TeamRole.ADMIN, TeamRole.WRITER, TeamRole.READER] 30 | for (const role of possibleRoles) { 31 | for (const user of teamListResult.members[pluralizeRole(role)] ?? []) { 32 | if (user.username === username) { 33 | return role 34 | } 35 | } 36 | } 37 | return null 38 | } 39 | 40 | describe('Team Methods', (): void => { 41 | const alice1 = new Bot() 42 | const bob1 = new Bot() 43 | 44 | beforeAll( 45 | async (): Promise => { 46 | await alice1.init(config.bots.alice1.username, config.bots.alice1.paperkey) 47 | await bob1.init(config.bots.bob1.username, config.bots.bob1.paperkey) 48 | // As a cleanup operation, we need to make sure bob isn't left in a team with alice from a previous 49 | // failure. 50 | try { 51 | await alice1.team.removeMember({ 52 | team: config.teams.alicesPlayground.teamname, 53 | username: config.bots.bob1.username, 54 | }) 55 | console.log("Had to remove bob from alice's playground - left from previous test?") 56 | } catch (err) { 57 | /* no-op */ 58 | } 59 | } 60 | ) 61 | afterAll( 62 | async (): Promise => { 63 | await alice1.deinit() 64 | await bob1.deinit() 65 | } 66 | ) 67 | 68 | describe('Team list', (): void => { 69 | const teamName = config.teams.alicesPlayground.teamname 70 | 71 | it('Returns members of a team', async (): Promise => { 72 | const list = await alice1.team.listTeamMemberships({team: teamName}) 73 | expect(checkMembershipLevel(config.bots.alice1.username, list)).toBeTruthy() 74 | expect(checkMembershipLevel(config.bots.bob1.username, list)).toBe(null) 75 | }) 76 | 77 | it('Lets Alice add Bob to her team', async (): Promise => { 78 | await alice1.team.addMembers({ 79 | team: teamName, 80 | usernames: [{username: config.bots.bob1.username, role: 'reader'}], 81 | }) 82 | const list = await alice1.team.listTeamMemberships({team: teamName}) 83 | expect(checkMembershipLevel(config.bots.bob1.username, list)).toBe('reader') 84 | }) 85 | 86 | it('Throws an error if Alice tries to add Bob twice', async (): Promise => { 87 | expect( 88 | alice1.team.addMembers({ 89 | team: teamName, 90 | usernames: [{username: config.bots.bob1.username, role: 'reader'}], 91 | }) 92 | ).rejects.toThrowError() 93 | }) 94 | 95 | it('Throws an error if Alice creates a team that already exists', async (): Promise => { 96 | expect( 97 | alice1.team.create({ 98 | team: teamName, 99 | }) 100 | ).rejects.toThrowError() 101 | }) 102 | 103 | it('Lets Alice create a new team', async (): Promise => { 104 | const unixTime = Date.now() 105 | .toString() 106 | .substr(0, 10) 107 | const randomDigits = Math.floor(Math.random() * 100).toString() 108 | const newTeamName = 'test' + unixTime + randomDigits 109 | const result = await alice1.team.create({ 110 | team: newTeamName, 111 | }) 112 | expect(typeof result.chatSent).toBe('boolean') 113 | expect(typeof result.creatorAdded).toBe('boolean') 114 | }) 115 | 116 | it('Lets Alice remove Bob from her team', async (): Promise => { 117 | await alice1.team.removeMember({ 118 | team: teamName, 119 | username: config.bots.bob1.username, 120 | }) 121 | const list = await alice1.team.listTeamMemberships({team: teamName}) 122 | expect(checkMembershipLevel(config.bots.bob1.username, list)).toBe(null) 123 | }) 124 | 125 | it('Throws an error if Bob tries to do things in Alice team', async (): Promise => { 126 | expect(bob1.team.listTeamMemberships({team: teamName})).rejects.toThrowError() 127 | expect( 128 | bob1.team.addMembers({ 129 | team: teamName, 130 | usernames: [{username: config.bots.bob1.username, role: 'reader'}], 131 | }) 132 | ).rejects.toThrowError() 133 | expect(bob1.team.removeMember({team: teamName, username: config.bots.bob1.username})).rejects.toThrowError() 134 | }) 135 | }) 136 | }) 137 | -------------------------------------------------------------------------------- /lib/types/stellar1/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* 3 | * stellar.1 4 | * 5 | * Auto-generated to TypeScript types by avdl-compiler v1.4.8 (https://github.com/keybase/node-avdl-compiler) 6 | * Input files: 7 | * - ../client/protocol/avdl/stellar1/bundle.avdl 8 | * - ../client/protocol/avdl/stellar1/common.avdl 9 | * - ../client/protocol/avdl/stellar1/gregor.avdl 10 | * - ../client/protocol/avdl/stellar1/local.avdl 11 | * - ../client/protocol/avdl/stellar1/notify.avdl 12 | * - ../client/protocol/avdl/stellar1/remote.avdl 13 | * - ../client/protocol/avdl/stellar1/ui.avdl 14 | */ 15 | Object.defineProperty(exports, "__esModule", { value: true }); 16 | var BundleVersion; 17 | (function (BundleVersion) { 18 | BundleVersion["V1"] = "v1"; 19 | BundleVersion["V2"] = "v2"; 20 | BundleVersion["V3"] = "v3"; 21 | BundleVersion["V4"] = "v4"; 22 | BundleVersion["V5"] = "v5"; 23 | BundleVersion["V6"] = "v6"; 24 | BundleVersion["V7"] = "v7"; 25 | BundleVersion["V8"] = "v8"; 26 | BundleVersion["V9"] = "v9"; 27 | BundleVersion["V10"] = "v10"; 28 | })(BundleVersion = exports.BundleVersion || (exports.BundleVersion = {})); 29 | var AccountBundleVersion; 30 | (function (AccountBundleVersion) { 31 | AccountBundleVersion["V1"] = "v1"; 32 | AccountBundleVersion["V2"] = "v2"; 33 | AccountBundleVersion["V3"] = "v3"; 34 | AccountBundleVersion["V4"] = "v4"; 35 | AccountBundleVersion["V5"] = "v5"; 36 | AccountBundleVersion["V6"] = "v6"; 37 | AccountBundleVersion["V7"] = "v7"; 38 | AccountBundleVersion["V8"] = "v8"; 39 | AccountBundleVersion["V9"] = "v9"; 40 | AccountBundleVersion["V10"] = "v10"; 41 | })(AccountBundleVersion = exports.AccountBundleVersion || (exports.AccountBundleVersion = {})); 42 | var TransactionStatus; 43 | (function (TransactionStatus) { 44 | TransactionStatus["NONE"] = "none"; 45 | TransactionStatus["PENDING"] = "pending"; 46 | TransactionStatus["SUCCESS"] = "success"; 47 | TransactionStatus["ERROR_TRANSIENT"] = "error_transient"; 48 | TransactionStatus["ERROR_PERMANENT"] = "error_permanent"; 49 | })(TransactionStatus = exports.TransactionStatus || (exports.TransactionStatus = {})); 50 | var RequestStatus; 51 | (function (RequestStatus) { 52 | RequestStatus["OK"] = "ok"; 53 | RequestStatus["CANCELED"] = "canceled"; 54 | RequestStatus["DONE"] = "done"; 55 | })(RequestStatus = exports.RequestStatus || (exports.RequestStatus = {})); 56 | var PaymentStrategy; 57 | (function (PaymentStrategy) { 58 | PaymentStrategy["NONE"] = "none"; 59 | PaymentStrategy["DIRECT"] = "direct"; 60 | PaymentStrategy["RELAY"] = "relay"; 61 | })(PaymentStrategy = exports.PaymentStrategy || (exports.PaymentStrategy = {})); 62 | var RelayDirection; 63 | (function (RelayDirection) { 64 | RelayDirection["CLAIM"] = "claim"; 65 | RelayDirection["YANK"] = "yank"; 66 | })(RelayDirection = exports.RelayDirection || (exports.RelayDirection = {})); 67 | var AccountMode; 68 | (function (AccountMode) { 69 | AccountMode["NONE"] = "none"; 70 | AccountMode["USER"] = "user"; 71 | AccountMode["MOBILE"] = "mobile"; 72 | })(AccountMode = exports.AccountMode || (exports.AccountMode = {})); 73 | var BalanceDelta; 74 | (function (BalanceDelta) { 75 | BalanceDelta["NONE"] = "none"; 76 | BalanceDelta["INCREASE"] = "increase"; 77 | BalanceDelta["DECREASE"] = "decrease"; 78 | })(BalanceDelta = exports.BalanceDelta || (exports.BalanceDelta = {})); 79 | var PaymentStatus; 80 | (function (PaymentStatus) { 81 | PaymentStatus["NONE"] = "none"; 82 | PaymentStatus["PENDING"] = "pending"; 83 | PaymentStatus["CLAIMABLE"] = "claimable"; 84 | PaymentStatus["COMPLETED"] = "completed"; 85 | PaymentStatus["ERROR"] = "error"; 86 | PaymentStatus["UNKNOWN"] = "unknown"; 87 | PaymentStatus["CANCELED"] = "canceled"; 88 | })(PaymentStatus = exports.PaymentStatus || (exports.PaymentStatus = {})); 89 | var ParticipantType; 90 | (function (ParticipantType) { 91 | ParticipantType["NONE"] = "none"; 92 | ParticipantType["KEYBASE"] = "keybase"; 93 | ParticipantType["STELLAR"] = "stellar"; 94 | ParticipantType["SBS"] = "sbs"; 95 | ParticipantType["OWNACCOUNT"] = "ownaccount"; 96 | })(ParticipantType = exports.ParticipantType || (exports.ParticipantType = {})); 97 | var AdvancedBanner; 98 | (function (AdvancedBanner) { 99 | AdvancedBanner["NO_BANNER"] = "no_banner"; 100 | AdvancedBanner["SENDER_BANNER"] = "sender_banner"; 101 | AdvancedBanner["RECEIVER_BANNER"] = "receiver_banner"; 102 | })(AdvancedBanner = exports.AdvancedBanner || (exports.AdvancedBanner = {})); 103 | var PublicNoteType; 104 | (function (PublicNoteType) { 105 | PublicNoteType["NONE"] = "none"; 106 | PublicNoteType["TEXT"] = "text"; 107 | PublicNoteType["ID"] = "id"; 108 | PublicNoteType["HASH"] = "hash"; 109 | PublicNoteType["RETURN"] = "return"; 110 | })(PublicNoteType = exports.PublicNoteType || (exports.PublicNoteType = {})); 111 | var PaymentSummaryType; 112 | (function (PaymentSummaryType) { 113 | PaymentSummaryType["NONE"] = "none"; 114 | PaymentSummaryType["STELLAR"] = "stellar"; 115 | PaymentSummaryType["DIRECT"] = "direct"; 116 | PaymentSummaryType["RELAY"] = "relay"; 117 | })(PaymentSummaryType = exports.PaymentSummaryType || (exports.PaymentSummaryType = {})); 118 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /src/utils/formatAPIObject.ts: -------------------------------------------------------------------------------- 1 | import snakeCase from 'lodash.snakecase' 2 | import camelCase from 'lodash.camelcase' 3 | import kebabCase from 'lodash.kebabcase' 4 | import {API_TYPES} from '../constants' 5 | 6 | /** 7 | Takes a Keybase API input JavaScript object and recursively formats it into snake_case or kebab-case instead of camelCase for the service. 8 | * @ignore 9 | * @param obj - The object to be formatted. 10 | * @param apiType - The type of api the the input is being served to. Currently Keybase has chat, team, and wallet apis. 11 | * @returns - The new, formatted object. 12 | * @example 13 | * const inputOptions = formatAPIObject({unreadOnly: true}) 14 | * console.log(inputOptions) // {unread_only: true} 15 | */ 16 | export function formatAPIObjectInput(obj: any, apiType: API_TYPES): any { 17 | if (obj === null || obj === undefined || typeof obj !== 'object') { 18 | return obj 19 | } else if (Array.isArray(obj)) { 20 | return obj.map(item => formatAPIObjectInput(item, apiType)) 21 | } else { 22 | return Object.keys(obj).reduce((newObj, key) => { 23 | // TODO: hopefully we standardize how the Keybase API handles input keys 24 | let formattedKey 25 | if (apiType === 'wallet') { 26 | formattedKey = kebabCase(key) 27 | } else { 28 | formattedKey = snakeCase(key) 29 | } 30 | 31 | if (typeof obj[key] === 'object') { 32 | return {...newObj, [formattedKey]: formatAPIObjectInput(obj[key], apiType)} 33 | } 34 | return {...newObj, [formattedKey]: obj[key]} 35 | }, {}) 36 | } 37 | } 38 | 39 | /* 40 | * An internal blacklist of parent levels at which formatAPIObjectOutput transformations 41 | * shouldn't be performed. A `null` value matches everything. 42 | */ 43 | const transformsBlacklist: any = { 44 | team: {}, 45 | wallet: {}, 46 | chat: { 47 | read: [['messages', null, 'msg', 'reactions', 'reactions', null]], 48 | }, 49 | } 50 | 51 | /** 52 | * Context of the object formatting process. 53 | * @ignore 54 | */ 55 | export type FormatAPIObjectOutputContext = { 56 | apiName: API_TYPES 57 | method: string 58 | parent?: any[] 59 | } 60 | 61 | /* 62 | * Matches a context against the list of blacklisted parent levels. 63 | * @ignore 64 | * @param context - The context to match. 65 | * @returns - Whether the context is blacklisted from being formatted. 66 | */ 67 | function matchBlacklist(context?: FormatAPIObjectOutputContext | null): boolean { 68 | if (!context || !transformsBlacklist[context.apiName] || !transformsBlacklist[context.apiName][context.method]) { 69 | return false 70 | } 71 | 72 | const parentLength = context.parent ? context.parent.length : 0 73 | 74 | for (const matcher of transformsBlacklist[context.apiName][context.method]) { 75 | if (matcher.length !== parentLength) { 76 | continue 77 | } 78 | 79 | // Iterate over the items of the matcher 80 | let mismatch = false 81 | for (const [matcherIndex, desiredValue] of matcher.entries()) { 82 | if (desiredValue === null) { 83 | continue 84 | } 85 | 86 | if (typeof context.parent === 'object' && context.parent[matcherIndex] !== desiredValue) { 87 | mismatch = true 88 | break 89 | } 90 | } 91 | if (!mismatch) { 92 | return true 93 | } 94 | } 95 | 96 | return false 97 | } 98 | 99 | /* 100 | * Appends a new key to the parents array in the formatting context. 101 | * @ignore 102 | * @param context - The context to copy and modify. 103 | * @param key - The key to apprent to the parent array. 104 | * @returns - A new context. 105 | */ 106 | function buildContext(context: FormatAPIObjectOutputContext | null, key: any): FormatAPIObjectOutputContext | null { 107 | if (!context) { 108 | return context ?? null 109 | } 110 | 111 | const copiedContext: FormatAPIObjectOutputContext = {...context} 112 | if (!copiedContext.parent) { 113 | copiedContext.parent = [key] 114 | } else { 115 | copiedContext.parent = copiedContext.parent.slice() 116 | copiedContext.parent.push(key) 117 | } 118 | 119 | return copiedContext 120 | } 121 | 122 | /** 123 | Takes a Keybase output object and formats it in a more digestable JavaScript style by using camelCase instead of snake_case. 124 | * @ignore 125 | * @param obj - The object to be formatted. 126 | * @param context - An optional context with information about the called method required to perform blacklist lookups. 127 | * @returns - The new, formatted object. 128 | * @example 129 | * const outputRes = formatAPIObject({unread_only: true}) 130 | * console.log(outputRes) // {unreadOnly: true} 131 | */ 132 | export function formatAPIObjectOutput(obj: any, context: FormatAPIObjectOutputContext | null): any { 133 | if (obj == null || typeof obj !== 'object') { 134 | return obj 135 | } else if (Array.isArray(obj)) { 136 | return obj.map((item, i) => formatAPIObjectOutput(item, buildContext(context, i))) 137 | } else { 138 | return Object.keys(obj).reduce((newObj, key) => { 139 | const formattedKey = matchBlacklist(context) ? key : camelCase(key) 140 | if (typeof obj[key] === 'object') { 141 | return {...newObj, [formattedKey]: formatAPIObjectOutput(obj[key], buildContext(context, key))} 142 | } 143 | return {...newObj, [formattedKey]: obj[key]} 144 | }, {}) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAA+B;AAC/B,8DAAsC;AACtC,kEAA0C;AAC1C,8DAAsC;AACtC,oEAA4C;AAC5C,oEAA4C;AAG5C,kDAA2B;AAC3B,iCAAuE;AACvE,6BAA8B;AAC9B,yBAA2B;AAC3B,8CAAuB;AAEvB,6DAAyD;AACzD,6CAAgC;AAChC,kDAA2B;AAC3B,0CAAmB;AAMnB,IAAM,WAAW,GAAuB;IACtC,YAAY,EAAE,KAAK;CACpB,CAAA;AAED,qBAAqB;AACrB;IAYE;;;;;OAKG;IACH,aAAmB,IAAyB;;QAC1C,IAAM,YAAY,eAAG,IAAI,0CAAE,YAAY,uCAAI,WAAW,CAAC,YAAY,EAAA,CAAA;QACnE,IAAI,CAAC,MAAM,GAAG,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACpD,IAAI,CAAC,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,MAAM,EAAE,EAAE,iBAAe,IAAI,CAAC,MAAQ,CAAC,CAAA;QACvE,IAAI,CAAC,iBAAiB,GAAG,IAAI,mCAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC1D,IAAI,CAAC,QAAQ,GAAG,IAAI,iBAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,iBAAiB,GAAE,YAAY,aAAZ,YAAY,cAAZ,YAAY,GAAI,KAAK,EAAC,CAAA;QAC5F,IAAI,CAAC,IAAI,GAAG,IAAI,qBAAU,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAA;QACpE,IAAI,CAAC,MAAM,GAAG,IAAI,uBAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAA;QACxE,IAAI,CAAC,IAAI,GAAG,IAAI,qBAAU,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAA;QACpE,IAAI,CAAC,OAAO,GAAG,IAAI,wBAAa,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAA;QAC1E,IAAI,CAAC,OAAO,GAAG,IAAI,wBAAa,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAA;QAC1E,IAAI,CAAC,WAAW,GAAG,SAAS,CAAA;IAC9B,CAAC;IAED;;;;;;;;OAQG;IACU,kBAAI,GAAjB,UAAkB,QAAgB,EAAE,QAAgB,EAAE,OAAqB;;;;;;wBACzE,IAAI,CAAC,eAAe,EAAE,CAAA;wBACtB,qBAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC,EAAA;;wBAA/E,SAA+E,CAAA;wBAC/E,qBAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAA;;wBAArD,SAAqD,CAAA;wBACrD,qBAAM,IAAI,CAAC,YAAY,EAAE,EAAA;;wBAAzB,SAAyB,CAAA;6BACrB,CAAA,OAAO,IAAI,OAAO,CAAC,mBAAmB,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAA,EAAtE,wBAAsE;wBACxE,4BAAM,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,IAAC;;wBAA7F,SAA6F,CAAA;;;wBAE/F,IAAI,CAAC,WAAW,GAAG,aAAa,CAAA;wBAChC,MAAA,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,aAAa,EAAC;;;;;KAC5C;IAED;;;;;;;OAOG;IACU,oCAAsB,GAAnC,UAAoC,OAAgB,EAAE,OAAqB;;;;;;wBACzE,IAAI,CAAC,eAAe,EAAE,CAAA;wBACtB,qBAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC,EAAA;;wBAA/E,SAA+E,CAAA;6BAC3E,CAAA,OAAO,IAAI,OAAO,CAAC,mBAAmB,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAA,EAAtE,wBAAsE;wBACxE,4BAAM,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,IAAC;;wBAA7F,SAA6F,CAAA;;4BAE/F,qBAAM,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAA;;wBAA5D,SAA4D,CAAA;wBAC5D,qBAAM,IAAI,CAAC,YAAY,EAAE,EAAA;;wBAAzB,SAAyB,CAAA;wBACzB,MAAA,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,aAAa,EAAC;;;;;KAC5C;IAEO,6BAAe,GAAvB;;QACE,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE;YAClC,MAAM,IAAI,KAAK,CAAC,yCAAuC,IAAI,CAAC,WAAa,CAAC,CAAA;SAC3E;QACD,IAAI,CAAC,WAAW,GAAG,cAAc,CAAA;QACjC,MAAA,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,0BAA0B,EAAC;IAC1D,CAAC;IAED;;;;;;OAMG;IACI,oBAAM,GAAb;;QACE,YAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,uCAAI,IAAI,EAAA;IACvC,CAAC;IAED;;;;;OAKG;IACU,oBAAM,GAAnB;;;;;;6BAGM,CAAA,IAAI,CAAC,WAAW,KAAK,gBAAgB,IAAI,IAAI,CAAC,WAAW,KAAK,eAAe,CAAA,EAA7E,wBAA6E;wBAC/E,MAAA,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,4CAA4C,EAAC;;;wBAE1E,IAAI,CAAC,WAAW,GAAG,gBAAgB,CAAA;wBACnC,MAAA,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,kBAAkB,EAAC;wBAChD,qBAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAA;;wBAAzB,SAAyB,CAAA;wBACzB,qBAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAA;;wBAA5B,SAA4B,CAAA;wBAC5B,qBAAM,sBAAc,CAAC,IAAI,CAAC,WAAW,CAAC,EAAA;;wBAAtC,SAAsC,CAAA;wBACtC,MAAA,IAAI,CAAC,iBAAiB,0CAAE,IAAI,CAAC,iBAAiB,EAAC;wBAC/C,MAAA,IAAI,CAAC,iBAAiB,0CAAE,MAAM,GAAE;wBAChC,IAAI,CAAC,WAAW,GAAG,eAAe,CAAA;;;;;;KAErC;IAED;;;;;OAKG;IAEU,+BAAiB,GAA9B,UAA+B,IAAY;;;gBACzC,IAAI,IAAI,CAAC,iBAAiB,EAAE;oBAC1B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;iBAClC;;;;KACF;IACD;;;;;OAKG;IAEU,gCAAkB,GAA/B,UAAgC,IAAY;;;gBAC1C,IAAI,IAAI,CAAC,iBAAiB,EAAE;oBAC1B,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;iBACnC;;;;KACF;IAED;;OAEG;IACU,qBAAO,GAApB;;;gBACE,sBAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAA;;;KAC/B;IAED;;;;OAIG;IACU,mBAAK,GAAlB,UAAmB,SAAmC,EAAE,QAAiB;;;;;;wBACjE,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAS,SAAS,SAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAI,CAAC,CAAA;wBAC1F,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;wBAC3C,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;wBAC5B,IAAI,CAAC,MAAM;4BAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;wBACjE,qBAAM,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO;gCACtD,OAAO;gCACP,SAAS;+BACN,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAK,GAAG,MAAG,CAAC,CAAC;gCAClD,UAAU;+BACV;4BACF,mEAAmE;0BADjE;;wBALF,SAKE,CAAA;wBACF,mEAAmE;wBACnE,qBAAM,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,EAAA;;wBADtC,mEAAmE;wBACnE,SAAsC,CAAA;wBACtC,sBAAO,UAAU,EAAA;;;;KAClB;IAED,sBAAW,sBAAK;aAAhB;YACE,OAAO,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC;;;OAAA;IAED,sBAAW,mCAAkB;aAA7B;YACE,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE;gBAChC,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAA;aACpC;iBAAM;gBACL,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAA;aAC/E;QACH,CAAC;;;OAAA;IAEa,6BAAe,GAA7B,UAA8B,qBAA8B;;;;;;6BACtD,CAAC,qBAAqB,EAAtB,wBAAsB;wBACA,qBAAM,oBAAY,EAAE,EAAA;;wBAA5C,qBAAqB,GAAG,SAAoB,CAAA;;;wBAExC,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,yBAAiB,CAAC,CAAA;wBAClE,qBAAM,gBAAS,CAAC,gBAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAA;;wBAAzC,SAAyC,CAAA;wBACzC,qBAAM,gBAAS,CAAC,aAAQ,CAAC,CAAC,qBAAqB,EAAE,WAAW,CAAC,EAAA;;wBAA7D,SAA6D,CAAA;;;;;KAC9D;IAEa,0BAAY,GAA1B;;;;;;wBACQ,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;6BACtB,IAAI,EAAJ,wBAAI;wBACN,qBAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAA;;wBAAnC,SAAmC,CAAA;wBACnC,qBAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAA;;wBAArC,SAAqC,CAAA;wBACrC,qBAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAA;;wBAAnC,SAAmC,CAAA;wBACnC,qBAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAA;;wBAAtC,SAAsC,CAAA;wBACtC,qBAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAA;;wBAAtC,SAAsC,CAAA;;4BAEtC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;;;;;KAE7C;IACH,UAAC;AAAD,CAAC,AAxMD,IAwMC;AAOD,iBAAS,GAAG,CAAA"} -------------------------------------------------------------------------------- /lib/helpers-client/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = function (d, b) { 4 | extendStatics = Object.setPrototypeOf || 5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 6 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 7 | return extendStatics(d, b); 8 | }; 9 | return function (d, b) { 10 | extendStatics(d, b); 11 | function __() { this.constructor = d; } 12 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 13 | }; 14 | })(); 15 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 16 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 17 | return new (P || (P = Promise))(function (resolve, reject) { 18 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 19 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 20 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 21 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 22 | }); 23 | }; 24 | var __generator = (this && this.__generator) || function (thisArg, body) { 25 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 26 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 27 | function verb(n) { return function (v) { return step([n, v]); }; } 28 | function step(op) { 29 | if (f) throw new TypeError("Generator is already executing."); 30 | while (_) try { 31 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 32 | if (y = 0, t) op = [op[0] & 2, t.value]; 33 | switch (op[0]) { 34 | case 0: case 1: t = op; break; 35 | case 4: _.label++; return { value: op[1], done: false }; 36 | case 5: _.label++; y = op[1]; op = [0]; continue; 37 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 38 | default: 39 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 40 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 41 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 42 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 43 | if (t[2]) _.ops.pop(); 44 | _.trys.pop(); continue; 45 | } 46 | op = body.call(thisArg, _); 47 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 48 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 49 | } 50 | }; 51 | var __importDefault = (this && this.__importDefault) || function (mod) { 52 | return (mod && mod.__esModule) ? mod : { "default": mod }; 53 | }; 54 | Object.defineProperty(exports, "__esModule", { value: true }); 55 | var client_base_1 = __importDefault(require("../client-base")); 56 | var utils_1 = require("../utils"); 57 | /* A module of various helper functions for your bot 58 | */ 59 | var Helpers = /** @class */ (function (_super) { 60 | __extends(Helpers, _super); 61 | function Helpers() { 62 | return _super !== null && _super.apply(this, arguments) || this; 63 | } 64 | /** 65 | * Make a call to a Keybase URL (TODO: add support for POST params) 66 | * @memberof Helpers 67 | * @param url - a full URL to hit 68 | * @returns - 69 | * @example 70 | * bot.helpers.rawApiCall({endpoint:"/me"}).then(res => console.log(res)) 71 | */ 72 | Helpers.prototype.rawApiCall = function (arg) { 73 | return __awaiter(this, void 0, void 0, function () { 74 | var args, k, output; 75 | return __generator(this, function (_a) { 76 | switch (_a.label) { 77 | case 0: return [4 /*yield*/, this._guardInitialized()]; 78 | case 1: 79 | _a.sent(); 80 | args = [arg.endpoint]; 81 | if (arg.arg) { 82 | for (k in arg.arg) { 83 | args.unshift('-a', k + "=" + arg.arg[k]); 84 | } 85 | } 86 | args.unshift('apicall'); 87 | return [4 /*yield*/, utils_1.keybaseExec(this._workingDir, this.homeDir, args, { json: true })]; 88 | case 2: 89 | output = _a.sent(); 90 | return [2 /*return*/, output]; 91 | } 92 | }); 93 | }); 94 | }; 95 | /** 96 | * Ping keybase server. The returned promise resolves the keybase daemon is 97 | * up and server is reachable. 98 | * @memberof Helpers 99 | * @returns - 100 | * @example 101 | * bot.helpers.ping() 102 | */ 103 | Helpers.prototype.ping = function () { 104 | return __awaiter(this, void 0, void 0, function () { 105 | return __generator(this, function (_a) { 106 | switch (_a.label) { 107 | case 0: return [4 /*yield*/, this._guardInitialized()]; 108 | case 1: 109 | _a.sent(); 110 | return [4 /*yield*/, utils_1.keybaseExec(this._workingDir, this.homeDir, ['ping'])]; 111 | case 2: return [2 /*return*/, _a.sent()]; 112 | } 113 | }); 114 | }); 115 | }; 116 | return Helpers; 117 | }(client_base_1.default)); 118 | exports.default = Helpers; 119 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/service/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /lib/utils/formatAPIObject.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __assign = (this && this.__assign) || function () { 3 | __assign = Object.assign || function(t) { 4 | for (var s, i = 1, n = arguments.length; i < n; i++) { 5 | s = arguments[i]; 6 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 7 | t[p] = s[p]; 8 | } 9 | return t; 10 | }; 11 | return __assign.apply(this, arguments); 12 | }; 13 | var __importDefault = (this && this.__importDefault) || function (mod) { 14 | return (mod && mod.__esModule) ? mod : { "default": mod }; 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | var lodash_snakecase_1 = __importDefault(require("lodash.snakecase")); 18 | var lodash_camelcase_1 = __importDefault(require("lodash.camelcase")); 19 | var lodash_kebabcase_1 = __importDefault(require("lodash.kebabcase")); 20 | /** 21 | Takes a Keybase API input JavaScript object and recursively formats it into snake_case or kebab-case instead of camelCase for the service. 22 | * @ignore 23 | * @param obj - The object to be formatted. 24 | * @param apiType - The type of api the the input is being served to. Currently Keybase has chat, team, and wallet apis. 25 | * @returns - The new, formatted object. 26 | * @example 27 | * const inputOptions = formatAPIObject({unreadOnly: true}) 28 | * console.log(inputOptions) // {unread_only: true} 29 | */ 30 | function formatAPIObjectInput(obj, apiType) { 31 | if (obj === null || obj === undefined || typeof obj !== 'object') { 32 | return obj; 33 | } 34 | else if (Array.isArray(obj)) { 35 | return obj.map(function (item) { return formatAPIObjectInput(item, apiType); }); 36 | } 37 | else { 38 | return Object.keys(obj).reduce(function (newObj, key) { 39 | var _a, _b; 40 | // TODO: hopefully we standardize how the Keybase API handles input keys 41 | var formattedKey; 42 | if (apiType === 'wallet') { 43 | formattedKey = lodash_kebabcase_1.default(key); 44 | } 45 | else { 46 | formattedKey = lodash_snakecase_1.default(key); 47 | } 48 | if (typeof obj[key] === 'object') { 49 | return __assign(__assign({}, newObj), (_a = {}, _a[formattedKey] = formatAPIObjectInput(obj[key], apiType), _a)); 50 | } 51 | return __assign(__assign({}, newObj), (_b = {}, _b[formattedKey] = obj[key], _b)); 52 | }, {}); 53 | } 54 | } 55 | exports.formatAPIObjectInput = formatAPIObjectInput; 56 | /* 57 | * An internal blacklist of parent levels at which formatAPIObjectOutput transformations 58 | * shouldn't be performed. A `null` value matches everything. 59 | */ 60 | var transformsBlacklist = { 61 | team: {}, 62 | wallet: {}, 63 | chat: { 64 | read: [['messages', null, 'msg', 'reactions', 'reactions', null]], 65 | }, 66 | }; 67 | /* 68 | * Matches a context against the list of blacklisted parent levels. 69 | * @ignore 70 | * @param context - The context to match. 71 | * @returns - Whether the context is blacklisted from being formatted. 72 | */ 73 | function matchBlacklist(context) { 74 | if (!context || !transformsBlacklist[context.apiName] || !transformsBlacklist[context.apiName][context.method]) { 75 | return false; 76 | } 77 | var parentLength = context.parent ? context.parent.length : 0; 78 | for (var _i = 0, _a = transformsBlacklist[context.apiName][context.method]; _i < _a.length; _i++) { 79 | var matcher = _a[_i]; 80 | if (matcher.length !== parentLength) { 81 | continue; 82 | } 83 | // Iterate over the items of the matcher 84 | var mismatch = false; 85 | for (var _b = 0, _c = matcher.entries(); _b < _c.length; _b++) { 86 | var _d = _c[_b], matcherIndex = _d[0], desiredValue = _d[1]; 87 | if (desiredValue === null) { 88 | continue; 89 | } 90 | if (typeof context.parent === 'object' && context.parent[matcherIndex] !== desiredValue) { 91 | mismatch = true; 92 | break; 93 | } 94 | } 95 | if (!mismatch) { 96 | return true; 97 | } 98 | } 99 | return false; 100 | } 101 | /* 102 | * Appends a new key to the parents array in the formatting context. 103 | * @ignore 104 | * @param context - The context to copy and modify. 105 | * @param key - The key to apprent to the parent array. 106 | * @returns - A new context. 107 | */ 108 | function buildContext(context, key) { 109 | if (!context) { 110 | return (context !== null && context !== void 0 ? context : null); 111 | } 112 | var copiedContext = __assign({}, context); 113 | if (!copiedContext.parent) { 114 | copiedContext.parent = [key]; 115 | } 116 | else { 117 | copiedContext.parent = copiedContext.parent.slice(); 118 | copiedContext.parent.push(key); 119 | } 120 | return copiedContext; 121 | } 122 | /** 123 | Takes a Keybase output object and formats it in a more digestable JavaScript style by using camelCase instead of snake_case. 124 | * @ignore 125 | * @param obj - The object to be formatted. 126 | * @param context - An optional context with information about the called method required to perform blacklist lookups. 127 | * @returns - The new, formatted object. 128 | * @example 129 | * const outputRes = formatAPIObject({unread_only: true}) 130 | * console.log(outputRes) // {unreadOnly: true} 131 | */ 132 | function formatAPIObjectOutput(obj, context) { 133 | if (obj == null || typeof obj !== 'object') { 134 | return obj; 135 | } 136 | else if (Array.isArray(obj)) { 137 | return obj.map(function (item, i) { return formatAPIObjectOutput(item, buildContext(context, i)); }); 138 | } 139 | else { 140 | return Object.keys(obj).reduce(function (newObj, key) { 141 | var _a, _b; 142 | var formattedKey = matchBlacklist(context) ? key : lodash_camelcase_1.default(key); 143 | if (typeof obj[key] === 'object') { 144 | return __assign(__assign({}, newObj), (_a = {}, _a[formattedKey] = formatAPIObjectOutput(obj[key], buildContext(context, key)), _a)); 145 | } 146 | return __assign(__assign({}, newObj), (_b = {}, _b[formattedKey] = obj[key], _b)); 147 | }, {}); 148 | } 149 | } 150 | exports.formatAPIObjectOutput = formatAPIObjectOutput; 151 | //# sourceMappingURL=formatAPIObject.js.map --------------------------------------------------------------------------------