Bladeren bron

Merge pull request #307 from traumschule/tg-discord-notifications

Combined Telegram & Discord Bot
mochet 3 jaren geleden
bovenliggende
commit
3ac78cfd46

+ 3 - 1
community-contributions/joystreamtelegrambot/package.json

@@ -23,12 +23,14 @@
     "tests": "ts-node src/tests.ts"
   },
   "dependencies": {
+    "@joystream/types": "^0.16.1",
+    "axios": "^0.21.1",
+    "discord.js": "^12.5.3",
     "moment": "^2.29.1",
     "node-telegram-bot-api": "^0.51.0",
     "ws": "^7.4.1"
   },
   "devDependencies": {
-    "@joystream/types": "^0.15.0",
     "@types/node-telegram-bot-api": "^0.51.0",
     "ts-node": "^9.0.0",
     "ts-node-dev": "^1.0.0-pre.63",

+ 96 - 31
community-contributions/joystreamtelegrambot/src/bot.ts

@@ -1,5 +1,13 @@
+import { Client } from "discord.js";
 import TelegramBot from "node-telegram-bot-api";
-import { token, chatid, heartbeat, proposalDelay, wsLocation } from "../config";
+import {
+  discordToken,
+  tgToken,
+  chatid,
+  heartbeat,
+  proposalDelay,
+  wsLocation,
+} from "../config";
 
 // types
 import { Block, Council, Options, Proposals } from "./types";
@@ -10,7 +18,7 @@ import { AccountId, Header } from "@polkadot/types/interfaces";
 // functions
 import * as announce from "./lib/announcements";
 import * as get from "./lib/getters";
-import { parseArgs, printStatus, passedTime, exit } from "./lib/util";
+import { parseArgs, printStatus, passedTime } from "./lib/util";
 import moment from "moment";
 
 const opts: Options = parseArgs(process.argv.slice(2));
@@ -18,17 +26,52 @@ const log = (msg: string): void | number => opts.verbose && console.log(msg);
 process.env.NTBA_FIX_319 ||
   log("TL;DR: Set NTBA_FIX_319 to hide this warning.");
 
-const bot = token ? new TelegramBot(token, { polling: true }) : null;
-
-let lastHeartbeat: number = moment().valueOf();
+// connect to telegram
+const bot = tgToken ? new TelegramBot(tgToken, { polling: true }) : null;
+
+// connect to discord
+let discordChannels: { [key: string]: any } = {};
+const client = new Client();
+client.login(discordToken);
+client.on("ready", async () => {
+  if (!client.user) return;
+  console.log(`Logged in to discord as ${client.user.tag}!`);
+  discordChannels.council = await findDiscordChannel("council");
+  discordChannels.proposals = await findDiscordChannel("proposals-bot");
+  discordChannels.forum = await findDiscordChannel("forum-bot");
+  discordChannels.tokenomics = await findDiscordChannel("tokenomics");
+});
+
+const findDiscordChannel = (name: string) =>
+  client.channels.cache.find((channel: any) => channel.name === name);
+
+client.on("message", async (msg) => {
+  const user = msg.author;
+  if (msg.content === "/status") {
+    msg.reply(`reporting to discord`);
+  }
+});
 
-const sendMessage = (msg: string) => {
-  if (msg === "") return;
+// send to telegram and discord
+const sendMessage = (msg: { tg: string; discord: string }, channel: any) => {
+  if (msg.tg === "") return;
+  sendDiscord(msg.discord, channel);
+  sendTelegram(msg.tg);
+};
+const sendTelegram = (msg: string) => {
   try {
     if (bot) bot.sendMessage(chatid, msg, { parse_mode: "HTML" });
     else console.log(msg);
   } catch (e) {
-    console.log(`Failed to send message: ${e}`);
+    console.log(`Failed to send to telegram: ${e}`);
+  }
+};
+const sendDiscord = (msg: string, channel: any) => {
+  if (!channel || !msg.length) return;
+  try {
+    channel.send(msg);
+  } catch (e) {
+    console.log(e);
   }
 };
 
@@ -42,14 +85,18 @@ const main = async () => {
     api.rpc.system.name(),
     api.rpc.system.version(),
   ]);
+  log(`Subscribed to ${chain} on ${node} v${version}`);
 
   let council: Council = { round: 0, last: "" };
   let blocks: Block[] = [];
   let lastEra = 0;
+  let timestamp = await get.timestamp(api);
+  let duration = 0;
+  let lastHeartbeat = timestamp;
   let lastBlock: Block = {
     id: 0,
-    duration: 6000,
-    timestamp: lastHeartbeat,
+    duration: 0,
+    timestamp: 0,
     stake: 0,
     noms: 0,
     vals: 0,
@@ -61,6 +108,7 @@ const main = async () => {
   let stake = 0;
   let vals = 0;
   let noms = 0;
+  let announced: { [key: string]: boolean } = {};
 
   const channels: number[] = [0, 0];
   const posts: number[] = [0, 0];
@@ -68,8 +116,6 @@ const main = async () => {
   let proposals: Proposals = { last: 0, current: 0, active: [], executing: [] };
   let lastProposalUpdate = 0;
 
-  if (opts.channel) channels[0] = await get.currentChannelId(api);
-
   if (opts.forum) {
     posts[0] = await get.currentPostId(api);
     threads[0] = await get.currentThreadId(api);
@@ -83,14 +129,14 @@ const main = async () => {
   const getReward = async (era: number) =>
     Number(await api.query.staking.erasValidatorReward(era));
 
-  log(`Subscribed to ${chain} on ${node} v${version}`);
   api.rpc.chain.subscribeNewHeads(
     async (header: Header): Promise<void> => {
       // current block
       const id = header.number.toNumber();
+
       if (lastBlock.id === id) return;
-      const timestamp = (await api.query.timestamp.now()).toNumber();
-      const duration = timestamp - lastBlock.timestamp;
+      timestamp = await get.timestamp(api);
+      duration = lastBlock.timestamp ? timestamp - lastBlock.timestamp : 0;
 
       // update validators and nominators every era
       const era = Number(await api.query.staking.currentEra());
@@ -126,45 +172,64 @@ const main = async () => {
         reward,
         issued,
       };
-      blocks = blocks.concat(block);
+      if (duration) blocks = blocks.concat(block);
 
       // heartbeat
       if (timestamp > lastHeartbeat + heartbeat) {
         const time = passedTime(lastHeartbeat, timestamp);
-        blocks = announce.heartbeat(api, blocks, time, proposals, sendMessage);
+        announce.heartbeat(
+          api,
+          blocks,
+          time,
+          proposals,
+          sendMessage,
+          discordChannels.tokenomics
+        );
         lastHeartbeat = block.timestamp;
+        blocks = [];
       }
 
       // announcements
       if (opts.council && block.id > lastBlock.id)
-        council = await announce.council(api, council, block.id, sendMessage);
-
-      if (opts.channel) {
-        channels[1] = await get.currentChannelId(api);
-        if (channels[1] > channels[0])
-          channels[0] = await announce.channels(api, channels, sendMessage);
-      }
+        council = await announce.council(
+          api,
+          council,
+          block.id,
+          sendMessage,
+          discordChannels.council
+        );
 
       if (opts.proposals) {
         proposals.current = await get.proposalCount(api);
+
         if (
-          proposals.current > proposals.last ||
-          (timestamp > lastProposalUpdate + 60000 * proposalDelay &&
-            (proposals.active || proposals.executing))
+          proposals.current > proposals.last &&
+          !announced[proposals.current]
         ) {
-          proposals = await announce.proposals(api, proposals, id, sendMessage);
+          announced[`proposal${proposals.current}`] = true;
+          proposals = await announce.proposals(
+            api,
+            proposals,
+            id,
+            sendMessage,
+            discordChannels.proposals
+          );
           lastProposalUpdate = timestamp;
         }
       }
 
       if (opts.forum) {
         posts[1] = await get.currentPostId(api);
-        posts[0] = await announce.posts(api, posts, sendMessage);
+        announce.posts(api, posts, sendMessage, discordChannels.forum);
+        posts[0] = posts[1];
       }
 
       printStatus(opts, { block: id, chain, posts, proposals });
-      lastBlock = block
+      lastBlock = block;
     }
   );
 };
-main().catch(() => exit(log));
+main().catch((error) => {
+  console.log(error);
+  process.exit();
+});

+ 107 - 56
community-contributions/joystreamtelegrambot/src/lib/announcements.ts

@@ -5,6 +5,7 @@ import {
   Member,
   ProposalDetail,
   Proposals,
+  Send,
 } from "../types";
 import { BlockNumber } from "@polkadot/types/interfaces";
 import { Channel, ElectionStage } from "@joystream/types/augment";
@@ -16,6 +17,8 @@ import {
   memberHandle,
   memberHandleByAccount,
   proposalDetail,
+  fetchTokenValue,
+  fetchStorageSize,
 } from "./getters";
 import moment from "moment";
 
@@ -35,24 +38,30 @@ const query = async (test: string, cb: () => Promise<any>): Promise<any> => {
 export const channels = async (
   api: Api,
   channels: number[],
-  sendMessage: (msg: string) => void
-): Promise<number> => {
+  sendMessage: Send,
+  channel: any
+): Promise<void> => {
   const [last, current] = channels;
-  const messages: string[] = [];
+  const messages: string[][] = [[], []];
 
   for (let id: number = +last + 1; id <= current; id++) {
     const channel: Channel = await query("title", () =>
       api.query.contentWorkingGroup.channelById(id)
     );
-    const member: Member = { id: channel.owner, handle: "", url: "" };
-    member.handle = await memberHandle(api, member.id.toJSON());
+    const member: Member = { id: channel.owner.asMember, handle: "", url: "" };
+    member.handle = await memberHandle(api, member.id);
     member.url = `${domain}/#/members/${member.handle}`;
-    messages.push(
-      `<b>Channel <a href="${domain}/#//media/channels/${id}">${channel.title}</a> by <a href="${member.url}">${member.handle} (${member.id})</a></b>`
+    messages[0].push(
+      `<b>Channel <a href="${domain}/#//media/channels/${id}">${id}</a> by <a href="${member.url}">${member.handle} (${member.id})</a></b>`
+    );
+    messages[1].push(
+      `**Channel ${id}** by ${member.handle} (${member.id})\n${domain}/#//media/channels/${id}`
     );
   }
-  sendMessage(messages.join("\r\n\r\n"));
-  return current;
+  sendMessage(
+    { tg: messages[0].join("\r\n\r\n"), discord: messages[1].join(`\n\n`) },
+    channel
+  );
 };
 
 // announce council change
@@ -61,13 +70,14 @@ export const council = async (
   api: Api,
   council: Council,
   currentBlock: number,
-  sendMessage: (msg: string) => void
+  sendMessage: Send,
+  channel: any
 ): Promise<Council> => {
   const round: number = await api.query.councilElection.round();
   const stage: any = await api.query.councilElection.stage();
   const stageObj = JSON.parse(JSON.stringify(stage));
   let stageString = stageObj ? Object.keys(stageObj)[0] : "";
-  let msg = "";
+  let msg: string[] = ["", ""];
 
   if (!stage || stage.toJSON() === null) {
     stageString = "elected";
@@ -87,19 +97,25 @@ export const council = async (
       );
       const members = handles.join(", ");
 
-      msg = `Council election ended: ${members} have been elected for <a href="${domain}/#/council/members">council ${round}</a>. Congratulations!\nNext election starts on ${endDate}.`;
+      msg[0] = `Council election ended: ${members} have been elected for <a href="${domain}/#/council/members">council ${round}</a>. Congratulations!\nNext election starts on ${endDate}.`;
+      msg[1] = `Council election ended: ${members} have been elected for council ${round}. Congratulations!\nNext election starts on ${endDate}.\n${domain}/#/council/members`;
     }
   } else {
     const remainingBlocks = stage.toJSON()[stageString] - currentBlock;
     const m = moment().add(remainingBlocks * 6, "second");
     const endDate = formatTime(m, "DD-MM-YYYY HH:mm (UTC)");
 
-    if (stageString === "Announcing")
-      msg = `Council election started. You can <b><a href="${domain}/#/council/applicants">announce your application</a></b> until ${endDate}`;
-    else if (stageString === "Voting")
-      msg = `Council election: <b><a href="${domain}/#/council/applicants">Vote</a></b> until ${endDate}`;
-    else if (stageString === "Revealing")
-      msg = `Council election: <b><a href="${domain}/#/council/votes">Reveal your votes</a></b> until ${endDate}`;
+    const link = `${domain}/#/council/`;
+    if (stageString === "Announcing") {
+      msg[0] = `Council election started. You can <b><a href="${link}applicants">announce your application</a></b> until ${endDate}`;
+      msg[1] = `Council election started. You can **announce your application** until ${endDate} ${link}applicants`;
+    } else if (stageString === "Voting") {
+      msg[0] = `Council election: <b><a href="${link}applicants">Vote</a></b> until ${endDate}`;
+      msg[1] = `Council election: **Vote* until ${endDate} ${link}applicants`;
+    } else if (stageString === "Revealing") {
+      msg[0] = `Council election: <b><a href="${link}votes">Reveal your votes</a></b> until ${endDate}`;
+      msg[1] = `Council election: **Reveal your votes** until ${endDate} ${link}votes`;
+    }
   }
 
   if (
@@ -107,7 +123,7 @@ export const council = async (
     round !== council.round &&
     stageString !== council.last
   )
-    sendMessage(msg);
+    sendMessage({ tg: msg[0], discord: msg[1] }, channel);
   return { round, last: stageString };
 };
 
@@ -116,18 +132,26 @@ export const council = async (
 export const categories = async (
   api: Api,
   category: number[],
-  sendMessage: (msg: string) => void
+  sendMessage: Send,
+  channel: any
 ): Promise<number> => {
   if (category[0] === category[1]) return category[0];
-  const messages: string[] = [];
+  const messages: string[][] = [[], []];
 
   for (let id: number = +category[0] + 1; id <= category[1]; id++) {
     const cat: Category = await query("title", () => categoryById(api, id));
-    const msg = `Category ${id}: <b><a href="${domain}/#/forum/categories/${id}">${cat.title}</a></b>`;
-    messages.push(msg);
+    messages[0].push(
+      `Category ${id}: <b><a href="${domain}/#/forum/categories/${id}">${cat.title}</a></b>`
+    );
+    messages[1].push(
+      `Category ${id}: **${cat.title}** ${domain}/#/forum/categories/${id}`
+    );
   }
 
-  sendMessage(messages.join("\r\n\r\n"));
+  sendMessage(
+    { tg: messages[0].join("\r\n\r\n"), discord: messages[1].join(`\n\n`) },
+    channel
+  );
   return category[1];
 };
 
@@ -135,11 +159,12 @@ export const categories = async (
 export const posts = async (
   api: Api,
   posts: number[],
-  sendMessage: (msg: string) => void
+  sendMessage: Send,
+  channel: any
 ): Promise<number> => {
   const [last, current] = posts;
   if (current === last) return last;
-  const messages: string[] = [];
+  const messages: string[][] = [[], []];
 
   for (let id: number = +last + 1; id <= current; id++) {
     const post: Post = await query("current_text", () =>
@@ -151,6 +176,9 @@ export const posts = async (
       api.query.forum.threadById(threadId)
     );
     const categoryId = thread.category_id.toNumber();
+    if (categoryId === 19 || categoryId === 38) continue; // hide: 19 Media, 38 Russian
+    if ([180, 265, 275].includes(threadId)) continue;
+    // 180 tokens, 265 faucet, 275 pets
 
     const category: Category = await query("title", () =>
       categoryById(api, categoryId)
@@ -158,19 +186,22 @@ export const posts = async (
     const handle = await memberHandleByAccount(api, post.author_id.toJSON());
 
     const s = {
-      author: `<a href="${domain}/#/members/${handle}">${handle}</a>`,
-      thread: `<a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">${thread.title}</a>`,
-      category: `<a href="${domain}/#/forum/categories/${category.id}">${category.title}</a>`,
-      content: `<i>${post.current_text.substring(0, 150)}</i> `,
-      link: `<a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">more</a>`,
+      content: post.current_text.substring(0, 250),
+      link: `${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}`,
     };
 
-    messages.push(
-      `<u>${s.category}</u> <b>${s.author}</b> posted in <b>${s.thread}</b>:\n\r${s.content}${s.link}`
+    messages[0].push(
+      `<u><a href="${domain}/#/forum/categories/${category.id}">${category.title}</a></u> <b><a href="${domain}/#/members/${handle}">${handle}</a></b> posted in <b><a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">${thread.title}</a></b>:\n\r<i>${s.content}</i> <a href="${s.link}">more</a>`
+    );
+    messages[1].push(
+      `**[${category.title}]** ${handle} posted in **${thread.title}**:\n*${s.content}*\nMore: ${s.link}`
     );
   }
 
-  sendMessage(messages.join("\r\n\r\n"));
+  sendMessage(
+    { tg: messages[0].join("\r\n\r\n"), discord: messages[1].join(`\n\n`) },
+    channel
+  );
   return current;
 };
 
@@ -179,7 +210,8 @@ export const proposals = async (
   api: Api,
   prop: Proposals,
   block: number,
-  sendMessage: (msg: string) => void
+  sendMessage: Send,
+  channel: any
 ): Promise<Proposals> => {
   let { current, last, active, executing } = prop;
 
@@ -190,8 +222,10 @@ export const proposals = async (
     const endTime = moment()
       .add(6 * (votingEndsAt - block), "second")
       .format("DD/MM/YYYY HH:mm");
-    const msg = `Proposal ${id} <b>created</b> at block ${createdAt}.\r\n${message}\r\nYou can vote until ${endTime} UTC (block ${votingEndsAt}).`;
-    sendMessage(msg);
+    const link = `${domain}/#/proposals/${id}`;
+    const tg = `<a href="${link}">Proposal ${id}</a> <b>created</b> at block ${createdAt}.\r\n${message.tg}\r\nYou can <a href="${link}">vote</a> until block ${votingEndsAt} (${endTime} UTC).`;
+    const discord = `Proposal ${id} **created** at block ${createdAt}.\n${message.discord}\nVote until block ${votingEndsAt} (${endTime} UTC): ${link}\n`;
+    sendMessage({ tg, discord }, channel);
     active.push(id);
   }
 
@@ -205,8 +239,10 @@ export const proposals = async (
         label = executed ? "executed" : "finalized";
         if (!executed) executing.push(id);
       }
-      const msg = `Proposal ${id} <b>${label}</b> at block ${finalizedAt}.\r\n${message}`;
-      sendMessage(msg);
+      const link = `${domain}/#/proposals/${id}`;
+      const tg = `<a href="${link}">Proposal ${id}</a> <b>${label}</b> at block ${finalizedAt}.\r\n${message.tg}`;
+      const discord = `Proposal ${id} **${label}** at block ${finalizedAt}.\n${message.discord}\n${link}\n`;
+      sendMessage({ tg, discord }, channel);
       active = active.filter((a) => a !== id);
     }
   }
@@ -216,8 +252,10 @@ export const proposals = async (
     const { finalizedAt, message, parameters } = proposal;
     const executesAt = +finalizedAt + parameters.gracePeriod.toNumber();
     if (block < executesAt) continue;
-    const msg = `Proposal ${id} <b>executed</b> at block ${executesAt}.\r\n${message}`;
-    sendMessage(msg);
+    const link = `${domain}/#/proposals/${id}`;
+    const tg = `<a href="${link}">Proposal ${id}</a> <b>executed</b> at block ${executesAt}.\r\n${message.tg}`;
+    const discord = `Proposal ${id} **executed** at block ${executesAt}.\n${message.discord}\n${link}\n`;
+    sendMessage({ tg, discord }, channel);
     executing = executing.filter((e) => e !== id);
   }
 
@@ -229,14 +267,18 @@ export const proposals = async (
 const getAverage = (array: number[]): number =>
   array.reduce((a: number, b: number) => a + b, 0) / array.length;
 
-export const heartbeat = (
+export const heartbeat = async (
   api: Api,
   blocks: Block[],
   timePassed: string,
   proposals: Proposals,
-  sendMessage: (msg: string) => void
-): [] => {
+  sendMessage: Send,
+  channel: any
+): Promise<void> => {
+  const price = await fetchTokenValue();
+  const storageSize = await fetchStorageSize();
   const durations = blocks.map((b) => b.duration);
+  console.log(durations);
   const blocktime = getAverage(durations) / 1000;
 
   const stake = blocks.map((b) => b.stake);
@@ -255,25 +297,34 @@ export const heartbeat = (
   const pending = proposals.active.length;
   const finalized = proposals.executing.length;
   const p = (n: number) => (n > 1 ? "proposals" : "proposal");
-  let proposalString = pending
-    ? `<a href="${domain}/#/proposals">${pending} pending ${p(pending)}</a> `
-    : "";
+  let proposalString: string[] = pending
+    ? [
+        `<a href="${domain}/#/proposals">${pending} pending ${p(pending)}</a> `,
+        `${pending} active ${p(pending)} ${domain}/#/proposals`,
+      ]
+    : ["", ""];
   if (finalized)
-    proposalString += `${finalized} ${p(finalized)} in grace period.`;
+    proposalString = proposalString.map(
+      (s) => (s += `${finalized} ${p(finalized)} in grace period.`)
+    );
 
-  sendMessage(
-    `  ${blocks.length} blocks produced in ${timePassed}
+  const msg = `  ${blocks.length} blocks produced in ${timePassed}
   Blocktime: ${blocktime.toFixed(3)}s
+  Price: ${price} / 1 M tJOY
   Stake: ${avgStake.toFixed(1)} / ${avgIssued.toFixed()} M tJOY (${percent}%)
   Validators: ${avgVals.toFixed()} (${reward} tJOY/h)
   Nominators: ${getAverage(noms).toFixed()}
-  ${proposalString}`
-  );
-
-  return [];
+  Volume: ${storageSize}\n`;
+  const tg = msg + proposalString[0];
+  const discord = msg + proposalString[1];
+  sendMessage({ tg, discord }, channel);
 };
 
-export const formatProposalMessage = (data: string[]): string => {
+export const formatProposalMessage = (
+  data: string[]
+): { tg: string; discord: string } => {
   const [id, title, type, stage, result, handle] = data;
-  return `<b>Type</b>: ${type}\r\n<b>Proposer</b>: <a href="${domain}/#/members/${handle}">${handle}</a>\r\n<b>Title</b>: <a href="${domain}/#/proposals/${id}">${title}</a>\r\n<b>Stage</b>: ${stage}\r\n<b>Result</b>: ${result}`;
+  const tg = `<b>Type</b>: ${type}\r\n<b>Proposer</b>: <a href="${domain}/#/members/${handle}">${handle}</a>\r\n<b>Title</b>: <a href="${domain}/#/proposals/${id}">${title}</a>\r\n<b>Stage</b>: ${stage}\r\n<b>Result</b>: ${result}`;
+  const discord = `**Type**: ${type}\n**Proposer**: ${handle}\n**Title**: ${title}\n**Stage**: ${stage}\n**Result**: ${result}`;
+  return { tg, discord };
 };

+ 28 - 6
community-contributions/joystreamtelegrambot/src/lib/getters.ts

@@ -1,4 +1,5 @@
 import { formatProposalMessage } from "./announcements";
+import axios from "axios";
 
 //types
 
@@ -13,12 +14,10 @@ import { Category, CategoryId } from "@joystream/types/forum";
 import { MemberId, Membership } from "@joystream/types/members";
 import { Proposal } from "@joystream/types/proposals";
 
-// channel
+// api
 
-export const currentChannelId = async (api: Api): Promise<number> => {
-  const id: ChannelId = await api.query.contentWorkingGroup.nextChannelId();
-  return id.toNumber() - 1;
-};
+export const timestamp = async (api: Api) =>
+  (await api.query.timestamp.now()).toNumber();
 
 export const memberHandle = async (api: Api, id: MemberId): Promise<string> => {
   const membership: Membership = await api.query.members.membershipById(id);
@@ -107,7 +106,30 @@ export const proposalDetail = async (
   const title: string = proposal.title.toString();
   const type: string = await getProposalType(api, id);
   const args: string[] = [String(id), title, type, stage, result, author];
-  const message: string = formatProposalMessage(args);
+  const message: { tg: string; discord: string } = formatProposalMessage(args);
   const createdAt: number = proposal.createdAt.toNumber();
   return { createdAt, finalizedAt, parameters, message, stage, result, exec };
 };
+
+// status endpoint
+
+export const fetchTokenValue = async (): Promise<string> =>
+  axios
+    .get("https://status.joystream.org/status")
+    .then(({ data }) => `${Math.floor(+data.price * 100000000) / 100} $`)
+    .catch((e) => {
+      console.log(`Failed to fetch status.`);
+      return `?`;
+    });
+
+// hdyra
+
+export const fetchStorageSize = async () => {
+  const dashboard = "https://analytics.dapplooker.com/api/public/dashboard";
+  const asset = "c70b56bd-09a0-4472-a557-796afdc64d3b/card/155";
+
+  const { data } = await axios.get(`${dashboard}/${asset}`);
+
+  const size = Math.round(data.data.rows[0][0]) + " GB";
+  return size;
+};

+ 0 - 5
community-contributions/joystreamtelegrambot/src/lib/util.ts

@@ -55,8 +55,3 @@ export const passedTime = (start: number, now: number): string => {
       : "mm:ss[m]";
   return formatTime(passed, format);
 };
-
-export const exit = (log: (s: string) => void) => {
-  log("\nNo connection, exiting.\n");
-  process.exit();
-};

+ 3 - 1
community-contributions/joystreamtelegrambot/src/types/index.ts

@@ -26,7 +26,7 @@ export interface Options {
 export interface ProposalDetail {
   createdAt: number;
   finalizedAt: number;
-  message: string;
+  message: { tg: string; discord: string };
   parameters: ProposalParameters;
   stage: string;
   result: string;
@@ -58,3 +58,5 @@ export interface Block {
   issued: number;
   reward: number;
 }
+
+export type Send = (msg: { tg: string; discord: string }, channel: any) => void;