announcements.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { Api, Council, ProposalDetail, Proposals, Summary } from "../../types";
  2. import { BlockNumber } from "@polkadot/types/interfaces";
  3. import { Channel } from "@joystream/types/augment";
  4. import { Category, Thread, Post } from "@joystream/types/forum";
  5. import { formatTime } from "./util";
  6. import {
  7. categoryById,
  8. memberHandle,
  9. memberHandleByAccount,
  10. proposalDetail,
  11. } from "./getters";
  12. const { domain } = require( "../../config")
  13. const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
  14. // query API repeatedly to ensure a result
  15. const query = async (test: string, cb: () => Promise<any>): Promise<any> => {
  16. let result = await cb();
  17. for (let i: number = 0; i < 10; i++) {
  18. if (result[test] !== "") return result;
  19. result = await cb();
  20. await sleep(5000);
  21. }
  22. };
  23. // announce latest channels
  24. export const channels = async (
  25. api: Api,
  26. channels: number[],
  27. sendMessage: (msg: string) => void
  28. ): Promise<number> => {
  29. const [last, current] = channels;
  30. const messages: string[] = [];
  31. for (let id: number = +last + 1; id <= current; id++) {
  32. const channel: Channel = await query("title", () =>
  33. api.query.contentWorkingGroup.channelById(id)
  34. );
  35. const member: any = { id: channel.owner, handle: "", url: "" };
  36. member.handle = await memberHandle(api, member.id.toJSON());
  37. member.url = `${domain}/#/members/${member.handle}`;
  38. messages.push(
  39. `<b>Channel <a href="${domain}/#//media/channels/${id}">${channel.title}</a> by <a href="${member.url}">${member.handle} (${member.id})</a></b>`
  40. );
  41. }
  42. sendMessage(messages.join("\r\n\r\n"));
  43. return current;
  44. };
  45. // forum
  46. // announce latest categories
  47. export const categories = async (
  48. api: Api,
  49. category: number[],
  50. sendMessage: (msg: string) => void
  51. ): Promise<number> => {
  52. const messages: string[] = [];
  53. for (let id: number = +category[0] + 1; id <= category[1]; id++) {
  54. const cat: Category = await query("title", () => categoryById(api, id));
  55. const msg = `Category ${id}: <b><a href="${domain}/#/forum/categories/${id}">${cat.title}</a></b>`;
  56. messages.push(msg);
  57. }
  58. sendMessage(messages.join("\r\n\r\n"));
  59. return category[1];
  60. };
  61. // announce latest posts
  62. export const posts = async (
  63. api: Api,
  64. posts: number[],
  65. sendMessage: (msg: string) => void
  66. ): Promise<number> => {
  67. const [last, current] = posts;
  68. const messages: string[] = [];
  69. for (let id: number = +last + 1; id <= current; id++) {
  70. const post: Post = await query("current_text", () =>
  71. api.query.forum.postById(id)
  72. );
  73. const replyId: number = +post.nr_in_thread
  74. const message: string = post.current_text;
  75. const excerpt: string = message.substring(0, 100);
  76. const threadId: number = +post.thread_id
  77. const thread: Thread = await query("title", () =>
  78. api.query.forum.threadById(threadId)
  79. );
  80. const threadTitle: string = thread.title;
  81. const category: Category = await query("title", () =>
  82. categoryById(api, +thread.category_id)
  83. );
  84. const handle = await memberHandleByAccount(api, post.author_id.toJSON());
  85. const msg = `<b><a href="${domain}/#/members/${handle}">${handle}</a> posted <a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">${threadTitle}</a> in <a href="${domain}/#/forum/categories/${category.id}">${category.title}</a>:</b>\n\r<i>${excerpt}</i> <a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">more</a>`;
  86. messages.push(msg);
  87. }
  88. sendMessage(messages.join("\r\n\r\n"));
  89. return current;
  90. };
  91. // announce latest threads
  92. export const threads = async (
  93. api: Api,
  94. threads: number[],
  95. sendMessage: (msg: string) => void
  96. ): Promise<number> => {
  97. const [last, current] = threads;
  98. const messages: string[] = [];
  99. for (let id: number = +last + 1; id <= current; id++) {
  100. const thread: Thread = await query("title", () =>
  101. api.query.forum.threadById(id)
  102. );
  103. const { title, author_id } = thread;
  104. const handle: string = await memberHandleByAccount(api, author_id.toJSON());
  105. const category: Category = await query("title", () =>
  106. categoryById(api, +thread.category_id)
  107. );
  108. const msg = `Thread ${id}: <a href="${domain}/#/forum/threads/${id}">"${title}"</a> by <a href="${domain}/#/members/${handle}">${handle}</a> in category "<a href="${domain}/#/forum/categories/${category.id}">${category.title}</a>" `;
  109. messages.push(msg);
  110. }
  111. sendMessage(messages.join("\r\n\r\n"));
  112. return current;
  113. };
  114. // announce latest proposals
  115. export const proposals = async (
  116. api: Api,
  117. prop: Proposals,
  118. sendMessage: (msg: string) => void
  119. ): Promise<Proposals> => {
  120. let { current, last, active, executing } = prop;
  121. for (let id: number = +last + 1; id <= current; id++) {
  122. const proposal: ProposalDetail = await proposalDetail(api, id);
  123. const { createdAt, message, parameters } = proposal;
  124. const votingEndsAt = createdAt + +parameters.votingPeriod
  125. const msg = `Proposal ${id} <b>created</b> at block ${createdAt}.\r\n${message}\r\nYou can vote until block ${votingEndsAt}.`;
  126. sendMessage(msg);
  127. active.push(id);
  128. }
  129. for (const id of active) {
  130. const proposal: ProposalDetail = await proposalDetail(api, id);
  131. const { finalizedAt, message, parameters, result, stage } = proposal;
  132. if (stage === "Finalized") {
  133. let label: string = result;
  134. if (result === "Approved") {
  135. const executed = +parameters.gracePeriod > 0 ? false : true;
  136. label = executed ? "Executed" : "Finalized";
  137. if (!executed) executing.push(id);
  138. }
  139. const msg = `Proposal ${id} <b>${label}</b> at block ${finalizedAt}.\r\n${message}`;
  140. sendMessage(msg);
  141. active = active.filter((a) => a !== id);
  142. }
  143. }
  144. for (const id of executing) {
  145. const proposal = await proposalDetail(api, id);
  146. const { exec, finalizedAt, message, parameters } = proposal;
  147. const execStatus = exec ? Object.keys(exec)[0] : "";
  148. const label = execStatus === "Executed" ? "has been" : "failed to be";
  149. const block = +finalizedAt + +parameters.gracePeriod;
  150. const msg = `Proposal ${id} <b>${label} executed</b> at block ${block}.\r\n${message}`;
  151. sendMessage(msg);
  152. executing = executing.filter((e) => e !== id);
  153. }
  154. return { current, last: current, active, executing };
  155. };
  156. // heartbeat
  157. const getAverage = (array: number[]) =>
  158. array.reduce((a: number, b: number) => a + b, 0) / array.length;
  159. export const heartbeat = async (
  160. api: Api,
  161. summary: Summary,
  162. timePassed: string,
  163. accountId: string,
  164. sendMessage: (msg: string) => void
  165. ): Promise<void> => {
  166. const { blocks, nominators, validators } = summary;
  167. const avgDuration =
  168. blocks.reduce((a, b) => a + b.duration, 0) / blocks.length;
  169. const era: any = await api.query.staking.currentEra();
  170. const totalStake: any = await api.query.staking.erasTotalStake(parseInt(era));
  171. const stakers = await api.query.staking.erasStakers(parseInt(era), accountId);
  172. const stakerCount = stakers.others.length;
  173. const avgStake = parseInt(totalStake.toString()) / stakerCount;
  174. console.log(`
  175. Blocks produced during ${timePassed}h in era ${era}: ${blocks.length}
  176. Average blocktime: ${Math.floor(avgDuration) / 1000} s
  177. Average stake: ${avgStake / 1000000} M JOY (${stakerCount} stakers)
  178. Average number of nominators: ${getAverage(nominators)}
  179. Average number of validators: ${getAverage(validators)}`);
  180. };
  181. export const formatProposalMessage = (data: string[]): string => {
  182. const [id, title, type, stage, result, handle] = data;
  183. 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}`;
  184. };
  185. // providers
  186. export const provider = (
  187. id: number,
  188. address: string,
  189. status: string,
  190. sendMessage: (msg: string) => void
  191. ): void => {
  192. const msg = `[${formatTime()}] Storage Provider ${id} (${address}) is ${status}`;
  193. sendMessage(msg);
  194. };
  195. export const newOpening = (id: number, sendMessage: (msg: string) => void) => {
  196. const msg = `New opening: <b><a href="${domain}/#/working-groups/opportunities/curators/${id}">Storage Provider ${id}</a></b>`;
  197. sendMessage(msg);
  198. };
  199. export const closeOpening = (
  200. id: number,
  201. handle: string,
  202. sendMessage: (msg: string) => void
  203. ): void => {
  204. const msg = `<a href="${domain}/#/members/${handle}">${handle}</a> was choosen as <b><a href="${domain}/#/working-groups/opportunities/curators/${id}">Storage Provider ${id}</a></b>`;
  205. sendMessage(msg);
  206. };