@mazees/mark 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- package/src/shared/parsers/mark-tag-parser.js +275 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { Telegraf } from 'telegraf'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import yts from 'yt-search'
|
|
6
|
+
import { execFile } from 'child_process'
|
|
7
|
+
import ffmpeg from 'ffmpeg-static'
|
|
8
|
+
import https from 'https'
|
|
9
|
+
import { abortAllFetches, activeAbortControllers } from '../../server/services/ai-bridge.js'
|
|
10
|
+
import { dbStore } from '../../server/memory/db-store.js'
|
|
11
|
+
import { wsHub } from '../../server/ws-hub.js'
|
|
12
|
+
|
|
13
|
+
let bot = null
|
|
14
|
+
let currentStatus = 'disconnected'
|
|
15
|
+
export const uiMessageHistory = []
|
|
16
|
+
const MAX_UI_HISTORY = 100
|
|
17
|
+
const pendingRequestsMap = new Map()
|
|
18
|
+
const pendingBroadcastQueue = []
|
|
19
|
+
|
|
20
|
+
const CHAT_IDS_FILE = path.join(os.homedir(), '.config', 'mark-agent', 'telegram-chats.json')
|
|
21
|
+
const adminChatIdsSet = new Set()
|
|
22
|
+
const usernameToChatIdMap = new Map()
|
|
23
|
+
|
|
24
|
+
const agent = new https.Agent({
|
|
25
|
+
keepAlive: true,
|
|
26
|
+
keepAliveMsecs: 10000,
|
|
27
|
+
rejectUnauthorized: false
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
function getTelegramConfig() {
|
|
31
|
+
try {
|
|
32
|
+
const cfgs = dbStore.config.getAll()
|
|
33
|
+
const cfg = cfgs[0] || {}
|
|
34
|
+
// SQLite config table stores settings inside row.data object
|
|
35
|
+
if (cfg.data && typeof cfg.data === 'object') {
|
|
36
|
+
return { ...cfg, ...cfg.data }
|
|
37
|
+
}
|
|
38
|
+
return cfg
|
|
39
|
+
} catch (_) {
|
|
40
|
+
return {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const getConnectionStatus = () => {
|
|
45
|
+
return { status: currentStatus }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const updateStatus = (status) => {
|
|
49
|
+
currentStatus = status
|
|
50
|
+
wsHub.broadcast('tg:connection', status)
|
|
51
|
+
if (status === 'connected') {
|
|
52
|
+
setTimeout(flushPendingBroadcasts, 500)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const loadSavedChatIds = () => {
|
|
57
|
+
try {
|
|
58
|
+
if (fs.existsSync(CHAT_IDS_FILE)) {
|
|
59
|
+
const data = JSON.parse(fs.readFileSync(CHAT_IDS_FILE, 'utf8'))
|
|
60
|
+
if (Array.isArray(data.chatIds)) {
|
|
61
|
+
data.chatIds.forEach((id) => adminChatIdsSet.add(String(id)))
|
|
62
|
+
}
|
|
63
|
+
if (data.usernameMap && typeof data.usernameMap === 'object') {
|
|
64
|
+
Object.entries(data.usernameMap).forEach(([user, id]) => {
|
|
65
|
+
usernameToChatIdMap.set(user, String(id))
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error('[Telegram] Gagal memuat saved chat IDs:', e)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const saveChatIdsToFile = () => {
|
|
75
|
+
try {
|
|
76
|
+
const dir = path.dirname(CHAT_IDS_FILE)
|
|
77
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
|
78
|
+
const data = {
|
|
79
|
+
chatIds: Array.from(adminChatIdsSet),
|
|
80
|
+
usernameMap: Object.fromEntries(usernameToChatIdMap)
|
|
81
|
+
}
|
|
82
|
+
fs.writeFileSync(CHAT_IDS_FILE, JSON.stringify(data, null, 2), 'utf8')
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('[Telegram] Gagal menyimpan chat IDs ke file:', e)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
loadSavedChatIds()
|
|
89
|
+
|
|
90
|
+
const formatMarkdownToTelegramHTML = (text) => {
|
|
91
|
+
if (!text) return ''
|
|
92
|
+
let html = String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
93
|
+
|
|
94
|
+
html = html.replace(/```([a-z0-9-]*)\n([\s\S]*?)```/gi, (_match, lang, code) => {
|
|
95
|
+
return lang
|
|
96
|
+
? `<pre><code class="language-${lang}">${code}</code></pre>`
|
|
97
|
+
: `<pre><code>${code}</code></pre>`
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
101
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>')
|
|
102
|
+
html = html.replace(/\*([^*]+)\*/g, '<i>$1</i>')
|
|
103
|
+
html = html.replace(/~~([^~]+)~~/g, '<s>$1</s>')
|
|
104
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
|
|
105
|
+
html = html.replace(/^#{1,6}\s+(.+)$/gm, '<b>$1</b>')
|
|
106
|
+
|
|
107
|
+
return html
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const stopTelegramBot = () => {
|
|
111
|
+
if (bot) {
|
|
112
|
+
try {
|
|
113
|
+
bot.stop('BOT_STOPPED')
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error('[Telegram] Error stopping bot:', e)
|
|
116
|
+
}
|
|
117
|
+
bot = null
|
|
118
|
+
}
|
|
119
|
+
updateStatus('disconnected')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const startTelegramBot = async (token) => {
|
|
123
|
+
if (!token || !token.trim()) {
|
|
124
|
+
console.error('[Telegram] Token kosong')
|
|
125
|
+
updateStatus('disconnected')
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (bot) {
|
|
130
|
+
stopTelegramBot()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
updateStatus('connecting')
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const config = getTelegramConfig()
|
|
137
|
+
const telegramOpts = { agent }
|
|
138
|
+
if (config.tgApiRoot && config.tgApiRoot.trim()) {
|
|
139
|
+
telegramOpts.apiRoot = config.tgApiRoot.trim()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
bot = new Telegraf(token.trim(), { telegram: telegramOpts })
|
|
143
|
+
|
|
144
|
+
if (config.tgAdminIds) {
|
|
145
|
+
const ids = config.tgAdminIds
|
|
146
|
+
.split(',')
|
|
147
|
+
.map((s) => s.trim())
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
ids.forEach((id) => {
|
|
150
|
+
const cleanId = id.replace(/^@/, '')
|
|
151
|
+
if (/^\d+$/.test(cleanId)) {
|
|
152
|
+
adminChatIdsSet.add(cleanId)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
saveChatIdsToFile()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bot.command('start', (ctx) => {
|
|
159
|
+
const chatId = String(ctx.chat?.id || ctx.from?.id || '')
|
|
160
|
+
const senderUsername = (ctx.from?.username || '').toLowerCase()
|
|
161
|
+
if (chatId) {
|
|
162
|
+
adminChatIdsSet.add(chatId)
|
|
163
|
+
if (senderUsername) usernameToChatIdMap.set(senderUsername, chatId)
|
|
164
|
+
saveChatIdsToFile()
|
|
165
|
+
}
|
|
166
|
+
ctx.reply('Halo! Saya Mark (AI OS Companion). Bot Telegram ini telah terhubung.')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
bot.command('info', (ctx) => {
|
|
170
|
+
ctx.reply(
|
|
171
|
+
'Daftar Perintah MARK:\n\n' +
|
|
172
|
+
'/start - Memulai bot\n' +
|
|
173
|
+
'/info - Menampilkan daftar perintah\n' +
|
|
174
|
+
'/abort - Menghentikan proses AI yang sedang berjalan\n' +
|
|
175
|
+
'/accept - Mengizinkan persetujuan sekali saja\n' +
|
|
176
|
+
'/always - Mengizinkan selamanya untuk path folder ini\n' +
|
|
177
|
+
'/reject - Menolak prompt persetujuan'
|
|
178
|
+
)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
bot.command('abort', (ctx) => {
|
|
182
|
+
if (activeAbortControllers.size > 0) {
|
|
183
|
+
abortAllFetches()
|
|
184
|
+
ctx.reply('[INFO]: Membatalkan proses AI saat ini...')
|
|
185
|
+
} else {
|
|
186
|
+
ctx.reply('[INFO]: Tidak ada proses AI yang sedang berjalan.')
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const isApprovalCommand = (cmdText) => {
|
|
191
|
+
const clean = (cmdText || '').trim().toLowerCase()
|
|
192
|
+
return (
|
|
193
|
+
clean === '/accept' ||
|
|
194
|
+
clean === 'accept' ||
|
|
195
|
+
clean === '/izinkan' ||
|
|
196
|
+
clean === 'izinkan' ||
|
|
197
|
+
clean.startsWith('/accept@') ||
|
|
198
|
+
clean === '/always' ||
|
|
199
|
+
clean === 'always' ||
|
|
200
|
+
clean === '/selamanya' ||
|
|
201
|
+
clean === 'selamanya' ||
|
|
202
|
+
clean.startsWith('/always@') ||
|
|
203
|
+
clean === '/reject' ||
|
|
204
|
+
clean === 'reject' ||
|
|
205
|
+
clean === '/tolak' ||
|
|
206
|
+
clean === 'tolak' ||
|
|
207
|
+
clean.startsWith('/reject@') ||
|
|
208
|
+
clean === '/session' ||
|
|
209
|
+
clean === 'session' ||
|
|
210
|
+
clean === '/sesi' ||
|
|
211
|
+
clean === 'sesi' ||
|
|
212
|
+
clean.startsWith('/session@') ||
|
|
213
|
+
clean.startsWith('/sesi@')
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
bot.command('accept', (ctx) => {
|
|
218
|
+
const chatId = String(ctx.chat?.id || ctx.from?.id || '')
|
|
219
|
+
wsHub.broadcast('tg:command-accept', { chatId })
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
bot.command('always', (ctx) => {
|
|
223
|
+
const chatId = String(ctx.chat?.id || ctx.from?.id || '')
|
|
224
|
+
wsHub.broadcast('tg:command-always', { chatId })
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
bot.command('reject', (ctx) => {
|
|
228
|
+
const chatId = String(ctx.chat?.id || ctx.from?.id || '')
|
|
229
|
+
wsHub.broadcast('tg:command-reject', { chatId })
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
bot.command(['session', 'sesi'], (ctx) => {
|
|
233
|
+
const chatId = String(ctx.chat?.id || ctx.from?.id || '')
|
|
234
|
+
wsHub.broadcast('tg:command-session', { chatId })
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
bot.command(['auto', 'automode'], async (ctx) => {
|
|
238
|
+
const arg = (ctx.message?.text || '')
|
|
239
|
+
.replace(/^\/(auto|automode)(@\w+)?/i, '')
|
|
240
|
+
.trim()
|
|
241
|
+
.toLowerCase()
|
|
242
|
+
try {
|
|
243
|
+
const session1 = dbStore.sessions.getById('1') || { id: '1' }
|
|
244
|
+
if (arg === 'on' || arg === '1' || arg === 'enable') {
|
|
245
|
+
session1.is_auto_mode = 1
|
|
246
|
+
session1.updated_at = Date.now()
|
|
247
|
+
dbStore.sessions.insert(session1)
|
|
248
|
+
wsHub.broadcast('session-auto-mode-updated', { sessionId: '1', isAutoMode: true })
|
|
249
|
+
await ctx.reply(
|
|
250
|
+
'[INFO]: Auto Mode untuk Sesi Utama telah DIAKTIFKAN (ON). Semua persetujuan tool akan disetujui otomatis.'
|
|
251
|
+
)
|
|
252
|
+
} else if (arg === 'off' || arg === '0' || arg === 'disable') {
|
|
253
|
+
session1.is_auto_mode = 0
|
|
254
|
+
session1.updated_at = Date.now()
|
|
255
|
+
dbStore.sessions.insert(session1)
|
|
256
|
+
wsHub.broadcast('session-auto-mode-updated', { sessionId: '1', isAutoMode: false })
|
|
257
|
+
await ctx.reply(
|
|
258
|
+
'[INFO]: Auto Mode untuk Sesi Utama telah DINONAKTIFKAN (OFF). Persetujuan tool aktif kembali.'
|
|
259
|
+
)
|
|
260
|
+
} else {
|
|
261
|
+
const current = Boolean(session1.is_auto_mode)
|
|
262
|
+
await ctx.reply(
|
|
263
|
+
`[INFO]: Status Auto Mode Sesi Utama saat ini: ${current ? 'AKTIF (ON)' : 'NONAKTIF (OFF)'}.\nKetik \`/auto on\` untuk mengaktifkan atau \`/auto off\` untuk mematikan.`,
|
|
264
|
+
{ parse_mode: 'Markdown' }
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
} catch (err) {
|
|
268
|
+
await ctx.reply(`[ERROR]: Gagal memperbarui Auto Mode: ${err.message}`)
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
bot.on('text', async (ctx) => {
|
|
273
|
+
const senderId = String(ctx.from?.id || '')
|
|
274
|
+
const senderName = ctx.from?.first_name
|
|
275
|
+
? `${ctx.from.first_name} ${ctx.from.last_name || ''}`.trim()
|
|
276
|
+
: ctx.from?.username || senderId
|
|
277
|
+
const chatId = String(ctx.chat?.id || senderId)
|
|
278
|
+
const text = ctx.message?.text || ''
|
|
279
|
+
|
|
280
|
+
const senderUsername = (ctx.from?.username || '').toLowerCase()
|
|
281
|
+
const config = getTelegramConfig()
|
|
282
|
+
const adminList = (config.tgAdminIds || '')
|
|
283
|
+
.split(',')
|
|
284
|
+
.map((item) => item.trim().toLowerCase().replace(/^@/, ''))
|
|
285
|
+
.filter(Boolean)
|
|
286
|
+
|
|
287
|
+
const isAdmin =
|
|
288
|
+
adminList.includes(senderId.toLowerCase()) ||
|
|
289
|
+
(senderUsername && adminList.includes(senderUsername))
|
|
290
|
+
|
|
291
|
+
if (!isAdmin) {
|
|
292
|
+
console.log(`[Telegram] Access denied for user ${senderId} (@${senderUsername})`)
|
|
293
|
+
await ctx.reply('Maaf, kamu belum punya akses ke MARK.')
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
adminChatIdsSet.add(chatId)
|
|
298
|
+
if (senderUsername) usernameToChatIdMap.set(senderUsername, chatId)
|
|
299
|
+
saveChatIdsToFile()
|
|
300
|
+
|
|
301
|
+
// Jika teks adalah perintah approval (/accept, /always, /reject), jangan picu loop planning agent baru
|
|
302
|
+
if (isApprovalCommand(text)) {
|
|
303
|
+
const clean = text.trim().toLowerCase()
|
|
304
|
+
if (
|
|
305
|
+
clean === '/accept' ||
|
|
306
|
+
clean === 'accept' ||
|
|
307
|
+
clean === '/izinkan' ||
|
|
308
|
+
clean === 'izinkan' ||
|
|
309
|
+
clean.startsWith('/accept@')
|
|
310
|
+
) {
|
|
311
|
+
wsHub.broadcast('tg:command-accept', { chatId })
|
|
312
|
+
} else if (
|
|
313
|
+
clean === '/always' ||
|
|
314
|
+
clean === 'always' ||
|
|
315
|
+
clean === '/selamanya' ||
|
|
316
|
+
clean === 'selamanya' ||
|
|
317
|
+
clean.startsWith('/always@')
|
|
318
|
+
) {
|
|
319
|
+
wsHub.broadcast('tg:command-always', { chatId })
|
|
320
|
+
} else if (
|
|
321
|
+
clean === '/reject' ||
|
|
322
|
+
clean === 'reject' ||
|
|
323
|
+
clean === '/tolak' ||
|
|
324
|
+
clean === 'tolak' ||
|
|
325
|
+
clean.startsWith('/reject@')
|
|
326
|
+
) {
|
|
327
|
+
wsHub.broadcast('tg:command-reject', { chatId })
|
|
328
|
+
}
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const msgId = `${chatId}-${ctx.message.message_id}`
|
|
333
|
+
|
|
334
|
+
const uiMsgPayload = {
|
|
335
|
+
id: msgId,
|
|
336
|
+
chatId: chatId,
|
|
337
|
+
sender: senderName,
|
|
338
|
+
text: text,
|
|
339
|
+
isGroup: ctx.chat?.type !== 'private',
|
|
340
|
+
chatTitle: ctx.chat?.title || senderName,
|
|
341
|
+
time: new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }),
|
|
342
|
+
type: 'incoming'
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
uiMessageHistory.push(uiMsgPayload)
|
|
346
|
+
if (uiMessageHistory.length > MAX_UI_HISTORY) uiMessageHistory.shift()
|
|
347
|
+
|
|
348
|
+
wsHub.broadcast('tg:message', uiMsgPayload)
|
|
349
|
+
wsHub.broadcast('tg:thinking', { sender: senderName, chatId })
|
|
350
|
+
|
|
351
|
+
let loadingMsgId = null
|
|
352
|
+
try {
|
|
353
|
+
const loadingMsg = await ctx.reply('[LOADING]: Sedang diproses...', {
|
|
354
|
+
disable_notification: true
|
|
355
|
+
})
|
|
356
|
+
loadingMsgId = loadingMsg.message_id
|
|
357
|
+
} catch (_) {}
|
|
358
|
+
|
|
359
|
+
pendingRequestsMap.set(msgId, { ctx, chatId, text, loadingMsgId })
|
|
360
|
+
setTimeout(() => pendingRequestsMap.delete(msgId), 300000)
|
|
361
|
+
|
|
362
|
+
const recentHistory = uiMessageHistory
|
|
363
|
+
.filter((m) => m.chatId === chatId)
|
|
364
|
+
.slice(-10)
|
|
365
|
+
.map((m) => ({
|
|
366
|
+
role: m.type === 'incoming' ? 'user' : 'assistant',
|
|
367
|
+
content: m.type === 'incoming' ? m.text : m.reply
|
|
368
|
+
}))
|
|
369
|
+
|
|
370
|
+
wsHub.broadcast('tg:request-agent-execution', {
|
|
371
|
+
text: `[Telegram from ${chatId} - ${senderName}]:\n${text}`,
|
|
372
|
+
isAdmin: true,
|
|
373
|
+
senderName,
|
|
374
|
+
msgId,
|
|
375
|
+
chatId,
|
|
376
|
+
isGroup: ctx.chat?.type !== 'private',
|
|
377
|
+
chatSession: recentHistory
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
bot.on(['document', 'photo'], async (ctx) => {
|
|
382
|
+
const senderId = String(ctx.from?.id || '')
|
|
383
|
+
const senderName = ctx.from?.first_name
|
|
384
|
+
? `${ctx.from.first_name} ${ctx.from.last_name || ''}`.trim()
|
|
385
|
+
: ctx.from?.username || senderId
|
|
386
|
+
const chatId = String(ctx.chat?.id || senderId)
|
|
387
|
+
|
|
388
|
+
const senderUsername = (ctx.from?.username || '').toLowerCase()
|
|
389
|
+
const config = getTelegramConfig()
|
|
390
|
+
const adminList = (config.tgAdminIds || '')
|
|
391
|
+
.split(',')
|
|
392
|
+
.map((item) => item.trim().toLowerCase().replace(/^@/, ''))
|
|
393
|
+
.filter(Boolean)
|
|
394
|
+
const isAdmin =
|
|
395
|
+
adminList.includes(senderId.toLowerCase()) ||
|
|
396
|
+
(senderUsername && adminList.includes(senderUsername))
|
|
397
|
+
|
|
398
|
+
if (!isAdmin) {
|
|
399
|
+
await ctx.reply('Maaf, kamu belum punya akses ke MARK.')
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
let fileId = ''
|
|
405
|
+
let originalName = ''
|
|
406
|
+
|
|
407
|
+
if (ctx.message.document) {
|
|
408
|
+
fileId = ctx.message.document.file_id
|
|
409
|
+
originalName = ctx.message.document.file_name || `document_${Date.now()}`
|
|
410
|
+
} else if (ctx.message.photo) {
|
|
411
|
+
const photo = ctx.message.photo[ctx.message.photo.length - 1]
|
|
412
|
+
fileId = photo.file_id
|
|
413
|
+
originalName = `photo_${Date.now()}.jpg`
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const statusMsg = await ctx.reply(`[INFO]: Sedang mengunduh file ${originalName}...`)
|
|
417
|
+
|
|
418
|
+
const fileUrl = await ctx.telegram.getFileLink(fileId)
|
|
419
|
+
const saveDir = path.join(os.homedir(), 'Documents', 'Mark Workspace', 'Telegram')
|
|
420
|
+
if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true })
|
|
421
|
+
|
|
422
|
+
const savePath = path.join(saveDir, originalName)
|
|
423
|
+
|
|
424
|
+
const response = await fetch(fileUrl)
|
|
425
|
+
const buffer = await response.arrayBuffer()
|
|
426
|
+
fs.writeFileSync(savePath, Buffer.from(buffer))
|
|
427
|
+
|
|
428
|
+
await ctx.telegram.editMessageText(
|
|
429
|
+
chatId,
|
|
430
|
+
statusMsg.message_id,
|
|
431
|
+
undefined,
|
|
432
|
+
`[INFO]: Berhasil mengunduh: ${originalName}\n[LOADING]: Sedang diproses...`
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
const caption = ctx.message.caption || ''
|
|
436
|
+
const text = `[FILE TERLAMPIR]: "${savePath}"\n${caption ? `Caption dari user: ${caption}` : 'Silakan baca/analisa file gambar atau dokumen ini jika perlu.'}`
|
|
437
|
+
|
|
438
|
+
const msgId = `${chatId}-${ctx.message.message_id}`
|
|
439
|
+
const uiMsgPayload = {
|
|
440
|
+
id: msgId,
|
|
441
|
+
chatId: chatId,
|
|
442
|
+
sender: senderName,
|
|
443
|
+
text: `[FILE]: Mengirim file: ${originalName}\n${caption}`,
|
|
444
|
+
isGroup: ctx.chat?.type !== 'private',
|
|
445
|
+
chatTitle: ctx.chat?.title || senderName,
|
|
446
|
+
time: new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }),
|
|
447
|
+
type: 'incoming'
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
uiMessageHistory.push(uiMsgPayload)
|
|
451
|
+
if (uiMessageHistory.length > MAX_UI_HISTORY) uiMessageHistory.shift()
|
|
452
|
+
|
|
453
|
+
wsHub.broadcast('tg:message', uiMsgPayload)
|
|
454
|
+
wsHub.broadcast('tg:thinking', { sender: senderName, chatId })
|
|
455
|
+
|
|
456
|
+
pendingRequestsMap.set(msgId, { ctx, chatId, text, loadingMsgId: statusMsg.message_id })
|
|
457
|
+
setTimeout(() => pendingRequestsMap.delete(msgId), 300000)
|
|
458
|
+
|
|
459
|
+
const recentHistory = uiMessageHistory
|
|
460
|
+
.filter((m) => m.chatId === chatId)
|
|
461
|
+
.slice(-10)
|
|
462
|
+
.map((m) => ({
|
|
463
|
+
role: m.type === 'incoming' ? 'user' : 'assistant',
|
|
464
|
+
content: m.type === 'incoming' ? m.text : m.reply
|
|
465
|
+
}))
|
|
466
|
+
|
|
467
|
+
wsHub.broadcast('tg:request-agent-execution', {
|
|
468
|
+
text: `[Telegram from ${chatId} - ${senderName}]:\n${text}`,
|
|
469
|
+
isAdmin: true,
|
|
470
|
+
senderName,
|
|
471
|
+
msgId,
|
|
472
|
+
chatId,
|
|
473
|
+
isGroup: ctx.chat?.type !== 'private',
|
|
474
|
+
chatSession: recentHistory
|
|
475
|
+
})
|
|
476
|
+
} catch (e) {
|
|
477
|
+
console.error('Failed to download file from Telegram:', e)
|
|
478
|
+
ctx.reply(`Gagal mengunduh file: ${e.message}`)
|
|
479
|
+
}
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
bot.catch((err, ctx) => {
|
|
483
|
+
console.error(`[Telegram] Error for ${ctx.updateType}:`, err)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
bot.launch({ allowedUpdates: ['message'] }).catch((err) => {
|
|
487
|
+
console.error('[Telegram] Polling error:', err)
|
|
488
|
+
bot = null
|
|
489
|
+
updateStatus('disconnected')
|
|
490
|
+
})
|
|
491
|
+
updateStatus('connected')
|
|
492
|
+
console.log('[Telegram] Bot berhasil dijalankan dan mendengarkan pesan.')
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.error('[Telegram] Gagal menjalankan bot:', err)
|
|
495
|
+
bot = null
|
|
496
|
+
updateStatus('disconnected')
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export const resolveTelegramChatIds = (chatId = null) => {
|
|
501
|
+
const cleanId = String(chatId || '').trim()
|
|
502
|
+
if (cleanId && /^-?\d+$/.test(cleanId)) {
|
|
503
|
+
return [cleanId]
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const cleanUser = cleanId.replace(/^@/, '').toLowerCase()
|
|
507
|
+
if (cleanUser && usernameToChatIdMap.has(cleanUser)) {
|
|
508
|
+
return [usernameToChatIdMap.get(cleanUser)]
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const config = getTelegramConfig()
|
|
512
|
+
const adminInputs = (config.tgAdminIds || '')
|
|
513
|
+
.split(',')
|
|
514
|
+
.map((id) => id.trim().toLowerCase().replace(/^@/, ''))
|
|
515
|
+
.filter(Boolean)
|
|
516
|
+
|
|
517
|
+
const targetChatIds = new Set(adminChatIdsSet)
|
|
518
|
+
for (const input of adminInputs) {
|
|
519
|
+
if (/^-?\d+$/.test(input)) {
|
|
520
|
+
targetChatIds.add(input)
|
|
521
|
+
} else if (usernameToChatIdMap.has(input)) {
|
|
522
|
+
targetChatIds.add(usernameToChatIdMap.get(input))
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return Array.from(targetChatIds)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export const sendTelegramMessage = async (chatId, text) => {
|
|
530
|
+
if (!bot || currentStatus !== 'connected') {
|
|
531
|
+
return { success: false, error: 'Telegram Bot belum terhubung.' }
|
|
532
|
+
}
|
|
533
|
+
const targets = resolveTelegramChatIds(chatId)
|
|
534
|
+
if (targets.length === 0) {
|
|
535
|
+
return { success: false, error: 'Tidak ada ID chat tujuan atau admin Telegram yang terdaftar.' }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
let lastError = null
|
|
539
|
+
let successCount = 0
|
|
540
|
+
const htmlText = formatMarkdownToTelegramHTML(text)
|
|
541
|
+
|
|
542
|
+
for (const target of targets) {
|
|
543
|
+
try {
|
|
544
|
+
await bot.telegram.sendMessage(target, htmlText, { parse_mode: 'HTML' })
|
|
545
|
+
successCount++
|
|
546
|
+
} catch {
|
|
547
|
+
try {
|
|
548
|
+
await bot.telegram.sendMessage(target, text)
|
|
549
|
+
successCount++
|
|
550
|
+
} catch (fallbackErr) {
|
|
551
|
+
lastError = fallbackErr.message
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return successCount > 0
|
|
557
|
+
? { success: true, targets }
|
|
558
|
+
: { success: false, error: lastError || 'Gagal mengirim pesan Telegram.' }
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export const sendTelegramFile = async (chatId, filePathOrData, caption = '', options = {}) => {
|
|
562
|
+
if (!bot || currentStatus !== 'connected') {
|
|
563
|
+
return { success: false, error: 'Telegram Bot belum terhubung.' }
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const targets = resolveTelegramChatIds(chatId)
|
|
567
|
+
if (targets.length === 0) {
|
|
568
|
+
return { success: false, error: 'Tidak ada target Telegram yang terdaftar atau valid.' }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let fileBuffer = null
|
|
572
|
+
let fileName = 'file'
|
|
573
|
+
let isImage = false
|
|
574
|
+
|
|
575
|
+
const IMAGE_EXTS = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp']
|
|
576
|
+
|
|
577
|
+
if (
|
|
578
|
+
typeof filePathOrData === 'string' &&
|
|
579
|
+
(filePathOrData.startsWith('data:') ||
|
|
580
|
+
(filePathOrData.length > 500 && !fs.existsSync(filePathOrData)))
|
|
581
|
+
) {
|
|
582
|
+
const cleanBase64 = filePathOrData.replace(/^data:[^;]+;base64,/, '')
|
|
583
|
+
fileBuffer = Buffer.from(cleanBase64, 'base64')
|
|
584
|
+
fileName = options.filename || `image-${Date.now()}.png`
|
|
585
|
+
isImage = true
|
|
586
|
+
} else if (typeof filePathOrData === 'string' && fs.existsSync(filePathOrData)) {
|
|
587
|
+
const rawPath = filePathOrData.trim()
|
|
588
|
+
fileName = path.basename(rawPath)
|
|
589
|
+
const ext = path.extname(rawPath).toLowerCase()
|
|
590
|
+
isImage = IMAGE_EXTS.includes(ext)
|
|
591
|
+
|
|
592
|
+
const rawBuf = fs.readFileSync(rawPath)
|
|
593
|
+
const previewStr = rawBuf.slice(0, 100).toString('utf8')
|
|
594
|
+
if (previewStr.startsWith('data:image/') || previewStr.startsWith('data:application/')) {
|
|
595
|
+
const cleanBase64 = rawBuf
|
|
596
|
+
.toString('utf8')
|
|
597
|
+
.replace(/^data:[^;]+;base64,/, '')
|
|
598
|
+
.trim()
|
|
599
|
+
fileBuffer = Buffer.from(cleanBase64, 'base64')
|
|
600
|
+
} else {
|
|
601
|
+
fileBuffer = rawBuf
|
|
602
|
+
}
|
|
603
|
+
} else if (Buffer.isBuffer(filePathOrData)) {
|
|
604
|
+
fileBuffer = filePathOrData
|
|
605
|
+
fileName = options.filename || `file-${Date.now()}.png`
|
|
606
|
+
isImage =
|
|
607
|
+
options.type === 'photo' || IMAGE_EXTS.some((ext) => fileName.toLowerCase().endsWith(ext))
|
|
608
|
+
} else {
|
|
609
|
+
return { success: false, error: `Berkas atau data tidak ditemukan: ${filePathOrData}` }
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (options.type === 'photo' || options.type === 'image') {
|
|
613
|
+
isImage = true
|
|
614
|
+
} else if (options.type === 'file' || options.type === 'document') {
|
|
615
|
+
isImage = false
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
let successCount = 0
|
|
619
|
+
let lastError = null
|
|
620
|
+
|
|
621
|
+
for (const target of targets) {
|
|
622
|
+
try {
|
|
623
|
+
if (isImage) {
|
|
624
|
+
try {
|
|
625
|
+
await bot.telegram.sendPhoto(
|
|
626
|
+
target,
|
|
627
|
+
{ source: fileBuffer, filename: fileName },
|
|
628
|
+
caption ? { caption } : undefined
|
|
629
|
+
)
|
|
630
|
+
successCount++
|
|
631
|
+
continue
|
|
632
|
+
} catch (photoErr) {
|
|
633
|
+
console.warn(
|
|
634
|
+
`[Telegram] sendPhoto gagal ke ${target}, fallback ke sendDocument:`,
|
|
635
|
+
photoErr.message
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
await bot.telegram.sendDocument(
|
|
641
|
+
target,
|
|
642
|
+
{ source: fileBuffer, filename: fileName },
|
|
643
|
+
caption ? { caption } : undefined
|
|
644
|
+
)
|
|
645
|
+
successCount++
|
|
646
|
+
} catch (err) {
|
|
647
|
+
console.error(`[Telegram] Gagal kirim berkas ke ${target}:`, err.message)
|
|
648
|
+
lastError = err.message
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return successCount > 0
|
|
653
|
+
? { success: true, targets }
|
|
654
|
+
: { success: false, error: lastError || 'Gagal mengirim berkas ke Telegram.' }
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export const sendTelegramScreenshot = async (chatId = null) => {
|
|
658
|
+
if (!bot || currentStatus !== 'connected') {
|
|
659
|
+
return { success: false, error: 'Telegram Bot belum terhubung.' }
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const { captureDesktopScreenshotsBase64 } = await import('../../server/tools/screen-service.js')
|
|
663
|
+
const base64List = await captureDesktopScreenshotsBase64()
|
|
664
|
+
|
|
665
|
+
const targets = resolveTelegramChatIds(chatId)
|
|
666
|
+
if (targets.length === 0) {
|
|
667
|
+
return { success: false, error: 'Tidak ada target admin Telegram yang terdaftar.' }
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
for (const target of targets) {
|
|
671
|
+
try {
|
|
672
|
+
if (base64List.length === 1) {
|
|
673
|
+
const cleanBase64 = base64List[0].replace(/^data:image\/\w+;base64,/, '')
|
|
674
|
+
const buffer = Buffer.from(cleanBase64, 'base64')
|
|
675
|
+
await bot.telegram.sendPhoto(
|
|
676
|
+
target,
|
|
677
|
+
{ source: buffer },
|
|
678
|
+
{ caption: 'Screenshot Layar PC Mark' }
|
|
679
|
+
)
|
|
680
|
+
} else {
|
|
681
|
+
for (let i = 0; i < base64List.length; i++) {
|
|
682
|
+
const cleanBase64 = base64List[i].replace(/^data:image\/\w+;base64,/, '')
|
|
683
|
+
const buffer = Buffer.from(cleanBase64, 'base64')
|
|
684
|
+
await bot.telegram.sendPhoto(
|
|
685
|
+
target,
|
|
686
|
+
{ source: buffer },
|
|
687
|
+
{ caption: `Screenshot Layar PC Mark - Monitor ${i + 1} dari ${base64List.length}` }
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch (err) {
|
|
692
|
+
console.error(`[Telegram] Gagal kirim screenshot ke ${target}:`, err.message)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return { success: true }
|
|
696
|
+
} catch (e) {
|
|
697
|
+
console.error('[Telegram] Gagal proses screenshot:', e)
|
|
698
|
+
return { success: false, error: e.message }
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export const sendTelegramToAdmins = async (text) => {
|
|
703
|
+
if (!bot || currentStatus !== 'connected') {
|
|
704
|
+
pendingBroadcastQueue.push(text)
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
const config = getTelegramConfig()
|
|
708
|
+
const adminInputs = (config.tgAdminIds || '')
|
|
709
|
+
.split(',')
|
|
710
|
+
.map((id) => id.trim().toLowerCase().replace(/^@/, ''))
|
|
711
|
+
.filter(Boolean)
|
|
712
|
+
|
|
713
|
+
const targetChatIds = new Set(adminChatIdsSet)
|
|
714
|
+
for (const input of adminInputs) {
|
|
715
|
+
if (/^\d+$/.test(input)) {
|
|
716
|
+
targetChatIds.add(input)
|
|
717
|
+
} else if (usernameToChatIdMap.has(input)) {
|
|
718
|
+
targetChatIds.add(usernameToChatIdMap.get(input))
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (targetChatIds.size === 0) return
|
|
723
|
+
|
|
724
|
+
for (const chatId of targetChatIds) {
|
|
725
|
+
try {
|
|
726
|
+
const htmlText = formatMarkdownToTelegramHTML(text)
|
|
727
|
+
await bot.telegram.sendMessage(chatId, htmlText, { parse_mode: 'HTML' })
|
|
728
|
+
} catch (_) {
|
|
729
|
+
try {
|
|
730
|
+
await bot.telegram.sendMessage(chatId, text)
|
|
731
|
+
} catch (e) {
|
|
732
|
+
console.error(`[Telegram] Gagal broadcast ke ${chatId}:`, e.message)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const flushPendingBroadcasts = async () => {
|
|
739
|
+
if (currentStatus !== 'connected' || pendingBroadcastQueue.length === 0) return
|
|
740
|
+
const queue = [...pendingBroadcastQueue]
|
|
741
|
+
pendingBroadcastQueue.length = 0
|
|
742
|
+
for (const text of queue) {
|
|
743
|
+
await sendTelegramToAdmins(text)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export const finishAgentExecution = async ({ chatId, result, msgId }) => {
|
|
748
|
+
const reqObj = pendingRequestsMap.get(msgId)
|
|
749
|
+
if (msgId && pendingRequestsMap.has(msgId)) {
|
|
750
|
+
pendingRequestsMap.delete(msgId)
|
|
751
|
+
}
|
|
752
|
+
const replyText = result?.answer || 'Selesai diproses.'
|
|
753
|
+
|
|
754
|
+
const uiReplyPayload = {
|
|
755
|
+
id: Date.now(),
|
|
756
|
+
chatId: chatId,
|
|
757
|
+
sender: 'Mark',
|
|
758
|
+
text: reqObj?.text || '',
|
|
759
|
+
reply: replyText,
|
|
760
|
+
toolsUsed: result?.toolsUsed || [],
|
|
761
|
+
time: new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }),
|
|
762
|
+
type: 'outgoing'
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
uiMessageHistory.push(uiReplyPayload)
|
|
766
|
+
if (uiMessageHistory.length > MAX_UI_HISTORY) uiMessageHistory.shift()
|
|
767
|
+
|
|
768
|
+
wsHub.broadcast('tg:reply-sent', uiReplyPayload)
|
|
769
|
+
|
|
770
|
+
if (bot && chatId) {
|
|
771
|
+
if (reqObj?.loadingMsgId) {
|
|
772
|
+
try {
|
|
773
|
+
await bot.telegram.deleteMessage(chatId, reqObj.loadingMsgId)
|
|
774
|
+
} catch (_) {}
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
const htmlText = formatMarkdownToTelegramHTML(replyText)
|
|
778
|
+
await bot.telegram.sendMessage(chatId, htmlText, { parse_mode: 'HTML' })
|
|
779
|
+
} catch (_) {
|
|
780
|
+
try {
|
|
781
|
+
await bot.telegram.sendMessage(chatId, replyText, { parse_mode: 'Markdown' })
|
|
782
|
+
} catch (fallbackErr) {
|
|
783
|
+
await bot.telegram.sendMessage(chatId, replyText).catch(() => {})
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
export const triggerTelegramMusicDownload = async ({ chatId, query }) => {
|
|
790
|
+
if (!bot || !chatId || !query) return
|
|
791
|
+
try {
|
|
792
|
+
const searchResult = await yts(query)
|
|
793
|
+
const video = searchResult.videos[0]
|
|
794
|
+
if (!video) {
|
|
795
|
+
await bot.telegram.sendMessage(chatId, `Lagu "${query}" tidak ditemukan di YouTube.`)
|
|
796
|
+
return
|
|
797
|
+
}
|
|
798
|
+
const tempPath = path.join(os.tmpdir(), `tg-audio-${Date.now()}.mp3`)
|
|
799
|
+
const unpackFfmpeg = ffmpeg ? ffmpeg.replace('app.asar', 'app.asar.unpacked') : 'ffmpeg'
|
|
800
|
+
const unpackYtdl = unpackFfmpeg.replace(
|
|
801
|
+
/ffmpeg-static[\\/]ffmpeg\.exe/i,
|
|
802
|
+
'youtube-dl-exec\\bin\\yt-dlp.exe'
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
await new Promise((resolve, reject) => {
|
|
806
|
+
execFile(
|
|
807
|
+
unpackYtdl,
|
|
808
|
+
[
|
|
809
|
+
video.url,
|
|
810
|
+
'--extract-audio',
|
|
811
|
+
'--audio-format',
|
|
812
|
+
'mp3',
|
|
813
|
+
'--ffmpeg-location',
|
|
814
|
+
unpackFfmpeg,
|
|
815
|
+
'--output',
|
|
816
|
+
tempPath
|
|
817
|
+
],
|
|
818
|
+
(err) => {
|
|
819
|
+
if (err) reject(err)
|
|
820
|
+
else resolve()
|
|
821
|
+
}
|
|
822
|
+
)
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
await bot.telegram.sendAudio(
|
|
826
|
+
chatId,
|
|
827
|
+
{ source: tempPath },
|
|
828
|
+
{ title: video.title, performer: video.author?.name || 'YouTube' }
|
|
829
|
+
)
|
|
830
|
+
fs.unlink(tempPath, () => {})
|
|
831
|
+
} catch (err) {
|
|
832
|
+
console.error('[Telegram] Error music download:', err)
|
|
833
|
+
await bot.telegram.sendMessage(chatId, `Gagal download lagu: ${err.message}`)
|
|
834
|
+
}
|
|
835
|
+
}
|