Browse Source

Merge pull request #14 from bwhm/telegram-bot-merge

merged bots into one
Martin 4 years ago
parent
commit
d4b4885c4b
1 changed files with 381 additions and 0 deletions
  1. 381 0
      community-contributions/joystreamtelegrambot/allbots.js

+ 381 - 0
community-contributions/joystreamtelegrambot/allbots.js

@@ -0,0 +1,381 @@
+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()