import * as path from 'path' import * as tribes from '../utils/tribes' import * as crypto from 'crypto' import { models } from '../models' import * as jsonUtils from '../utils/json' import { success, failure } from '../utils/res' import * as network from '../network' import {finalAction,Action} from './actions' import * as socket from '../utils/socket' import fetch from 'node-fetch' import * as SphinxBot from 'sphinx-bot' import {Msg} from '../network/interfaces' const constants = require(path.join(__dirname, '../../config/constants.json')) export const getBots = async (req, res) => { try { const bots = await models.Bot.findAll() success(res, { bots: bots.map(b => jsonUtils.botToJson(b)) }) } catch (e) { failure(res, 'no bots') } } export const createBot = async (req, res) => { const { name, webhook, price_per_use, img, description, tags, } = req.body const uuid = await tribes.genSignedTimestamp() const newBot = { name, uuid, webhook, id: crypto.randomBytes(12).toString('hex').toUpperCase(), secret: crypto.randomBytes(16).toString('hex').toUpperCase(), pricePerUse: price_per_use||0 } try { const owner = await models.Contact.findOne({ where: { isOwner: true } }) const theBot = await models.Bot.create(newBot) // post to tribes.sphinx.chat tribes.declare_bot({ uuid, owner_pubkey: owner.publicKey, price_per_use, name: name, description: description||'', tags: tags||[], img: img||'', unlisted:false, deleted:false, }) success(res, jsonUtils.botToJson(theBot)) } catch (e) { failure(res, 'bot creation failed') } } export const deleteBot = async (req, res) => { const id = req.params.id if (!id) return try { models.Bot.destroy({ where: { id } }) success(res, true) } catch (e) { console.log('ERROR deleteBot', e) failure(res, e) } } export async function installBot(chat, bot_json) { const chatId = chat && chat.id const chat_uuid = chat && chat.uuid if(!chatId || !chat_uuid) return console.log('no chat id in installBot') console.log("=> chat to install bot into", chat) const owner = await models.Contact.findOne({ where: { isOwner: true } }) const isTribeOwner = (owner && owner.publicKey) === (chat && chat.ownerPubkey) if(!isTribeOwner) return console.log('=> only tribe owner can install bots') const {uuid,owner_pubkey,unique_name,price_per_use} = bot_json const isLocal = owner_pubkey===owner.publicKey let botType = constants.bot_types.remote if(isLocal) { console.log('=> install local bot now!') botType=constants.bot_types.local } const chatBot = { chatId, botPrefix: '/' +unique_name, botType: botType, botUuid: uuid, botMakerPubkey: owner_pubkey, pricePerUse: price_per_use } if(isLocal) { await models.ChatBot.create(chatBot) } else { console.log("installBot INSTALL REMOTE BOT NOW",chatBot) const succeeded = await keysendBotInstall(chatBot, chat_uuid) if(succeeded) { try { // could fail await models.ChatBot.create(chatBot) } catch(e){} } } } export async function keysendBotInstall(b, chat_uuid:string): Promise { return await botKeysend( constants.message_types.bot_install, b.botUuid, b.botMakerPubkey, b.pricePerUse, chat_uuid ) } export async function keysendBotCmd(msg, b): Promise { return await botKeysend( constants.message_types.bot_cmd, b.botUuid, b.botMakerPubkey, b.pricePerUse, msg.chat.uuid, msg.message.content, ) } export async function botKeysend(msg_type, bot_uuid, botmaker_pubkey, amount, chat_uuid:string, content?:string): Promise { const owner = await models.Contact.findOne({ where: { isOwner: true } }) const dest = botmaker_pubkey const MIN_SATS = 3 const amt = Math.max(amount || MIN_SATS) const opts = { amt, dest, data: { type: msg_type, bot_uuid, message: {content: content||'', amount:amt}, sender: {pub_key: owner.publicKey, alias: owner.alias}, chat: {uuid:chat_uuid} }, } try { await network.signAndSend(opts) return true } catch (e) { return false } } export async function receiveBotInstall(payload) { console.log('=> receiveBotInstall',payload) const dat = payload.content || payload const sender_pub_key = dat.sender && dat.sender.pub_key const bot_uuid = dat.bot_uuid const chat_uuid = dat.chat && dat.chat.uuid if(!chat_uuid || !sender_pub_key) return console.log('no chat uuid or sender pub key') const owner = await models.Contact.findOne({ where: { isOwner: true } }) const bot = await models.Bot.findOne({where:{ uuid: bot_uuid }}) if(!bot) return const verifiedOwnerPubkey = await tribes.verifySignedTimestamp(bot_uuid) if(verifiedOwnerPubkey===owner.publicKey){ const botMember = { botId: bot.id, memberPubkey:sender_pub_key, tribeUuid:chat_uuid, msgCount:0, } console.log("CREATE bot MEMBER", botMember) await models.BotMember.create(botMember) } //- need to pub back MQTT bot_install?? //- and if the pubkey=the botOwnerPubkey, confirm chatbot? // NO - send a /guildjoin msg to BOT lib! // and add routes to lib express with the strings for MSG_TYPE // and here - postToBotServer /install (also do this for /uninstall) postToBotServer(payload, bot, SphinxBot.MSG_TYPE.INSTALL) } // ONLY FOR BOT MAKER export async function receiveBotCmd(payload) { console.log("=> receiveBotCmd", payload) const dat = payload.content || payload // const sender_pub_key = dat.sender.pub_key const bot_uuid = dat.bot_uuid const chat_uuid = dat.chat && dat.chat.uuid if(!chat_uuid) return console.log('no chat uuid') // const amount = dat.message.amount - check price_per_use const bot = await models.Bot.findOne({where:{ uuid: bot_uuid }}) if(!bot) return const botMember = await models.BotMember.findOne({ where:{ botId: bot.id, tribeUuid: chat_uuid, }}) if(!botMember) return botMember.update({ msgCount: (botMember||0)+1 }) console.log('=> post to remote BOT!!!!! bot owner') postToBotServer(payload, bot, SphinxBot.MSG_TYPE.MESSAGE) // forward to the entire Action back over MQTT } export async function postToBotServer(msg, bot, route:string): Promise { if(!bot) return false if(!bot.webhook || !bot.secret) return false let url = bot.webhook if(url.charAt(url.length-1)==='/') { url += route } else { url += ('/'+route) } const r = await fetch(url, { method:'POST', body:JSON.stringify( buildBotPayload(msg) ), headers:{ 'x-secret': bot.secret, 'Content-Type': 'application/json' } }) return r.ok } export function buildBotPayload(msg:Msg): SphinxBot.Message { const m = { channel:{ id: msg.chat.uuid, send:function(){}, }, reply:function(){}, content: msg.message.content, amount: msg.message.amount, type: msg.type, member: { id: msg.sender.pub_key, nickname: msg.sender.alias, roles:[] } } if(msg.sender.role===constants.chat_roles.owner) { if(m.member) m.member.roles=[{ name:'Admin' }] } return m } export async function receiveBotRes(payload) { console.log("=> receiveBotRes", payload) const dat = payload.content || payload if(!dat.chat || !dat.message || !dat.sender) { return console.log('=> receiveBotRes error, no chat||msg||sender') } const chat_uuid = dat.chat && dat.chat.uuid const sender_pub_key =dat.sender.pub_key const amount = dat.message.amount||0 const msg_uuid = dat.message.uuid||'' const content = dat.message.content const action = dat.action const bot_name = dat.bot_name const sender_alias = dat.sender.alias if(!chat_uuid) return console.log('=> receiveBotRes Error no chat_uuid') const chat = await models.Chat.findOne({where:{uuid:chat_uuid}}) if(!chat) return console.log('=> receiveBotRes Error no chat') const tribeOwnerPubKey = chat && chat.ownerPubkey const owner = await models.Contact.findOne({where: {isOwner:true}}) const isTribeOwner = owner.publicKey===tribeOwnerPubKey if(isTribeOwner){ console.log("=> is tribeOwner, do finalAction!") // IF IS TRIBE ADMIN forward to the tribe // received the entire action? const bot_id = payload.bot_id finalAction({ action, bot_name, chat_uuid, content, amount, }, bot_id) } else { const theChat = await models.Chat.findOne({where:{ uuid: chat_uuid }}) if(!chat) return console.log('=> receiveBotRes as sub error no chat') var date = new Date(); date.setMilliseconds(0) const sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key } }) const msg: { [k: string]: any } = { chatId: chat.id, uuid: msg_uuid, type: constants.message_types.bot_res, sender: (sender&&sender.id) || 0, amount: amount || 0, date: date, messageContent: content, status: constants.statuses.confirmed, createdAt: date, updatedAt: date, senderAlias: sender_alias || 'Bot', } const message = await models.Message.create(msg) socket.sendJson({ type: 'message', response: jsonUtils.messageToJson(message, theChat, owner) }) } }