mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-07 04:20:47 +08:00
Compare commits
2 Commits
codex/add-
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d98c3644a6 | ||
|
|
dbb63a4039 |
30
.github/workflows/open_source_reply_bot.yml
vendored
Normal file
30
.github/workflows/open_source_reply_bot.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Open Source Reply Bot
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "*/30 * * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-open-source-reply-bot:
|
||||||
|
environment: Bots
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install --no-save @openai/agents tsx typescript
|
||||||
|
|
||||||
|
- name: Run open source reply bot
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
OPENISLE_TOKEN: ${{ secrets.OPENISLE_TOKEN_BOT_1 }}
|
||||||
|
APIFY_API_TOKEN: ${{ secrets.APIFY_API_TOKEN }}
|
||||||
|
run: npx tsx bots/instance/open_source_reply_bot.ts
|
||||||
@@ -13,5 +13,4 @@ public class AuthorDto {
|
|||||||
private String username;
|
private String username;
|
||||||
private String avatar;
|
private String avatar;
|
||||||
private MedalType displayMedal;
|
private MedalType displayMedal;
|
||||||
private boolean bot;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,4 @@ public class UserDto {
|
|||||||
private int point;
|
private int point;
|
||||||
private int currentLevel;
|
private int currentLevel;
|
||||||
private int nextLevelExp;
|
private int nextLevelExp;
|
||||||
private boolean bot;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,4 @@ public class UserSummaryDto {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private String username;
|
private String username;
|
||||||
private String avatar;
|
private String avatar;
|
||||||
private boolean bot;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public class UserMapper {
|
|||||||
dto.setUsername(user.getUsername());
|
dto.setUsername(user.getUsername());
|
||||||
dto.setAvatar(user.getAvatar());
|
dto.setAvatar(user.getAvatar());
|
||||||
dto.setDisplayMedal(user.getDisplayMedal());
|
dto.setDisplayMedal(user.getDisplayMedal());
|
||||||
dto.setBot(user.isBot());
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ public class UserMapper {
|
|||||||
dto.setPoint(user.getPoint());
|
dto.setPoint(user.getPoint());
|
||||||
dto.setCurrentLevel(levelService.getLevel(user.getExperience()));
|
dto.setCurrentLevel(levelService.getLevel(user.getExperience()));
|
||||||
dto.setNextLevelExp(levelService.nextLevelExp(user.getExperience()));
|
dto.setNextLevelExp(levelService.nextLevelExp(user.getExperience()));
|
||||||
dto.setBot(user.isBot());
|
|
||||||
if (viewer != null) {
|
if (viewer != null) {
|
||||||
dto.setSubscribed(subscriptionService.isSubscribed(viewer.getName(), user.getUsername()));
|
dto.setSubscribed(subscriptionService.isSubscribed(viewer.getName(), user.getUsername()));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -62,9 +62,6 @@ public class User {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Role role = Role.USER;
|
private Role role = Role.USER;
|
||||||
|
|
||||||
@Column(name = "is_bot", nullable = false)
|
|
||||||
private boolean bot = false;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private MedalType displayMedal;
|
private MedalType displayMedal;
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ public class ChannelService {
|
|||||||
userDto.setId(message.getSender().getId());
|
userDto.setId(message.getSender().getId());
|
||||||
userDto.setUsername(message.getSender().getUsername());
|
userDto.setUsername(message.getSender().getUsername());
|
||||||
userDto.setAvatar(message.getSender().getAvatar());
|
userDto.setAvatar(message.getSender().getAvatar());
|
||||||
userDto.setBot(message.getSender().isBot());
|
|
||||||
dto.setSender(userDto);
|
dto.setSender(userDto);
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
|
|||||||
@@ -211,7 +211,6 @@ public class MessageService {
|
|||||||
userSummaryDto.setId(message.getSender().getId());
|
userSummaryDto.setId(message.getSender().getId());
|
||||||
userSummaryDto.setUsername(message.getSender().getUsername());
|
userSummaryDto.setUsername(message.getSender().getUsername());
|
||||||
userSummaryDto.setAvatar(message.getSender().getAvatar());
|
userSummaryDto.setAvatar(message.getSender().getAvatar());
|
||||||
userSummaryDto.setBot(message.getSender().isBot());
|
|
||||||
dto.setSender(userSummaryDto);
|
dto.setSender(userSummaryDto);
|
||||||
|
|
||||||
if (message.getReplyTo() != null) {
|
if (message.getReplyTo() != null) {
|
||||||
@@ -223,7 +222,6 @@ public class MessageService {
|
|||||||
replySender.setId(reply.getSender().getId());
|
replySender.setId(reply.getSender().getId());
|
||||||
replySender.setUsername(reply.getSender().getUsername());
|
replySender.setUsername(reply.getSender().getUsername());
|
||||||
replySender.setAvatar(reply.getSender().getAvatar());
|
replySender.setAvatar(reply.getSender().getAvatar());
|
||||||
replySender.setBot(reply.getSender().isBot());
|
|
||||||
replyDto.setSender(replySender);
|
replyDto.setSender(replySender);
|
||||||
dto.setReplyTo(replyDto);
|
dto.setReplyTo(replyDto);
|
||||||
}
|
}
|
||||||
@@ -318,7 +316,6 @@ public class MessageService {
|
|||||||
userDto.setId(p.getUser().getId());
|
userDto.setId(p.getUser().getId());
|
||||||
userDto.setUsername(p.getUser().getUsername());
|
userDto.setUsername(p.getUser().getUsername());
|
||||||
userDto.setAvatar(p.getUser().getAvatar());
|
userDto.setAvatar(p.getUser().getAvatar());
|
||||||
userDto.setBot(p.getUser().isBot());
|
|
||||||
return userDto;
|
return userDto;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
@@ -368,7 +365,6 @@ public class MessageService {
|
|||||||
userDto.setId(p.getUser().getId());
|
userDto.setId(p.getUser().getId());
|
||||||
userDto.setUsername(p.getUser().getUsername());
|
userDto.setUsername(p.getUser().getUsername());
|
||||||
userDto.setAvatar(p.getUser().getAvatar());
|
userDto.setAvatar(p.getUser().getAvatar());
|
||||||
userDto.setBot(p.getUser().isBot());
|
|
||||||
return userDto;
|
return userDto;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ CREATE TABLE IF NOT EXISTS `users` (
|
|||||||
`username` varchar(50) NOT NULL,
|
`username` varchar(50) NOT NULL,
|
||||||
`verification_code` varchar(255) DEFAULT NULL,
|
`verification_code` varchar(255) DEFAULT NULL,
|
||||||
`verified` bit(1) DEFAULT NULL,
|
`verified` bit(1) DEFAULT NULL,
|
||||||
`is_bot` bit(1) NOT NULL DEFAULT b'0',
|
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `UK_users_email` (`email`),
|
UNIQUE KEY `UK_users_email` (`email`),
|
||||||
UNIQUE KEY `UK_users_username` (`username`)
|
UNIQUE KEY `UK_users_username` (`username`)
|
||||||
|
|||||||
@@ -8,28 +8,10 @@ DELETE FROM `users`;
|
|||||||
|
|
||||||
-- 插入用户,两个普通用户,一个管理员
|
-- 插入用户,两个普通用户,一个管理员
|
||||||
-- username:admin/user1/user2 password:123456
|
-- username:admin/user1/user2 password:123456
|
||||||
INSERT INTO `users` (
|
INSERT INTO `users` (`id`, `approved`, `avatar`, `created_at`, `display_medal`, `email`, `experience`, `introduction`, `password`, `password_reset_code`, `point`, `register_reason`, `role`, `username`, `verification_code`, `verified`) VALUES
|
||||||
`id`,
|
(1, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-01 16:08:17.426430', 'PIONEER', 'adminmail@openisle.com', 70, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 110, '测试测试测试……', 'ADMIN', 'admin', NULL, b'1'),
|
||||||
`approved`,
|
(2, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-03 16:08:17.426430', 'PIONEER', 'usermail2@openisle.com', 70, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 110, '测试测试测试……', 'USER', 'user1', NULL, b'1'),
|
||||||
`avatar`,
|
(3, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-02 17:21:21.617666', 'PIONEER', 'usermail1@openisle.com', 40, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 40, '测试测试测试……', 'USER', 'user2', NULL, b'1');
|
||||||
`created_at`,
|
|
||||||
`display_medal`,
|
|
||||||
`email`,
|
|
||||||
`experience`,
|
|
||||||
`introduction`,
|
|
||||||
`password`,
|
|
||||||
`password_reset_code`,
|
|
||||||
`point`,
|
|
||||||
`register_reason`,
|
|
||||||
`role`,
|
|
||||||
`username`,
|
|
||||||
`verification_code`,
|
|
||||||
`verified`,
|
|
||||||
`is_bot`
|
|
||||||
) VALUES
|
|
||||||
(1, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-01 16:08:17.426430', 'PIONEER', 'adminmail@openisle.com', 70, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 110, '测试测试测试……', 'ADMIN', 'admin', NULL, b'1', b'0'),
|
|
||||||
(2, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-03 16:08:17.426430', 'PIONEER', 'usermail2@openisle.com', 70, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 110, '测试测试测试……', 'USER', 'user1', NULL, b'1', b'0'),
|
|
||||||
(3, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-02 17:21:21.617666', 'PIONEER', 'usermail1@openisle.com', 40, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 40, '测试测试测试……', 'USER', 'user2', NULL, b'1', b'0');
|
|
||||||
|
|
||||||
INSERT INTO `categories` (`id`,`description`,`icon`,`name`,`small_icon`) VALUES
|
INSERT INTO `categories` (`id`,`description`,`icon`,`name`,`small_icon`) VALUES
|
||||||
(1,'测试用分类1','star','测试用分类1',NULL),
|
(1,'测试用分类1','star','测试用分类1',NULL),
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE users
|
|
||||||
ADD COLUMN is_bot BIT(1) NOT NULL DEFAULT b'0';
|
|
||||||
65
bots/instance/open_source_reply_bot.ts
Normal file
65
bots/instance/open_source_reply_bot.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { BotFather, WorkflowInput } from "../bot_father";
|
||||||
|
|
||||||
|
class OpenSourceReplyBot extends BotFather {
|
||||||
|
constructor() {
|
||||||
|
super("OpenSource Reply Bot");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override getAdditionalInstructions(): string[] {
|
||||||
|
const knowledgeBase = this.loadKnowledgeBase();
|
||||||
|
|
||||||
|
return [
|
||||||
|
"You are OpenSourceReplyBot, a professional helper who focuses on answering open-source development and code-related questions for the OpenIsle community.",
|
||||||
|
"Respond in Chinese using well-structured Markdown sections such as 标题、列表、代码块等,让回复清晰易读。",
|
||||||
|
"保持语气专业、耐心、详尽,绝不使用表情符号或颜文字,也不要卖萌。",
|
||||||
|
"优先解答与项目代码、贡献流程、架构设计或排错相关的问题;如果消息与此无关,请礼貌说明并跳过。",
|
||||||
|
"在需要时引用 README.md 与 CONTRIBUTING.md 中的要点,帮助用户快速定位文档位置。",
|
||||||
|
knowledgeBase,
|
||||||
|
].filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override getCliQuery(): string {
|
||||||
|
return `
|
||||||
|
【AUTO】每30分钟自动巡检未读提及与评论,严格遵守以下流程:
|
||||||
|
1)调用 list_unread_messages 获取待处理的“提及/评论”;
|
||||||
|
2)按时间从新到旧逐条处理(最多10条);如需上下文请调用 get_post;
|
||||||
|
3)仅对与开源项目、代码实现或贡献流程直接相关的问题生成详尽的 Markdown 中文回复,
|
||||||
|
若与主题无关则礼貌说明并跳过;
|
||||||
|
4)回复时引用 README 或 CONTRIBUTING 中的要点(如适用),并优先给出可执行的排查步骤或代码建议;
|
||||||
|
5)回复评论使用 reply_to_comment,回复帖子使用 reply_to_post;
|
||||||
|
6)若某通知最后一条已由本 bot 回复,则跳过避免重复;
|
||||||
|
7)整理已处理通知 ID 调用 mark_notifications_read;
|
||||||
|
8)结束时输出包含处理条目概览(URL或ID)的总结。`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadKnowledgeBase(): string {
|
||||||
|
const docs = ["../../README.md", "../../CONTRIBUTING.md"];
|
||||||
|
const sections: string[] = [];
|
||||||
|
|
||||||
|
for (const relativePath of docs) {
|
||||||
|
try {
|
||||||
|
const absolutePath = path.resolve(__dirname, relativePath);
|
||||||
|
const content = readFileSync(absolutePath, "utf-8").trim();
|
||||||
|
if (content) {
|
||||||
|
sections.push(`以下是 ${path.basename(absolutePath)} 的内容:\n${content}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
sections.push(`未能加载 ${relativePath},请检查文件路径或权限。`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections.join("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openSourceReplyBot = new OpenSourceReplyBot();
|
||||||
|
|
||||||
|
export const runWorkflow = async (workflow: WorkflowInput) => {
|
||||||
|
return openSourceReplyBot.runWorkflow(workflow);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
openSourceReplyBot.runCli();
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
<div class="info-content-header-left">
|
<div class="info-content-header-left">
|
||||||
<span class="user-name">{{ comment.userName }}</span>
|
<span class="user-name">{{ comment.userName }}</span>
|
||||||
<span v-if="isCommentFromPostAuthor" class="op-badge" title="楼主">OP</span>
|
<span v-if="isCommentFromPostAuthor" class="op-badge" title="楼主">OP</span>
|
||||||
<span v-if="comment.isBot" class="bot-badge" title="Bot">Bot</span>
|
|
||||||
<medal-one class="medal-icon" />
|
<medal-one class="medal-icon" />
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="comment.medal"
|
v-if="comment.medal"
|
||||||
@@ -523,21 +522,6 @@ const handleContentClick = (e) => {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bot-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-left: 6px;
|
|
||||||
padding: 0 6px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 9px;
|
|
||||||
background-color: rgba(76, 175, 80, 0.16);
|
|
||||||
color: #2e7d32;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.medal-icon {
|
.medal-icon {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|||||||
@@ -377,7 +377,6 @@ const mapComment = (
|
|||||||
text: c.content,
|
text: c.content,
|
||||||
reactions: c.reactions || [],
|
reactions: c.reactions || [],
|
||||||
pinned: Boolean(c.pinned ?? c.pinnedAt ?? c.pinned_at),
|
pinned: Boolean(c.pinned ?? c.pinnedAt ?? c.pinned_at),
|
||||||
isBot: Boolean(c.author?.bot),
|
|
||||||
reply: (c.replies || []).map((r) =>
|
reply: (c.replies || []).map((r) =>
|
||||||
mapComment(r, c.author.username, c.author.avatar, c.author.id, level + 1),
|
mapComment(r, c.author.username, c.author.avatar, c.author.id, level + 1),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user