123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- 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;proposalcount<currentproposal;proposalcount++) {
- const proposal = await getproposalDetail(api,proposalcount+1)
- const propcreatedtime = proposal.detail().createdAt.toJSON()
- console.log(`New proposal (${proposalcount+1}) created at block ${propcreatedtime}.\r\n ${proposal.postmessage()}`)
- bot.sendMessage(chatid, `New proposal (${proposalcount+1}) created at block ${propcreatedtime}.\r\n ${proposal.postmessage()}`, { parse_mode: 'html' })
- activeproposals.push(proposalcount+1)
- }
- }
- if (activeproposals[0]>0) {
- 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 <b>"Finalized:${propstatus[0]}"</b> 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 <b>"Finalized:${propstatus[0]}"</b> 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}) <b>has been executed</b> 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}) <b>failed to be executed</b> 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<currentcat; lastcatnotif++){
- const categorytitle = await getcategoryTitle(api,lastcatnotif+1)
- console.log('Notify category',lastcatnotif+1, 'to Telegram')
- 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' })
- }
- }
-
- //thread forum checking
- if (currentthread>lastthreadnotif){
- const newthread = []
- for (lastthreadnotif+1; lastthreadnotif<currentthread; lastthreadnotif++){
- const threadid = await api.query.forum.threadById(lastthreadnotif+1)
- const threadtitle = threadid.title
- const currentcategory = threadid.category_id
- const categorytitle = await getcategoryTitle(api,currentcategory)
- const authoraddress = threadid.author_id.toJSON()
- const memberraw = await api.query.members.memberIdsByRootAccountId(authoraddress)
- const memberid = memberraw[0].toNumber()
- const handler = await getmemberHandle(api, memberid)
- console.log('Notify thread',lastthreadnotif+1, 'to Telegram')
- //sent to array
- 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>" `)
- }
- 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<currentpost; lastpostnotif++) {
- //begin chaining query info
- const postbyid = await api.query.forum.postById(lastpostnotif+1)
- const postpos = postbyid.nr_in_thread
- const message = postbyid.current_text
- //limit characters for message on telegram
- const excerpt = message.substring(0,100)
- const currentthreadid = postbyid.thread_id.toNumber()
- const threadid = await api.query.forum.threadById(currentthreadid)
- const threadtitle = threadid.title
- const currentcategory = threadid.category_id
- const categorytitle = await getcategoryTitle(api,currentcategory)
- const authoraddress = postbyid.author_id.toJSON()
- const memberraw = await api.query.members.memberIdsByRootAccountId(authoraddress)
- const memberid = memberraw[0].toNumber()
- const handler = await getmemberHandle(api, memberid)
- console.log('Notify post',lastpostnotif+1, 'to Telegram')
- //sent to array
- 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`)
- }
- //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;lastchannelnotif<currentchannelid;lastchannelnotif++) {
- const channel = await (await api.query.contentWorkingGroup.channelById(lastchannelnotif+1)).toJSON()
- const channeltitle = channel[0].title
- const memberid = channel[0].owner
- const channelownerhandler = await getmemberHandle(api, memberid)
- console.log(`Posting channel id: ${lastchannelnotif+1} to Telegram`)
- 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>`)
- }
- 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, `<a href="https://testnet.joystream.org/#/council/members"> New council for round ${councilround}</a> 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}.<a href="https://testnet.joystream.org/#/council/applicants"> You can apply now!</a>`, { 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}. <a href="https://testnet.joystream.org/#/council/applicants">You can vote now!</a>`, { 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}. <a href="https://testnet.joystream.org/#/council/votes">Don't forget to reveal your vote!</a>`, { 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 `<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)}`;
- // postmessage : function () {
- // 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()}`;
- }
- }
- }
- //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()
|