You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
7.8 KiB
282 lines
7.8 KiB
|
|
import * as moment from 'moment'
|
|
import * as zbase32 from './zbase32'
|
|
import * as LND from './lightning'
|
|
import * as path from 'path'
|
|
import * as mqtt from 'mqtt'
|
|
import fetch from 'node-fetch'
|
|
import { models } from '../models'
|
|
|
|
const env = process.env.NODE_ENV || 'development'
|
|
const config = require(path.join(__dirname, '../../config/app.json'))[env]
|
|
|
|
let client: any
|
|
|
|
export async function connect(onMessage) {
|
|
try {
|
|
const info = await LND.getInfo()
|
|
|
|
async function reconnect() {
|
|
client = null
|
|
const pwd = await genSignedTimestamp()
|
|
console.log('[tribes] try to connect:', `tls://${config.tribes_host}:8883`)
|
|
client = mqtt.connect(`tls://${config.tribes_host}:8883`, {
|
|
username: info.identity_pubkey,
|
|
password: pwd,
|
|
reconnectPeriod: 0, // dont auto reconnect
|
|
})
|
|
client.on('connect', async function () {
|
|
console.log("[tribes] connected!")
|
|
client.subscribe(`${info.identity_pubkey}/#`)
|
|
updateTribeStats(info.identity_pubkey)
|
|
const rndToken = await genSignedTimestamp()
|
|
console.log('=> random sig', rndToken)
|
|
})
|
|
client.on('close', function (e) {
|
|
setTimeout(() => reconnect(), 2000)
|
|
})
|
|
client.on('error', function (e) {
|
|
console.log('[tribes] error: ', e.message || e)
|
|
})
|
|
client.on('message', function (topic, message) {
|
|
if (onMessage) onMessage(topic, message)
|
|
})
|
|
}
|
|
reconnect()
|
|
|
|
} catch (e) {
|
|
console.log("TRIBES ERROR", e)
|
|
}
|
|
}
|
|
|
|
async function updateTribeStats(myPubkey) {
|
|
const myTribes = await models.Chat.findAll({
|
|
where: {
|
|
ownerPubkey: myPubkey
|
|
}
|
|
})
|
|
await asyncForEach(myTribes, async (tribe) => {
|
|
try {
|
|
const contactIds = JSON.parse(tribe.contactIds)
|
|
const member_count = (contactIds && contactIds.length) || 0
|
|
await putstats({ uuid: tribe.uuid, host: tribe.host, member_count, chatId: tribe.id })
|
|
} catch (e) { }
|
|
})
|
|
console.log(`[tribes] updated stats for ${myTribes.length} tribes`)
|
|
}
|
|
|
|
export function subscribe(topic) {
|
|
if (client) client.subscribe(topic)
|
|
}
|
|
|
|
export function publish(topic, msg, cb) {
|
|
if (client) client.publish(topic, msg, null, function (err) {
|
|
if (err) console.log(err)
|
|
else if (cb) cb()
|
|
})
|
|
}
|
|
|
|
export async function declare({ uuid, name, description, tags, img, group_key, host, price_per_message, price_to_join, owner_alias, owner_pubkey, escrow_amount, escrow_millis, unlisted, is_private, app_url }) {
|
|
try {
|
|
await fetch('https://' + host + '/tribes', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
uuid, group_key,
|
|
name, description, tags, img: img || '',
|
|
price_per_message: price_per_message || 0,
|
|
price_to_join: price_to_join || 0,
|
|
owner_alias, owner_pubkey,
|
|
escrow_amount: escrow_amount || 0,
|
|
escrow_millis: escrow_millis || 0,
|
|
unlisted: unlisted || false,
|
|
private: is_private || false,
|
|
app_url: app_url || '',
|
|
}),
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
// const j = await r.json()
|
|
} catch (e) {
|
|
console.log('[tribes] unauthorized to declare')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
export async function declare_bot({ uuid, name, description, tags, img, price_per_use, owner_pubkey, unlisted, deleted }) {
|
|
const host = getHost()
|
|
try {
|
|
await fetch('https://' + host + '/bots', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
uuid, owner_pubkey,
|
|
name, description, tags, img: img || '',
|
|
price_per_use: price_per_use || 0,
|
|
unlisted: unlisted || false,
|
|
deleted: deleted || false,
|
|
}),
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
// const j = await r.json()
|
|
// console.log('=> j',j)
|
|
} catch (e) {
|
|
console.log('[tribes] unauthorized to declare')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
export async function edit({ uuid, host, name, description, tags, img, price_per_message, price_to_join, owner_alias, escrow_amount, escrow_millis, unlisted, is_private, app_url, deleted }) {
|
|
try {
|
|
const token = await genSignedTimestamp()
|
|
await fetch('https://' + host + '/tribe?token=' + token, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
uuid,
|
|
name, description, tags, img: img || '',
|
|
price_per_message: price_per_message || 0,
|
|
price_to_join: price_to_join || 0,
|
|
escrow_amount: escrow_amount || 0,
|
|
escrow_millis: escrow_millis || 0,
|
|
owner_alias,
|
|
unlisted: unlisted || false,
|
|
private: is_private || false,
|
|
deleted: deleted || false,
|
|
app_url: app_url || '',
|
|
}),
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
// const j = await r.json()
|
|
} catch (e) {
|
|
console.log('[tribes] unauthorized to edit')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
|
|
export async function delete_tribe({ uuid }) {
|
|
const host = getHost()
|
|
try {
|
|
const token = await genSignedTimestamp()
|
|
await fetch(`https://${host}/tribe/${uuid}?token=` + token, {
|
|
method: 'DELETE',
|
|
})
|
|
// const j = await r.json()
|
|
} catch (e) {
|
|
console.log('[tribes] unauthorized to delete')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
export async function putActivity(uuid: string, host: string) {
|
|
try {
|
|
const token = await genSignedTimestamp()
|
|
await fetch(`https://${host}/tribeactivity/${uuid}?token=` + token, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
} catch (e) {
|
|
console.log('[tribes] unauthorized to putActivity')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
async function makeBotsJSON(tribeID) {
|
|
const bots = await models.ChatBot.findAll({
|
|
where: {
|
|
chatId: tribeID
|
|
}
|
|
})
|
|
if (!bots) return []
|
|
if (!bots.length) return []
|
|
return bots.map(b => {
|
|
const bot = b.dataValues
|
|
if (bot.botPrefix === '/loopout') {
|
|
return loopoutBotJSON()
|
|
}
|
|
return <BotJSON>{
|
|
prefix: bot.botPrefix,
|
|
price: bot.pricePerUse||0,
|
|
commands: null,
|
|
}
|
|
})
|
|
}
|
|
|
|
interface BotJSON {
|
|
prefix: string,
|
|
price: number,
|
|
commands: BotCommand[] | null,
|
|
}
|
|
interface BotCommand {
|
|
command: string,
|
|
price: number,
|
|
min_price: number,
|
|
max_price: number,
|
|
price_index: number,
|
|
admin_only: boolean,
|
|
}
|
|
function loopoutBotJSON(): BotJSON {
|
|
return <BotJSON>{
|
|
prefix: '/loopout',
|
|
price: 0,
|
|
commands: [{
|
|
command: '*',
|
|
price: 0,
|
|
min_price: 250000,
|
|
max_price: 16777215,
|
|
price_index: 2,
|
|
admin_only: false
|
|
}]
|
|
}
|
|
}
|
|
|
|
export async function putstats({ uuid, host, member_count, chatId }) {
|
|
if (!uuid) return
|
|
const bots = await makeBotsJSON(chatId)
|
|
try {
|
|
const token = await genSignedTimestamp()
|
|
await fetch('https://' + host + '/tribestats?token=' + token, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
uuid, member_count, bots: JSON.stringify(bots || [])
|
|
}),
|
|
headers: { 'Content-Type': 'application/json' }
|
|
})
|
|
} catch (e) {
|
|
console.log('[tribes] unauthorized to putstats')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
export async function genSignedTimestamp() {
|
|
const now = moment().unix()
|
|
const tsBytes = Buffer.from(now.toString(16), 'hex')
|
|
const sig = await LND.signBuffer(tsBytes)
|
|
const sigBytes = zbase32.decode(sig)
|
|
const totalLength = tsBytes.length + sigBytes.length
|
|
const buf = Buffer.concat([tsBytes, sigBytes], totalLength)
|
|
return urlBase64(buf)
|
|
}
|
|
|
|
export async function verifySignedTimestamp(stsBase64) {
|
|
const stsBuf = Buffer.from(stsBase64, 'base64')
|
|
const sig = stsBuf.subarray(4, 92)
|
|
const sigZbase32 = zbase32.encode(sig)
|
|
const r = await LND.verifyBytes(stsBuf.subarray(0, 4), sigZbase32) // sig needs to be zbase32 :(
|
|
if (r.valid) {
|
|
return r.pubkey
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export function getHost() {
|
|
return config.tribes_host || ''
|
|
}
|
|
|
|
function urlBase64(buf) {
|
|
return buf.toString('base64').replace(/\//g, '_').replace(/\+/g, '-')
|
|
}
|
|
|
|
async function asyncForEach(array, callback) {
|
|
for (let index = 0; index < array.length; index++) {
|
|
await callback(array[index], index, array);
|
|
}
|
|
}
|