Tuesday, December 08, 2009 by Tim Geyssens

In todays chapter (notice that it's 2.0.8 , yesterdays should have been 2.0.7, small mistake by santa's magic elf) we'll be improving Blog 4 Umbraco to include some spam protection. We'll be checking the comments against the Akismet spam filtering service. But since Akismet isn't the only spam filtering service we'll be building a simple provider model to make it easy to add support for whatever service you want to use.

Before you can use Akismet you need to have a valid API Key ( You can get a free API key by registering for a user account here). And we'll also need to store this API Key, since Akismet will be set as the default on new Blog 4 Umbraco installations we'll just store this on the blog node. So I'll need to add a new property on the Blog document type.

image

After adding the new property I'll be able to set the API Key on the Blog document.

image 

Now I'm ready to start using Akismet. I'll first start by setting up a simple provider model. The way these spam filtering services work is that you feed them all the comment info and they return whether or not the comment is considered spam.

So I'll define an interface that has a Check method with all the comment info (useragent, ip, author, author email, author url and the actual comment).

public interface ISpamChecker
    {
        String ProviderName { get; set; }

        Boolean Check(int nodeid,
            string UserAgent, string UserIp, string Author,
            string AuthorEmail, string AuthorUrl, string Content);
    }

Next I'll add an abstract class that implements the new interface

public abstract class SpamChecker: ISpamChecker
    {
        #region ISpamChecker Members

        private string m_providername;
        public string ProviderName
        {
            get { return m_providername; }
            set { m_providername = value; }
        }

        public virtual bool Check(
            int nodeid, string UserAgent, string UserIp,
            string Author, string AuthorEmail, string AuthorUrl, string Content)
        {
            return false;
        }

        #endregion
    }

And then I'll add the actual Akismet spam checker class (that inherits from the abstract class and overrides the check method). There's allready an open source .net wrapper for the Akistmet service so I'll make use of that.

 

public class AkismetSpamChecker: SpamChecker   {      

       public AkismetSpamChecker()
       {
           this.ProviderName = "Akismet";
       }

       public override Boolean Check(int nodeid,
           string UserAgent, string UserIp, string Author,
           string AuthorEmail, string AuthorUrl, string Content)
       {

           umbraco.presentation.nodeFactory.Node blog =
               new umbraco.presentation.nodeFactory.Node(nodeid);

           while (blog.NodeTypeAlias != "Blog")
           {
               blog = blog.Parent;
           }

           if (blog.GetProperty("akismetAPIKey").Value != string.Empty)
           {               
               Akismet api = new Joel.Net.Akismet(
                   blog.GetProperty("akismetAPIKey").Value,
                   "http://" + HttpContext.Current.Request.ServerVariables["HTTP_HOST"] + blog.Url,
                   "Blog4Umbraco2");

               if (!api.VerifyKey())
               {
                   umbraco.BusinessLogic.Log.Add(
                       umbraco.BusinessLogic.LogTypes.Error,
                       -1,
                       "Akismet Key could not be verified, please check if you have a valid Akismet API Key");

                   return false;
               };

               AkismetComment comment = new AkismetComment();
               comment.UserAgent = UserAgent;
               comment.UserIp = UserIp;
               comment.CommentType = "comment";
               comment.CommentAuthor = Author;
               comment.CommentAuthorEmail = AuthorEmail;
               comment.CommentAuthorUrl = AuthorUrl;
               comment.CommentContent = Content;

               return api.CommentCheck(comment);
           }
           else
           {
               return false;
           }
       }

   }

 

So in the check method I'll first see if we have a valid API key (using the nodefactory to get it of the blog document), if that's ok I'll return the value of the CommentCheck function.

The final step is to actually call this check method when a new comment is submitted.

Since we want to make it possible to plug in your own spam checker I'll add a little config file.

<?xml version="1.0"?>
<SpamChecker assembly="/bin/Umlaut.Umb.Blog" type="Umlaut.Umb.Blog.Spam.AkismetSpamChecker">
</SpamChecker>

 

That contains the assembly and the type of the spam checker class that will be used (default we'll use the Akismet one).

By using some reflection we'll call the Check method on the correct Type (that's setup in the config file).

bool isspam = false;

           try
           {
               string assemblyFile =
                   HttpContext.Current.Server.MapPath(
                   String.Format("{0}/..{1}.dll",
                   GlobalSettings.Path,
                   Config.GetProviderAssembly()));

               Assembly checkerAssembly = Assembly.LoadFrom(assemblyFile);

               SpamChecker checker =
                   (SpamChecker)Activator.CreateInstance(
                   checkerAssembly.GetType(Config.GetProviderType()));

               isspam = checker.Check(parentId,
                   post.UserAgent, post.UserHostAddress,
                   name, email, website, comment);
           }
           catch (Exception ex) { }

And we'll update the insert statement from yesterdays chapter to store the value of isspam instead of always storing false.

That's it, our comments will now by checked by Akismet and if they are seen as spam they won't appear on the blog (since we only display the comments that aren't flagged as spam).

5 comment(s) for “Blog 4 Umbraco 2.0.8 – Tackling comment spam”

  1. Gravatar ImageWarren Buckley Says:

    This is great work. I use the exact same wrapper library for my comment form currently.

    How are you handling false positives?

    As the askimet wrapper supports submitting an item as ham when its been marked as spam and vice versa.

    It would be good to roll that out in a dashboard maybe, if comments are stored in this external database?

    Warren :)

  2. Gravatar ImageTim Geyssens Says:

    @Warren, we'll see that in a future chapter. It will be possible to manage the comments and submit ham back to the service.

  3. Gravatar ImageLee Kelleher Says:

    Excellent! Loving the Xmas Calendar series so far... looking forward to the comment/spam management chapter!

  4. Gravatar ImageJason Says:

    Any chance you guys could address a couple of publishing issues?

    1. If I change the publish date, it stays in the old date folder.

    2. Can't publish to a past date. Can't remember now, but it may not have worked for a future date either...

    3. Any way to get scheduled publishing?

    ----------------

    Also, any chance there will be anything about importing from blogger or WordPress?

  5. Gravatar ImageJason Says:

    BTW, I love the series you've been running as well!

    I love the tutorials and the functionality. Such a great idea!

    Thanks!

    Can you tell I'm excited ;)

Leave comment:


Brilliant umbraco hosting provided by FAB-IT