Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ SPOKEN_DB='13thJune'
SECRET_KEY=''
VIDEO_PATH=''
DEBUG=True
TEMPLATE_DEBUG=True
TEMPLATE_DEBUG=True
SPAM_LOG_FILE=''
Empty file added forums/logs/spam_detection.log
Empty file.
32 changes: 29 additions & 3 deletions forums/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SPAM_LOG_FILE = os.getenv("SPAM_LOG_FILE")

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
Expand Down Expand Up @@ -267,29 +268,54 @@
except ImportError:
pass


LOGGING = {
'version': 1,
'disable_existing_loggers': False,

'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},

'formatters': {
'verbose': {
'format': '[{asctime}] {levelname} {name}: {message}',
'style': '{',
},
},

'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},

# New handler for spam detection
'spam_file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': SPAM_LOG_FILE, # create logs/ dir
'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 5, # keep 5 old logs
'formatter': 'verbose',
},
},

'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},

# New dedicated logger
'spam_detection': {
'handlers': ['spam_file'],
'level': 'INFO',
'propagate': False,
},
}
}

VIDEO_PATH = os.getenv("VIDEO_PATH")
116 changes: 116 additions & 0 deletions seed_spam_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Script to seed the database with predefined spam rules
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "forums.settings")
django.setup()

from django.db.models import Q
from website.models import SpamRule



def seed_spam_rules():
rules = {
# Certification/Exam dump patterns
"Certification/Exam Spam": {
"score": 30,
"type": SpamRule.KEYWORD,
"patterns": [
r"exam\s+dumps?", r"braindumps?", r"practice\s+test",
r"certification\s+exam", r"test\s+preparation",
r"exam\s+questions?", r"study\s+guides?",
r"pdf\s+\+\s+testing\s+engine", r"testing\s+engine",
r"exam\s+prep", r"mock\s+exam", r"real\s+exam",
r"dumps\s+pdf", r"braindump"
],
},

# Promotional spam
"Promotional Spam": {
"score": 25,
"type": SpamRule.KEYWORD,
"patterns": [
r"click\s+here", r"join\s+now", r"limited\s+time",
r"discount", r"coupon\s+code", r"20%\s+off",
r"free\s+download", r"get\s+certified",
r"unlock\s+your\s+career", r"master\s+the",
r"boost\s+your\s+career", r"cert20",
r"at\s+checkout", r"special\s+offer",
],
},

# Suspicious domains
"Suspicious Domain": {
"score": 35,
"type": SpamRule.DOMAIN,
"patterns": [
r"dumpscafe\.com", r"certsout\.com", r"mycertshub\.com",
r"vmexam\.com", r"kissnutra\.com", r"dumps.*\.com",
r"cert.*\.com", r"exam.*\.com",
],
},

# Generic business language
"Business/Career Spam": {
"score": 15,
"type": SpamRule.KEYWORD,
"patterns": [
r"attests\s+to\s+your\s+proficiency",
r"esteemed\s+(?:accreditation|certification|credential)",
r"valuable\s+asset\s+to\s+companies",
r"demonstrates\s+your\s+ability",
r"comprehensive\s+study\s+(?:tools|materials)",
r"interactive\s+practice\s+tests",
r"real\s+exam\s+questions",
r"actual\s+exam\s+questions",
r"validated\s+by\s+.*certification",
r"urgently\s+need\s+experts",
],
},

# Gaming content
"Gaming Spam": {
"score": 20,
"type": SpamRule.KEYWORD,
"patterns": [
r"spacebar\s+clicker", r"clicker\s+game",
r"addictive\s+game", r"upgrades\s+available",
r"instant\s+rewards",
],
},

# Health/Supplement spam
"Health Spam": {
"score": 22,
"type": SpamRule.KEYWORD,
"patterns": [
r"vitalit[äa]t", r"nahrungserg[äa]nzungsmittel",
r"libido", r"fruchtbarkeit", r"energie",
r"hormonelle\s+balance", r"perforan",
],
},
}

inserted, skipped = 0, 0
for note, config in rules.items():
for pattern in config["patterns"]:
exists = SpamRule.objects.filter(
Q(pattern=pattern) & Q(type=config["type"])
).exists()
if not exists:
SpamRule.objects.create(
type=config["type"],
pattern=pattern,
score=config["score"],
notes=note,
)
inserted += 1
else:
skipped += 1

print(f"✅ Inserted {inserted} new rules, skipped {skipped} existing ones.")


# Run it
seed_spam_rules()
78 changes: 77 additions & 1 deletion static/website/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ <h3 align="center">Answers</h3>
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#recent_question">Recent questions</a></li>
<li><a data-toggle="tab" href="#most_active">Most active questions</a></li>
<li><a data-toggle="tab" href="#spam_pending">Spam Pending-Approval</a></li>
</ul>
</div>
<div class="panel-body">
Expand All @@ -114,6 +115,7 @@ <h3 align="center">Answers</h3>
</thead>
<tbody>
{% for question in questions %}
{% if 'test-' not in question.category %}
<tr>
<td>
<span href="#" class="category" data-toggle="tooltip" data-placement="top" title="{{ question.category}}">
Expand Down Expand Up @@ -177,6 +179,7 @@ <h3 align="center">Answers</h3>
</span>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
Expand Down Expand Up @@ -279,7 +282,45 @@ <h3 align="center">Answers</h3>
</tbody>
</table>
</div>
</div>
<div id="spam_pending" class="tab-pane fade">
<table id="spamTable" class="tablesorter-blue">
<thead>
<tr>
<th>FOSS</th>
<th>Tutorial</th>
<th>Min</th>
<th>Sec</th>
<th>Question</th>
<th>Date</th>
<th>User</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for question in spam_questions %}
<tr id="spam-row-{{ question.id }}">
<td>{{ question.category|truncatechars:12 }}</td>
<td>{{ question.tutorial|truncatechars:12 }}</td>
<td>{{ question.minute_range }}</td>
<td>{{ question.second_range }}</td>
<td>
<a href="{% url 'website:get_question' question.id %}{% prettify question.title %}">
{{ question.title|truncatechars:40 }}
</a>
</td>
<td>{{ question.date_created|date:"d-m-y" }}</td>
<td>{{ question.user|truncatechars:10 }}</td>
<td>
<button class="btn btn-xs btn-success spam-approve" data-qid="{{ question.id }}">Approve</button>
<button class="btn btn-xs btn-danger spam-reject" data-qid="{{ question.id }}">Reject</button>
</td>
</tr>
{% empty %}
<tr><td colspan="8">No spam questions pending approval.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div> <!-- /.panel-body -->
</div> <!-- /.panel -->
{% endblock %}
Expand Down Expand Up @@ -324,6 +365,41 @@ <h3 align="center">Answers</h3>
}
e.preventDefault();
});

// Spam approval handlers
$('.spam-approve').click(function() {
var qid = $(this).data('qid');
var row = $('#spam-row-' + qid);

$.post('/ajax-spam-approve/', {
question_id: qid,
csrfmiddlewaretoken: $('[name=csrfmiddlewaretoken]').val()
}, function(response) {
if(response.success) {
row.fadeOut(function() { $(this).remove(); });
alert('Question approved!');
} else {
alert('Error approving question: ' + (response.error || 'Unknown error'));
}
}, 'json');
});

$('.spam-reject').click(function() {
var qid = $(this).data('qid');
var row = $('#spam-row-' + qid);

$.post('/ajax-spam-reject/', {
question_id: qid,
csrfmiddlewaretoken: $('[name=csrfmiddlewaretoken]').val()
}, function(response) {
if(response.success) {
row.fadeOut(function() { $(this).remove(); });
alert('Question rejected as spam!');
} else {
alert('Error rejecting question: ' + (response.error || 'Unknown error'));
}
}, 'json');
});
});
$('span').tooltip();
</script>
Expand Down
2 changes: 1 addition & 1 deletion website/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ def __init__(self, *args, **kwargs):

class AnswerQuesitionForm(forms.Form):
question = forms.IntegerField(widget=forms.HiddenInput())
body = forms.CharField(widget=forms.Textarea())
body = forms.CharField(widget=forms.Textarea())
Loading