Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions telegram-bot/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Telegram Bot Token from @BotFather
TELEGRAM_BOT_TOKEN=your_bot_token_here

# Optional: Set log level (debug, info, warn, error)
LOG_LEVEL=info
36 changes: 36 additions & 0 deletions telegram-bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SoroSave Telegram Bot

A Telegram bot for monitoring SoroSave group events.

## Features

- Subscribe to groups for notifications
- Real-time alerts for new contributions
- Payout distribution notifications
- Round start notifications

#2 Setup

1. Create a bot with [@BotFather](https://t.me/BotFather)
2. Copy the bot token
3. Set `TELEGRAM_BOT_TOKEN` in `.env`
4. Run `npm install && npm run dev

## Commands

| Command | Description |
|--------|-----------|
| `/subscribe <group_id>` | Subscribe to a group |
| `/unsubscribe <group_id>` | Unsubscribe |
| `/status` | View subscriptions |
| `/help` | Show help |

## Notification Types

- 💰 New Contribution
- 🎉 Payout Distributed
- 🔄 Round Started

## License

MIT
1 change: 1 addition & 0 deletions telegram-bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"@sorosave/telegram-bot","version":"0.1.0","description":"Telegram bot for SoroSave group notifications","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","start":"node dist/index.js","dev":"ts-node src/index.ts","dev:watch":"ts-node-dev --respawn src/index.ts"},"dependencies":{"grammy":"^1.21.0","dotenv":"^16.4.0"},"devDependencies":{"@types/node":"^20.0.0","typescript":"^5.5.0","ts-node":"^10.9.0","ts-node-dev":"^2.0.0"},"keywords":["telegram","bot","sorosave","notifications"],"author":"","license":"MIT"}
236 changes: 236 additions & 0 deletions telegram-bot/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { Bot } from "grammy";
import * as dotenv from "dotenv";

// Load environment variables
dotenv.config({ path: ".env" });

// Required environment variables
const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
if (!BOT_TOKEN) {
throw new Error("TELEGRAM_BOT_TOKEN is required. Please set it in .env file.");
}

// Initialize the bot
const bot = new Bot(BOT_TOKEN);

// In-memory storage for subscriptions (in production, use a database)
const subscriptions = new Map<string, Set<string>>(); // groupId -> Set of chatIds
const chatIdToGroups = new Map<string, Set<string>>(); // chatId -> Set of groupIds

// Command: /subscribe <group_id>
bot.command("subscribe", async (ctx) => {
const args = ctx.match;
const chatId = ctx.chat!.id.toString();
const groupId = args?.trim();

if (!groupId) {
await ctx.reply("⚠️ Please provide a group ID.\n\nUsage: /subscribe <group_id>");
return;
}

// Add subscription
if (!subscriptions.has(groupId)) {
subscriptions.set(groupId, new Set());
}
subscriptions.get(groupId)!.add(chatId);

// Track user's subscriptions
if (!chatIdToGroups.has(chatId)) {
chatIdToGroups.set(chatId, new Set());
}
chatIdToGroups.get(chatId)!.add(groupId);

await ctx.reply(
`✅ Successfully subscribed to group: *${groupId}*\n\n` +
`You'll receive notifications for:\n` +
`• New contributions\n` +
`• Payout distributions\n` +
`• New rounds started`,
{ parse_mode: "Markdown" }
);
});

// Command: /unsubscribe
bot.command("unsubscribe", async (ctx) => {
const args = ctx.match;
const chatId = ctx.chat!.id.toString();
const groupId = args?.trim();

if (!groupId) {
await ctx.reply(
"⚠️ Please provide a group ID.\n\nUsage: /unsubscribe <group_id>\n\n" +
`Current subscriptions:\n${Array.from(chatIdToGroups.get(chatId) || []).join("\n") || "None"}`
);
return;
}

// Remove subscription
if (subscriptions.has(groupId)) {
subscriptions.get(groupId)!.delete(chatId);
}
if (chatIdToGroups.has(chatId)) {
chatIdToGroups.get(chatId)!.delete(groupId);
}

await ctx.reply(`✅ Successfully unsubscribed from group: *${groupId}*`, {
parse_mode: "Markdown"
});
});

// Command: /status
bot.command("status", async (ctx) => {
const chatId = ctx.chat!.id.toString();
const subscribedGroups = chatIdToGroups.get(chatId);

if (!subscribedGroups || subscribedGroups.size === 0) {
await ctx.reply("📊 **Your Subscriptions**\n\nYou are not subscribed to any groups yet.\n\nUse /subscribe <group_id> to start receiving notifications.");
return;
}

const groupList = Array.from(subscribedGroups)
.map(
(groupId, index) => `${index + 1}. \`${groupId}\` - Active`
)
.join("\n");

await ctx.reply(
`📊 **Your Subscriptions**\n\nYou are monitoring ${subscribedGroups.size} group(s):\n\n${groupList}`,
{ parse_mode: "Markdown" }
);
});

// Command: /help
bot.command("help", async (ctx) => {
const helpText = `🤖 **SoroSave Telegram Bot Help**

**Commands:**

\`/subscribe <group_id>\` - Subscribe to notifications for a SoroSave group
\`/unsubscribe <group_id>\` - Unsubscribe from a group
\`/status\` - View your current subscriptions
\`/help\` - Show this help message

**Notifications you'll receive:**
• 💰 New contribution events
• 🎉 Payout distributed events
• 🔄 Round started events

**Example:**
\`/subscribe CA7X...\`

Need help? Visit: https://github.com/sorosave-protocol/sdk/issues/60`;

await ctx.reply(helpText, { parse_mode: "Markdown" });
});

// Middleware: Handle unknown commands
bot.on("msg:text", async (ctx) => {
const text = ctx.message?.text || "";
if (text.startsWith("/")) {
await ctx.reply(
"❓ Unknown command. Use /help to see available commands."
);
}
});

// Functions to send notifications (can be called from other modules)
export class NotificationService {
/**
* Send notification about a new contribution
*/
static async notifyNewContribution(
groupId: string,
contributor: string,
amount: string
) {
const subscribers = subscriptions.get(groupId);
if (!subscribers || subscribers.size === 0) return;

const message = `💰 **New Contribution**

**Group:** \`${groupId}\`
**Contributor:** \`${shortenAddress(contributor)}\`
**Amount:** ${amount}

A new contribution has been made to the group pot!`;

for (const chatId of subscribers) {
try {
await bot.api.sendMessage(chatId, message, { parse_mode: "Markdown" });
} catch (error) {
console.error(`Failed to send notification to ${chatId}:`, error);
}
}
}

/**
* Send notification about payout distribution
*/
static async notifyPayoutDistributed(
groupId: string,
amount: string,
round: number
) {
const subscribers = subscriptions.get(groupId);
if (!subscribers || subscribers.size === 0) return;

const message = `🎉 **Payout Distributed**

**Group:** \`${groupId}\`
**Round:** #${round}
**Amount:** ${amount}

The round has ended and payouts have been distributed to members!`;

for (const chatId of subscribers) {
try {
await bot.api.sendMessage(chatId, message, { parse_mode: "Markdown" });
} catch (error) {
console.error(`Failed to send notification to ${chatId}:`, error);
}
}
}

/**
* Send notification about a new round starting
*/
static async notifyRoundStarted(groupId: string, round: number, startDate: string) {
const subscribers = subscriptions.get(groupId);
if (!subscribers || subscribers.size === 0) return;

const message = `🔄 **New Round Started**

**Group:** \`${groupId}\`
**Round:** #${round}
**Start Date:** ${startDate}

A new savings round has started! Members can now make contributions.`;

for (const chatId of subscribers) {
try {
await bot.api.sendMessage(chatId, message, { parse_mode: "Markdown" });
} catch (error) {
console.error(`Failed to send notification to ${chatId}:`, error);
}
}
}
}

// Helper function to shorten addresses
function shortenAddress(address: string): string {
if (address.length <= 10) return address;
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}

// Start the bot
console.log("🤖 SoroSave Telegram Bot is starting...");
bot.api
.getMe()
.then((botInfo) => {
console.log(`✅ Bot started successfully as @${botInfo.username}`);
})
.catch((error) => {
console.error("❌ Failed to start bot:", error);
});

export { bot };
1 change: 1 addition & 0 deletions telegram-bot/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"extends":"../tsconfig.json","compilerOptions":{"outDir":"./dist","rootDir":"./src"},"include":["src/**/*"],"exclude":["node_modules","dist"]}