April 2023 - Present
Python Flask Fly.io HTML CSS JavaScript Websockets

There is a little gnome sitting behind the screen, typing up motivational messages for you. Sometimes, he will make mistakes. Maybe more often than “sometimes”. You do not want to know what happens when he makes too many mistakes.

Can I’mAHuman solve a CAPTCHA?

Yes! Well, no. What he can do is type like a human and feel bad when nobody is reading his motivational messages. After all, he is a human.

There is a little gnome typing this up in the backend.

The cool thing about I’mAHuman is that every device that has the site open will see the exact same thing, including synchronized blinking of the cursor. I’mAHuman will also “mash the keyboard” (and promptly apologize) when he makes too many mistakes in quick succession.

At least he apologized?

In between messages, he will spit out a little quirk, occasionally flirting with the site visitor.

Maybe the subsequent message should match the tone of the quirk...

The “cursor” is actually a Unicode character, called “Left Seven Eighths Block”. All of the blinking is manually controlled, with each delay and frequency carefully calibrated through tedious trial and error. The backspace function is similarly configured, with a slight delay after the first character deletion to simulate someone holding down the backspace key (try it with your keyboard!). The setting is called the “Repeat delay” on Windows and “Delay until repeat” on Mac, respectively.

Let’s get technical

I’mAHuman lives in a Flask program on a Fly.io app (business as usual). Each motivational message is pulled from a text file and stored in a stack of length 10 to avoid repeating any of the 10 most recently outputted messages. The same exact process also applies to quirks.

Each character is outputted with a slight delay that targets a WPM (words per minute) of 180, not too fast to be terribly unrealistic but not too slow where the visitor gets bored. Each character delay has an additional randomly changing coefficient, ranging between -0.7 and 0.5, intended to remove uniformity in typing style. Now that I think about it, the disbalance towards the negative side likely indicates that I need to increase the target WPM.

There’s also a variable delay after punctuation, depending on the specific mark. There’s also randomness in the length of the mistakes and mashes, the characters that go into the mistake, when a mistake is triggered (a 3.5% probability on any given character), and the specific messages that are selected.

The exact text that is to be displayed on screen (including the cursor) is stored in a variable. This allows for easy text modification, such as character deletion and keyboard mashing through the usage of an auxiliary variable that tracks the number of characters added to said variable. We can display up-to-date text at will, typically immediately after there is an update.

Brace yourself for what’s coming next. You might have to read this through your fingers. In an earlier, local iteration, the displayOutput() function would just output the text to the terminal, but since I’mAHuman is a program in the cloud now, there needed to be some sort of connectivity factor. For the upgrade, I settled on Websockets. Yes, each message is transmitted via a websocket connection to the Flask backend. Each character is blasted through the interwebs and onto your screen, like, 14 times a second during normal typing and up to 60 times a second during keyboard mashes. I know. Read up how I feel about this implementation here.

Motivation

In April 2023, I had written a program that spat out motivational messages in a realistic typing manner into the terminal (seem familiar?). It would output like a million newlines to make it seem like it was updating the text rather than outputting the entire message to the terminal. I also tried to port it to a microcontroller to be able to display the messages on a tiny screen, but that ultimately failed due to RAM limitations of the device and my desire for a responsive, high-performance, yet inexpensive, microcontroller. I guess I could opt for a Raspberry Pi, but that was not under consideration at the start.

Now, while the project hasn’t matured a lot past that point, I wanted to be able to show it off because I was incredibly proud of it. Most of the underlying logic for the typing simulation remains the same — the biggest changes lie in the display of the output.

A feature I can no longer demonstrate is the program’s ability to get frustrated if interrupted (killed) while typing, leaving an angry message upon the next startup. Since there is no such thing as getting “interrupted” now, this feature has been removed. A large focus during the development was, well, the human aspect of the program. The program had “feelings”, he was “emotional”. A future upgrade could include running a classifier on the current message to determine its mood or tone. This would be reflected through a gentle pulsing/breathing effect of the background color, which is dictated by the deduced mood.

So, does I’mAHuman live up to its standards, ideologies, goals, and motivations?

Verdict:
No

It really hurts to say this because of how much time I’ve poured into the project, but I’mAHuman did not live up to my expectations. Although I wouldn’t change anything visually (aside from the planned breathing effect), design-wise or anything concerning the typing logic, there is a large and much-needed change for the backend that must be implemented. Unfortunately, the real-time typing effect becomes unreliable on slower networks.

I have already planned a somewhat sophisticated rework that utilizes the recording of the random seeds used throughout the program, calculation of typing progress and sync points, balancing of optimistic and pessimistic storage behavior, and more, all just to maintain the synchronized typing effect while significantly improving performance across all networks. While these changes are planned, as of the time of writing I do not have a set implementation date as I am currently prioritizing other projects.