Index: setup.py
===================================================================
--- setup.py	(revision 5182)
+++ setup.py	(working copy)
@@ -15,6 +15,7 @@
     'TracDiscussion.timeline = tracdiscussion.timeline',
     'TracDiscussion.admin = tracdiscussion.admin',
     'TracDiscussion.search = tracdiscussion.search',
+    'TracDiscussion.spam = tracdiscussion.spam',
     'TracDiscussion.notification = tracdiscussion.notification']},
   install_requires = [''],
   keywords = 'trac discussion',
Index: tracdiscussion/core.py
===================================================================
--- tracdiscussion/core.py	(revision 5182)
+++ tracdiscussion/core.py	(working copy)
@@ -21,6 +21,8 @@
     implements(INavigationContributor, IRequestHandler, ITemplateProvider,
       IPermissionRequestor)
 
+    discussion_manipulators = ExtensionPoint(IDiscussionManipulator)
+
     title = Option('discussion', 'title', 'Discussion',
       'Main navigation bar button title.')
 
Index: tracdiscussion/api.py
===================================================================
--- tracdiscussion/api.py	(revision 5182)
+++ tracdiscussion/api.py	(working copy)
@@ -17,6 +17,28 @@
 
 from tracdiscussion.notification import *
 
+class InvalidDiscussionPost(TracError):
+    """Exception raised when a ticket fails validation."""
+
+class IDiscussionManipulator(Interface):
+    """Miscellaneous manipulation of forum posts."""
+
+    def validate_message(self, req, author, body):
+        """Validate a new message post in a topic.
+
+        Must return a list of `(field, message)` tuples, one for each problem
+        detected. `field` can be `None` to indicate an overall problem with the
+        ticket. Therefore, a return value of `[]` means everything is OK."""
+
+    def validate_topic(self, req, author, subject, body):
+        """Validate a new topic.
+
+        Must return a list of `(field, message)` tuples, one for each problem
+        detected. `field` can be `None` to indicate an overall problem with the
+        ticket. Therefore, a return value of `[]` means everything is OK."""
+
+
+
 class DiscussionApi(Component):
 
     default_display = Option('discussion', 'default_display', 'tree',
@@ -32,6 +54,8 @@
     topic_sort_direction = Option('discussion', 'topic_sort_direction', 'asc',
       'Direction of topic lists sorting. Possible values are: asc desc.')
 
+    discussion_manipulators = ExtensionPoint(IDiscussionManipulator) 
+
     # Main request processing function.
 
     def process_discussion(self, context):
@@ -578,6 +602,14 @@
                 new_body = context.req.args.get('body')
                 new_time = to_timestamp(datetime.now(utc))
 
+                for manipulator in self.discussion_manipulators:
+                    for field, message in manipulator.validate_topic(context.req, new_author, new_subject, new_body):
+                        if field:
+                            raise InvalidDiscussionPost("The field %s in message is invalid: %s" %
+                                                (field, message))
+                        else:
+                            raise InvalidDiscussionPost("Invalid post: %s" % message)
+
                 self.log.debug((new_subject, new_body))
 
                 # Add topic.
@@ -692,6 +724,15 @@
                 new_body = context.req.args.get('body')
                 new_time = to_timestamp(datetime.now(utc))
 
+                # Custom validation rules
+                for manipulator in self.discussion_manipulators:
+                    for field, message in manipulator.validate_message(context.req, new_author, new_body):
+                        if field:
+                            raise InvalidDiscussionPost("The field %s in message is invalid: %s" %
+                                                (field, message))
+                        else:
+                            raise InvalidDiscussionPost("Invalid post: %s" % message)
+
                 # Add message.
                 self.add_message(context, forum['id'], topic['id'], message and
                   message['id'] or '-1', new_time, new_author, new_body)
