122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
|
|
#!/usr/bin/env node
|
||
|
|
/**
|
||
|
|
* Check Signal group membership status and update Zammad tickets
|
||
|
|
*
|
||
|
|
* This task queries the Signal CLI API to check if users have joined
|
||
|
|
* their assigned groups. When a user joins (moves from pendingInvites to members),
|
||
|
|
* it updates the ticket's group_joined flag in Zammad.
|
||
|
|
*
|
||
|
|
* Note: This task sends webhooks for all group members every time it runs.
|
||
|
|
* The Zammad webhook handler is idempotent and will ignore duplicate notifications
|
||
|
|
* if group_joined is already true.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
||
|
|
import { createLogger } from "@link-stack/logger";
|
||
|
|
import * as signalApi from "@link-stack/signal-api";
|
||
|
|
|
||
|
|
const logger = createLogger("check-group-membership");
|
||
|
|
|
||
|
|
const { Configuration, GroupsApi } = signalApi;
|
||
|
|
|
||
|
|
interface CheckGroupMembershipTaskOptions {
|
||
|
|
// Optional: Check specific group. If not provided, checks all groups with group_joined=false
|
||
|
|
groupId?: string;
|
||
|
|
botToken?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
const checkGroupMembershipTask = async (
|
||
|
|
options: CheckGroupMembershipTaskOptions = {},
|
||
|
|
): Promise<void> => {
|
||
|
|
const config = new Configuration({
|
||
|
|
basePath: process.env.BRIDGE_SIGNAL_URL,
|
||
|
|
});
|
||
|
|
const groupsClient = new GroupsApi(config);
|
||
|
|
const worker = await getWorkerUtils();
|
||
|
|
|
||
|
|
// Get all Signal bots
|
||
|
|
const bots = await db.selectFrom("SignalBot").selectAll().execute();
|
||
|
|
|
||
|
|
for (const bot of bots) {
|
||
|
|
try {
|
||
|
|
logger.debug(
|
||
|
|
{ botId: bot.id, phoneNumber: bot.phoneNumber },
|
||
|
|
"Checking groups for bot",
|
||
|
|
);
|
||
|
|
|
||
|
|
// Get all groups for this bot
|
||
|
|
const groups = await groupsClient.v1GroupsNumberGet({
|
||
|
|
number: bot.phoneNumber,
|
||
|
|
});
|
||
|
|
|
||
|
|
logger.debug(
|
||
|
|
{ botId: bot.id, groupCount: groups.length },
|
||
|
|
"Retrieved groups from Signal CLI",
|
||
|
|
);
|
||
|
|
|
||
|
|
// For each group, check if we have tickets waiting for members to join
|
||
|
|
for (const group of groups) {
|
||
|
|
if (!group.id || !group.internalId) {
|
||
|
|
logger.debug({ groupName: group.name }, "Skipping group without ID");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log info about each group temporarily for debugging
|
||
|
|
logger.info(
|
||
|
|
{
|
||
|
|
groupId: group.id,
|
||
|
|
groupName: group.name,
|
||
|
|
membersCount: group.members?.length || 0,
|
||
|
|
members: group.members,
|
||
|
|
pendingInvitesCount: group.pendingInvites?.length || 0,
|
||
|
|
pendingInvites: group.pendingInvites,
|
||
|
|
pendingRequestsCount: group.pendingRequests?.length || 0,
|
||
|
|
},
|
||
|
|
"Checking group membership",
|
||
|
|
);
|
||
|
|
|
||
|
|
// Notify Zammad about each member who has joined
|
||
|
|
// This handles both cases:
|
||
|
|
// 1. New contacts who must accept invite (they move from pendingInvites to members)
|
||
|
|
// 2. Existing contacts who are auto-added (they appear directly in members)
|
||
|
|
if (group.members && group.members.length > 0) {
|
||
|
|
for (const memberPhone of group.members) {
|
||
|
|
// Check if this member was previously pending
|
||
|
|
// We'll send the webhook and let Zammad decide if it needs to update
|
||
|
|
await worker.addJob("common/notify-webhooks", {
|
||
|
|
backendId: bot.id,
|
||
|
|
payload: {
|
||
|
|
event: "group_member_joined",
|
||
|
|
group_id: group.id,
|
||
|
|
member_phone: memberPhone,
|
||
|
|
timestamp: new Date().toISOString(),
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
logger.info(
|
||
|
|
{
|
||
|
|
groupId: group.id,
|
||
|
|
memberPhone,
|
||
|
|
},
|
||
|
|
"Notified Zammad about group member",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error(
|
||
|
|
{
|
||
|
|
botId: bot.id,
|
||
|
|
error: error.message,
|
||
|
|
stack: error.stack,
|
||
|
|
},
|
||
|
|
"Error checking group membership for bot",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.info("Completed group membership check");
|
||
|
|
};
|
||
|
|
|
||
|
|
export default checkGroupMembershipTask;
|