sphinx-relay/src/controllers/chatTribes.ts
2024-03-19 19:09:31 +01:00

1054 lines
32 KiB
TypeScript

import { Op } from 'sequelize'
import { Response } from 'express'
import {
models,
Chat,
ChatMember,
ChatRecord,
Contact,
MediaKey,
Message,
} from '../models'
import * as jsonUtils from '../utils/json'
import { success, failure } from '../utils/res'
import * as network from '../network'
import * as rsa from '../crypto/rsa'
import * as helpers from '../helpers'
import * as socket from '../utils/socket'
import * as tribes from '../utils/tribes'
import { sendNotification } from '../hub'
import { personalizeMessage, decryptMessage } from '../utils/msg'
import constants from '../constants'
import { logging, sphinxLogger } from '../utils/logger'
import type { Tribe } from '../models/ts/tribe'
import { Req, Res } from '../types'
/**
* @function joinTribe
* @param {Req} req - The request object containing information about the request made to the server.
* @param {Res} res - The response object used to send a response back to the client.
*
* @returns {Promise<void>} - A promise that resolves when the user has successfully joined the tribe, or rejects with an error if something goes wrong.
*/
export async function joinTribe(req: Req, res: Res): Promise<void | Response> {
if (!req.owner) return failure(res, 'no owner')
sphinxLogger.info('=> joinTribe', logging.Express)
try {
const json = await doJoinTribe(req.body, req.owner)
success(res, json)
} catch (e) {
failure(res, e)
}
}
export async function doJoinTribe(body: { [k: string]: any }, owner: Contact) {
const {
uuid,
group_key,
name,
host,
amount,
img,
owner_pubkey,
owner_route_hint,
owner_alias,
my_alias,
my_photo_url,
} = body
sphinxLogger.info(
['doJoinTribe: with a tribe owner route hint', owner_route_hint],
logging.Express
)
const is_private = body.private ? true : false
const tenant = owner.id
return new Promise(async (resolve, reject) => {
const existing: Chat = (await models.Chat.findOne({
where: { uuid, tenant },
})) as Chat
if (existing) {
sphinxLogger.error('You are already in this tribe', logging.Tribes)
reject('cant find tribe')
}
if (!owner_pubkey || !group_key || !uuid) {
sphinxLogger.error('missing required params', logging.Tribes)
reject('missing required params')
}
const ownerPubKey = owner_pubkey
// verify signature here?
const tribeOwner: Contact = (await models.Contact.findOne({
where: { publicKey: ownerPubKey, tenant },
})) as Contact
let theTribeOwner
const contactIds = [owner.id]
if (tribeOwner) {
theTribeOwner = tribeOwner // might already include??
if (owner_route_hint && owner_route_hint !== tribeOwner.routeHint) {
await tribeOwner.update({ routeHint: owner_route_hint })
}
if (!contactIds.includes(tribeOwner.id)) contactIds.push(tribeOwner.id)
} else {
const createdContact: Contact = (await models.Contact.create({
publicKey: ownerPubKey,
contactKey: '',
alias: owner_alias || 'Unknown',
status: 1,
fromGroup: true,
tenant,
routeHint: owner_route_hint || '',
})) as Contact
theTribeOwner = createdContact
// console.log("CREATE TRIBE OWNER", createdContact);
contactIds.push(createdContact.id)
}
const date = new Date()
date.setMilliseconds(0)
const chatStatus = is_private
? constants.chat_statuses.pending
: constants.chat_statuses.approved
const chatParams: Partial<Chat> = {
uuid: uuid,
contactIds: JSON.stringify(contactIds),
photoUrl: img || '',
createdAt: date,
updatedAt: date,
name: name,
type: constants.chat_types.tribe,
host: host || tribes.getHost(),
groupKey: group_key,
ownerPubkey: owner_pubkey,
private: is_private || false,
status: chatStatus,
priceToJoin: amount || 0,
tenant,
}
if (my_alias) chatParams.myAlias = my_alias
if (my_photo_url) chatParams.myPhotoUrl = my_photo_url
const typeToSend = is_private
? constants.message_types.member_request
: constants.message_types.group_join
const contactIdsToSend: string = is_private
? JSON.stringify([theTribeOwner.id]) // ONLY SEND TO TRIBE OWNER IF ITS A REQUEST
: JSON.stringify(contactIds)
console.log('=> joinTribe: typeToSend', typeToSend)
console.log('=> joinTribe: contactIdsToSend', contactIdsToSend)
// set my alias to be the custom one
const theOwner = owner
if (my_alias) theOwner.alias = my_alias
network.sendMessage({
// send my data to tribe owner
chat: {
...chatParams,
contactIds: contactIdsToSend,
members: {
[owner.publicKey]: {
key: owner.contactKey,
alias: my_alias || owner.alias || '',
},
},
},
amount: amount || 0,
sender: theOwner,
message: {},
type: typeToSend,
failure: function (e) {
reject(e)
},
success: async function () {
console.log('=> joinTribe: sent groupJoin')
const chat: ChatRecord = (await models.Chat.create(
chatParams
)) as ChatRecord
models.ChatMember.create({
contactId: theTribeOwner.id,
chatId: chat.id,
role: constants.chat_roles.owner,
lastActive: date,
status: constants.chat_statuses.approved,
tenant,
})
tribes.addExtraHost(theOwner, host, network.receiveMqttMessage)
resolve(jsonUtils.chatToJson(chat))
},
})
})
}
/**
* @function createChannel
* @param {Req} req
* @param {res} res
*
* @returns {Promise<void>}
*/
export async function createChannel(
req: Req,
res: Res
): Promise<void | Response> {
if (!req.owner) return failure(res, 'no owner')
const owner = req.owner
//const tenant: number = req.owner.id
const { tribe_uuid, name, host } = req.body
const channel = await tribes.createChannel({
tribe_uuid,
name,
host,
owner_pubkey: owner.publicKey,
})
success(res, channel)
}
/**
* @function deleteChannel
* @param {Req} req - The request object containing information about the request made to the server.
* @param {res} res - The response object used to send a response back to the client.
*
* @returns {Promise<void>} - A promise that resolves when the channel has been successfully deleted, or rejects with an error if something goes wrong.
*/
export async function deleteChannel(
req: Req,
res: Res
): Promise<void | Response> {
if (!req.owner) return failure(res, 'no owner')
const owner = req.owner
const { id, host } = req.body
const channel = await tribes.deleteChannel({
id,
host,
owner_pubkey: owner.publicKey,
})
success(res, channel)
}
/**
* @function receiveMemberRequest
* @param {Object} payload - An object containing information about the request to join a tribe.
*
* @returns {Promise<void>} - A promise that resolves when the member request has been successfully processed, or rejects with an error if something goes wrong.
*/
export async function receiveMemberRequest(payload) {
sphinxLogger.info('=> receiveMemberRequest', logging.Network)
const {
owner,
chat,
sender_pub_key,
sender_alias,
chat_members,
chat_type,
isTribeOwner,
network_type,
sender_photo_url,
sender_route_hint,
} = await helpers.parseReceiveParams(payload)
const tenant: number = owner.id
if (!chat) return sphinxLogger.error('no chat')
const isTribe = chat_type === constants.chat_types.tribe
if (!isTribe || !isTribeOwner) return sphinxLogger.error('not a tribe')
const date = new Date()
date.setMilliseconds(0)
let theSender: any = null
const member = chat_members[sender_pub_key]
const senderAlias = (member && member.alias) || sender_alias || 'Unknown'
const sender: Contact = (await models.Contact.findOne({
where: { publicKey: sender_pub_key, tenant },
})) as Contact
if (sender) {
theSender = sender // might already include??
} else {
if (member && member.key) {
const createdContact: Contact = (await models.Contact.create({
publicKey: sender_pub_key,
contactKey: member.key,
alias: sender_alias || senderAlias,
status: 1,
fromGroup: true,
photoUrl: sender_photo_url,
tenant,
routeHint: sender_route_hint || '',
})) as Contact
theSender = createdContact
}
}
if (!theSender) return sphinxLogger.error('no sender') // fail (no contact key?)
sphinxLogger.info([
'UPSERT',
{
contactId: theSender.id,
chatId: chat.id,
role: constants.chat_roles.reader,
status: constants.chat_statuses.pending,
lastActive: date,
lastAlias: senderAlias,
},
])
// maybe check here manually????
try {
await models.ChatMember.upsert({
contactId: theSender.id,
chatId: chat.id,
role: constants.chat_roles.reader,
status: constants.chat_statuses.pending,
lastActive: date,
lastAlias: senderAlias,
tenant,
})
// also update the chat
const theChat: Chat = (await models.Chat.findOne({
where: { id: chat.id },
})) as Chat
if (theChat) {
await theChat.update({ updatedAt: date })
}
} catch (e) {
//we want to do nothing here
}
const msg: { [k: string]: any } = {
chatId: chat.id,
type: constants.message_types.member_request,
sender: (theSender && theSender.id) || 0,
messageContent: '',
remoteMessageContent: '',
status: constants.statuses.confirmed,
date: date,
createdAt: date,
updatedAt: date,
network_type,
tenant,
}
if (isTribe) {
msg.senderAlias = senderAlias
msg.senderPic = sender_photo_url
}
const message: Message = (await models.Message.create(msg)) as Message
const theChat = await addPendingContactIdsToChat(chat, tenant)
socket.sendJson(
{
type: 'member_request',
response: {
contact: jsonUtils.contactToJson(theSender || {}),
chat: jsonUtils.chatToJson(theChat),
message: jsonUtils.messageToJson(message, theChat),
},
},
tenant
)
}
/**
* @function pinToTribe
* @param {Object} req - An Express request object, containing information about the HTTP request.
* @param {Object} res - An Express response object, used to send a response to the client.
*
* @returns {Promise<void>} - A promise that resolves when the pin has been successfully added to the tribe, or rejects with an error if something goes wrong.
*/
export async function pinToTribe(req: Req, res: Res): Promise<void | Response> {
if (!req.owner) return failure(res, 'no owner')
const tenant: number = req.owner.id
const { pin } = req.body
const { id } = req.params
if (!id) return failure(res, 'group id is required')
const chat: ChatRecord = (await models.Chat.findOne({
where: { id, tenant },
})) as ChatRecord
if (!chat) {
return failure(res, 'cant find chat')
}
const owner = req.owner
if (owner.publicKey !== chat.ownerPubkey) {
return failure(res, 'not your tribe')
}
try {
const td = await tribes.get_tribe_data(chat.uuid)
const chatData = chat.dataValues || chat
chatData.pin = pin
await tribes.edit(mergeTribeAndChatData(chatData, td, owner))
await models.Chat.update({ pin }, { where: { id, tenant } })
success(res, { pin })
} catch (e) {
return failure(res, 'failed to update pin')
}
}
/**
* Edits the specified tribe.
*
* @param {Req} req - The request object containing the owner, body, and params. The body should have the following properties: name, price_per_message, price_to_join, escrow_amount, escrow_millis, img, description, tags, unlisted, app_url, feed_url, feed_type, pin, and profile_filters. The params should have the id of the tribe to be edited.
* @param {Res} res - The response object used to return the edited tribe.
*
* @returns {Object} - Returns the edited tribe or an error message if the tribe could not be edited.
*/
export async function editTribe(req: Req, res: Res): Promise<void | Response> {
if (!req.owner) return failure(res, 'no owner')
const tenant: number = req.owner.id
const {
name,
price_per_message,
price_to_join,
escrow_amount,
escrow_millis,
img,
description,
tags,
unlisted,
app_url,
feed_url,
feed_type,
pin,
call_recording,
meme_server_location,
jitsi_server,
stakwork_api_key,
stakwork_webhook,
second_brain_url,
} = req.body
const { id } = req.params
if (!id) return failure(res, 'group id is required')
let { profile_filters } = req.body
if (profile_filters) {
if (!Array.isArray(profile_filters)) {
return failure(res, 'invalid profile filters')
} else {
profile_filters = profile_filters.join(',')
}
}
if (call_recording) {
if (typeof call_recording !== 'number') {
return failure(res, 'invalid call recording value')
} else {
if (call_recording !== 0 && call_recording !== 1) {
return failure(res, 'invalid call recording value')
}
}
}
const chat: Chat = (await models.Chat.findOne({
where: { id, tenant },
})) as Chat
if (!chat) {
return failure(res, 'cant find chat')
}
const owner = req.owner
let okToUpdate = true
if (owner.publicKey === chat.ownerPubkey) {
try {
await tribes.edit({
uuid: chat.uuid,
name: name,
host: chat.host,
price_per_message: price_per_message || 0,
price_to_join: price_to_join || 0,
escrow_amount: escrow_amount || 0,
escrow_millis: escrow_millis || 0,
description,
tags,
img,
owner_alias: owner.alias,
unlisted,
is_private: req.body.private,
app_url,
feed_url,
feed_type,
deleted: false,
owner_route_hint: owner.routeHint || '',
owner_pubkey: owner.publicKey,
pin: pin || '',
profile_filters: profile_filters || '',
})
} catch (e) {
okToUpdate = false
}
}
if (okToUpdate) {
const obj: { [k: string]: any } = {}
if (img) obj.photoUrl = img
if (name) obj.name = name
if (price_per_message || price_per_message === 0)
obj.pricePerMessage = price_per_message
if (price_to_join || price_to_join === 0) obj.priceToJoin = price_to_join
if (escrow_amount || escrow_amount === 0) obj.escrowAmount = escrow_amount
if (escrow_millis || escrow_millis === 0) obj.escrowMillis = escrow_millis
if (unlisted || unlisted === false) obj.unlisted = unlisted
if (app_url) obj.appUrl = app_url
if (feed_url) obj.feedUrl = feed_url
if (feed_type) obj.feedType = feed_type
if (req.body.private || req.body.private === false)
obj.private = req.body.private
if (profile_filters) obj.profileFilters = profile_filters
if (call_recording || call_recording === 0)
obj.callRecording = call_recording
if (meme_server_location) obj.memeServerLocation = meme_server_location
if (jitsi_server) obj.jitsiServer = jitsi_server
if (stakwork_api_key) obj.stakworkApiKey = stakwork_api_key
if (stakwork_webhook) obj.stakworkWebhook = stakwork_webhook
if (second_brain_url) obj.secondBrainUrl = second_brain_url
if (Object.keys(obj).length > 0) {
await chat.update(obj)
}
success(res, jsonUtils.chatToJson(chat))
} else {
failure(res, 'failed to update tribe')
}
}
type ChatMemberStatus = 'approved' | 'rejected'
/**
* Approves or rejects a member's request to join a tribe.
*
* @param {Req} req - The incoming request object.
* @param {object} req.owner - The owner object that contains the user's ID and public key.
* @param {number} req.owner.id - The user's ID.
* @param {string} req.owner.publicKey - The user's public key.
* @param {number} req.params.messageId - The ID of the message that the user sent to join the tribe.
* @param {number} req.params.contactId - The ID of the user who sent the request to join the tribe.
* @param {string} req.params.status - The status of the member request. Can be either "approved" or "rejected".
* @param {Res} res - The response object.
*
* @returns {object} - Returns an object that contains the updated chat and message.
* @throws {string} - Returns a string if there is an error.
*/
export async function approveOrRejectMember(req: Req, res) {
if (!req.owner) return failure(res, 'no owner')
const tenant: number = req.owner.id
sphinxLogger.info('=> approve or reject tribe member')
const msgId = parseInt(req.params['messageId'])
const contactId = parseInt(req.params['contactId'])
const status: ChatMemberStatus = req.params['status'] as ChatMemberStatus
const msg: Message = (await models.Message.findOne({
where: { id: msgId, tenant },
})) as Message
if (!msg) return failure(res, 'no message')
const chatId = msg.chatId
const chat: ChatRecord = (await models.Chat.findOne({
where: { id: chatId, tenant },
})) as ChatRecord
if (!chat) return failure(res, 'no chat')
if (
!msgId ||
!contactId ||
!(status === 'approved' || status === 'rejected')
) {
return failure(res, 'incorrect status')
}
let memberStatus = constants.chat_statuses.rejected
let msgType = constants.message_types.member_reject
if (status === 'approved') {
memberStatus = constants.chat_statuses.approved
msgType = constants.message_types.member_approve
const contactIds = JSON.parse(chat.contactIds || '[]')
if (!contactIds.includes(contactId)) contactIds.push(contactId)
await chat.update({ contactIds: JSON.stringify(contactIds) })
}
await msg.update({ type: msgType })
const member: ChatMember = (await models.ChatMember.findOne({
where: { contactId, chatId },
})) as ChatMember
if (!member) {
return failure(res, 'cant find chat member')
}
if (status === 'approved') {
// update ChatMember status
await member.update({ status: memberStatus })
} else if (status === 'rejected') {
// destroy the row
await member.destroy()
}
const owner = req.owner
const chatToSend = chat.dataValues || chat
network.sendMessage({
// send to the requester
chat: { ...chatToSend, contactIds: JSON.stringify([member.contactId]) },
amount: 0,
sender: owner,
message: {},
type: msgType,
})
const theChat = await addPendingContactIdsToChat(chat, tenant)
success(res, {
chat: jsonUtils.chatToJson(theChat),
message: jsonUtils.messageToJson(msg, theChat),
})
}
/**
* Receive a message that a member has been approved to join a tribe.
*
* @param {Object} payload - The message payload from the server.
* @param {Object} payload.owner - The owner object for the current user.
* @param {Object} payload.chat - The chat object for the tribe.
* @param {Object} payload.sender - The sender object for the user who approved the member.
* @param {number} payload.network_type - The network type (testnet or mainnet).
*/
export async function receiveMemberApprove(payload) {
sphinxLogger.info('-> receiveMemberApprove', logging.Network)
const { owner, chat, sender, network_type } =
await helpers.parseReceiveParams(payload)
if (!chat) return sphinxLogger.error('no chat')
await chat.update({ status: constants.chat_statuses.approved })
const tenant: number = owner.id
const date = new Date()
date.setMilliseconds(0)
const msg: { [k: string]: any } = {
chatId: chat.id,
type: constants.message_types.member_approve,
sender: (sender && sender.id) || 0,
messageContent: '',
remoteMessageContent: '',
status: constants.statuses.confirmed,
date: date,
createdAt: date,
updatedAt: date,
network_type,
tenant,
}
const message: Message = (await models.Message.create(msg)) as Message
socket.sendJson(
{
type: 'member_approve',
response: {
message: jsonUtils.messageToJson(message, chat),
chat: jsonUtils.chatToJson(chat),
},
},
tenant
)
const amount = chat.priceToJoin || 0
const theChat = chat.dataValues || chat
const theOwner = owner
const theAlias = chat.myAlias || owner.alias
if (theAlias) theOwner.alias = theAlias
// send JOIN and my info to all
network.sendMessage({
chat: {
...theChat,
members: {
[owner.publicKey]: {
key: owner.contactKey,
alias: theAlias || '',
},
},
},
amount,
sender: theOwner,
message: {},
type: constants.message_types.group_join,
})
// sendNotification(chat, chat_name, "group", theOwner);
}
/**
* Processes a tribe member rejection notification.
*
* @param {Object} payload - The notification payload containing details of the rejection.
* @param {Object} payload.owner - The owner of the tribe who rejected the member.
* @param {Object} payload.chat - The tribe the rejected member belongs to.
* @param {Object} payload.sender - The user who sent the member rejection notification.
* @param {string} payload.chat_name - The name of the tribe.
* @param {string} payload.network_type - The type of network the notification was sent from.
*
* @returns {Promise<void>}
*/
export async function receiveMemberReject(payload) {
sphinxLogger.info('-> receiveMemberReject', logging.Network)
const { owner, chat, sender, chat_name, network_type } =
await helpers.parseReceiveParams(payload)
if (!chat) return sphinxLogger.error('no chat')
await chat.update({ status: constants.chat_statuses.rejected })
const tenant: number = owner.id
const date = new Date()
date.setMilliseconds(0)
const msg: { [k: string]: any } = {
chatId: chat.id,
type: constants.message_types.member_reject,
sender: (sender && sender.id) || 0,
messageContent: '',
remoteMessageContent: '',
status: constants.statuses.confirmed,
date: date,
createdAt: date,
updatedAt: date,
network_type,
tenant,
}
const message: Message = (await models.Message.create(msg)) as Message
socket.sendJson(
{
type: 'member_reject',
response: {
message: jsonUtils.messageToJson(message, chat),
chat: jsonUtils.chatToJson(chat),
},
},
tenant
)
sendNotification(chat, chat_name, 'reject', owner)
}
/**
* Receives a tribe delete request and updates the chat and sends a notification.
*
* @param {Object} payload - The payload containing the request information.
* @param {Object} payload.owner - The owner of the chat.
* @param {Object} payload.chat - The chat that was deleted.
* @param {Object} payload.sender - The sender of the request.
* @param {string} payload.network_type - The network type.
*
* @returns {undefined} - Returns nothing.
*/
export async function receiveTribeDelete(payload) {
sphinxLogger.info('-> receiveTribeDelete', logging.Network)
const { owner, chat, sender, network_type } =
await helpers.parseReceiveParams(payload)
if (!chat) return sphinxLogger.error('no chat')
const tenant: number = owner.id
// await chat.update({status: constants.chat_statuses.rejected})
// update on tribes server too
const date = new Date()
date.setMilliseconds(0)
const msg: { [k: string]: any } = {
chatId: chat.id,
type: constants.message_types.tribe_delete,
sender: (sender && sender.id) || 0,
messageContent: '',
remoteMessageContent: '',
status: constants.statuses.confirmed,
date: date,
createdAt: date,
updatedAt: date,
network_type,
tenant,
}
const message: Message = (await models.Message.create(msg)) as Message
socket.sendJson(
{
type: 'tribe_delete',
response: {
message: jsonUtils.messageToJson(message, chat),
chat: jsonUtils.chatToJson(chat),
},
},
tenant
)
}
/**
* Replays the chat history for a given contact in the given chat.
* @param {Object} chat - The chat object.
* @param {Object} contact - The contact object.
* @param {Object} ownerRecord - The owner record object.
*/
export async function replayChatHistory(chat, contact, ownerRecord) {
const owner = ownerRecord.dataValues || ownerRecord
const tenant: number = owner.id
sphinxLogger.info('-> replayHistory', logging.Tribes)
if (!(chat && chat.id && contact && contact.id)) {
return sphinxLogger.info('cant replay history', logging.Tribes)
}
try {
const msgs: Message[] = (await models.Message.findAll({
where: {
tenant,
chatId: chat.id,
type: { [Op.in]: network.typesToReplay },
onlyOwner: { [Op.or]: [false, null] },
},
order: [['id', 'desc']],
limit: 40,
})) as Message[]
msgs.reverse()
// if theres a pinned msg in this chat
if (chat.pin) {
const pinned = msgs.find((m) => m.uuid === chat.pin)
// if the pinned msg is not already included
if (!pinned) {
const pinnedMsg: Message = (await models.Message.findOne({
where: {
tenant,
chatId: chat.id,
type: { [Op.in]: network.typesToReplay },
uuid: chat.pin,
},
})) as Message
// add it
if (pinnedMsg) {
msgs.push(pinnedMsg)
}
}
}
asyncForEach(msgs, async (m) => {
if (!network.typesToReplay.includes(m.type)) return // only for message for now
if (chat.skipBroadcastJoins) {
if (network.typesToSkipIfSkipBroadcastJoins.includes(m.type)) {
return // no join or leave announcements if set this way
}
}
const sender = {
...owner,
alias: m.senderAlias || 'unknown',
role: constants.chat_roles.reader,
photoUrl: m.senderPic || '',
}
let content = ''
try {
content = JSON.parse(m.remoteMessageContent)
} catch (e) {
//We want to do nothing here
}
let mdate = m.date
if (!mdate) mdate = new Date()
const dateString = mdate.toISOString()
let mediaKeyMap
let newMediaTerms
if (m.type === constants.message_types.attachment) {
if (m.mediaKey && m.mediaToken) {
const muid =
m.mediaToken.split('.').length && m.mediaToken.split('.')[1]
if (muid) {
const mediaKey: MediaKey = (await models.MediaKey.findOne({
where: {
muid,
chatId: chat.id,
tenant,
},
})) as MediaKey
// console.log("FOUND MEDIA KEY!!",mediaKey.dataValues)
mediaKeyMap = { chat: mediaKey.key }
newMediaTerms = { muid: mediaKey.muid }
}
}
}
const isForwarded = m.sender !== tenant
const includeStatus = true
let msg = network.newmsg(
m.type,
chat,
sender,
{
content, // replaced with the remoteMessageContent (u are owner) {}
uuid: m.uuid,
replyUuid: m.replyUuid,
parentId: m.parentId || 0,
status: m.status,
amount: m.amount,
...(mediaKeyMap && { mediaKey: mediaKeyMap }),
...(newMediaTerms && { mediaToken: newMediaTerms }),
...(m.mediaType && { mediaType: m.mediaType }),
...(dateString && { date: dateString }),
...(m.recipientAlias && { recipientAlias: m.recipientAlias }),
...(m.recipientPic && { recipientPic: m.recipientPic }),
},
isForwarded,
includeStatus
)
msg = await decryptMessage(msg, chat)
const data = await personalizeMessage(msg, contact, true)
const mqttTopic = `${contact.publicKey}/${chat.uuid}`
const replayingHistory = true
// console.log("-> HISTORY DATA:",data)
await network.signAndSend(
{
data,
dest: contact.publicKey,
route_hint: contact.routeHint,
},
owner,
mqttTopic,
replayingHistory
)
}) // end forEach
} catch (e) {
sphinxLogger.error(['replayChatHistory ERROR', e])
}
}
/**
* Create tribe chat parameters for a new chat.
* @param {Object} owner - The owner of the chat.
* @param {number[]} contactIds - An array of contact IDs for the members of the chat.
* @param {string} name - The name of the chat.
* @param {string} [img] - The image URL for the chat.
* @param {number} [price_per_message] - The price per message for the chat.
* @param {number} [price_to_join] - The price to join the chat.
* @param {number} [escrow_amount] - The escrow amount for the chat.
* @param {number} [escrow_millis] - The escrow time in milliseconds for the chat.
* @param {boolean} [unlisted] - Whether the chat is unlisted.
* @param {boolean} [is_private] - Whether the chat is private.
* @param {string} [app_url] - The URL for the chat's app.
* @param {string} [feed_url] - The URL for the chat's feed.
* @param {number} [feed_type] - The type of feed for the chat.
* @param {number} tenant - The tenant ID for the chat.
* @param {string} [pin] - The UUID of the pinned message for the chat.
* @param {Object} [profile_filters] - The profile filters for the chat.
* @returns {Promise<Object>} - An object containing the tribe chat parameters.
*/
export async function createTribeChatParams(
owner,
contactIds,
name: string,
img: string,
price_per_message: number,
price_to_join: number,
escrow_amount: number,
escrow_millis: number,
unlisted: boolean,
is_private: boolean,
app_url: string,
feed_url: string,
feed_type: number,
tenant: number,
pin: string,
profile_filters: string,
call_recording: number,
meme_server_location: string,
jitsi_server: string,
stakwork_api_key: string,
stakwork_webhook: string,
second_brain_url: string
): Promise<{ [k: string]: any }> {
const date = new Date()
date.setMilliseconds(0)
if (!(owner && contactIds && Array.isArray(contactIds))) {
return {}
}
// make ts sig here w LNd pubkey - that is UUID
const keys = await rsa.genKeys()
const groupUUID = await tribes.genSignedTimestamp(owner.publicKey)
const theContactIds = contactIds.includes(owner.id)
? contactIds
: [owner.id].concat(contactIds)
return {
uuid: groupUUID,
ownerPubkey: owner.publicKey,
contactIds: JSON.stringify(theContactIds),
createdAt: date,
updatedAt: date,
photoUrl: img || '',
name: name,
type: constants.chat_types.tribe,
groupKey: keys.public,
groupPrivateKey: keys.private,
host: tribes.getHost(),
pricePerMessage: price_per_message || 0,
priceToJoin: price_to_join || 0,
escrowMillis: escrow_millis || 0,
escrowAmount: escrow_amount || 0,
unlisted: unlisted || false,
private: is_private || false,
appUrl: app_url || '',
feedUrl: feed_url || '',
feedType: feed_type || 0,
tenant,
pin: pin || '',
profileFilters: profile_filters,
callRecording: call_recording || 0,
memeServerLocation: meme_server_location || '',
jitsiServer: jitsi_server || '',
stakworkApiKey: stakwork_api_key || '',
stakworkWebhook: stakwork_webhook || '',
secondBrainUrl: second_brain_url || '',
}
}
/**
* @async
* @function addPendingContactIdsToChat
* @description Adds the pending contact IDs of a chat to the chat object
* @param {Object} achat - The chat object
* @param {number} tenant - The tenant ID
* @returns {Object} The updated chat object
*
* @throws {Error} If the chat object is not valid
*/
export async function addPendingContactIdsToChat(achat, tenant) {
const members: ChatMember[] = (await models.ChatMember.findAll({
where: {
chatId: achat.id,
status: constants.chat_statuses.pending, // only pending
tenant,
},
})) as ChatMember[]
if (!members) return achat
const pendingContactIds: number[] = members.map((m) => m.contactId)
const chat = achat.dataValues || achat
return {
...chat,
pendingContactIds,
}
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
function mergeTribeAndChatData(chat, td: Tribe, owner) {
return {
uuid: chat.uuid,
name: chat.name,
host: chat.host,
price_per_message: chat.pricePerMessage,
price_to_join: chat.priceToJoin,
escrow_amount: chat.escrowAmount,
escrow_millis: chat.escrowMillis,
app_url: chat.appUrl,
feed_url: chat.feedUrl,
feed_type: chat.feedType,
pin: chat.pin || '',
deleted: false,
owner_alias: owner.alias,
owner_route_hint: owner.routeHint || '',
owner_pubkey: owner.publicKey,
description: td.description,
tags: td.tags,
img: td.img,
unlisted: td.unlisted,
is_private: td.private,
}
}