@DADIHelperBot started out as a pretty basic Node.js application utilising Telegraf to interact with Telegram’s Bot API. Between the 15th January and the 28th Febuary there were 94 commits to the bot’s codebase, all relatively substantial in size.

The first build did very little. It offered a few slash commands, including /admins which, as you’d expect, listed the current group admins. This might seem trivial, but by that point in time we’d already had a handful of reports of members being messaged by fake admins using our names and profile pictures in attempts to lure innocent participants into handing over Ethereum to unofficial wallet addresses. By adding the command, and advising members to only initiate conversations with admins by clicking on their names in the list, we hoped to reduce the number of instances of fraud.

The bot started blocking all images, videos and audio messages as well as bad language and Ethereum addresses. This started out as a couple of simple regular expressions:

const abusiveWord = /(fuck|shit|twat|bollocks|farage)/gi

const hexRexExp = /(\[0|O\]x)?\[a-fA-F0-9\]{40}/gmi

As admin fraud increased, we realised that a lot of the fake admins were entering the channel, gathering a handful of active users and exiting unnoticed. We puzzled over how best to automate the identification and removal of scammers, and settled on a rather complex routine of image matching. Using the new_chat_members event handler, the bot would now download the entire historic profile image library for a user, and compare those images to those of our community admins. We tried a few image comparison libraries before choosing JIMP. Now that the bot was able to detect and kick based on images, we topped it off with a little name comparison detection. For this, we used string-similarity.

As things heated up towards the start of the sale we began to see an increase in repetitive spamming, prompting a fresh requirement to throttle messages. Whilst it’s often difficult for admins to read, we didn’t want to stop someone splitting their questions over a few lines so the bot had to be smart enough to know when a single user was repeating the same message. Storing each and every message against each user has some serious overheads, especially during the sale period where 1,000+ messages per second were reaching the channel.

Using Node.js’s built-in cryptographic functionality along with Redis, we introduced a session storage mechanism to persist an md5 hash of each unique user ID and message body. At the point of message delivery, each hash total count was incremented up until a throttle limit breach, at which point the user was temporarily restricted. To avoid throttling some common responses that a user might repeat a lot over the duration of the sale, such as ‘OK’, a mean interval was added to each record.

As we moved towards the start of the public sale and messages from the admins were frequently being missed, the @DADIHelperBot gained another new feature: announcements. Admins were now able to call the /announce slash command, followed by a Markdown-formatted message to be repeated at interval until /announce stop was called.

When the email service was compromised and an email with a fraudulent address went out, we took to every channel we could to issue warnings in an effort to contain the issue. Prior to this incident we had regularly reminded the community to only use the contract address found within the profile pages on https://dadi.cloud. However when the channel began to react to the compromise we again employed the services of the announcements feature.

Finally, we introduced one of the most powerful commands the bot had: /mute , to give admins complete control over the channel until the FUD ended.

bot.context.mute = {

muted: (status) => {

bot.context.mute.status = status

}

} bot.command('mute', ctx => {

if (this.isFromAdmin(ctx.update.message.from.id)) {

ctx.mute.muted(true)

}

}) bot.command('unmute', ctx => {

if (this.isFromAdmin(ctx.update.message.from.id)) {

ctx.mute.muted(false)

}

}) // Mutes all messages

bot.on('message', (ctx, next) => {

const isFromAdmin = this.isFromAdmin(ctx.update.message.from.id) if (isFromAdmin) return next() if (bot.context.mute.status) {

ctx.telegram.deleteMessage(ctx.chat.id, ctx.update.message.message_id)

.then(() => {})

.catch(err => {})

}

})

We learned a lot about community management during the Crowdsale period, especially the importance of making sure that the right messages were being delivered, no matter how busy things got.

The @DADIHelperBot was certainly important in the process, but the real heroes and heroines were our wonderful community admins, who remained professional, courteous and friendly at all times.