Skip to content

Commit eb6f00d

Browse files
Spam_questions
1 parent 563a122 commit eb6f00d

12 files changed

Lines changed: 665 additions & 126 deletions

File tree

.env.example

Lines changed: 0 additions & 9 deletions
This file was deleted.

forums/settings.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,16 @@
114114
'HOST': '',
115115
'PORT': '', # Set to empty string for default.
116116
},
117-
'cdeep': {
118-
'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
119-
'NAME': 'CDEEP', # Or path to database file if using sqlite3.
120-
# The following settings are not used with sqlite3:
121-
'USER': os.getenv("DB_USER"),
122-
'PASSWORD': os.getenv("DB_PASSWORD"),
123-
# Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
124-
'HOST': '',
125-
'PORT': '', # Set to empty string for default.
126-
},
117+
#'cdeep': {
118+
# 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
119+
# 'NAME': 'CDEEP', # Or path to database file if using sqlite3.
120+
# # The following settings are not used with sqlite3:
121+
# 'USER': os.getenv("DB_USER"),
122+
# 'PASSWORD': os.getenv("DB_PASSWORD"),
123+
# # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
124+
# 'HOST': '',
125+
# 'PORT': '', # Set to empty string for default.
126+
#},
127127
}
128128

129129
# Password validation

forums/views.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.http import HttpResponseRedirect, HttpResponse
22
from django.contrib.auth import login, logout
3-
from django.shortcuts import render_to_response
3+
from django.shortcuts import render
44
from django.template.context_processors import csrf
55

66
from forums.forms import UserLoginForm
@@ -29,7 +29,7 @@ def user_login(request):
2929
'resetpasssucs': resetpasssucs
3030
}
3131
context.update(csrf(request))
32-
return render_to_response('forums/templates/user-login.html', context)
32+
return render(request,'forums/templates/user-login.html', context)
3333
else:
3434
return HttpResponseRedirect('/')
3535

@@ -49,7 +49,7 @@ def updatepassword(request):
4949
confirm = request.POST['confirm_new_password']
5050
if new_password == "" or confirm == "":
5151
context['empty'] = True
52-
return render_to_response("update-password.html", context)
52+
return render(request,"update-password.html", context)
5353
if new_password == confirm:
5454
user.set_password(new_password)
5555
user.save()
@@ -61,14 +61,14 @@ def updatepassword(request):
6161
return HttpResponseRedirect('/')
6262
else:
6363
context['no_match'] = True
64-
return render_to_response("forums/templates/update-password.html", context)
64+
return render(request,"forums/templates/update-password.html", context)
6565
else:
66-
return render_to_response("forums/templates/update-password.html", context)
66+
return render(request,"forums/templates/update-password.html", context)
6767
else:
6868
form = UserLoginForm()
6969
context['form'] = form
7070
context['for_update_password'] = True
71-
return render_to_response('website/templates/index.html', context)
71+
return render(request,'website/templates/index.html', context)
7272

7373
def robots_txt(request):
7474
with open('robots.txt', 'r') as f:

seed_spam_rules.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Script to seed the database with predefined spam rules
2+
import os
3+
import django
4+
5+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "forums.settings")
6+
django.setup()
7+
8+
from django.db.models import Q
9+
from website.models import SpamRule
10+
11+
12+
13+
def seed_spam_rules():
14+
rules = {
15+
# Certification/Exam dump patterns
16+
"Certification/Exam Spam": {
17+
"score": 30,
18+
"type": SpamRule.KEYWORD,
19+
"patterns": [
20+
r"exam\s+dumps?", r"braindumps?", r"practice\s+test",
21+
r"certification\s+exam", r"test\s+preparation",
22+
r"exam\s+questions?", r"study\s+guides?",
23+
r"pdf\s+\+\s+testing\s+engine", r"testing\s+engine",
24+
r"exam\s+prep", r"mock\s+exam", r"real\s+exam",
25+
r"dumps\s+pdf", r"braindump"
26+
],
27+
},
28+
29+
# Promotional spam
30+
"Promotional Spam": {
31+
"score": 25,
32+
"type": SpamRule.KEYWORD,
33+
"patterns": [
34+
r"click\s+here", r"join\s+now", r"limited\s+time",
35+
r"discount", r"coupon\s+code", r"20%\s+off",
36+
r"free\s+download", r"get\s+certified",
37+
r"unlock\s+your\s+career", r"master\s+the",
38+
r"boost\s+your\s+career", r"cert20",
39+
r"at\s+checkout", r"special\s+offer",
40+
],
41+
},
42+
43+
# Suspicious domains
44+
"Suspicious Domain": {
45+
"score": 35,
46+
"type": SpamRule.DOMAIN,
47+
"patterns": [
48+
r"dumpscafe\.com", r"certsout\.com", r"mycertshub\.com",
49+
r"vmexam\.com", r"kissnutra\.com", r"dumps.*\.com",
50+
r"cert.*\.com", r"exam.*\.com",
51+
],
52+
},
53+
54+
# Generic business language
55+
"Business/Career Spam": {
56+
"score": 15,
57+
"type": SpamRule.KEYWORD,
58+
"patterns": [
59+
r"attests\s+to\s+your\s+proficiency",
60+
r"esteemed\s+(?:accreditation|certification|credential)",
61+
r"valuable\s+asset\s+to\s+companies",
62+
r"demonstrates\s+your\s+ability",
63+
r"comprehensive\s+study\s+(?:tools|materials)",
64+
r"interactive\s+practice\s+tests",
65+
r"real\s+exam\s+questions",
66+
r"actual\s+exam\s+questions",
67+
r"validated\s+by\s+.*certification",
68+
r"urgently\s+need\s+experts",
69+
],
70+
},
71+
72+
# Gaming content
73+
"Gaming Spam": {
74+
"score": 20,
75+
"type": SpamRule.KEYWORD,
76+
"patterns": [
77+
r"spacebar\s+clicker", r"clicker\s+game",
78+
r"addictive\s+game", r"upgrades\s+available",
79+
r"instant\s+rewards",
80+
],
81+
},
82+
83+
# Health/Supplement spam
84+
"Health Spam": {
85+
"score": 22,
86+
"type": SpamRule.KEYWORD,
87+
"patterns": [
88+
r"vitalit[äa]t", r"nahrungserg[äa]nzungsmittel",
89+
r"libido", r"fruchtbarkeit", r"energie",
90+
r"hormonelle\s+balance", r"perforan",
91+
],
92+
},
93+
}
94+
95+
inserted, skipped = 0, 0
96+
for note, config in rules.items():
97+
for pattern in config["patterns"]:
98+
exists = SpamRule.objects.filter(
99+
Q(pattern=pattern) & Q(type=config["type"])
100+
).exists()
101+
if not exists:
102+
SpamRule.objects.create(
103+
type=config["type"],
104+
pattern=pattern,
105+
score=config["score"],
106+
notes=note,
107+
)
108+
inserted += 1
109+
else:
110+
skipped += 1
111+
112+
print(f"✅ Inserted {inserted} new rules, skipped {skipped} existing ones.")
113+
114+
115+
# Run it
116+
seed_spam_rules()

static/website/js/thread-user.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ bkLib.onDomLoaded(function() {
66
questionNicEditor.setPanel('questionNicPanel');
77
questionNicEditor.addInstance('questionInstance');
88
});
9+
function getCookie(name) {
10+
let cookieValue = null;
11+
if (document.cookie && document.cookie !== '') {
12+
const cookies = document.cookie.split(';');
13+
for (let i = 0; i < cookies.length; i++) {
14+
const cookie = cookies[i].trim();
15+
if (cookie.substring(0, name.length + 1) === (name + '=')) {
16+
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
17+
break;
18+
}
19+
}
20+
}
21+
return cookieValue;
22+
}
23+
const csrftoken = getCookie('csrftoken');
24+
25+
$.ajaxSetup({
26+
beforeSend: function(xhr, settings) {
27+
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
28+
xhr.setRequestHeader("X-CSRFToken", csrftoken);
29+
}
30+
}
31+
});
32+
933

1034
$(document).ready(function() {
1135
/*

website/forms.py

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,34 @@
88
minutes = ()
99
seconds = ()
1010

11+
# Pre-fetch FOSS ids shown on homepage
12+
foss_ids = list(
13+
FossCategory.objects.using('spoken')
14+
.filter(show_on_homepage=1)
15+
.values_list('id', flat=True) # get ids only
16+
)
17+
1118

1219
class NewQuestionForm(forms.Form):
13-
category = forms.ChoiceField(choices=[('', 'Select a Category'), ] + list(TutorialResources.objects.filter(
14-
Q(status=1) | Q(status=2), language__name='English',tutorial_detail__foss__show_on_homepage=1).values('tutorial_detail__foss__foss').order_by(
15-
'tutorial_detail__foss__foss').values_list('tutorial_detail__foss__foss',
16-
'tutorial_detail__foss__foss').distinct()),
17-
widget=forms.Select(attrs={}), required=True, error_messages={'required': 'State field is required.'})
20+
category = forms.ChoiceField(
21+
choices=[('', 'Select a Category')] +
22+
list(
23+
TutorialResources.objects.using('spoken')
24+
.filter(
25+
Q(status__in=[1, 2]), # cleaner than Q(status=1)|Q(status=2)
26+
language_id=22, # use id instead of name
27+
tutorial_detail__foss_id__in=foss_ids
28+
)
29+
.values('tutorial_detail__foss__foss')
30+
.order_by('tutorial_detail__foss__foss')
31+
.values_list('tutorial_detail__foss__foss', 'tutorial_detail__foss__foss')
32+
.distinct()
33+
),
34+
widget=forms.Select(attrs={}),
35+
required=True,
36+
error_messages={'required': 'State field is required.'}
37+
)
38+
1839
title = forms.CharField(max_length=200)
1940
body = forms.CharField(widget=forms.Textarea())
2041

@@ -24,49 +45,43 @@ def __init__(self, *args, **kwargs):
2445

2546
select_min = kwargs.pop('minute_range', None)
2647
select_sec = kwargs.pop('second_range', None)
48+
2749
super(NewQuestionForm, self).__init__(*args, **kwargs)
28-
tutorial_choices = (
29-
("Select a Tutorial", "Select a Tutorial"),
30-
)
31-
# check minute_range, secpnd_range coming from spoken website
32-
# user clicks on post question link through website
33-
if (select_min is None and select_sec is None):
34-
minutes = (
35-
(select_min, select_min),
36-
)
37-
seconds = (
38-
(select_sec, select_sec),
39-
)
50+
51+
tutorial_choices = (("Select a Tutorial", "Select a Tutorial"),)
52+
53+
# Set minutes & seconds
54+
if select_min is None and select_sec is None:
55+
minutes = ((select_min, select_min),)
56+
seconds = ((select_sec, select_sec),)
4057
else:
41-
minutes = (
42-
("", "min"),
43-
)
44-
seconds = (
45-
("", "sec"),
46-
)
58+
minutes = (("", "min"),)
59+
seconds = (("", "sec"),)
4760

61+
# Handle category logic
4862
if not category and args and 'category' in args[0] and args[0]['category']:
4963
category = args[0]['category']
50-
if FossCategory.objects.filter(foss=category).exists():
64+
65+
if FossCategory.objects.using('spoken').filter(foss=category).exists():
5166
self.fields['category'].initial = category
5267
tutorials = TutorialDetails.objects.using('spoken').filter(foss__foss=category)
5368
for tutorial in tutorials:
5469
tutorial_choices += ((tutorial.tutorial, tutorial.tutorial),)
70+
5571
self.fields['tutorial'] = forms.CharField(widget=forms.Select(choices=tutorial_choices))
72+
5673
if TutorialDetails.objects.using('spoken').filter(tutorial=selecttutorial).exists():
5774
self.fields['tutorial'].initial = selecttutorial
5875

59-
self.fields['minute_range'] = forms.CharField(widget=forms.Select(choices=minutes))
60-
self.fields['second_range'] = forms.CharField(widget=forms.Select(choices=seconds))
61-
else:
62-
self.fields['minute_range'] = forms.CharField(widget=forms.Select(choices=minutes))
63-
self.fields['second_range'] = forms.CharField(widget=forms.Select(choices=seconds))
76+
self.fields['minute_range'] = forms.CharField(widget=forms.Select(choices=minutes))
77+
self.fields['second_range'] = forms.CharField(widget=forms.Select(choices=seconds))
6478
else:
6579
self.fields['tutorial'] = forms.CharField(widget=forms.Select(choices=tutorial_choices))
6680
self.fields['minute_range'] = forms.CharField(widget=forms.Select(choices=minutes))
6781
self.fields['second_range'] = forms.CharField(widget=forms.Select(choices=seconds))
6882

6983

84+
7085
class AnswerQuesitionForm(forms.Form):
7186
question = forms.IntegerField(widget=forms.HiddenInput())
7287
body = forms.CharField(widget=forms.Textarea())

0 commit comments

Comments
 (0)