Pārlūkot izejas kodu

upd joystream founding bot: add discord bot + refact + rewrite to TS

agrafen 3 gadi atpakaļ
vecāks
revīzija
f96f17d974

+ 18 - 1
community-contributions/joystream-leaderboard-bot/README.md

@@ -1 +1,18 @@
-http://t.me/JoystreamLeaderboardBot
+# Joystrem founding bots
+
+## Telegram
+
+http://t.me/JoystreamLeaderboardBot - official bot
+
+http://t.me/JoystreamLeaderboardBotTest - test bot
+
+
+**Starting the bot:**
+- npm i -g pm2
+- npm ci
+- pm2 start telegram.pm2.config.js
+
+## Discord
+
+**Starting the discord bot:**
+- pm2 start discord.pm2.config

+ 13 - 0
community-contributions/joystream-leaderboard-bot/discord.pm2.config.js

@@ -0,0 +1,13 @@
+module.exports = {
+  apps : [
+    {
+      name: 'discord-bot',
+      script: './src/discord.ts',
+      cwd: './',
+      exec_mode: 'fork',
+      interpreter: 'node',
+      watch: './src',
+      interpreter_args: '--require ts-node/register --require tsconfig-paths/register'
+    },
+],
+};

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 428 - 240
community-contributions/joystream-leaderboard-bot/package-lock.json


+ 13 - 4
community-contributions/joystream-leaderboard-bot/package.json

@@ -1,6 +1,6 @@
 {
   "name": "joystream-leaderboard-bot",
-  "version": "0.1.0",
+  "version": "0.2.0",
   "description": "",
   "main": "index.js",
   "scripts": {
@@ -10,10 +10,19 @@
   "license": "ISC",
   "dependencies": {
     "axios": "^0.21.1",
+    "bluebird": "^3.7.2",
     "discord.js": "^12.5.3",
     "dotenv": "^10.0.0",
-    "mongodb": "^4.0.0",
-    "mongoose": "^5.13.2",
-    "node-telegram-bot-api": "^0.53.0"
+    "mongoose": "5.9.25",
+    "node-telegram-bot-api": "^0.53.0",
+    "ts-node": "^10.1.0",
+    "tsconfig-paths": "^3.10.1",
+    "typescript": "^4.3.5"
+  },
+  "devDependencies": {
+    "@types/axios": "^0.14.0",
+    "@types/mongoose": "5.7.32",
+    "@types/node": "^15.14.2",
+    "@types/node-telegram-bot-api": "^0.51.4"
   }
 }

+ 177 - 0
community-contributions/joystream-leaderboard-bot/src/botService.ts

@@ -0,0 +1,177 @@
+import { MemberModel, IMember } from './db';
+import axios from 'axios';
+
+type SendMessage = (message: any, text: string) => Promise<any>;
+type CommandPrefix = '/' | '!' | string;
+
+interface BotServiceProps {
+  send: SendMessage;
+  commandPrefix: CommandPrefix;
+  client: any; // TelegramBot | DiscordBot,
+  getId: Function;
+  getText: Function;
+  getDate: Function;
+  dbId: 'tgId' | 'disId' | string;
+  log: Function;
+}
+
+interface BotProps {
+  send: SendMessage;
+  commandPrefix: CommandPrefix;
+  client: any;
+  id: number;
+  text: string;
+  dbId: 'tgId' | 'disId' | string;
+}
+
+let cachedFmInfo: any = null;
+let lastUpdateFmDate = 0;
+
+async function getFmInfo() {
+  if (!cachedFmInfo || new Date().getTime() - lastUpdateFmDate > 1000 * 600) {
+    console.log('get new fmData');
+    cachedFmInfo = await axios.get(
+      'https://raw.githubusercontent.com/Joystream/founding-members/main/data/fm-info.json'
+    );
+    lastUpdateFmDate = new Date().getTime();
+  }
+
+  return cachedFmInfo;
+}
+
+function startCommand(message: any, props: BotServiceProps) {
+  props.log(props.getText(message));
+
+  const regexp = new RegExp(`^${props.commandPrefix}start`);
+  if (regexp.test(props.getText(message))) {
+    props.log('welcome', props.getId(message));
+    props.send(
+      message,
+      'Welcome! Using this bot, you can get information about founding members.' +
+        (props.commandPrefix === '/'
+          ? `\n\nTo view your statistics, you need enter your name via the ${props.commandPrefix}sethandle command to save it OR use the command "${props.commandPrefix}lookup handle". `
+          : `\n\nTo view your statistics, use the command "${props.commandPrefix}lookup handle". `) +
+        '*Please note that the handle is case sensitive!*.'
+    );
+  }
+}
+
+async function lookupCommand(
+  member: IMember,
+  message: any,
+  props: BotServiceProps
+) {
+  const regexp = new RegExp(`^${props.commandPrefix}lookup(.*)`);
+  const regexpMatch = new RegExp(`${props.commandPrefix}lookup (.*)`);
+
+  if (regexp.test(props.getText(message))) {
+    const match = props.getText(message).match(regexpMatch);
+    console.log('message', message);
+    
+    const handle = match ? match[1] : member?.handle;
+
+    if (handle) {
+      const fmInfo = await getFmInfo();
+      const memberData = fmInfo.data.scores.totalScores.find(
+        (m: any) => m.memberHandle === handle
+      );
+
+      if (memberData) {
+        const memberDataStr =
+          `Direct Score = *${memberData.totalDirectScore}*\n` +
+          `Referral Score = *${memberData.totalReferralScore}*\n` +
+          `Total Score = *${memberData.totalScore}*\n\n` +
+          memberData.directScores
+            .map((m: any, index: number) => `Period ${index} = *${m}*`)
+            .join('\n');
+
+        props.send(message, memberDataStr);
+      } else {
+        props.send(
+          message,
+          `Don't find member ${handle}. Please note that the handle is case sensitive.`
+        );
+      }
+    } else {
+      props.send(message, 'Please, set your handle.');
+    }
+  }
+}
+
+async function setHandleCommand(
+  member: IMember,
+  message: any,
+  props: BotServiceProps
+) {
+  const regexp = new RegExp(`^${props.commandPrefix}sethandle`);
+  if (regexp.test(props.getText(message))) {
+    props.log('sethandle');
+    if (member === null) {
+      const newMember = {
+        [props.dbId]: props.getId(message),
+        date: props.getDate(message),
+        lastCommand: 'sethandle',
+      };
+
+      await MemberModel.create(newMember);
+    } else {
+      await MemberModel.updateOne(
+        { [props.dbId]: props.getId(message) },
+        { $set: { lastCommand: 'sethandle' } }
+      );
+    }
+
+    props.send(
+      message,
+      'Write your handle. Please note that the handle is case sensitive.'
+    );
+  } else if (member && member.lastCommand === 'sethandle') {
+    const updMember = await MemberModel.findOne({
+      handle: props.getText(message),
+    });
+
+    if (updMember) {
+      await MemberModel.remove({
+        [props.dbId]: props.getId(message),
+        handle: null,
+      });
+
+      await updMember.updateOne({
+        $set: {
+          lastCommand: null,
+          [props.dbId]: props.getId(message),
+          handle: props.getText(message),
+        },
+      });
+    } else {
+      await MemberModel.updateOne(
+        { [props.dbId]: props.getId(message) },
+        { $set: { lastCommand: null, handle: props.getText(message) } }
+      );
+    }
+
+    props.send(message, 'Good! Now you can get statistics =)');
+  }
+}
+
+export default async function BotService(props: BotServiceProps) {
+  props.client.on('message', async (message: any) => {
+    const id = props.getId(message);
+    let member = null;
+
+    try {
+      member = await MemberModel.findOne({ [props.dbId]: id });
+      console.log('member =>', member);
+    } catch (e) {
+      console.log(e);
+      props.send(message, 'Error =( please try later');
+    }
+
+    startCommand(message, props);
+    lookupCommand(member, message, props);
+
+    if (props.commandPrefix === '/') {
+      setHandleCommand(member, message, props);
+    }
+  });
+}

+ 41 - 0
community-contributions/joystream-leaderboard-bot/src/db.ts

@@ -0,0 +1,41 @@
+import mongoose from 'mongoose';
+
+export interface IMember extends mongoose.Document {
+  handle?: String,
+  tgId?: Number,
+  disId?: Number,
+  date?: number,
+  lastCommand: String
+};
+
+const memberSchema = new mongoose.Schema({
+  handle: String,
+  tgId: Number,
+  disId: Number,
+  date: Date,
+  lastCommand: String
+});
+
+export const MemberModel = mongoose.model<IMember>('members', memberSchema);
+
+const dbString = 'mongodb://127.0.0.1:27017/JoystreamLeaderboardBotTest';
+
+export default () => {
+  const connect = () => {
+    mongoose
+      .connect(
+        dbString,
+        { useNewUrlParser: true }
+      )
+      .then(() => {
+        return console.info(`Successfully connected to ${dbString}`);
+      })
+      .catch(error => {
+        console.error('Error connecting to database: ', error);
+        return process.exit(1);
+      });
+  };
+  connect();
+
+  mongoose.connection.on('disconnected', connect);
+}

+ 26 - 0
community-contributions/joystream-leaderboard-bot/src/discord.ts

@@ -0,0 +1,26 @@
+require('dotenv').config();
+import { Client, Intents, Message } from 'discord.js';
+
+import connect, { MemberModel } from './db';
+import botService from './botService';
+// const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] });
+const client = new Client();
+
+connect();
+
+botService({
+  send: (message: Message, text: string) => message.channel.send(text),
+  commandPrefix: '!',
+  client,
+  getId: (message: Message) => message.author.id,
+  getText: (message: Message) => message.content,
+  getDate: (message: Message) => message.createdTimestamp,
+  dbId: 'disId',
+  log: (...args: any) => console.log('Discord:', ...args)
+});
+
+client.on('ready', () => {
+  console.log(`Logged in as ${client.user.tag}!`);
+});
+
+client.login(process.env.DISCORD_BOT_TOKEN);

+ 38 - 0
community-contributions/joystream-leaderboard-bot/src/telegram.ts

@@ -0,0 +1,38 @@
+require('dotenv').config();
+import TelegramBot from 'node-telegram-bot-api';
+
+import connect from './db';
+import botService from './botService';
+
+console.log('================ start ================');
+
+connect();
+
+const token = process.env.TELEGRAM_BOT_TEST_TOKEN;
+
+const bot = new TelegramBot(token, { polling: true });
+
+bot.on('polling_error', console.log);
+
+bot.setMyCommands([
+  {
+    command: '/sethandle',
+    description: 'set your handle name',
+  },
+  {
+    command: '/lookup',
+    description: 'return the score of the user',
+  },
+]);
+
+botService({
+  send: (message: TelegramBot.Message, text: string) =>
+    bot.sendMessage(message.chat.id, text, { parse_mode: 'Markdown' }),
+  commandPrefix: '/',
+  client: bot,
+  getId: (message: TelegramBot.Message) => message.from.id,
+  getText: (message: TelegramBot.Message) => message.text,
+  getDate: (message: TelegramBot.Message) => message.date,
+  dbId: 'tgId',
+  log: (...args: any) => console.log('Telegram: ', ...args),
+});

+ 13 - 0
community-contributions/joystream-leaderboard-bot/telegram.pm2.config.js

@@ -0,0 +1,13 @@
+module.exports = {
+  apps : [
+    {
+      name: 'telegram-bot',
+      script: './src/telegram.ts',
+      cwd: './',
+      exec_mode: 'fork',
+      interpreter: 'node',
+      watch: './src',
+      interpreter_args: '--require ts-node/register --require tsconfig-paths/register'
+    },
+],
+};

+ 23 - 0
community-contributions/joystream-leaderboard-bot/tsconfig.json

@@ -0,0 +1,23 @@
+{
+  "compilerOptions": {
+    "module": "commonjs",
+    "esModuleInterop": true,
+    "target": "es6",
+    "noImplicitAny": true,
+    "moduleResolution": "node",
+    "sourceMap": true,
+    "outDir": "dist",
+    "baseUrl": ".",
+    "paths": {
+        "*": [
+            "node_modules/*",
+        ]
+    },
+  },
+  "include": [
+    "src/**/*"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels