Wednesday, 24 January 2007

Why spam a blog?

This has to be the lowest bottom-feeding trick in the book. As soon as the URL for my blog shows up on a search engine, I get comment spam. Do these dickheads actually think we're going to follow their links and buy into their retarded, broken English pitches?

So, fine. I close off comments to everybody but registered users. "But you could use spam filters!". I could, and in different circumstances, I probably would. Let's jump to a different part of the story for a moment.

I still have the first email address I ever had; it's been something like a dozen years. Back in those days, it was perfectly reasonable to post your email address on your web site or in newsgroups. Sometime between then and now, the spammers figured out that web pages and newsgroups were a good source of email addresses, so we all removed our email links and started using fake addresses on the newsgroups. I expect I still get a spam now and again due to some spider hitting The Wayback Machine.

That probably wouldn't be so bad, but like an idiot, I used that address to sign up for an MSN account. What the hell was I thinking? Either Microsoft sold their list, or somebody managed to access it, because since then my rate of spam emails has skyrocketed. At last estimate, it's about 75 a day. Ack.

Sure, the junk filters work great, but it's an inexact science. Software has bugs and spammers change their tactics. So while 95% of the spam gets binned, I still have to mark a few "legitimate" emails as spam, and (rarely) I have to rescue a binned message and mark it as not spam. So the only real difference between using a spam filter and not, is that now I have to check all the messages in two folders instead of one. How is this saving me any time?

Back to the blog. The concept is exactly the same; I could use filters, but I still have to check every post, just in case something legitimate gets binned, or something illegitimate doesn't. I'm told that many Captcha implementations have been defeated, and with that kind of AI it's a good bet that any similar sort of scheme will be beaten quickly enough. Screw it, I have better things to do - I'll just lock them out and be done with it. Cracking long passwords is still out of the reach of mere mortals, and the spammers won't bother - there's still plenty of people out there so desperate for attention that they leave their blogs open.

Follow the money.

Building a bot, part... ah, screw it

I've been pounding on the "database-centric" portions of my new bot. It wasn't quite as simple as I thought it was going to be, but it is coming together pretty well. I found a MySQL API bug along the way, so I'm pretty pleased with myself.

It's a good thing I upgraded to MySQL 5.0, as there's a feature I absolutely had to have to pull this off in a reasonable manner: multiple statement execution. The idea is that I want to execute a bunch of statements with a single call to mysql_real_query() so that I can pull a single line - containing multiple queries - from the database and just execute the whole business at once. It's a lot less back-and-forth, and allows me to say, "OK, here's a bunch of statements. Execute these and just give me the result of the very last call".

The API bug I mentioned earlier is that if you have multiple statements and any but the last query returns a result, the final call to mysql_more_results() will crash. The manual recommends the use of mysql_next_result() instead, which is fine unless you don't want to iterate past the last result set, which I don't. I'm hiding all these hideous details in a separate class, and I want the results from the last query to be available once the query is done executing, and mysql_more_results() lets me do this, but mysql_next_result() doesn't. Anyhow, I came up with a reasonably elegant workaround, so all is well with that.

I've discovered the joys of SQL variables. I wanted the database to do all the work, allowing me to add, remove and fix features without having to recompile & restart the program. However, it got a little tricky at some points that require me to insert or update before selecting any rows, and you you simply can't do that with joins and subqueries. So you save in variables whatever you would have in code, and Bob's your uncle.

Regex is always your friend. Without regex matching, I'd have to execute all queries for any given event and then undo anything that shouldn't have been done in the first place. Instead, I can "SELECT query FROM events WHERE 'channel message' REGEXP `msg_match`".

Most of the previous bot's functions have been replicated in SQL, so I've chucked him and the new code will be running full time now. He's not quite ready to replace the old eggdrop, but I'm reasonably happy with the progress so far. I've still got 17 or 18 IRC "events" to cover in code before I can start writing queries for them, but the really hard ones are done, and I've learned most of the gotchas now. So it should go quickly (in theory) if I can stop writing queries for the implemented events.

Saturday, 20 January 2007

Building a bot, part tres

After much study and some soul-searching, I've decided to cut the scripting engines from the nibblebot. It pains me to do so, but there's a couple good reaons:

  • The Perl intepreter adds about 1MB to the runtime binary, and

  • there's no way to implement a truly persistent interpreter, so you'd have to fork() a lot.


When you put those two things together, it means that you'd potentially have a fork() and then a bunch of database activity for every incoming line of IRC text, and it's just too heavy and slow.

So, my approach will be a little different. I've pretty much covered all the RFC-defined stuff in a base class now, (class Bot), so now I'm going to extend that class (class NibbleBot), and mostly all it will do is consult the database for what to do next. The idea is this: set up a "scripts" table that can act as an interpreter of sorts.

We'd have an `event` column, in which you'd have things like 'onJoin', 'onPart', 'onChanMsg', 'onKick' and so forth. The next column would contain a query that the bot should execute after substituting the appropriate information in special "macros"; for instance, perhaps you'd have something like "SELECT * FROM `users` WHERE `name` = $NICK", and $NICK would be replaced by the nickname of the user who uttered the line that activated this "script".

Next column over would contain the `action`, i.e. where the result from the query (if any) should be directed (or not). So perhaps we have an enum like, 'chanMsg', 'op', 'topic', etc, and if the query returned any results, the bot would do the appropriate thing with the resulting rows. Add on a final row - `priority` - which would dictate the order in which the bot should execute multiple scripts for a particular event, and you could have the bot doing all sorts of stuff when something interesting occured.

I think this will work for most things; there may be a few special cases, but I think for the most part everything I want to do can be accomplished in this manner. So in theory, once I have all the core code for this in place, I'll still be able to re-program the bot on the fly. Good for me.

Tuesday, 2 January 2007

Vacation is a two-headed beast

Happy New Year. Are you all settled back into your job, rested and ready to hit the ground running? Me neither.

Taking a week or more off work really screws with my head. Everybody needs a vacation now and again or they'll find themselves on the roof of a water tower with a high-powered rifle, and I'm no different. However, it's always an uphill battle to get back to "real life". I'm a slave to my habits, and as soon as I deviate from my usual routine I have to create new habits to adapt. Good survival technique, but it always ends with me having to go back to work before I've adapted back to my normal routine.

So I'm laying in bed this morning thinking, "Do I really need this job? No really, do I? Do I really?" Obviously good sense won out, as I did eventually show up to the office, but it wasn't early and it wasn't easy. January sucks.