mirror of
https://github.com/SrIzan10/hctv.git
synced 2026-06-06 00:56:56 +00:00
feat(notif): actually send messages
This commit is contained in:
@@ -1109,7 +1109,7 @@ export default function ChannelSettingsClient({
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
Notification channels
|
||||
</label>
|
||||
<Textarea
|
||||
<Textarea
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
value={
|
||||
@@ -1119,13 +1119,20 @@ export default function ChannelSettingsClient({
|
||||
? field.value.join('\n')
|
||||
: ''
|
||||
}
|
||||
disabled={channel.is247}
|
||||
rows={4}
|
||||
placeholder="Enter channel IDs, one per line"
|
||||
placeholder={
|
||||
channel.is247
|
||||
? 'Notifications are disabled for 24/7 channels'
|
||||
: 'Enter channel IDs, one per line'
|
||||
}
|
||||
onBlur={field.onBlur}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Enter one channel ID per line. You can find channel IDs in their URLs.
|
||||
{channel.is247
|
||||
? '24/7 channels do not send go-live notifications, so notification channels cannot be edited.'
|
||||
: 'Enter one channel ID per line. You can find channel IDs in their URLs.'}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -59,21 +59,20 @@ export const changeUsernameSchema = z.object({
|
||||
newUsername: username,
|
||||
});
|
||||
|
||||
const notificationChannelsSchema: z.ZodType<string[], z.ZodTypeDef, string | string[]> =
|
||||
z.preprocess(
|
||||
(value) => {
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
.replace(/\r\n/g, '\n')
|
||||
.split('\n')
|
||||
.map((channel) => channel.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
const notificationChannelsSchema = z
|
||||
.union([z.string(), z.array(z.string())])
|
||||
.transform((value) => {
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
.replace(/\r\n/g, '\n')
|
||||
.split('\n')
|
||||
.map((channel) => channel.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
z.array(z.string()).max(10)
|
||||
);
|
||||
return value.map((channel) => channel.trim()).filter(Boolean);
|
||||
})
|
||||
.pipe(z.array(z.string()).max(10));
|
||||
|
||||
export const updateNotificationChannelsSchema = z.object({
|
||||
channelId: z.string().min(1),
|
||||
|
||||
@@ -141,7 +141,13 @@ export async function syncStream() {
|
||||
for (const [username, regionKey] of allActiveStreams) {
|
||||
const existingStream = await prisma.streamInfo.findUnique({
|
||||
where: { username },
|
||||
include: { channel: true },
|
||||
include: {
|
||||
channel: {
|
||||
include: {
|
||||
owner: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existingStream && !existingStream.isLive) {
|
||||
@@ -173,6 +179,20 @@ export async function syncStream() {
|
||||
channel: process.env.NOTIFICATION_CHANNEL_ID!,
|
||||
unfurl_links: true,
|
||||
});
|
||||
|
||||
for (const channelId of existingStream.channel.notifChannels) {
|
||||
queue.add(`streamStartChannel:${existingStream.username}`, {
|
||||
text: `${existingStream.username} is now *live*, streaming *${existingStream.title}* (${existingStream.category})!\n<https://hackclub.tv/${existingStream.username}|Go check them out>`,
|
||||
channel: channelId,
|
||||
unfurl_links: true,
|
||||
metadata: {
|
||||
type: 'custom_stream_announcement',
|
||||
managedChannelId: existingStream.channel.id,
|
||||
ownerSlackId: existingStream.channel.owner.slack_id,
|
||||
ownerChannelName: existingStream.channel.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (existingStream.enableNotifications && !existingStream.channel.is247) {
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { Queue, Worker } from 'bullmq';
|
||||
import { getRedisConnection } from '@hctv/db';
|
||||
|
||||
export type SlackNotificationJobData = {
|
||||
channel: string;
|
||||
text: string;
|
||||
unfurl_links?: boolean;
|
||||
metadata?: {
|
||||
type: 'custom_stream_announcement';
|
||||
managedChannelId: string;
|
||||
ownerSlackId: string;
|
||||
ownerChannelName: string;
|
||||
};
|
||||
};
|
||||
|
||||
const globalForNotifier = global as unknown as {
|
||||
notificationQueue: Queue | null;
|
||||
notificationQueue: Queue<SlackNotificationJobData> | null;
|
||||
notificationWorker: Worker | null;
|
||||
|
||||
thumbnailQueue: Queue | null;
|
||||
@@ -14,9 +26,9 @@ if (!globalForNotifier.notificationQueue) {
|
||||
globalForNotifier.notificationWorker = null;
|
||||
}
|
||||
|
||||
export function getNotificationQueue(): Queue {
|
||||
export function getNotificationQueue(): Queue<SlackNotificationJobData> {
|
||||
if (!globalForNotifier.notificationQueue) {
|
||||
globalForNotifier.notificationQueue = new Queue('notifications', {
|
||||
globalForNotifier.notificationQueue = new Queue<SlackNotificationJobData>('notifications', {
|
||||
connection: getRedisConnection().options,
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
@@ -44,4 +56,4 @@ export function getThumbnailQueue(): Queue {
|
||||
});
|
||||
}
|
||||
return globalForNotifier.thumbnailQueue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import { getRedisConnection } from '@hctv/db';
|
||||
import { getRedisConnection, prisma } from '@hctv/db';
|
||||
import snClient from '@/lib/services/slackNotifier';
|
||||
import type { SlackNotificationJobData } from '@/lib/workers';
|
||||
|
||||
const globalForWorker = global as unknown as {
|
||||
notificationWorker: Worker | null;
|
||||
@@ -18,14 +19,47 @@ export async function registerNotificationWorker(): Promise<void> {
|
||||
|
||||
console.log('Registering notification worker...');
|
||||
|
||||
const worker = new Worker('notifications', async (job) => {
|
||||
const worker = new Worker<SlackNotificationJobData>('notifications', async (job) => {
|
||||
try {
|
||||
await snClient.chat.postMessage(job.data);
|
||||
const { metadata: _metadata, ...slackMessage } = job.data;
|
||||
await snClient.chat.postMessage(slackMessage);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
console.error('Slack notification failed:', e);
|
||||
// @ts-ignore e is unknown
|
||||
return { success: false, error: e.message };
|
||||
|
||||
if (job.data.metadata?.type === 'custom_stream_announcement') {
|
||||
const channel = await prisma.channel.findUnique({
|
||||
where: { id: job.data.metadata.managedChannelId },
|
||||
select: {
|
||||
notifChannels: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (channel?.notifChannels.includes(job.data.channel)) {
|
||||
await prisma.channel.update({
|
||||
where: { id: job.data.metadata.managedChannelId },
|
||||
data: {
|
||||
notifChannels: channel.notifChannels.filter(
|
||||
(channelId) => channelId !== job.data.channel
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await snClient.chat.postMessage({
|
||||
channel: job.data.metadata.ownerSlackId,
|
||||
text: `I couldn't send a go-live notification for *${job.data.metadata.ownerChannelName}* to Slack channel \`${job.data.channel}\`, so I removed it from that channel's notification list.\nIf you still want notifications there, please make sure the bot can post in that channel and add it again in settings.`,
|
||||
});
|
||||
} catch (ownerNotificationError) {
|
||||
console.error('Failed to notify channel owner about Slack notification removal:', ownerNotificationError);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: e instanceof Error ? e.message : 'Unknown Slack notification error',
|
||||
};
|
||||
}
|
||||
}, {
|
||||
connection: getRedisConnection().options,
|
||||
@@ -45,4 +79,4 @@ export async function closeNotificationWorker(): Promise<void> {
|
||||
await globalForWorker.notificationWorker.close();
|
||||
globalForWorker.notificationWorker = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user