Whether you love them or hate them, a big part of social media is the hive of bots ready to help, entertain, and inform. I love them as they typically offer a wide variety of choices, and they allow you to explore the more “creative” side of software development. At this point, I’ve built two Twitter bots: @GuidGenie and @TiredManhattan. Each both with a unique purpose and charm. I’ve learned a lot about what to do and what not to do when building bots, with TiredManhattan being the better of the two projects.

In this post, I’ll show you the trick I used to build a well-behaved bot that respects your users and saves you from processing unnecessary requests.

What Is GUID Genie and Tired Manhattan?

Modeled after the Genie from Aladdin, GUID Genie is a bot that, when mentioned, generates a unique GUID for you. Currently, the implementation is very crude, and he will continue to create GUIDs as long as you include him in a Twitter thread. However, even with the best modern tools, you sometimes need a GUID in the most awkward circumstances. Well, GUID Genie can help. What about the other bot?

The character of Dr. Manhattan is a tragic figure in Alan Moore’s seminal graphic novel, Watchmen. For intents and purposes, he is a man who receives the powers of a god, allowing him to see through space and time while also having seemingly unlimited powers to affect all those around him. However, while receiving his powers, he loses what it means to be human, growing tired of the Earth and its inhabitants, and in an attempt to get away, travels to Mars, where he utters the famous phrase “I am tired of Earth. These People. I am tired of being caught in the tangle of their lives.”

Tired Manhattan

The comic panel has become a popular meme on the internet, and I thought it would make for an excellent image generator bot. So you tell @TiredManhattan what you’re tired of, and the bot echoes back a custom image just for you. It’s a fun project in a currently dark social media landscape.

Try them out for yourself. You’ll find that Tired Manhattan works a bit better, only responding once and only once to any tweet, where the GUID Genie will be as annoying as his Disney counterpart. So, what’s the difference?

Twitter Bot Rules

Note: The code samples use TweetinviAPI, but you can adapt the rules to other Twitter API libraries.

There are a few rules I ultimately want all my bots to follow:

  1. Do not respond to profanity or vulgar phrases (as much as is possible)
  2. Only respond when directly mentioned.
  3. Only respond once to the initial user request.

The way to accomplish this was surprisingly challenging to uncover, but hopefully, this post will ease your attempts at building a bot.

The first thing you’ll need to do is make sure your Filtered Stream listens for mentions. The rule is straightforward, typically using the bot’s screen name.

await twitterClient.StreamsV2.AddRulesToFilteredStreamAsync(
    new FilteredStreamRuleConfig($"@{_user.ScreenName}", "mention"));

Whenever someone mentions the bot, Twitter will notify us, but remember, we don’t want to respond every time. So before listening to the filtered stream, we need to inform Twitter of the information we’d like pushed to us. In my case, I chose to get all Tweet Fields, User Fields, and Expansions. These pieces of information include attached media, users mentioned, and other vital data. We need this information to make our rules work.

await _stream.StartAsync(new StartFilteredStreamV2Parameters
    TweetFields = new TweetFields().ALL,
    UserFields = new UserFields().ALL,
    Expansions = TweetResponseFields.Expansions.ALL

The first rule in our responder is to make sure we have a tweet. Sometimes the Twitter API will send you events unrelated to user interaction.

private async void Received(TweetV2EventArgs args)
    if (args.Tweet is null)
        _logger.LogInformation("Not a tweet: {Information}", args.Json);
   // more code later...

The most important rule is ensuring that the tweet is the first interaction with our bot. We can do this by checking that the conversation identifier is the same as the tweet identifier. We don’t want to reply if the two identifiers are different.

tweet.ConversationId.Equals(tweet.Id) == false

Then, we check to see if there are any images on the tweet. The check is for safety measures. We may have already replied if there are images attached to the tweet. We can only check attachments because we previously requested All the tweet fields.

tweet.Attachments?.MediaKeys?.Any() == true 

Finally, the tweet should be separate from any other tweet, meaning we shouldn’t have any referenced tweets.

tweet.ReferencedTweets?.Any() == true

Putting all the rules together, we get this code block.

if (
    tweet.ConversationId.Equals(tweet.Id) == false ||
    tweet.Attachments?.MediaKeys?.Any() == true ||
    tweet.ReferencedTweets?.Any() == true
    _logger.LogInformation("Ignore conversations, as they can get noisy");

To handle profanity, I used the ProfanityDetector library found on NuGet. It works well but be careful that it does have the occasional false-positive meaning it may detect things as profane when they’re perfectly harmless.

var text = tweet.Text.Replace($"@{_user.ScreenName}", "").Trim();
// I'm not dealing with this s#@$!
if (_profanityFilter.ContainsProfanity(text))
    _logger.LogInformation("Filtered out {Text} from {@From}", text, mentions);

Once the tweet has passed all our rules, it is free to respond with a custom-generated image like the following one below.

Tired Manhattan is tired of documentation


Twitter bots take a little bit of work to get just right, but once you understand some of the data you have access to, you too can change the behavior of your bots. If you’re interested in seeing the code for GUID Genie and Tired Manhattan, you can find them at the following GitHub repositories:

As always, thanks for reading and sharing my posts. I genuinely appreciate it.