Comments
Spam & Akismet

May 26

Spam, spam, spam. Something we have to deal with eventually when we allow people to make comments on our website. Akismet offers a good service for identifying spam comments for you. In this post I have a look at implementing it in Sitefinity and customising the comments module.

This is an update to an old post I did on the same topic.

It is not uncommon for clients to have in their brief a desire to have Facebook integration and/or comments on their blog posts. The first thing I usually do is talk about dealing with the potential negative comments that may be submitted. The second, is the inevitable submission of spam.

When you are targeted by spam its often not just one or two but 12 every day and it turns comment management into a full time job. Good news is that there is a service out there, Akismet, that will parse comments and let you know if they consider it spam or not. This then allows you to avoid displaying it on your site. In this article I will look at adding this service and general customisations of the comments service in Sitefinity.

Customise Comments

Let's start with something you may already know. To customise the comments widget, start by getting a copy of it from the Bootstrap4 theme at /resourcepackages/bootstrap4/Mvc/Views/Comments/Comments.Default.cshtml. Here you can look to alter the HTML to work with your HTML and site styles. Drop it into your own theme, /resourcepackages/my-site-theme/Mvc/Views/Comments/Comments.Default.cshtml for it to override Sitefinity's version. Take note at the bottom of the file, a reference to the JavaScript file. Later on we will look to customise this a bit.

@Html.Script(Url.WidgetContent("Mvc/Scripts/comments-list.js"), "bottom"false)

Dealing with the spam

Sitefinity did offer instructions on how to include Akimist into your Sitefinity project. Awesome! But that was for an older releases and the link to the .NET 2.0 Akimist project is so old it is now archived to internet history. Stink!

Still, if you go to the Akismet site you will see it is a really simple API to implement. The primary call is to https://your-api-key.rest.akismet.com/1.1/comment-check. You need to make a POST call to this API passing along the comment and a range of other parameters to help Akismet decide if the comment is spam or not.

You need to sign up to Akismet and get an api key before you can use the service.

The best way to intercept a comment being created in Sitefinity is in the ICommentCreatingEvent event. Here is ideal to make a call to Akismet to know if it is most likely spam or not. If Akismet tells us this comment is spam we can mark the comment as such as we save it.

eventInfo.Item.Status = StatusConstants.Spam;

I'll present some code soon but first I will go through all the events we are going to consider using.

When this comment is marked as spam it will not show in the comments list and is marked as such in the back-end comments module.

Teaching Akismet

Akismet is good but not perfect. If Akismet marks a comment as spam but we consider it OK, we can change the status of it to published in Sitefinity. Also, if a comment gets through but we consider it to be spam we can change the status to spam. We can help train and improve the Akismet service by letting it know when it gets things wrong. We can intercept this change in the ICommentUpdatingEvent. If we are approving a comment from spam to published we tell Akismet that this is ham by calling the submit-ham method while if we mark a published comment as spam then we call the submit-spam method.

Ham? Coined about 2001 to be the opposite of spam. A message that is considered OK.

Being Notified

Another helpful function may be to be notified when a comment is made. When a comment is made you may want to reply to the comment or alter its status of spam or ham. A good place to do this is in the ICommentCreatedEvent. In this event we can send a notification to the blog owner that a comment was posted on their blog.

POST'ing to Akismet

When in our event handlers, what do we need to do to submit a comment to Akismet? You can read about the parameters you need and can send at the Comment Check method. Here is some sample code using a simple web request. Note the CustomData field. A look at this later on in the post.

private static String PostToAkismet(String apiActionString authorIpString authorNameString authorEmailString messageString userAgent = ""String permalink = "")
{
    PdrCommentsConfig config = Telerik.Sitefinity.Configuration.Config.Get<PdrCommentsConfig>();
 
    String details = String.Empty;
    details += "blog=" + config.Comments.Website;
    details += "&user_ip=" + authorIp;
    details += "&comment_type=comment";
    details += "&comment_author=" + authorName;
    details += "&comment_author_email=" + authorEmail;
    details += "&comment_content=" + message;
    details += "&is_test=" + config.Comments.TestMode;
 
    if(!String.IsNullOrEmpty(userAgent))
    {
        details += "&user_agent=" + userAgent;
    }
 
    if (!String.IsNullOrEmpty(permalink))
    {
        details += "&permalink=" + permalink;
    }
 
    Byte[] data = Encoding.ASCII.GetBytes(details);
 
    WebRequest request = WebRequest.Create(String.Format("https://{0}.rest.akismet.com/1.1/{1}"config.Comments.AkismetApiKey, apiAction));
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";
    request.ContentLength = data.Length;
    using (Stream stream = request.GetRequestStream())
    {
        stream.Write(data, 0, data.Length);
    }
 
    using (WebResponse response = request.GetResponse())
    {
        using (Stream stream = response.GetResponseStream())
        {
            using (StreamReader returnData = new StreamReader(stream))
            {
                return returnData.ReadToEnd();
            }
        }
    }
}

In my Creating event.

public static void CreatingEventHandler(ICommentCreatingEvent eventInfo)
{
    var context = SystemManager.CurrentHttpContext;
    String pageUrl = "";
    String userAgent = context?.Request?.UserAgent;
 
    if (!String.IsNullOrEmpty(eventInfo.Item.CustomData))
    {
        pageUrl = eventInfo.Item.CustomData;
    }
 
    String result = PostToAkismet("comment-check", 
        eventInfo.Item.AuthorIpAddress, 
        eventInfo.Item.Author.Name, 
        eventInfo.Item.Author.Email, 
        eventInfo.Item.Message,
        userAgent, 
        pageUrl);
 
    if (Boolean.TryParse(resultout Boolean isSpam))
    {
        if (isSpam)
        {
            eventInfo.Item.Status = StatusConstants.Spam;
        }
    }
}

And handling my Updating event.

public static void UpdatingEventHandler(ICommentUpdatingEvent eventInfo)
{
    if (eventInfo.OriginalItem.Status == StatusConstants.Spam 
        && eventInfo.Item.Status != StatusConstants.Spam)
    {
        PostToAkismet("submit-ham", 
            eventInfo.Item.AuthorIpAddress, 
            eventInfo.Item.Author.Name, 
            eventInfo.Item.Author.Email, 
            eventInfo.Item.Message);
    }
    else if (eventInfo.OriginalItem.Status != StatusConstants.Spam 
        && eventInfo.Item.Status == StatusConstants.Spam)
    {
        PostToAkismet("submit-spam", 
            eventInfo.Item.AuthorIpAddress, 
            eventInfo.Item.Author.Name, 
            eventInfo.Item.Author.Email, 
            eventInfo.Item.Message);
    }
}

In the Created event, I am just taking the event info and sending an email. I won't put the code for that here as that is a different topic. You may notice my custom config reference. I use this to store the Akismet API key as well as a flag to determine if I am in test mode or not. I also use it to specify what email(s) should be recipients of the notifications.

Help in getting extra information

In the data submitted to the ICommentCreatingEvent is a string field eventInfo.Item.CustomData. This is always empty but we can change this and add in our own data. One piece of data I want to add is the blog page URL so that I can populate the permalink field in the Akismet POST. To do this we must get a copy of Sitefinity's JavaScript file comments-list.js. Recall this from the start of this post. I got this by using Progress's Just Decompile tool and opening the Telerik.Sitefinity.Frontend.Comments.dll, and looking in the resources.

Just Decompile

Take a copy of this script and implement it in your project. Then change the comments cshtml file to load this custom version. For example:

@Html.Script("/resourcepackages/stuff/mvc/scripts/custom-comments-list.js""bottom"false)

Find the method buildNewCommentFromForm and add some code to populate the CustomData field. This will now come through in the event data and you can use it as required.

comment.Thread.Group.Key = comment.Thread.Group.Key || comment.Thread.groupKey;
 
//Custom Start
comment.CustomData = window.location.href;
//Custom End
 
return comment;

Downside

I highly recommend commenting clearly the changes you make to this file. When ever you upgrade Sitefinity you will need to update this file taking in any changes Sitefinity has made to stay compatible with fixes and new features.

Need more control?

You may need more control over the comments functionality. Perhaps you need to implement Google's reCaptcha service or have some particular UI/UX to apply. If this is your case consider the Sitefinity comments API. With this service, you can look to implement your own front end widget using REST API calls to Sitefinitys back-end.


Darrin Robertson - Sitefinity Developer

Thanks for reading and feel free to comment - Darrin Robertson

If I was really helpful and you would buy me a coffee if you could, yay! You can.


Leave a comment
Load more comments

Make a Comment

recapcha code