mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-22 06:00:12 +00:00
89422fc77a
Introduce full authors browsing/detail feature and enhance notifications to support type-specific titles. - Add server APIs: authors search, author detail, and author books routes (audnexus integration) that require auth and enrich results with library matches. - Add frontend pages/components: /authors listing and /authors/[asin] detail pages; AuthorCard, AuthorGrid, AuthorDetailCard, SimilarAuthorsRow, and related skeletons. - Add hook and integration stubs: new useAuthors hook and audnexus-authors integration; update audible service to expose audibleBaseUrl. - Update AudiobookDetailsModal to use audibleBaseUrl and link author names to author detail pages. - Add header navigation link to Authors. - Notifications: extend docs and code to include requestType (audiobook|ebook), add getEventTitle/getEventMeta helpers, update queue signature and providers/processors/tests to pass/handle requestType so titles can be resolved per request type. - Misc: job queue, processors, provider tests and notification tests updated to reflect new behavior. This change enables browsing authors and provides type-aware notification titles without per-provider changes.
88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
/**
|
|
* Component: Discord Notification Provider
|
|
* Documentation: documentation/backend/services/notifications.md
|
|
*/
|
|
|
|
import { INotificationProvider, NotificationPayload, ProviderMetadata } from '../INotificationProvider';
|
|
import { getEventMeta, getEventTitle, type NotificationSeverity } from '@/lib/constants/notification-events';
|
|
|
|
export interface DiscordConfig {
|
|
webhookUrl: string;
|
|
username?: string;
|
|
avatarUrl?: string;
|
|
}
|
|
|
|
// Discord embed colors by severity
|
|
const SEVERITY_COLORS: Record<NotificationSeverity, number> = {
|
|
info: 0xfbbf24, // yellow-400
|
|
success: 0x22c55e, // green-500
|
|
error: 0xef4444, // red-500
|
|
warning: 0xf97316, // orange-500
|
|
};
|
|
|
|
export class DiscordProvider implements INotificationProvider {
|
|
type = 'discord' as const;
|
|
sensitiveFields = ['webhookUrl'];
|
|
metadata: ProviderMetadata = {
|
|
type: 'discord',
|
|
displayName: 'Discord',
|
|
description: 'Send notifications via Discord webhook',
|
|
iconLabel: 'D',
|
|
iconColor: 'bg-indigo-500',
|
|
configFields: [
|
|
{ name: 'webhookUrl', label: 'Webhook URL', type: 'text', required: true, placeholder: 'https://discord.com/api/webhooks/...' },
|
|
{ name: 'username', label: 'Username', type: 'text', required: false, placeholder: 'ReadMeABook', defaultValue: 'ReadMeABook' },
|
|
{ name: 'avatarUrl', label: 'Avatar URL', type: 'text', required: false, placeholder: 'https://example.com/avatar.png', defaultValue: '' },
|
|
],
|
|
};
|
|
|
|
async send(config: Record<string, any>, payload: NotificationPayload): Promise<void> {
|
|
const discordConfig = config as unknown as DiscordConfig;
|
|
const embed = this.formatEmbed(payload);
|
|
|
|
const body = {
|
|
username: discordConfig.username || 'ReadMeABook',
|
|
avatar_url: discordConfig.avatarUrl,
|
|
embeds: [embed],
|
|
};
|
|
|
|
const response = await fetch(discordConfig.webhookUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
throw new Error(`Discord webhook failed: ${response.status} ${errorText}`);
|
|
}
|
|
}
|
|
|
|
private formatEmbed(payload: NotificationPayload): any {
|
|
const { event, title, author, userName, message, requestId, requestType, timestamp } = payload;
|
|
const meta = getEventMeta(event);
|
|
const resolvedTitle = getEventTitle(event, requestType);
|
|
|
|
const isIssue = event === 'issue_reported';
|
|
const fields = [
|
|
{ name: 'Title', value: title, inline: false },
|
|
{ name: 'Author', value: author, inline: true },
|
|
{ name: isIssue ? 'Reported By' : 'Requested By', value: userName, inline: true },
|
|
];
|
|
|
|
if (message) {
|
|
fields.push({ name: isIssue ? 'Reason' : 'Error', value: message, inline: false });
|
|
}
|
|
|
|
return {
|
|
title: `${meta.emoji} ${resolvedTitle}`,
|
|
color: SEVERITY_COLORS[meta.severity],
|
|
fields,
|
|
footer: {
|
|
text: isIssue ? `Issue ID: ${payload.issueId}` : `Request ID: ${requestId}`,
|
|
},
|
|
timestamp: timestamp.toISOString(),
|
|
};
|
|
}
|
|
}
|