// app.js // Node.js Slack Agent: Bolt + OpenAI + Google Drive/Gmail/Calendar + Notion + Jira + GitHub // احتياجات: node 18+, install packages listed أدناه import express from "express"; import { App, LogLevel } from "@slack/bolt"; import OpenAI from "op

// app.js
// Node.js Slack Agent: Bolt + OpenAI + Google Drive/Gmail/Calendar + Notion + Jira + GitHub
// احتياجات: node 18+, install packages listed أدناه

import express from “express”;
import { App, LogLevel } from “@slack/bolt”;
import OpenAI from “openai”;
import { google } from “googleapis”;
import { Client as NotionClient } from “@notionhq/client”;
import JiraClient from “jira-connector”;
import { Octokit } from “octokit”;
import dotenv from “dotenv”;

dotenv.config();

/* -------------------------
Config & clients
-------------------------*/
const {
SLACK_BOT_TOKEN,
SLACK_SIGNING_SECRET,
PORT = 3000,
OPENAI_API_KEY,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
GOOGLE_REFRESH_TOKEN, // for service account-like access (or use OAuth flow)
NOTION_TOKEN,
NOTION_DATABASE_ID,
JIRA_HOST,
JIRA_USERNAME,
JIRA_API_TOKEN,
GITHUB_TOKEN,
DEFAULT_PROJECT_KEY // eg: “PROJ” for Jira
} = process.env;

if (!SLACK_BOT_TOKEN || !SLACK_SIGNING_SECRET || !OPENAI_API_KEY) {
console.error(“Missing required env variables (SLACK_* or OPENAI_API_KEY).”);
process.exit(1);
}

const openai = new OpenAI({ apiKey: OPENAI_API_KEY });

/* Slack App (Bolt) */
const app = new App({
token: SLACK_BOT_TOKEN,
signingSecret: SLACK_SIGNING_SECRET,
logLevel: LogLevel.INFO,
// use built-in express receiver (app.start will run its own server)
});

/* Google OAuth2 client (use refresh token for server-to-server) */
const oAuth2Client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET);
if (GOOGLE_REFRESH_TOKEN) oAuth2Client.setCredentials({ refresh_token: GOOGLE_REFRESH_TOKEN });
const drive = google.drive({ version: “v3”, auth: oAuth2Client });
const calendar = google.calendar({ version: “v3”, auth: oAuth2Client });
const gmail = google.gmail({ version: “v1”, auth: oAuth2Client });

/* Notion */
const notion = new NotionClient({ auth: NOTION_TOKEN });

/* Jira */
const jira = new JiraClient({
host: JIRA_HOST,
basic_auth: {
username: JIRA_USERNAME,
password: JIRA_API_TOKEN,
},
});

/* GitHub */
const octokit = new Octokit({ auth: GITHUB_TOKEN });

/* -------------------------
Helper integration functions
-------------------------*/

// Create a Google Drive file (simple text file)
export async function createDriveFile({ name = “Note.txt”, content = “Hello from Slack Agent” } = {}) {
const res = await drive.files.create({
requestBody: { name, mimeType: “text/plain” },
media: { mimeType: “text/plain”, body: content },
fields: “id, webViewLink”,
});
return res.data; // { id, webViewLink }
}

// Send email via Gmail (basic)
export async function sendGmail({ to, subject, body }) {
const raw = [
To: ${to},
“Content-Type: text/plain; charset=utf-8”,
Subject: ${subject},
“”,
body,
].join(“\n”);

const encoded = Buffer.from(raw).toString(“base64”).replace(/+/g, “-”).replace(///g, “_”).replace(/=+$/, “”);
const res = await gmail.users.messages.send({
userId: “me”,
requestBody: { raw: encoded },
});
return res.data;
}

// Create Google Calendar event
export async function createCalendarEvent({ summary, startDateTime, endDateTime, attendees = }) {
const res = await calendar.events.insert({
calendarId: “primary”,
requestBody: {
summary,
start: { dateTime: startDateTime },
end: { dateTime: endDateTime },
attendees: attendees.map((e) => ({ email: e })),
},
});
return res.data;
}

// Create Notion page (simple)
export async function createNotionPage({ title, content }) {
if (!NOTION_DATABASE_ID) throw new Error(“NOTION_DATABASE_ID missing”);
const res = await notion.pages.create({
parent: { database_id: NOTION_DATABASE_ID },
properties: {
Name: {
title: [{ text: { content: title } }],
},
},
children: [
{
object: “block”,
type: “paragraph”,
paragraph: { text: [{ type: “text”, text: { content } }] },
},
],
});
return res;
}

// Create Jira issue
export async function createJiraIssue({ projectKey = DEFAULT_PROJECT_KEY || “PROJ”, summary, description, issuetype = “Task” }) {
const issue = await jira.issue.createIssue({
fields: {
project: { key: projectKey },
summary,
description,
issuetype: { name: issuetype },
},
});
return issue;
}

// Create GitHub Issue
export async function createGithubIssue({ owner, repo, title, body }) {
const res = await octokit.rest.issues.create({
owner,
repo,
title,
body,
});
return res.data;
}

/* -------------------------
OpenAI helpers
-------------------------*/
async function askOpenAI_SystemUser(systemPrompt, userText) {
// Uses Chat Completions v1 style - adjust if your SDK differs
const response = await openai.chat.completions.create({
model: “gpt-4o-mini”, // أو اختار النموذج المتاح عندك
messages: [
{ role: “system”, content: systemPrompt },
{ role: “user”, content: userText },
],
max_tokens: 800,
temperature: 0.2,
});
return response.choices[0].message.content;
}

/* -------------------------
Core behaviors / event handlers
-------------------------*/

// 1) Auto-respond to messages: send message to OpenAI and reply
app.message(async ({ message, say, context }) => {
try {
// ignore bot messages
if (message.subtype === “bot_message” || message.bot_id) return;

const text = message.text || "";

// Quick heuristics: commands in natural language to trigger workflows
// Examples: "create task: Fix login bug", "summarize this", "create doc in drive: meeting notes"
// 1. create task -> create Jira issue + slack confirmation
const createTaskMatch = text.match(/create (task|issue|ticket):\s*(.+)/i);
if (createTaskMatch) {
  const title = createTaskMatch[2].trim().slice(0, 250);
  const issue = await createJiraIssue({ summary: title, description: `Created from Slack message: "${text}"` });
  await say(`✅ تم إنشاء تذكرة في Jira: ${issue.key || issue.id}`);
  return;
}

// 2. create github issue
const createGhMatch = text.match(/create github issue in (\S+\/\S+):\s*(.+)/i);
if (createGhMatch) {
  const [owner, repo] = createGhMatch[1].split("/");
  const title = createGhMatch[2].trim();
  const gh = await createGithubIssue({ owner, repo, title, body: `From Slack message: ${text}` });
  await say(`✅ GitHub issue created: #${gh.number} (${gh.html_url})`);
  return;
}

// 3. summarize request
const wantsSummary = /summarize|tl;dr|خلاصة|لخص/i.test(text);
if (wantsSummary) {
  const summary = await askOpenAI_SystemUser(
    "You are a helpful assistant. Provide a short summary (3-5 lines) in Arabic when the user writes in Arabic, otherwise summarize in user's language.",
    text
  );
  await say(`📝 **خلاصة:**\n${summary}`);
  return;
}

// default: AI reply
const reply = await askOpenAI_SystemUser(
  "You are a helpful Slack assistant. Give concise helpful replies, propose follow-ups, and if requested produce action items.",
  text
);

// Respond in thread if not in DM
const channel = message.channel;
await app.client.chat.postMessage({
  token: context.botToken,
  channel,
  thread_ts: message.ts,
  text: reply,
});

} catch (err) {
console.error(“message handler error:”, err);
}
});

/* 2) Slash commands: /summarize /create-task /create-doc */
app.command(“/summarize”, async ({ command, ack, respond }) => {
await ack();
try {
const text = command.text || “”;
const summary = await askOpenAI_SystemUser(“You are a summarizer. Keep it short and actionable.”, text);
await respond({ response_type: “in_channel”, text: 📝 **Summary:**\n${summary} });
} catch (err) {
console.error(err);
await respond(“خطأ أثناء التلخيص.”);
}
});

app.command(“/create-task”, async ({ command, ack, respond }) => {
await ack();
try {
// usage: /create-task Fix the CI build → creates Jira issue
const title = command.text || “New task from Slack”;
const issue = await createJiraIssue({ summary: title, description: From Slack command by ${command.user_name} });
await respond(✅ تم إنشاء التذكرة: ${issue.key || issue.id});
} catch (err) {
console.error(err);
await respond(“خطأ أثناء إنشاء التذكرة.”);
}
});

app.command(“/create-doc”, async ({ command, ack, respond }) => {
await ack();
try {
const title = command.text || Doc from ${command.user_name};
const file = await createDriveFile({ name: ${title}.txt, content: Created by ${command.user_name}\n\n });
await respond(✅ مستند تم إنشاؤه في Google Drive — ${file.webViewLink || file.id});
} catch (err) {
console.error(err);
await respond(“خطأ أثناء إنشاء المستند.”);
}
});

/* 3) Add interactive shortcut to convert message to task / create note */
app.shortcut(“create_task_from_message”, async ({ shortcut, ack, client, body }) => {
await ack();
try {
const msg = body.message?.text || “”;
// open modal to confirm
await client.views.open({
trigger_id: shortcut.trigger_id,
view: {
type: “modal”,
callback_id: “confirm_task_modal”,
title: { type: “plain_text”, text: “Create Task” },
submit: { type: “plain_text”, text: “Create” },
close: { type: “plain_text”, text: “Cancel” },
blocks: [
{
type: “input”,
block_id: “title_block”,
element: { type: “plain_text_input”, action_id: “title”, initial_value: msg.slice(0, 240) },
label: { type: “plain_text”, text: “Task title” },
},
{
type: “input”,
block_id: “desc_block”,
element: { type: “plain_text_input”, multiline: true, action_id: “desc”, initial_value: msg },
label: { type: “plain_text”, text: “Description” },
},
],
},
});
} catch (err) {
console.error(err);
}
});

app.view(“confirm_task_modal”, async ({ ack, view, body, client }) => {
await ack();
try {
const title = view.state.values.title_block.title.value;
const desc = view.state.values.desc_block.desc.value;
const issue = await createJiraIssue({ summary: title, description: desc });
await client.chat.postMessage({
channel: body.user.id,
text: ✅ تم إنشاء تذكرة: ${issue.key || issue.id},
});
} catch (err) {
console.error(err);
}
});

/* 4) Example: scheduled report (simple) */
async function sendWeeklyReportToChannel(channel = “#general”) {
// compose a short AI-generated report
const prompt = “Create a short weekly status report template and sample content (bullet points) in Arabic for a software team.”;
const report = await askOpenAI_SystemUser(“You are a report generator”, prompt);

// post to channel (use bot token)
await app.client.chat.postMessage({
token: SLACK_BOT_TOKEN,
channel,
text: 📊 تقرير أسبوعي:\n${report},
});
}

/* -------------------------
Start the app
-------------------------*/
(async () => {
await app.start(PORT);
console.log(⚡ Slack AI Agent running on port ${PORT});
// optional: trigger an initial weekly report scheduler (or use cron)
// setInterval(() => sendWeeklyReportToChannel(“#general”), 1000 * 60 * 60 * 24 * 7);
})();

/* -------------------------
Exports for testing / re-use
-------------------------*/
export default app;