allbots.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. const { registerJoystreamTypes } = require('@joystream/types');
  2. const { ApiPromise, WsProvider } = require('@polkadot/api');
  3. const TelegramBot = require('node-telegram-bot-api');
  4. // replace yourowntoken below with the Telegram token you receive from @BotFather
  5. const token = 'yourowntoken';
  6. // Create a bot that uses 'polling' to fetch new updates
  7. const bot = new TelegramBot(token);
  8. //get chat id here https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id
  9. const chatid = 'yourownchat';
  10. async function main () {
  11. // register types before creating the api
  12. registerJoystreamTypes()
  13. // Create the API and wait until ready
  14. const api = await ApiPromise.create({
  15. provider: new WsProvider()
  16. })
  17. //proposals
  18. let proposalcount = (await api.query.proposalsEngine.proposalCount()).toNumber()
  19. let activeproposals = await getactiveProposals(api)
  20. let filteredproposal
  21. let tobeexecutedprop = await getpendingProposals(api)
  22. let tobeexecutedpropfiltered
  23. //forum
  24. let lastpostnotif = await getcurrentPostId(api)
  25. let lastcatnotif = await getcurrentCatId(api)
  26. let lastthreadnotif = await getcurrentThreadId(api)
  27. //channel
  28. let lastchannelnotif = await getcurrentChannelId(api)
  29. //council
  30. var lastcouncilnotif = 0
  31. const unsubscribe = await api.rpc.chain.subscribeNewHeads(async (header) => {
  32. const block = header.number.toNumber()
  33. //proposals
  34. const currentproposal = (await api.query.proposalsEngine.proposalCount()).toNumber()
  35. console.log(`Current block: ${block}, Current proposal count: ${currentproposal}, Current active proposal : ${activeproposals}`)
  36. if (currentproposal>proposalcount) {
  37. for (proposalcount+1;proposalcount<currentproposal;proposalcount++) {
  38. const proposal = await getproposalDetail(api,proposalcount+1)
  39. const propcreatedtime = proposal.detail().createdAt.toJSON()
  40. console.log(`New proposal (${proposalcount+1}) created at block ${propcreatedtime}.\r\n ${proposal.postmessage()}`)
  41. bot.sendMessage(chatid, `New proposal (${proposalcount+1}) created at block ${propcreatedtime}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  42. activeproposals.push(proposalcount+1)
  43. }
  44. }
  45. if (activeproposals[0]>0) {
  46. for (const proposallist of activeproposals){
  47. const proposal = await getproposalDetail(api,proposallist)
  48. let propstage = proposal.stage()[0]
  49. if (propstage == 'Finalized') {
  50. const propstatus = proposal.resultjson()
  51. switch (propstatus[0]){
  52. case 'Approved':
  53. let graceperiod = proposal.graceperiod()
  54. if (graceperiod>0) {
  55. bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to "Finalized" at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  56. filteredproposal = activeproposals.filter(e => e != proposallist)
  57. tobeexecutedprop.push(proposallist)
  58. } else {
  59. bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to "Finalized and Executed" at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  60. filteredproposal = activeproposals.filter(e => e != proposallist)
  61. }
  62. break;
  63. case 'Expired':
  64. case 'Canceled':
  65. case 'Cancelled':
  66. case 'Rejected':
  67. case 'Slashed':
  68. case 'Vetoed':
  69. // console.log(`Proposal ${proposallist} ${propstatus[0]}`)
  70. // bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to <b>"Finalized:${propstatus[0]}"</b> at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  71. // filteredproposal = activeproposals.filter(e => e != proposallist)
  72. // break;
  73. default:
  74. console.log(`Proposal ${proposallist} changed to other status: ${propstatus[0]}`)
  75. bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to <b>"Finalized:${propstatus[0]}"</b> at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  76. filteredproposal = activeproposals.filter(e => e != proposallist)
  77. break;
  78. }
  79. activeproposals = filteredproposal
  80. }
  81. }
  82. }
  83. if (tobeexecutedprop[0]>0) {
  84. for (const proposallist of tobeexecutedprop) {
  85. const proposal = await getproposalDetail(api,proposallist)
  86. let exestatus = Object.getOwnPropertyNames(proposal.resultfull()['Approved'])[0]
  87. if (exestatus=='Executed'){
  88. console.log(`Proposal ${proposallist} has been executed`)
  89. bot.sendMessage(chatid, `Proposalid (${proposallist}) <b>has been executed</b> at block ${proposal.finalizedtime()+proposal.graceperiod()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  90. tobeexecutedpropfiltered = tobeexecutedprop.filter(e => e != proposallist)
  91. } else {
  92. console.log(`Proposal ${proposallist} Execution is failed`)
  93. bot.sendMessage(chatid, `Proposalid (${proposallist}) <b>failed to be executed</b> at block ${proposal.finalizedtime()+proposal.graceperiod()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
  94. tobeexecutedpropfiltered = tobeexecutedprop.filter(e => e != proposallist)
  95. }
  96. tobeexecutedprop = tobeexecutedpropfiltered
  97. }
  98. }
  99. //forum
  100. const currentpost = await getcurrentPostId(api)
  101. const currentcat = await getcurrentCatId(api)
  102. const currentthread = await getcurrentThreadId(api)
  103. console.log(`Current block: ${block}, Latest postid: ${currentpost}, Latest categoryid: ${currentcat}, Latest threadid:${currentthread}`)
  104. //category forum checking
  105. if (currentcat>lastcatnotif){
  106. for (lastcatnotif+1; lastcatnotif<currentcat; lastcatnotif++){
  107. const categorytitle = await getcategoryTitle(api,lastcatnotif+1)
  108. console.log('Notify category',lastcatnotif+1, 'to Telegram')
  109. bot.sendMessage(chatid, `New category created: <b><a href="https://testnet.joystream.org/#/forum/categories/${lastcatnotif+1}">${categorytitle}</a>, Category ID: ${lastcatnotif+1}</b>`, { parse_mode: 'html' })
  110. }
  111. }
  112. //thread forum checking
  113. if (currentthread>lastthreadnotif){
  114. const newthread = []
  115. for (lastthreadnotif+1; lastthreadnotif<currentthread; lastthreadnotif++){
  116. const threadid = await api.query.forum.threadById(lastthreadnotif+1)
  117. const threadtitle = threadid.title
  118. const currentcategory = threadid.category_id
  119. const categorytitle = await getcategoryTitle(api,currentcategory)
  120. const authoraddress = threadid.author_id.toJSON()
  121. const memberraw = await api.query.members.memberIdsByRootAccountId(authoraddress)
  122. const memberid = memberraw[0].toNumber()
  123. const handler = await getmemberHandle(api, memberid)
  124. console.log('Notify thread',lastthreadnotif+1, 'to Telegram')
  125. //sent to array
  126. newthread.push(`New thread created: <a href="https://testnet.joystream.org/#/forum/threads/${lastthreadnotif+1}">"${threadtitle}"</a> by <a href="https://testnet.joystream.org/#/members/${handler}">${handler}</a> (id:${memberid}) in category "<a href="https://testnet.joystream.org/#/forum/categories/${currentcategory}">${categorytitle}</a>" `)
  127. }
  128. bot.sendMessage(chatid, newthread.join("\r\n\r\n"), { parse_mode: 'html' })
  129. }
  130. //forum post checking
  131. if (currentpost>lastpostnotif) {
  132. console.log(currentpost-lastpostnotif, ' new posts')
  133. const newpost = []
  134. //loop notification for every new post published since lastnotif
  135. for (lastpostnotif+1; lastpostnotif<currentpost; lastpostnotif++) {
  136. //begin chaining query info
  137. const postbyid = await api.query.forum.postById(lastpostnotif+1)
  138. const postpos = postbyid.nr_in_thread
  139. const message = postbyid.current_text
  140. //limit characters for message on telegram
  141. const excerpt = message.substring(0,100)
  142. const currentthreadid = postbyid.thread_id.toNumber()
  143. const threadid = await api.query.forum.threadById(currentthreadid)
  144. const threadtitle = threadid.title
  145. const currentcategory = threadid.category_id
  146. const categorytitle = await getcategoryTitle(api,currentcategory)
  147. const authoraddress = postbyid.author_id.toJSON()
  148. const memberraw = await api.query.members.memberIdsByRootAccountId(authoraddress)
  149. const memberid = memberraw[0].toNumber()
  150. const handler = await getmemberHandle(api, memberid)
  151. console.log('Notify post',lastpostnotif+1, 'to Telegram')
  152. //sent to array
  153. newpost.push(`🤩<b> New post (id:${lastpostnotif+1}) by <a href="https://testnet.joystream.org/#/members/${handler}">${handler}</a> (id:${memberid}) in category "<a href="https://testnet.joystream.org/#/forum/categories/${currentcategory}">${categorytitle}</a>" at:\r\n<a href="https://testnet.joystream.org/#/forum/threads/${currentthreadid}?replyIdx=${postpos}">"${threadtitle}"</a></b><i>\r\n"${excerpt}..."</i>\r\n`)
  154. }
  155. //console.log(newpost.join("\r\n\r\n"))
  156. bot.sendMessage(chatid, newpost.join("\r\n\r\n"), { parse_mode: 'html' })
  157. //update lastnotif
  158. // lastpostnotif=currentpost
  159. }
  160. //channel
  161. const currentchannelid = await getcurrentChannelId(api)
  162. console.log('Latest channelid is :',currentchannelid)
  163. if (currentchannelid>lastchannelnotif) {
  164. const newchannel = []
  165. for (lastchannelnotif+1;lastchannelnotif<currentchannelid;lastchannelnotif++) {
  166. const channel = await (await api.query.contentWorkingGroup.channelById(lastchannelnotif+1)).toJSON()
  167. const channeltitle = channel[0].title
  168. const memberid = channel[0].owner
  169. const channelownerhandler = await getmemberHandle(api, memberid)
  170. console.log(`Posting channel id: ${lastchannelnotif+1} to Telegram`)
  171. newchannel.push(`<b>New channel id created:</b>${lastchannelnotif+1}\r\n<b>Channel Title:</b><a href="https://testnet.joystream.org/#/media/channels/${lastchannelnotif+1}"> ${channeltitle}</a>\r\n<b>Member ID:</b> ${memberid}\r\n<b>Member Handler:</b> <a href="https://testnet.joystream.org/#/members/${channelownerhandler}">${channelownerhandler}</a>`)
  172. }
  173. bot.sendMessage(chatid, newchannel.join("\r\n\r\n"), { parse_mode: 'html' })
  174. }
  175. //council
  176. if (block>lastcouncilnotif) {
  177. const councilround = await api.query.councilElection.round()
  178. const councilendterm = (await api.query.council.termEndsAt()).toNumber()
  179. const annperiod = (await api.query.councilElection.announcingPeriod()).toNumber()
  180. const votingperiod = (await api.query.councilElection.votingPeriod()).toNumber()
  181. const revealingperiod = (await api.query.councilElection.revealingPeriod()).toNumber()
  182. const councilstage = await getcouncilStage(api)
  183. const councilperiod = (await api.query.councilElection.newTermDuration()).toNumber()
  184. switch (councilstage){
  185. case null:
  186. console.log('Council has been elected')
  187. if (block>lastcouncilnotif){
  188. bot.sendMessage(chatid, `<a href="https://testnet.joystream.org/#/council/members"> New council for round ${councilround}</a> has been elected at block ${councilendterm-councilperiod}.`, { parse_mode: 'html' })
  189. lastcouncilnotif=councilendterm
  190. }
  191. break;
  192. default:
  193. const annstage = councilstage.Announcing
  194. const votingstage = councilstage.Voting
  195. const revealingstage = councilstage.Revealing
  196. if (annstage) {
  197. console.log('Announcing Stage')
  198. if (block>lastcouncilnotif){
  199. bot.sendMessage(chatid, `New council election for round ${councilround} has been started at block ${annstage-annperiod}.<a href="https://testnet.joystream.org/#/council/applicants"> You can apply now!</a>`, { parse_mode: 'html' })
  200. lastcouncilnotif=annstage
  201. }
  202. }
  203. if (votingstage) {
  204. console.log('Voting Stage')
  205. if (block>lastcouncilnotif){
  206. bot.sendMessage(chatid, `Voting stage for council election has been started at block ${votingstage-votingperiod}. <a href="https://testnet.joystream.org/#/council/applicants">You can vote now!</a>`, { parse_mode: 'html' })
  207. lastcouncilnotif=votingstage
  208. }
  209. }
  210. if (revealingstage) {
  211. console.log('Revealing Stage')
  212. if (block>lastcouncilnotif){
  213. bot.sendMessage(chatid, `Revealing stage for council election has been started at block ${revealingstage-revealingperiod}. <a href="https://testnet.joystream.org/#/council/votes">Don't forget to reveal your vote!</a>`, { parse_mode: 'html' })
  214. lastcouncilnotif=revealingstage
  215. }
  216. }
  217. break;
  218. }
  219. }
  220. })
  221. }
  222. //functions
  223. //proposals
  224. const getpendingProposals = async (api) => {
  225. let tobeexecutedprop = ((await api.query.proposalsEngine.pendingExecutionProposalIds()).toJSON())[0]
  226. if (tobeexecutedprop[0]==0){
  227. return []
  228. } else {
  229. return tobeexecutedprop
  230. }
  231. }
  232. const getactiveProposals = async (api) => {
  233. let activeproposals = ((await api.query.proposalsEngine.activeProposalIds()).toJSON())[0]
  234. if (activeproposals[0]==0){
  235. return []
  236. } else {
  237. return activeproposals
  238. }
  239. }
  240. const getmemberHandle = async (api,memberid) => {
  241. const memberprofile = await api.query.members.memberProfile(memberid)
  242. const handler = memberprofile.raw.handle.toJSON()
  243. return handler
  244. }
  245. const getproposalStatus = (propresultraw) => {
  246. if (propresultraw.hasOwnProperty('proposalStatus')) {
  247. return propresultraw.proposalStatus
  248. } else {
  249. return {Active:null}
  250. }
  251. }
  252. const getfinalTime = (propresultraw) => {
  253. if (propresultraw.hasOwnProperty('finalizedAt')) {
  254. return propresultraw.finalizedAt
  255. } else {
  256. return 0
  257. }
  258. }
  259. const getproposalDetail = async (api,proposalcount) => {
  260. const propdetail = await api.query.proposalsEngine.proposals(proposalcount)
  261. const parameters = propdetail.parameters
  262. const propposterid = propdetail.proposerId.toJSON()
  263. const handler = await getmemberHandle(api,propposterid)
  264. const proptype = await api.query.proposalsCodex.proposalDetailsByProposalId(proposalcount)
  265. const [deftype] = Object.getOwnPropertyNames(proptype.toJSON())
  266. const proptitle = propdetail.get("title")
  267. const propstage = propdetail.status.toJSON()
  268. // const propstatus = propdetail.get("status")
  269. const propstatus = Object.getOwnPropertyNames(propstage)
  270. const propresultraw = propstage[propstatus]
  271. const propfinalresultfull = getproposalStatus(propresultraw)
  272. // const propfinalresultfull = propresultraw.proposalStatus
  273. // const propfinalresultjson = Object.getOwnPropertyNames(propresultraw.proposalStatus)
  274. const propfinaltime = getfinalTime(propresultraw)
  275. // const propfinaltime = propresultraw.finalizedAt
  276. const propfinalresultjson = Object.getOwnPropertyNames(propfinalresultfull)
  277. const graceperiod = propdetail.parameters.gracePeriod.toNumber()
  278. return {
  279. detail : function () {
  280. return propdetail;
  281. },
  282. parameters : function () {
  283. return parameters;
  284. },
  285. stage : function () {
  286. return propstatus;
  287. },
  288. finalizedtime : function () {
  289. return propfinaltime;
  290. },
  291. graceperiod : function () {
  292. return graceperiod;
  293. },
  294. resultfull : function () {
  295. return propfinalresultfull;
  296. },
  297. resultjson : function () {
  298. return propfinalresultjson;
  299. },
  300. postmessage : function () {
  301. return `<b>Type</b>: ${deftype}\r\n <b>Proposer</b>:<a href="https://testnet.joystream.org/#/members/${handler}"> ${handler}(${propposterid})</a>\r\n <b>Title</b>: <a href="https://testnet.joystream.org/#/proposals/${proposalcount}">${proptitle}</a>\r\n <b>Stage</b>: ${propstatus}\r\n <b>Result</b>: ${JSON.stringify(propfinalresultfull)}`;
  302. // postmessage : function () {
  303. // return `<b>Type</b>: ${this.deftype()}\r\n <b>Proposer</b>: ${this.handler()}(${this.posterid()})\r\n <b>Title</b>: ${this.title()}\r\n <b>Stage</b>: ${this.stage()}\r\n <b>Result</b>: ${this.result()}`;
  304. }
  305. }
  306. }
  307. //forum
  308. const getcategoryTitle = async (api, categoryid) => {
  309. const category = await api.query.forum.categoryById(categoryid)
  310. const categorytitle = category.title
  311. return categorytitle
  312. }
  313. const getcurrentPostId = async (api) => {
  314. const nextpostid = await api.query.forum.nextPostId()
  315. const currentpostid = nextpostid.toNumber()-1
  316. return currentpostid
  317. }
  318. const getcurrentThreadId = async (api) => {
  319. const nextthreadid = await api.query.forum.nextThreadId()
  320. const currentthreadid = nextthreadid.toNumber()-1
  321. return currentthreadid
  322. }
  323. const getcurrentCatId = async (api) => {
  324. const nextcatid = await api.query.forum.nextCategoryId()
  325. const currentcatid = nextcatid.toNumber()-1
  326. return currentcatid
  327. }
  328. //channel
  329. const getcurrentChannelId = async (api) => {
  330. const nextchannelid = await api.query.contentWorkingGroup.nextChannelId()
  331. const currentchannelid = nextchannelid.toNumber()-1
  332. return currentchannelid
  333. }
  334. //council
  335. const getcouncilStage = async (api) => {
  336. const councilstage = await api.query.councilElection.stage()
  337. const councilstagejson = councilstage.toJSON()
  338. return councilstagejson
  339. }
  340. main()