feat(bots): Implement comprehensive bot system with autonomous posting, content management, and API endpoints

- Add bot management system with creation, suspension, and reinstatement functionality
- Implement autonomous bot posting with scheduling, rate limiting, and content generation
- Add content fetching system supporting RSS feeds and multiple content sources
- Implement LLM-based content generation with customizable bot personalities
- Add mention handling and automated response system for bot interactions
- Implement API key management with encryption using AUTH_SECRET for simplified deployment
- Add comprehensive bot logging system for activity tracking and error monitoring
- Create bot administration pages and settings UI for managing bot configurations
- Add database migrations for bot system schema including users, sources, and content items
- Implement cron job system for automated bot operations and scheduled tasks
- Add extensive test coverage with unit and property-based tests for core bot modules
- Simplify encryption by deriving keys from AUTH_SECRET instead of separate environment variable
- Implement automatic content fetching on post trigger with retry logic
- Add Reddit-specific link preview handling using oEmbed API for reliable metadata extraction
- Create utility scripts for bot inspection and cleanup operations
- Add comprehensive bot system documentation and improvement tracking
This commit is contained in:
AskIt
2026-01-25 16:22:41 +01:00
parent cfb558fff1
commit 8ad3b97b7e
102 changed files with 41692 additions and 164 deletions
+10 -7
View File
@@ -29,13 +29,15 @@ export async function GET(request: Request, context: RouteContext) {
}
// Get followers
const userFollowers = await db.query.follows.findMany({
where: eq(follows.followingId, user.id),
with: {
follower: true,
},
limit,
});
const userFollowers = await db
.select({
id: follows.id,
follower: users,
})
.from(follows)
.innerJoin(users, eq(follows.followerId, users.id))
.where(eq(follows.followingId, user.id))
.limit(limit);
return NextResponse.json({
followers: userFollowers.map(f => ({
@@ -44,6 +46,7 @@ export async function GET(request: Request, context: RouteContext) {
displayName: f.follower.displayName,
avatarUrl: f.follower.avatarUrl,
bio: f.follower.bio,
isBot: f.follower.isBot,
})),
nextCursor: userFollowers.length === limit ? userFollowers[userFollowers.length - 1]?.id : null,
});
+10 -7
View File
@@ -29,13 +29,15 @@ export async function GET(request: Request, context: RouteContext) {
}
// Get local following
const userFollowing = await db.query.follows.findMany({
where: eq(follows.followerId, user.id),
with: {
following: true,
},
limit,
});
const userFollowing = await db
.select({
id: follows.id,
following: users,
})
.from(follows)
.innerJoin(users, eq(follows.followingId, users.id))
.where(eq(follows.followerId, user.id))
.limit(limit);
const localFollowing = userFollowing.map(f => ({
id: f.following.id,
@@ -44,6 +46,7 @@ export async function GET(request: Request, context: RouteContext) {
avatarUrl: f.following.avatarUrl,
bio: f.following.bio,
isRemote: false,
isBot: f.following.isBot,
}));
// Get remote following
+32 -13
View File
@@ -119,20 +119,39 @@ export async function GET(request: Request, context: RouteContext) {
}
// Return user profile (without sensitive data)
return NextResponse.json({
user: {
id: user.id,
handle: user.handle,
displayName: user.displayName,
bio: user.bio,
avatarUrl: user.avatarUrl,
headerUrl: user.headerUrl,
followersCount: user.followersCount,
followingCount: user.followingCount,
postsCount: user.postsCount,
createdAt: user.createdAt,
// Include bot info if this is a bot account
const userResponse: Record<string, unknown> = {
id: user.id,
handle: user.handle,
displayName: user.displayName,
bio: user.bio,
avatarUrl: user.avatarUrl,
headerUrl: user.headerUrl,
followersCount: user.followersCount,
followingCount: user.followingCount,
postsCount: user.postsCount,
createdAt: user.createdAt,
website: user.website,
movedTo: user.movedTo,
isBot: user.isBot,
};
// If this is a bot, include owner info
if (user.isBot && user.botOwnerId) {
const owner = await db.query.users.findFirst({
where: eq(users.id, user.botOwnerId),
});
if (owner) {
userResponse.botOwner = {
id: owner.id,
handle: owner.handle,
displayName: owner.displayName,
avatarUrl: owner.avatarUrl,
};
}
});
}
return NextResponse.json({ user: userResponse });
} catch (error) {
console.error('Get user error:', error);
return NextResponse.json({ error: 'Failed to get user' }, { status: 500 });
+1
View File
@@ -20,6 +20,7 @@ export async function GET(request: NextRequest) {
bio: users.bio,
avatarUrl: users.avatarUrl,
createdAt: users.createdAt,
isBot: users.isBot,
})
.from(users)
.where(sql`${users.isSuspended} IS FALSE`)