const { registerJoystreamTypes } = require('@joystream/types'); const { ApiPromise, WsProvider } = require('@polkadot/api'); const TelegramBot = require('node-telegram-bot-api'); // replace yourowntoken below with the Telegram token you receive from @BotFather const token = 'yourowntoken'; // Create a bot that uses 'polling' to fetch new updates const bot = new TelegramBot(token); //get chat id here https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id const chatid = 'yourownchat'; async function main () { // register types before creating the api registerJoystreamTypes() // Create the API and wait until ready const api = await ApiPromise.create({ provider: new WsProvider() }) //proposals let proposalcount = (await api.query.proposalsEngine.proposalCount()).toNumber() let activeproposals = await getactiveProposals(api) let filteredproposal let tobeexecutedprop = await getpendingProposals(api) let tobeexecutedpropfiltered //forum let lastpostnotif = await getcurrentPostId(api) let lastcatnotif = await getcurrentCatId(api) let lastthreadnotif = await getcurrentThreadId(api) //channel let lastchannelnotif = await getcurrentChannelId(api) //council var lastcouncilnotif = 0 const unsubscribe = await api.rpc.chain.subscribeNewHeads(async (header) => { const block = header.number.toNumber() //proposals const currentproposal = (await api.query.proposalsEngine.proposalCount()).toNumber() console.log(`Current block: ${block}, Current proposal count: ${currentproposal}, Current active proposal : ${activeproposals}`) if (currentproposal>proposalcount) { for (proposalcount+1;proposalcount0) { for (const proposallist of activeproposals){ const proposal = await getproposalDetail(api,proposallist) let propstage = proposal.stage()[0] if (propstage == 'Finalized') { const propstatus = proposal.resultjson() switch (propstatus[0]){ case 'Approved': let graceperiod = proposal.graceperiod() if (graceperiod>0) { bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to "Finalized" at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' }) filteredproposal = activeproposals.filter(e => e != proposallist) tobeexecutedprop.push(proposallist) } else { bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to "Finalized and Executed" at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' }) filteredproposal = activeproposals.filter(e => e != proposallist) } break; case 'Expired': case 'Canceled': case 'Cancelled': case 'Rejected': case 'Slashed': case 'Vetoed': // console.log(`Proposal ${proposallist} ${propstatus[0]}`) // bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to "Finalized:${propstatus[0]}" at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' }) // filteredproposal = activeproposals.filter(e => e != proposallist) // break; default: console.log(`Proposal ${proposallist} changed to other status: ${propstatus[0]}`) bot.sendMessage(chatid, `Proposalid (${proposallist}) status changed to "Finalized:${propstatus[0]}" at block ${proposal.finalizedtime()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' }) filteredproposal = activeproposals.filter(e => e != proposallist) break; } activeproposals = filteredproposal } } } if (tobeexecutedprop[0]>0) { for (const proposallist of tobeexecutedprop) { const proposal = await getproposalDetail(api,proposallist) let exestatus = Object.getOwnPropertyNames(proposal.resultfull()['Approved'])[0] if (exestatus=='Executed'){ console.log(`Proposal ${proposallist} has been executed`) bot.sendMessage(chatid, `Proposalid (${proposallist}) has been executed at block ${proposal.finalizedtime()+proposal.graceperiod()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' }) tobeexecutedpropfiltered = tobeexecutedprop.filter(e => e != proposallist) } else { console.log(`Proposal ${proposallist} Execution is failed`) bot.sendMessage(chatid, `Proposalid (${proposallist}) failed to be executed at block ${proposal.finalizedtime()+proposal.graceperiod()}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' }) tobeexecutedpropfiltered = tobeexecutedprop.filter(e => e != proposallist) } tobeexecutedprop = tobeexecutedpropfiltered } } //forum const currentpost = await getcurrentPostId(api) const currentcat = await getcurrentCatId(api) const currentthread = await getcurrentThreadId(api) console.log(`Current block: ${block}, Latest postid: ${currentpost}, Latest categoryid: ${currentcat}, Latest threadid:${currentthread}`) //category forum checking if (currentcat>lastcatnotif){ for (lastcatnotif+1; lastcatnotif${categorytitle}, Category ID: ${lastcatnotif+1}`, { parse_mode: 'html' }) } } //thread forum checking if (currentthread>lastthreadnotif){ const newthread = [] for (lastthreadnotif+1; lastthreadnotif"${threadtitle}" by ${handler} (id:${memberid}) in category "${categorytitle}" `) } bot.sendMessage(chatid, newthread.join("\r\n\r\n"), { parse_mode: 'html' }) } //forum post checking if (currentpost>lastpostnotif) { console.log(currentpost-lastpostnotif, ' new posts') const newpost = [] //loop notification for every new post published since lastnotif for (lastpostnotif+1; lastpostnotif New post (id:${lastpostnotif+1}) by ${handler} (id:${memberid}) in category "${categorytitle}" at:\r\n"${threadtitle}"\r\n"${excerpt}..."\r\n`) } //console.log(newpost.join("\r\n\r\n")) bot.sendMessage(chatid, newpost.join("\r\n\r\n"), { parse_mode: 'html' }) //update lastnotif // lastpostnotif=currentpost } //channel const currentchannelid = await getcurrentChannelId(api) console.log('Latest channelid is :',currentchannelid) if (currentchannelid>lastchannelnotif) { const newchannel = [] for (lastchannelnotif+1;lastchannelnotifNew channel id created:${lastchannelnotif+1}\r\nChannel Title: ${channeltitle}\r\nMember ID: ${memberid}\r\nMember Handler: ${channelownerhandler}`) } bot.sendMessage(chatid, newchannel.join("\r\n\r\n"), { parse_mode: 'html' }) } //council if (block>lastcouncilnotif) { const councilround = await api.query.councilElection.round() const councilendterm = (await api.query.council.termEndsAt()).toNumber() const annperiod = (await api.query.councilElection.announcingPeriod()).toNumber() const votingperiod = (await api.query.councilElection.votingPeriod()).toNumber() const revealingperiod = (await api.query.councilElection.revealingPeriod()).toNumber() const councilstage = await getcouncilStage(api) const councilperiod = (await api.query.councilElection.newTermDuration()).toNumber() switch (councilstage){ case null: console.log('Council has been elected') if (block>lastcouncilnotif){ bot.sendMessage(chatid, ` New council for round ${councilround} has been elected at block ${councilendterm-councilperiod}.`, { parse_mode: 'html' }) lastcouncilnotif=councilendterm } break; default: const annstage = councilstage.Announcing const votingstage = councilstage.Voting const revealingstage = councilstage.Revealing if (annstage) { console.log('Announcing Stage') if (block>lastcouncilnotif){ bot.sendMessage(chatid, `New council election for round ${councilround} has been started at block ${annstage-annperiod}. You can apply now!`, { parse_mode: 'html' }) lastcouncilnotif=annstage } } if (votingstage) { console.log('Voting Stage') if (block>lastcouncilnotif){ bot.sendMessage(chatid, `Voting stage for council election has been started at block ${votingstage-votingperiod}. You can vote now!`, { parse_mode: 'html' }) lastcouncilnotif=votingstage } } if (revealingstage) { console.log('Revealing Stage') if (block>lastcouncilnotif){ bot.sendMessage(chatid, `Revealing stage for council election has been started at block ${revealingstage-revealingperiod}. Don't forget to reveal your vote!`, { parse_mode: 'html' }) lastcouncilnotif=revealingstage } } break; } } }) } //functions //proposals const getpendingProposals = async (api) => { let tobeexecutedprop = ((await api.query.proposalsEngine.pendingExecutionProposalIds()).toJSON())[0] if (tobeexecutedprop[0]==0){ return [] } else { return tobeexecutedprop } } const getactiveProposals = async (api) => { let activeproposals = ((await api.query.proposalsEngine.activeProposalIds()).toJSON())[0] if (activeproposals[0]==0){ return [] } else { return activeproposals } } const getmemberHandle = async (api,memberid) => { const memberprofile = await api.query.members.memberProfile(memberid) const handler = memberprofile.raw.handle.toJSON() return handler } const getproposalStatus = (propresultraw) => { if (propresultraw.hasOwnProperty('proposalStatus')) { return propresultraw.proposalStatus } else { return {Active:null} } } const getfinalTime = (propresultraw) => { if (propresultraw.hasOwnProperty('finalizedAt')) { return propresultraw.finalizedAt } else { return 0 } } const getproposalDetail = async (api,proposalcount) => { const propdetail = await api.query.proposalsEngine.proposals(proposalcount) const parameters = propdetail.parameters const propposterid = propdetail.proposerId.toJSON() const handler = await getmemberHandle(api,propposterid) const proptype = await api.query.proposalsCodex.proposalDetailsByProposalId(proposalcount) const [deftype] = Object.getOwnPropertyNames(proptype.toJSON()) const proptitle = propdetail.get("title") const propstage = propdetail.status.toJSON() // const propstatus = propdetail.get("status") const propstatus = Object.getOwnPropertyNames(propstage) const propresultraw = propstage[propstatus] const propfinalresultfull = getproposalStatus(propresultraw) // const propfinalresultfull = propresultraw.proposalStatus // const propfinalresultjson = Object.getOwnPropertyNames(propresultraw.proposalStatus) const propfinaltime = getfinalTime(propresultraw) // const propfinaltime = propresultraw.finalizedAt const propfinalresultjson = Object.getOwnPropertyNames(propfinalresultfull) const graceperiod = propdetail.parameters.gracePeriod.toNumber() return { detail : function () { return propdetail; }, parameters : function () { return parameters; }, stage : function () { return propstatus; }, finalizedtime : function () { return propfinaltime; }, graceperiod : function () { return graceperiod; }, resultfull : function () { return propfinalresultfull; }, resultjson : function () { return propfinalresultjson; }, postmessage : function () { return `Type: ${deftype}\r\n Proposer: ${handler}(${propposterid})\r\n Title: ${proptitle}\r\n Stage: ${propstatus}\r\n Result: ${JSON.stringify(propfinalresultfull)}`; // postmessage : function () { // return `Type: ${this.deftype()}\r\n Proposer: ${this.handler()}(${this.posterid()})\r\n Title: ${this.title()}\r\n Stage: ${this.stage()}\r\n Result: ${this.result()}`; } } } //forum const getcategoryTitle = async (api, categoryid) => { const category = await api.query.forum.categoryById(categoryid) const categorytitle = category.title return categorytitle } const getcurrentPostId = async (api) => { const nextpostid = await api.query.forum.nextPostId() const currentpostid = nextpostid.toNumber()-1 return currentpostid } const getcurrentThreadId = async (api) => { const nextthreadid = await api.query.forum.nextThreadId() const currentthreadid = nextthreadid.toNumber()-1 return currentthreadid } const getcurrentCatId = async (api) => { const nextcatid = await api.query.forum.nextCategoryId() const currentcatid = nextcatid.toNumber()-1 return currentcatid } //channel const getcurrentChannelId = async (api) => { const nextchannelid = await api.query.contentWorkingGroup.nextChannelId() const currentchannelid = nextchannelid.toNumber()-1 return currentchannelid } //council const getcouncilStage = async (api) => { const councilstage = await api.query.councilElection.stage() const councilstagejson = councilstage.toJSON() return councilstagejson } main()