feat(notifications): Add bot owner notifications for interactions
- Create new botOwnerNotify module with notifyBotOwnerForPost and notifyBotOwnerForFollow functions - Add bot owner notifications to local post interactions (likes, reposts) - Add bot owner notifications to swarm post interactions (likes, reposts, mentions) - Add bot owner notifications to follow interactions (local and swarm) - Implement mention notification creation for local mentions in post creation - Notify bot owners when their bots receive interactions from other users - Ensure bot owners are alerted to engagement on their bot accounts across federated network
This commit is contained in:
@@ -83,6 +83,10 @@ export async function POST(request: Request, context: RouteContext) {
|
|||||||
postId,
|
postId,
|
||||||
type: 'like',
|
type: 'like',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot's post
|
||||||
|
const { notifyBotOwnerForPost } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForPost(post.userId, user.id, 'like', postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SWARM-FIRST: Check if this is a swarm post and deliver directly
|
// SWARM-FIRST: Check if this is a swarm post and deliver directly
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ export async function POST(request: Request, context: RouteContext) {
|
|||||||
postId,
|
postId,
|
||||||
type: 'repost',
|
type: 'repost',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot's post
|
||||||
|
const { notifyBotOwnerForPost } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForPost(originalPost.userId, user.id, 'repost', postId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SWARM-FIRST: Deliver repost to swarm node
|
// SWARM-FIRST: Deliver repost to swarm node
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { db, posts, users, media, follows, mutes, blocks, likes, remoteFollows, remotePosts } from '@/db';
|
import { db, posts, users, media, follows, mutes, blocks, likes, remoteFollows, remotePosts, notifications } from '@/db';
|
||||||
import { requireAuth } from '@/lib/auth';
|
import { requireAuth } from '@/lib/auth';
|
||||||
import { eq, desc, and, inArray, isNull, notInArray, or } from 'drizzle-orm';
|
import { eq, desc, and, inArray, isNull, notInArray, or } from 'drizzle-orm';
|
||||||
import type { SQL } from 'drizzle-orm';
|
import type { SQL } from 'drizzle-orm';
|
||||||
@@ -134,6 +134,42 @@ export async function POST(request: Request) {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle local mentions (create notifications for users on this node)
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const { extractMentions } = await import('@/lib/swarm/interactions');
|
||||||
|
const { notifications } = await import('@/db');
|
||||||
|
|
||||||
|
const mentions = extractMentions(data.content);
|
||||||
|
|
||||||
|
for (const mention of mentions) {
|
||||||
|
// Only handle local mentions (no domain)
|
||||||
|
if (mention.domain) continue;
|
||||||
|
|
||||||
|
// Find the mentioned user
|
||||||
|
const mentionedUser = await db.query.users.findFirst({
|
||||||
|
where: eq(users.handle, mention.handle.toLowerCase()),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mentionedUser && mentionedUser.id !== user.id && !mentionedUser.isSuspended) {
|
||||||
|
// Create notification for the mentioned user
|
||||||
|
await db.insert(notifications).values({
|
||||||
|
userId: mentionedUser.id,
|
||||||
|
actorId: user.id,
|
||||||
|
postId: post.id,
|
||||||
|
type: 'mention',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot being mentioned
|
||||||
|
const { notifyBotOwnerForPost } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForPost(mentionedUser.id, user.id, 'mention', post.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Local] Error creating mention notifications:', err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// SWARM-FIRST: Deliver mentions to swarm nodes
|
// SWARM-FIRST: Deliver mentions to swarm nodes
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ export async function POST(request: NextRequest) {
|
|||||||
type: 'follow',
|
type: 'follow',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot being followed
|
||||||
|
const { notifyBotOwnerForFollow } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForFollow(targetUser.id, remoteUser.id);
|
||||||
|
|
||||||
console.log(`[Swarm] Received follow from ${remoteHandle} for @${data.targetHandle}`);
|
console.log(`[Swarm] Received follow from ${remoteHandle} for @${data.targetHandle}`);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ export async function POST(request: NextRequest) {
|
|||||||
type: 'like',
|
type: 'like',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot's post
|
||||||
|
const { notifyBotOwnerForPost } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForPost(post.userId, remoteUser.id, 'like', data.postId);
|
||||||
|
|
||||||
console.log(`[Swarm] Received like from ${remoteHandle} on post ${data.postId}`);
|
console.log(`[Swarm] Received like from ${remoteHandle} on post ${data.postId}`);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ export async function POST(request: NextRequest) {
|
|||||||
type: 'mention',
|
type: 'mention',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot being mentioned
|
||||||
|
const { notifyBotOwnerForPost } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForPost(mentionedUser.id, remoteUser.id, 'mention', post.id);
|
||||||
|
|
||||||
console.log(`[Swarm] Received mention from ${remoteHandle} for @${data.mentionedHandle}`);
|
console.log(`[Swarm] Received mention from ${remoteHandle} for @${data.mentionedHandle}`);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ export async function POST(request: NextRequest) {
|
|||||||
type: 'repost',
|
type: 'repost',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot's post
|
||||||
|
const { notifyBotOwnerForPost } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForPost(post.userId, remoteUser.id, 'repost', data.postId);
|
||||||
|
|
||||||
console.log(`[Swarm] Received repost from ${remoteHandle} on post ${data.postId}`);
|
console.log(`[Swarm] Received repost from ${remoteHandle} on post ${data.postId}`);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -268,6 +268,10 @@ export async function POST(request: Request, context: RouteContext) {
|
|||||||
actorId: currentUser.id,
|
actorId: currentUser.id,
|
||||||
type: 'follow',
|
type: 'follow',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also notify bot owner if this is a bot being followed
|
||||||
|
const { notifyBotOwnerForFollow } = await import('@/lib/notifications/botOwnerNotify');
|
||||||
|
await notifyBotOwnerForFollow(targetUser.id, currentUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update counts
|
// Update counts
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Bot Owner Notification Helper
|
||||||
|
*
|
||||||
|
* When someone interacts with a bot (likes, reposts, follows, mentions),
|
||||||
|
* this helper creates a notification for the bot's owner so they can
|
||||||
|
* see engagement on their bots.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { db, notifications, users } from '@/db';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export type BotInteractionType = 'like' | 'repost' | 'follow' | 'mention';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is a bot and get their owner's ID.
|
||||||
|
*
|
||||||
|
* @param userId - The user ID to check
|
||||||
|
* @returns The bot owner's ID if this is a bot, null otherwise
|
||||||
|
*/
|
||||||
|
export async function getBotOwnerId(userId: string): Promise<string | null> {
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
where: eq(users.id, userId),
|
||||||
|
columns: {
|
||||||
|
isBot: true,
|
||||||
|
botOwnerId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user?.isBot && user.botOwnerId) {
|
||||||
|
return user.botOwnerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a notification for a bot's owner when someone interacts with the bot.
|
||||||
|
*
|
||||||
|
* This is called in addition to the normal notification (which goes to the bot's
|
||||||
|
* user account). The owner gets notified so they can see engagement on their bots.
|
||||||
|
*
|
||||||
|
* @param botUserId - The bot's user ID (the one receiving the interaction)
|
||||||
|
* @param actorId - The user who performed the interaction
|
||||||
|
* @param type - The type of interaction
|
||||||
|
* @param postId - Optional post ID (for likes, reposts, mentions)
|
||||||
|
* @returns True if a notification was created, false otherwise
|
||||||
|
*/
|
||||||
|
export async function notifyBotOwner(
|
||||||
|
botUserId: string,
|
||||||
|
actorId: string,
|
||||||
|
type: BotInteractionType,
|
||||||
|
postId?: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const ownerId = await getBotOwnerId(botUserId);
|
||||||
|
|
||||||
|
if (!ownerId) {
|
||||||
|
// Not a bot, no owner to notify
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't notify owner if they're the one doing the interaction
|
||||||
|
if (ownerId === actorId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create notification for the bot owner
|
||||||
|
await db.insert(notifications).values({
|
||||||
|
userId: ownerId,
|
||||||
|
actorId,
|
||||||
|
postId: postId || null,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BotOwnerNotify] Error creating notification:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify bot owner about an interaction on a bot's post.
|
||||||
|
*
|
||||||
|
* Checks if the post author is a bot and notifies the owner.
|
||||||
|
*
|
||||||
|
* @param postAuthorId - The post author's user ID
|
||||||
|
* @param actorId - The user who performed the interaction
|
||||||
|
* @param type - The type of interaction (like, repost, mention)
|
||||||
|
* @param postId - The post ID
|
||||||
|
*/
|
||||||
|
export async function notifyBotOwnerForPost(
|
||||||
|
postAuthorId: string,
|
||||||
|
actorId: string,
|
||||||
|
type: 'like' | 'repost' | 'mention',
|
||||||
|
postId: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
return notifyBotOwner(postAuthorId, actorId, type, postId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify bot owner about a new follower.
|
||||||
|
*
|
||||||
|
* @param botUserId - The bot's user ID being followed
|
||||||
|
* @param followerId - The user who followed the bot
|
||||||
|
*/
|
||||||
|
export async function notifyBotOwnerForFollow(
|
||||||
|
botUserId: string,
|
||||||
|
followerId: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
return notifyBotOwner(botUserId, followerId, 'follow');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user