@@ -0,0 +1,8 @@ | |||
*~ | |||
\#* | |||
__pycache__/* | |||
*\\\.* | |||
*pyc | |||
./threads/* | |||
*list* | |||
*ips* |
@@ -0,0 +1,152 @@ | |||
#/usr/bin/env python3 | |||
import webtools as wt | |||
import os, crypt, cgitb | |||
cgitb.enable() | |||
modes = {"0": "no mode", | |||
"1": "lock", | |||
"2": "sticky", | |||
"3": "stickylock", | |||
"4": "permasage" | |||
} | |||
settings = "./settings.txt" | |||
b_conf = [] | |||
cd = {} | |||
with open(settings, "r") as settings: | |||
settings = settings.read().splitlines() | |||
for s in settings: | |||
if len(s) == 0 or s[0] == "#" or ": " not in s: | |||
continue | |||
elif "#" in s: | |||
s = s.split("#")[0] | |||
s = s.split(": ") | |||
if len(s) > 2: | |||
s[1] = ": ".join(s[1:]) | |||
try: | |||
s[1] = int(s[1]) | |||
except: | |||
pass | |||
b_conf.append(s[1]) | |||
cd[s[0]] = s[1] | |||
with open("./admin/op.html", 'r') as op: | |||
op = op.read() | |||
def mode_icons(mo=""): | |||
micons = ["", "lock.png", "sticky.png", | |||
["lock.png", "sticky.png"], "ghost.png"] | |||
ic = micons[int(mo)] | |||
if len(ic) == 2: | |||
ic = ["./img/" + i for i in ic if len(ic) == 2] | |||
elif len(ic): | |||
ic = ["./img/" + ic] | |||
return ic | |||
def login_admin(): | |||
# if wt.get_cookie(): | |||
cookies = wt.get_cookie() | |||
if 'pw' in cookies.keys(): | |||
if tripcode(cookies['pw']) == b_conf[3]: | |||
return 1 | |||
elif wt.get_form('pw') and \ | |||
tripcode(wt.get_form('pw')) == b_conf[3]: | |||
print(wt.put_cookie('pw', wt.get_form('pw'))) | |||
return 1 | |||
else: | |||
if wt.get_form('pw'): | |||
print("Password incorrect.<br>") | |||
print("<h1>Login</h1>") | |||
print("<p>", wt.new_form('admin.py', 'post')) | |||
print("#" + wt.put_form('password', 'pw')) | |||
print(wt.put_form('submit', '', 'Submit')) | |||
print("</form><p>") | |||
return 0 | |||
def admin_splash(): | |||
print("""<pre> | |||
- change settings | |||
- moderate threads | |||
- modify wordfilter | |||
</pre>""") | |||
if not wt.get_form('mode') or not wt.get_form('thread'): | |||
print("<h2>Settings</h2>") | |||
print("\n".join(["<br> - "+str(i) for i in b_conf])) | |||
for s in cd.keys(): | |||
print("<p>",s + ":<br> ", cd[s]) | |||
print("<h2>Threads</h2>") | |||
if wt.get_form('more'): | |||
print("<a href='.'>Back</a><br>") | |||
print(wt.get_form('thread'), "<hr>") | |||
# print(load_thread(wt.get_form('thread'))) | |||
show_thread(load_thread(wt.get_form('thread'))) | |||
else: | |||
mod_threads() | |||
def mod_threads(): | |||
print("<pre>") | |||
with open(b_conf[6]) as t_list: | |||
print(t_list.read()) | |||
print("</pre>") | |||
ti = thread_index() | |||
for t in ti["ti"]: | |||
# t = filename | |||
# t[0] last reply time, t[1] thread title | |||
# t[2] reply count, t[3] thread mode | |||
mic = mode_icons(ti[t][3]) | |||
tm = [f"<img src='{m}'>" for m in mic] | |||
if ti[t][3] in modes: | |||
ti[t][3] = modes[ti[t][3]] | |||
mk = list(modes.keys()) | |||
mv = [modes[i] for i in mk] | |||
dropdown = wt.dropdown("mode", mk, mv) | |||
ti[t][3] = dropdown.replace(f">{ti[t][3]}", \ | |||
f" selected>{ti[t][3]}") | |||
print(op.format(t, ti[t], " ".join(tm))) | |||
def thread_index(): | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines() | |||
t = {} | |||
t["ti"] = [] | |||
for th in t_list: | |||
th = th.split(" >< ") | |||
t["ti"].append(th[0]) | |||
t[th[0]] = th[1:] | |||
return t | |||
def load_thread(thr='0'): | |||
# print(b_conf[5] + thr) | |||
with open(b_conf[5] + thr, 'r') as thr: | |||
thr = thr.read().splitlines() | |||
for n, th in enumerate(thr): | |||
thr[n] = th.split(' >< ') | |||
return thr | |||
def show_thread(thr=[]): | |||
if not thr: | |||
return None | |||
table = ["<table>"] | |||
table.append("<tr><td><td>Name<td>Date<td>Comment") | |||
print("<tr><th colspan='4'>", thr.pop(0)[0]) | |||
for n, t in enumerate(thr): | |||
t.pop(2) | |||
t = f"<tr><td>{n+1}.<td>" + "<td>".join(t) | |||
table.append(t) | |||
print("\n".join(table), "</table>") | |||
def tripcode(pw): | |||
pw = pw[:8] | |||
salt = (pw + "H..")[1:3] | |||
trip = crypt.crypt(pw, salt) | |||
return (trip[-10:]) | |||
def main(): | |||
print(wt.head(b_conf[0])) | |||
print("<h2>", b_conf[0], "admin</h2>") | |||
# print(wt.get_cookie()) | |||
if login_admin() == 1: | |||
admin_splash() | |||
main() |
@@ -0,0 +1,22 @@ | |||
<hr><form action='admin.py' method='get'> | |||
<table> | |||
<tr> | |||
<td>{2} {1[3]} | |||
<td>   | |||
<input type='submit' name='more' value='More ›'> | |||
<input type='hidden' name='thread' value='{0}.txt'> | |||
  <input type='submit' value='Submit'> | |||
<tr> | |||
<td>Thread FN: | |||
<td><code style='padding: 4px; margin: 4px; line-height: 1.4em; | |||
background-color:#222;color:#ddd'>{0}.txt</code> | |||
<a href="http://127.0.0.1/bbs/?m=thread;t={0}">(live)</a> | |||
<tr> | |||
<td>last reply @ | |||
<td><b>{1[0]}</b> | |||
<tr> | |||
<td>Title: | |||
<td>{1[1]} | |||
<tr> | |||
<td colspan='2'>{1[2]} replies | |||
</table></form> |
@@ -0,0 +1,49 @@ | |||
import os | |||
import re | |||
from collections import defaultdict | |||
import webtools as wt | |||
with open('./set.txt', 'r') as sett: | |||
sett = sett.read().splitlines() | |||
sd = {} | |||
for n, s in enumerate(sett): | |||
if len(s) > 1 and s[0] not in ["", "#", " "]: | |||
s = s.split(':') | |||
if len(s) > 2: | |||
s[1] = ":".join(s[1:]) | |||
sd[s[0]] = s[1] | |||
sett[n] = s[0] | |||
else: | |||
sett[n] = '' | |||
sett = [i for i in sett if i] | |||
def load_thread(th): | |||
th = int(th) | |||
thp = str(sd['thrdb'] + str(th) + '.txt') | |||
if not os.path.exists(thp): | |||
return None | |||
with open(thp, 'r') as mt: | |||
mt = mt.read().splitlines() | |||
return mt | |||
def do_backlink(th='0'): | |||
mt = load_thread(th) | |||
bld = defaultdict(list) | |||
for n, t in enumerate(mt): | |||
repl = re.findall(r'\>\>[1-9][00-99]*', t) | |||
repl = [r[8:] for r in repl if r] | |||
for r in repl: | |||
if int(r) >= len(mt) or str(n) in bld[r]: | |||
continue | |||
bld[r].append(str(n)) | |||
for r in sorted([int(k) for k in bld.keys()]): | |||
r = str(r) | |||
return bld | |||
def main(): | |||
print(" ") | |||
if __name__ == "__main__": | |||
main() |
@@ -19,68 +19,106 @@ table {border-collapse: seperate; | |||
margin-left: 4%} | |||
th {background-color: #444; color: #e4e4e4} | |||
td {background-color: #f4f4f4} | |||
th, td {border: 1px solid #000; | |||
padding: 3px;} | |||
th, td { | |||
border: 1px solid #000; | |||
padding: 3px; | |||
} | |||
.front > .thread { | |||
margin-left: 2.5%; | |||
margin-right: 2.5%; | |||
border: inset 1px black; | |||
outline: solid 5px #efefef;} | |||
margin-left: 2.5%; | |||
margin-right: 2.5%; | |||
border: inset 1px black; | |||
outline: solid 5px #efefef; | |||
} | |||
.thread { | |||
background-color: #EFEFEF; | |||
padding: 7px; | |||
margin-bottom: 2em; | |||
clear: both; | |||
text-align: justify; | |||
word-wrap: break-word; | |||
hyphens: auto; | |||
background-color: #EFEFEF; | |||
padding: 7px; | |||
margin-bottom: 2em; | |||
clear: both; | |||
text-align: justify; | |||
word-wrap: break-word; | |||
hyphens: auto; | |||
} | |||
div.thread > h3 > a {color:#CC1105; | |||
font-size: 25px;} | |||
.thread > h3 > a { | |||
color:#CC1105; | |||
font-size: 25px; | |||
} | |||
.thread > hr { | |||
width: 96%; | |||
height: 2px; | |||
color: black; background-color: black; | |||
color: black; | |||
background-color: black; | |||
text-align: center; | |||
margin-left: 0; | |||
margin-right: 0; | |||
} | |||
div.thread > p {padding-left: 4%; padding-right:4%; line-height: 1.4em} | |||
.thread > i {padding-left: 0%} | |||
.title { | |||
overflow: visible; | |||
padding-top: 6px; | |||
padding-bottom:6px; | |||
} | |||
div.thread > i {padding-left: 0%} | |||
.reply > p { | |||
text-size: small; | |||
padding-left: 4%; | |||
padding-right: 6%; | |||
line-height: 1.6em | |||
} | |||
.bump {color: #117743; font-weight: bold} | |||
.sage {color: blue; font-weight: bold;} | |||
.trip {color: #889; font-weight: normal} | |||
.admin{color: #0cc; font-weight: bold} | |||
.rmr {padding-top:0.5em; | |||
padding-left: 2em; | |||
height: 1.6em; | |||
width: 40%; | |||
border: 2px dotted #888; | |||
background-color:#ffc; | |||
font-style: italic;} | |||
.rmr { | |||
padding-top:0.5em; | |||
padding-left: 2em; | |||
max-width: 600px; | |||
padding-bottom: 0.5em; | |||
border: 2px dotted #888; | |||
background-color:#ffc; | |||
font-style: italic; | |||
} | |||
.bl { | |||
border: 1px dashed #777; | |||
margin-top: 8px; | |||
margin-left: 12px; | |||
padding: 12px; | |||
padding-left: 13px; | |||
max-width: 410px; | |||
font-size: small; | |||
background-color: #ccc; | |||
} | |||
.quote {color: #580} | |||
a.thread {color: #153; font-weight:bold} | |||
.post {background-color: #f2f2f2} | |||
pre { max-width: 500px; | |||
font-size-adjust:none; | |||
background-color: #fff; | |||
border:1px solid black; | |||
white-space: pre; | |||
margin-left:5%} | |||
pre { | |||
max-width: 500px; | |||
font-size-adjust:none; | |||
background-color: #fff; | |||
border:1px solid black; | |||
white-space: pre; | |||
margin-left:5% | |||
} | |||
pre > hr {max-width: 500px;} | |||
.spoiler, .spoiler > .quote {color: black; background-color:black !important} | |||
.spoiler:hover, .spoiler:hover > .quote {color: white; background-color: black !important} | |||
.spoiler, .spoiler > .quote { | |||
color: black; | |||
background-color:black !important | |||
} | |||
.spoiler:hover, .spoiler:hover > .quote { | |||
color: white; | |||
background-color: black !important | |||
} | |||
.youtube { | |||
background-size: 480px 320px; | |||
@@ -119,4 +157,6 @@ no-repeat center center; | |||
.youtube .play:hover { | |||
opacity: 1; | |||
filter: alpha(opacity=100); | |||
} | |||
} | |||
@@ -7,7 +7,7 @@ pre {background-color: #eee; border:1px solid black; width: 45em;} | |||
pre > hr {width: 45em;} | |||
pre > b {font-size: 120%;} | |||
a {color:#153} | |||
body {padding:2% 5%; background-color:#e0e4e9; max-width: 850px;} | |||
body {padding:2% 5%; background-color:#e0e4e9; max-width: 890px;} | |||
table {border-collapse: seperate; | |||
border-spacing: 0; | |||
border: 0px solid #000;} | |||
@@ -16,7 +16,7 @@ td {background-color: #f4f4f4} | |||
th, td {border: 1px solid #000; | |||
padding: 3px;} | |||
div.thread{ | |||
.thread{ | |||
max-width: 680px; | |||
padding-right: 28px; | |||
padding-bottom: 0; | |||
@@ -25,11 +25,24 @@ div.thread{ | |||
hyphens: auto; | |||
border-right: 10px solid black; | |||
} | |||
a.thread {color: #153; font-weight:bold} | |||
.thread > i {padding-left: 0%;border-bottom: 1px solid black} | |||
.front > .thread{ | |||
border-top: 5px solid black; | |||
padding-top: 7px; | |||
} | |||
.front > .thread | |||
{ border-top: 5px solid black; | |||
padding-top: 7px; } | |||
.bl { | |||
border: 1px dashed #777; | |||
margin-top: 8px; | |||
margin-left: 12px; | |||
padding: 12px; | |||
padding-left: 13px; | |||
max-width: 410px; | |||
font-size: small; | |||
background-color: #ccc; | |||
} | |||
hr { | |||
max-width: 620px; | |||
@@ -37,53 +50,79 @@ hr { | |||
margin-left: 0; | |||
} | |||
div.thread > p {padding-left: 4%; line-height: 1.6em} | |||
div.thread > i {padding-left: 0%} | |||
.motd {background-color:white; | |||
padding-left: 30px; | |||
max-width:580px; | |||
border:1px dashed black | |||
text-align: justify;} | |||
.title {width:100%; overflow: visible} | |||
.motd { | |||
background-color:white; | |||
padding-left: 30px; | |||
max-width:580px; | |||
border:1px dashed black; | |||
text-align: justify; | |||
} | |||
.reply { | |||
width: 800px; | |||
background-color:#e0e4e9; | |||
border: 0; | |||
padding-left: 6%; | |||
max-width: 550px; | |||
padding-bottom: 35px; | |||
} | |||
.reply {width: 800px; background-color:#e0e4e9; border: 0; | |||
padding-bottom: 35px;} | |||
.reply > p {padding-left: 4%; line-height: 1.6em} | |||
.bump {color: #153; font-weight: bold} | |||
.sage {color: #444; font-weight: bold; font-style: italic} | |||
.trip {color: #889; font-weight: normal} | |||
.admin{color: #0cc; font-weight: bold} | |||
.rmr {padding-top:0.5em; | |||
padding-left: 2em; | |||
height: 1.4em; | |||
background-color:#ffc; | |||
font-style: italic;} | |||
.closed {padding-top:0.5em; | |||
padding-left: 2em; | |||
padding-right: 2em; | |||
padding-bottom:0.2em; | |||
line-height: 2em; | |||
background-color:#fff; | |||
width: 620px; | |||
} | |||
.closed > img {background-color: #fff; | |||
border: 1px solid black; | |||
padding: 2px; | |||
padding-right:4px;} | |||
padding-left: 1em; | |||
max-width: 420px; | |||
# height: 1.4em; | |||
padding-bottom: 1.3em; | |||
background-color:#ffc; | |||
font-style: italic;} | |||
.closed { | |||
padding-left: 4%; | |||
max-width: 420px; | |||
padding-left: 2em; | |||
padding-right: 2em; | |||
padding-bottom:0.2em; | |||
line-height: 2em; | |||
background-color: #e3ea28; | |||
} | |||
.closed img { | |||
background-color:#ffa; | |||
padding: 12px; | |||
margin: 4px; | |||
} | |||
.quote {color: #580} | |||
a.thread {color: #153; font-weight:bold} | |||
.post {background-color: #f2f2f2} | |||
.aa { | |||
font-size-adjust:none; | |||
background-color: #eee; | |||
border:1px solid black; | |||
white-space: pre} | |||
font-size-adjust:none; | |||
background-color: #eee; | |||
border:1px solid black; | |||
white-space: pre; | |||
} | |||
.spoiler, .spoiler > .quote { | |||
color: black; | |||
background-color:black !important; | |||
} | |||
.spoiler:hover, .spoiler:hover > .quote { | |||
color: white; background-color: black !important | |||
} | |||
.spoiler, .spoiler > .quote {color: black; background-color:black !important} | |||
.spoiler:hover, .spoiler:hover > .quote {color: white; background-color: black !important} | |||
/* | |||
Youtube follows | |||
messy | |||
*/ | |||
.youtube { | |||
background-size: 480px 320px; | |||
@@ -123,3 +162,4 @@ no-repeat center center; | |||
opacity: 1; | |||
filter: alpha(opacity=100); | |||
} | |||
@@ -2,7 +2,7 @@ | |||
<form method='post' action='.' style='width:520px'> | |||
<input type='hidden' name='m' value='reply'> | |||
<input type='hidden' name='t' value='{0}'> | |||
<table style='width:420px'> | |||
<table style='width:480px'> | |||
<tr><td colspan='3' style='padding: 0.7em 0 0.7em 10%; | |||
background-color: #444; color: #efefef'> | |||
<b>Reply</b> | |||
@@ -0,0 +1,601 @@ | |||
#!/usr/bin/env python3 | |||
import os, cgi, cgitb | |||
import time, re, crypt | |||
import webtools as wt | |||
import backlink | |||
form = cgi.FieldStorage() | |||
settings = "./settings.txt" | |||
cgitb.enable() | |||
# To generate a mod password, use tripcode.py3 to generate a tripkey. | |||
# What comes out is the result of a code that generates something like | |||
# a public key. Type #password in the name field to have your post | |||
# render as the mod_un using a special color. Save your !tripcode in the | |||
# mod_pw field to do so, without an exclamation mark in front. Your tripkey | |||
# should be 10 letters long, the result of an 8 character secure password | |||
# that you know others can't guess easily. | |||
with open(settings, "r") as settings: | |||
b_conf = [] | |||
settings = settings.read().splitlines() | |||
for s in settings: | |||
if len(s) == 0 or s[0] == "#" or ": " not in s: | |||
continue | |||
elif "#" in s: | |||
s = s.split("#")[0] | |||
s = s.split(": ") | |||
if len(s) > 2: | |||
s[1] = ": ".join(s[1:]) | |||
try: | |||
s[1] = int(s[1]) | |||
except: | |||
pass | |||
b_conf.append(s[1]) | |||
f = {"main":"bbs_main()", "thread":"bbs_thread()", | |||
"list":"bbs_list()", "create":"bbs_create()", | |||
"reply":"do_reply()", "atom":"bbs_atom()"} | |||
t_modes = {"0":"", \ | |||
"1":"<img src='./img/lock.png' alt='Lock'>", | |||
"2":"<img src='./img/sticky.png' alt='Sticky'>", | |||
"3":"<img src='./img/sticky.png'><img src='./img/lock.png'>", | |||
"4":"<img src='./img/ghost.png' alt='Nobump'>"} | |||
def main(): | |||
select_func = wt.get_form('m') | |||
if select_func == 'atom': | |||
bbs_atom() | |||
return | |||
bbs_header() | |||
if select_func in f.keys(): | |||
print("<a href='.'><< back</a><br>") | |||
print("----"*10, "<p>") | |||
eval(f[select_func]) | |||
else: | |||
bbs_main() | |||
bbs_foot() | |||
print("</div></div>") | |||
def bbs_header(): | |||
with open("./html/head.html", "r") as head: | |||
print(head.read().format(b_conf[0])) | |||
def bbs_main(): | |||
print("<div class='front'>") | |||
print("""Styles: [<a href="javascript:setActiveStyleSheet('4x13');">4x13</a>] | |||
[<a href="javascript:setActiveStyleSheet('0ch');">0ch</a>]""") | |||
print("<h2>{0}</h2>".format(b_conf[0])) | |||
with open('./html/motd.html', 'r') as motd: | |||
print(motd.read()) | |||
bbs_list(prev='1') | |||
print("<p><hr>") | |||
do_prev() | |||
print("<hr>") | |||
bbs_create() | |||
# print("</div>") | |||
def bbs_thread(t_id='', prev=0): | |||
if not t_id and wt.get_form('t'): | |||
t_id = wt.get_form('t') | |||
if t_id: | |||
if t_id.isdigit(): | |||
t_fn = b_conf[5] + t_id + ".txt" | |||
else: | |||
bbs_list() | |||
return | |||
else: | |||
bbs_list() | |||
return | |||
if not os.path.isfile(t_fn): | |||
bbs_list() | |||
return | |||
with open(t_fn, "r") as the_thread: | |||
the_thread = the_thread.readlines() | |||
r_cnt = str(len(the_thread) - 1) | |||
t_m = '' | |||
if "><" in the_thread[0]: | |||
t_m = the_thread[0].split("><")[1].strip() | |||
if t_m in t_modes.keys(): | |||
t_m = t_modes[t_m] | |||
else: | |||
t_m = '' | |||
the_thread[0] = the_thread[0].split("><")[0] | |||
elif int(r_cnt) >= b_conf[7]: | |||
t_m = t_modes['1'] | |||
bld = {} | |||
if prev == 0: | |||
bld = backlink.do_backlink(t_id) | |||
print("<div class='thread'>") | |||
print("<h3>", t_m, the_thread[0] + "[" + r_cnt + "]", "</h3>") | |||
p_n = 0 | |||
replies=[] | |||
for reply in the_thread[1:]: | |||
p_n += 1 | |||
reply = reply.split(' >< ') | |||
if len(reply) > 4: | |||
reply[3] = " >< ".join(reply[3:]) | |||
if reply[2]: | |||
reply.pop(2) | |||
reply[0] = "<span class='sage'>" \ | |||
+ reply[0] + "</span>" | |||
else: | |||
reply.pop(2) #30c | |||
reply[0] = "<span class='bump'>" \ | |||
+ reply[0] + "</span>" | |||
if prev == 0: | |||
if re.compile(r'>>[\d]').search(reply[2]): | |||
reply[2] = re.sub(r'>>([\d]+)', \ | |||
r'<a href="#\1">>>\1</a>', \ | |||
reply[2]) | |||
reply[2] = do_format(reply[2]) | |||
print("<div class='reply'><div class='title'>") | |||
print("<a name='{0}' href='#reply'".format(p_n)) | |||
print("onclick='addText(\"{1}\", \"{0}\")'>"\ | |||
.format(p_n, t_id)) | |||
print("#{0}</a> //".format(p_n)) | |||
print("Name: {0} :\nDate: {1} \n"\ | |||
.format(reply[0], reply[1])) | |||
if str(p_n) in bld.keys(): | |||
print("<div class='bl'>Replies: ") | |||
aref = "<a href='#{0}'><i>>>{0}</a></i>" | |||
print(", ".join([aref.format(r) \ | |||
for r in bld[str(p_n)]])) | |||
print("</div>") | |||
print("</div>") | |||
print("<p>", reply[2], "</p></div>") | |||
else: | |||
if re.compile(r'>>[\d]').search(reply[2]): | |||
reply[2] = re.sub(r'>>([\d]+)', \ | |||
r'<a href="?m=thread;t={0}#\1">>>\1</a>'.format(t_id), reply[2]) | |||
reply[2] = do_format(reply[2]) | |||
if len(reply[2].split('<br>')) > 8: | |||
reply[2] = '<br>'.join(reply[2].split('<br>')[:9])[:850] | |||
if "<pre" and not "</pre>" in reply[2]: | |||
reply[2] += "</pre>" | |||
if "<code" and not "</code>" in reply[2]: | |||
reply[2] += "</code>" | |||
reply[2] += "</p><div class='rmr'>Post shortened. " \ | |||
+ "<a href='?m=thread;t={0}'>[".format(t_id) \ | |||
+ "View full thread]</a></div>" | |||
elif len(reply[2].split('<span class="youtube" ')) > 2: | |||
reply[2] = '<span class="youtube"'.join(reply[2].split('<span class="youtube"')[:2]) | |||
if "<pre" and not "</pre>" in reply[2]: | |||
reply[2] += "</pre>" | |||
if "<code" and not "</code>" in reply[2]: | |||
reply[2] += "</code>" | |||
reply[2] += "</p><div class='rmr'>Post shortened. " \ | |||
+ "<a href='?m=thread;t={0}'>[".format(t_id) \ | |||
+ "View full thread]</a></div>" | |||
elif len(reply[2]) > 850: | |||
reply[2] = reply[2][:850] | |||
if "<pre" and not "</pre>" in reply[2]: | |||
reply[2] += "</pre>" | |||
if "<code" and not "</code>" in reply[2]: | |||
reply[2] += "</code>" | |||
reply[2] += "</p><div class='rmr'>Post shortened. " \ | |||
+ "<a href='?m=thread;t={0}'>".format(t_id) \ | |||
+ "[View full thread]</a></div>" | |||
show_r = b_conf[13] | |||
if int(r_cnt) > show_r and p_n == int(r_cnt): | |||
reply[2] = reply[2] + "</p><br><div class='rmr'>" \ | |||
+"<a href='?m=thread;t={0}'".format(t_id) \ | |||
+">[Read all posts]</a></div><br>" | |||
reply[2] += "</div>" | |||
replies.append(reply) | |||
if "lock" in t_m.strip() and prev == 0: | |||
print("<div class='rmr'>") | |||
print(t_m.strip()) | |||
print("<b>This thread is locked.</b>") | |||
print("<p>No more replies can be added.") | |||
print("</div>") | |||
elif int(r_cnt) >= b_conf[7]: | |||
print("<div class='closed'>", t_modes['1']) | |||
print("This thread is locked. Reply limit hit.") | |||
print("</div>") | |||
elif prev == 0: | |||
bbs_reply(t_fn, t_id) | |||
return replies | |||
def bbs_create(): | |||
thread_attrs = {'title':'', 'name':'', 'content':''} | |||
for key in thread_attrs.keys(): | |||
if wt.get_form(key): | |||
thread_attrs[key] = wt.get_form(key) | |||
if thread_attrs['title'] and thread_attrs['content']: | |||
thread_attrs['title'] = thread_attrs['title'][:30].strip() | |||
if thread_attrs['name']: | |||
thread_attrs['name'] = \ | |||
thread_attrs['name'][:18].strip() | |||
if '#' in thread_attrs['name']: | |||
namentrip = thread_attrs['name'].split('#')[:2] | |||
namentrip[1] = tripcode(namentrip[1]) | |||
thread_attrs['name'] = '</span> <span class="trip">'.join(namentrip) | |||
thread_attrs['content'] = thread_attrs['content'].strip().replace('\r\n', "<br>")[:2000] | |||
thread_attrs['dt'] = str(time.time())[:10] | |||
if not thread_attrs['name']: | |||
thread_attrs['name'] = 'Anonymous' | |||
local_dt = time.localtime(int(thread_attrs['dt'])) | |||
date_str = "%Y-%m-%d [%a] %H:%M" | |||
thread_attrs['ldt'] = time.strftime(date_str, local_dt) | |||
t_fn = thread_attrs['dt'] + ".txt" | |||
with open(b_conf[5] + t_fn, "x") as new_thread: | |||
new_thread.write(thread_attrs['title'] + "\n" \ | |||
+ thread_attrs['name'] + " >< " \ | |||
+ thread_attrs['ldt'] + " >< >< " \ | |||
+ thread_attrs['content'] + "\n" ) | |||
print("Thread <i>{0}</i> posted successfully!".format(thread_attrs['title'])) | |||
with open(b_conf[11], "a") as log: | |||
ip = os.environ["REMOTE_ADDR"] | |||
# IP | location | filename | ldt | comment | |||
log_data = " | ".join([ip, t_fn, thread_attrs['ldt'], \ | |||
thread_attrs['title'], thread_attrs['name'], thread_attrs['content']+"\n"]) | |||
log.write(log_data) | |||
print("Redirecting you in 5 seconds...") | |||
print("<meta http-equiv='refresh' content='5;.'") | |||
with open(b_conf[6], "r") as t_list: | |||
t_list = t_list.read().splitlines() | |||
new_t = " >< ".join([thread_attrs['dt'], \ | |||
thread_attrs['ldt'], thread_attrs['title'], \ | |||
"1", "0"]) | |||
for n, t in enumerate(t_list): | |||
t = t.split(" >< ") | |||
if len(t) == 5 and t[4] not in ["2", "3"] or len(t) == 4: | |||
t_list.insert(n, new_t) | |||
break | |||
else: | |||
pass | |||
else: | |||
t_list.insert(0, new_t) | |||
with open(b_conf[6], "w") as upd_list: | |||
upd_list.write('\n'.join(t_list)) | |||
else: | |||
if not thread_attrs['title']: | |||
if thread_attrs['content']: | |||
print("You need to enter a title to post a new thread.<br>") | |||
elif not thread_attrs['content']: | |||
print("You need to write a message to post a new thread.<br>") | |||
with open("./html/create.html") as c_thread: | |||
print(c_thread.read()) | |||
def bbs_list(prev='0', rss=False): | |||
if prev == '0': | |||
s_ts = None | |||
else: | |||
s_ts = b_conf[12] | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines() | |||
t_cnt = len(t_list) | |||
cnt = 1 | |||
if not rss: | |||
print("<a name='tbox'></a><table>") | |||
print("<th>{0} <th>Title <th>Posts <th>Last post".format(t_cnt)) | |||
else: | |||
rss_list = [] | |||
for t in t_list[:s_ts]: | |||
t = t.split(" >< ") | |||
if not rss: | |||
print("<tr><td><a href='?m=thread;t={0}'>{1}.".format(t[0], cnt)) | |||
if int(t[3]) >= b_conf[7] and t[4] not in ["1", "3"]: | |||
t[2] = t_modes['1'] + t[2] | |||
elif int(t[3]) >= b_conf[7] and t[4] == "2": | |||
t[2] = t_modes['3'] + t[2] | |||
elif t[4] in t_modes.keys(): | |||
t[2] = t_modes[t[4]] + t[2] | |||
if not rss: | |||
print("</a><td><a href='#" \ | |||
+ "{0}'>{2}</a> <td>{3} <td>{1} ".format(*t)) | |||
else: | |||
rss_list.append(t) | |||
cnt += 1 | |||
if rss: | |||
return rss_list | |||
elif prev == "1": | |||
print("<tr><th>") | |||
if (t_cnt - s_ts) > 0: | |||
print("<td colspan='2'><a href='?m=list'>") | |||
print("View all threads</a> ({0} hidden)".format(t_cnt - s_ts)) | |||
print("<td>") | |||
else: | |||
print("<td colspan='3'>") | |||
print("<a href='#create'>Create new thread</a>") | |||
print("</table>") | |||
def bbs_reply(t_fn='', t_id=''): | |||
with open("./html/reply.html") as r_thread: | |||
print(r_thread.read().format(t_fn, t_id)) | |||
def bbs_atom(m='t'): | |||
amode = wt.get_form('r') | |||
if amode not in ['p', 't']: | |||
bbs_header() | |||
print(b_conf[8]) | |||
return | |||
print("""Content-type: application/atom+xml\r\n | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<feed xmlns="http://www.w3.org/2005/Atom">""") | |||
if amode == 'p': | |||
print("<title>{0}: 50 latest posts</title>".format(b_conf[0])) | |||
print("<link rel='self' href='" + b_conf[8] + "?m=atom;r=p' />") | |||
print("<id>{0}#posts</id>".format(b_conf[8])) | |||
isot = "%Y-%m-%dT%H:%M:00%z" | |||
with open(b_conf[6], 'r') as tlist: | |||
tlist = tlist.read().splitlines() | |||
tdict = {} | |||
for t in tlist: | |||
t = t.split(" >< ") | |||
tdict[t[0]] = t[2] | |||
with open(b_conf[10]) as ip_l: | |||
ip_l = ip_l.read().splitlines()[::-1][:50] | |||
l_upd = ip_l[0].split(" >< ")[1].replace(" ", "").replace(".", "-") | |||
l_upd = re.sub(r'\[(.*?)\]', 'T', l_upd) | |||
print("<updated>" + l_upd + b_conf[9] + "</updated>") | |||
for p in ip_l: | |||
print("\n<entry>") | |||
# 0: thread fn, 1: time, 2: post, | |||
# 3: thread title, 4: link | |||
p = p.split(" >< ") | |||
p = [p[0].split(" | ")[1], p[1], cgi.escape(p[3])] | |||
p[0] = p[0].split(".")[0] | |||
p[1] = p[1].replace(" ", "").replace(".", "-") | |||
p[1] = re.sub(r'\[(.*?)\]', 'T', p[1]) + b_conf[9] | |||
p.append('"' + cgi.escape(tdict[p[0]]) + '"') | |||
p.append(b_conf[8] + "?m=thread;t=" + p[0]) | |||
print("<updated>" + p[1] + "</updated>") | |||
print("<id>" + p[4] + "#" + p[1] + "</id>") | |||
print("<title>reply in thread", p[3], "</title>") | |||
print("<link rel='alternate' href='" + p[4] + "'/>") | |||
print("<content type='html'>", p[2], "</content>") | |||
print("</entry>\n") | |||
print("</feed>") | |||
elif amode == 't': | |||
print("<title>{0}: 50 latest threads</title>".format(b_conf[0])) | |||
print("<link rel='self' href='" + b_conf[8] + "?m=atom;r=t' />") | |||
print("<id>{0}#threads</id>".format(b_conf[8])) | |||
t_list = bbs_list('0', "1") | |||
t_list.sort(key=lambda t_list:t_list[0]) | |||
upd = t_list[-1][0] | |||
# print(t_list) | |||
l_upd = time.localtime(int(upd)) | |||
isot = "%Y-%m-%dT%H:%M" + b_conf[9] | |||
l_upd = time.strftime(isot, l_upd) | |||
print("<updated>" + l_upd + "</updated>") | |||
for t in t_list[::-1]: | |||
print("\n<entry>") | |||
upd = time.localtime(int(t[0])) | |||
t_url = b_conf[8] + "?m=thread;t=" + t[0] | |||
print("<updated>" + time.strftime(isot, upd) + "</updated>") | |||
print("<id>" + t_url + "</id>") | |||
print("<link rel='alternate' href='" + t_url + "' />") | |||
print("<title>", cgi.escape(t[2]), "</title>") | |||
with open(b_conf[5] + t[0] + ".txt", "r") as tt: | |||
tt = tt.read().splitlines()[1] | |||
tt = " >< ".join(tt.split(" >< ")[3:]) | |||
tt = cgi.escape(tt) | |||
print("<content type='html'>", tt, "</content>") | |||
print("</entry>") | |||
print("</feed>") | |||
def bbs_foot(): | |||
with open("./html/foot.html") as b_foot: | |||
print(b_foot.read()) | |||
def do_reply(): | |||
reply_attrs = {'name':'', 'bump':'', 'comment':'', 't':''} | |||
for key in reply_attrs.keys(): | |||
if wt.get_form(key): | |||
reply_attrs[key] = wt.get_form(key) | |||
# Comment and thread are necessary params | |||
if not reply_attrs['comment']: | |||
print("You need to write something to post a comment.") | |||
return None | |||
elif not reply_attrs['t']: | |||
return None | |||
reply_attrs['comment'] = reply_attrs['comment'].\ | |||
strip().replace('\r\n', "<br>")[:5000] | |||
# Get the name, generating trip / capcoding admin as needed | |||
if not reply_attrs['name']: | |||
reply_attrs['name'] = "Anonymous" | |||
elif '#' in reply_attrs['name']: | |||
namentrip = reply_attrs['name'][:18].split('#')[:2] | |||
namentrip[1] = tripcode(namentrip[1]) | |||
if not namentrip[0]: | |||
namentrip[0] = "Anonymous" | |||
if b_conf[3] == namentrip[1][2:]: | |||
namentrip = ['', b_conf[2]] | |||
reply_attrs['name'] = '</span> <span class="admin">'\ | |||
.join(namentrip) | |||
else: | |||
reply_attrs['name'] = '</span> <span clas="trip">'\ | |||
.join(namentrip) | |||
else: | |||
reply_attrs['name'] = reply_attrs['name'][:18] | |||
# Check if sage or not | |||
if not reply_attrs['bump']: | |||
reply_attrs['bump'] = "1" | |||
elif reply_attrs['bump'] != "1": | |||
reply_attrs['bump'] = '' | |||
reply_attrs['ldt'] = wt.fancy_time(None, "human") | |||
reply_string = " >< ".join([reply_attrs['name'], \ | |||
reply_attrs['ldt'], reply_attrs['bump'], \ | |||
reply_attrs['comment'] + "\n"]) | |||
fale = 0 | |||
with open(reply_attrs['t'], "r") as the_thread: | |||
ter = the_thread.read().splitlines() | |||
num_replies = len(ter) - 1 | |||
if "><" in ter[0] and ter[0].split(">< ")[1] in ["1", "3"]: | |||
fale = 3 | |||
elif num_replies >= b_conf[7]: | |||
fale = 1 | |||
else: | |||
ter = ter[-1].split(' >< ') | |||
if ter[-1] == reply_string.split(' >< ')[-1][:-1]: | |||
fale = 2 | |||
f_mesg = { | |||
1:"Sorry, thread limit reached.", | |||
2:"Sorry, you already posted that.", | |||
3:"Sorry, the thread is locked."} | |||
# Update thread with latest post by appending to the file. | |||
if fale == 0: | |||
with open(reply_attrs['t'], "a+") as the_thread: | |||
the_thread.write(reply_string) | |||
else: | |||
print(f_mesg[fale]) | |||
return None | |||
ip = os.environ["REMOTE_ADDR"] | |||
reply_attrs['t'] = reply_attrs['t'][len(b_conf[5]):] | |||
log_data = " | ".join([ip, reply_attrs['t'], | |||
f"#{num_replies} | {reply_string}"]) | |||
# update ips.txt (log of posts + ips) | |||
with open(b_conf[10], "a") as log: | |||
log.write(log_data) | |||
print("comment successfully posted<p>Redirecting you in" | |||
" 5 seconds...", wt.redirect()) | |||
reply_attrs['t'] = ''.join([i for i in reply_attrs['t'] \ | |||
if i.isdigit()]) | |||
t_line = [reply_attrs['t'], reply_attrs['ldt'], reply_attrs['bump']] | |||
nt_list = [] | |||
new_t = [] | |||
sage = 0 | |||
# load list.txt | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines() | |||
for n, t in enumerate(t_list): | |||
t = t.split(' >< ') | |||
nt_list.append(t) | |||
if t[0] == t_line[0]: | |||
if len(t) is 4 and t[4] in ["1", "3"]: | |||
print("you should not be posting in a locked thread") | |||
return None | |||
elif t_line[2] == "1" or t[4] in ("2", "4"): | |||
# Sage, posting in sticky, posting in permasage | |||
# does not affect the thread's position | |||
sage = 1 | |||
t_line.pop(2) | |||
t_line.insert(2, t[2]) | |||
t_line.insert(3, str(int(t[3])+1)) | |||
t_line.insert(4, t[4]) | |||
new_t = [' >< '.join(t), ' >< '.join(t_line)] | |||
# Update list.txt | |||
posted = 0 | |||
for n, t in enumerate(nt_list): | |||
# Maintain position of sage/stickies | |||
if sage: | |||
if t[0] == new_t[0].split(" >< ")[0]: | |||
nt_list[n] = new_t[1].split(" >< ") | |||
break | |||
# Do not move a bumped thread above stickied threads | |||
elif posted == 0 and t[4] not in ["2", "3"]: | |||
nt_list.insert(n, new_t[1].split(" >< ")) | |||
posted = 1 | |||
elif t == new_t[0].split(" >< "): | |||
nt_list.remove(t) | |||
break | |||
for n, l in enumerate(nt_list): | |||
nt_list[n] = " >< ".join(l) | |||
with open(b_conf[6], "w") as new_tl: | |||
new_tl.write('\n'.join(nt_list)) | |||
def do_prev(bbt=[]): | |||
if not bbt: | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines()[:b_conf[12]] | |||
for n, t in enumerate(t_list): | |||
t = t.split(" >< ") | |||
bbs = bbs_thread(t[0], 1) | |||
print("<div class='thread'><a name={0}>".format(t[0])) | |||
print("<h3><a>" + str(n+1)+".</a>") | |||
do_prev([bbs, t[0]]) | |||
return | |||
pstcnt = 0 | |||
bbn = len(bbt[0]) | |||
if bbn > b_conf[13]: | |||
bbn = len(bbt[0]) - b_conf[13] + 1 | |||
else: | |||
bbn = 1 | |||
with open(b_conf[5] + str(bbt[1]) + ".txt") as t: | |||
t_t = t.readline()[:-1] | |||
t_r = len(t.read().splitlines()) | |||
t_m = '' | |||
if "><" in t_t: | |||
print(t_modes[t_t.split(">< ")[1]]) | |||
t_m = t_modes[t_t.split(">< ")[1]] | |||
if t_m in t_modes.keys(): | |||
t_t = t_t.split(" >< ")[0] + t_m | |||
else: | |||
t_t = t_t.split(" >< ")[0] | |||
print("<a href='?m=thread;t={0}'>{1} [{2}]"\ | |||
.format(bbt[1], t_t, len(bbt[0]))) | |||
print("</a></h3>") | |||
for replies in bbt[0]: | |||
pstcnt += 1 | |||
if pstcnt == 1 or pstcnt >= bbn: | |||
print("<div class='reply'><div class='title'>") | |||
print("#{0} //".format(pstcnt)) | |||
print("Name: {0} \n: Date: {1} \n</div><p>{2}".format(*replies)) | |||
elif pstcnt == (bbn - 1): | |||
print("<hr>") | |||
if "lock" in t_m or t_r >= b_conf[7]: | |||
print("""<div class='reply'> | |||
<div class='rmr'><br>{0} | |||
Thread locked.<p> | |||
No more comments allowed | |||
</div><br></div></div>""".format(t_m)) | |||
elif t_r < b_conf[7]: | |||
# print("<hr width='420px' align='left'>") | |||
bbs_reply(b_conf[5] + bbt[1]+".txt") | |||
def do_format(urp=''): | |||
x = "(text omitted)<br>" + urp.split('[/yt]')[-1] \ | |||
if len(urp.split('[yt]')) > 3 \ | |||
else '' | |||
urp = '[yt]'.join(urp.split('[yt]')[:3]) | |||
urp += x | |||
# urp += if len(urpl.split('[yt]')) > 3 urp.split('[/yt]')[-1] | |||
urp = re.sub(r'\[yt\]http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?\[/yt\]', | |||
r'<div style="width:480; height:320" class="youtube" id="\1"></div><p>', urp) | |||
urp = re.sub(r'\[spoiler\](.*?)\[/spoiler\]', | |||
r'<span class="spoiler">\1</span>', urp) | |||
urp = urp.split("<br>") | |||
for n, l in enumerate(urp): | |||
if l[:4] == '>': | |||
urp[n] = "<span class='quote'>" + l + "</span>" | |||
urp = "<br>".join(urp) | |||
urp = re.sub(r'(<br>{4,})', r'<br>', urp) | |||
urp = re.sub(r'\[code\](.*?)\[/code\]', r'<pre><b>Code:</b><hr><code>\1</code></pre><p>', urp) | |||
urp = urp.replace('&', '&').encode('ascii', 'xmlcharrefreplace').decode() | |||
return urp | |||
def tripcode(pw): | |||
pw = pw[:8] | |||
salt = (pw + "H..")[1:3] | |||
trip = crypt.crypt(pw, salt) | |||
return (" !" + trip[-10:]) | |||
if __name__ == "__main__": | |||
main() |
@@ -0,0 +1 @@ | |||
@@ -1 +0,0 @@ | |||
@@ -0,0 +1,601 @@ | |||
#!/usr/bin/env python3 | |||
import os, cgi, cgitb | |||
import time, re, crypt | |||
import webtools as wt | |||
import backlink | |||
form = cgi.FieldStorage() | |||
settings = "./settings.txt" | |||
cgitb.enable() | |||
# To generate a mod password, use tripcode.py3 to generate a tripkey. | |||
# What comes out is the result of a code that generates something like | |||
# a public key. Type #password in the name field to have your post | |||
# render as the mod_un using a special color. Save your !tripcode in the | |||
# mod_pw field to do so, without an exclamation mark in front. Your tripkey | |||
# should be 10 letters long, the result of an 8 character secure password | |||
# that you know others can't guess easily. | |||
with open(settings, "r") as settings: | |||
b_conf = [] | |||
settings = settings.read().splitlines() | |||
for s in settings: | |||
if len(s) == 0 or s[0] == "#" or ": " not in s: | |||
continue | |||
elif "#" in s: | |||
s = s.split("#")[0] | |||
s = s.split(": ") | |||
if len(s) > 2: | |||
s[1] = ": ".join(s[1:]) | |||
try: | |||
s[1] = int(s[1]) | |||
except: | |||
pass | |||
b_conf.append(s[1]) | |||
f = {"main":"bbs_main()", "thread":"bbs_thread()", | |||
"list":"bbs_list()", "create":"bbs_create()", | |||
"reply":"do_reply()", "atom":"bbs_atom()"} | |||
t_modes = {"0":"", \ | |||
"1":"<img src='./img/lock.png' alt='Lock'>", | |||
"2":"<img src='./img/sticky.png' alt='Sticky'>", | |||
"3":"<img src='./img/sticky.png'><img src='./img/lock.png'>", | |||
"4":"<img src='./img/ghost.png' alt='Nobump'>"} | |||
def main(): | |||
select_func = wt.get_form('m') | |||
if select_func == 'atom': | |||
bbs_atom() | |||
return | |||
bbs_header() | |||
if select_func in f.keys(): | |||
print("<a href='.'><< back</a><br>") | |||
print("----"*10, "<p>") | |||
eval(f[select_func]) | |||
else: | |||
bbs_main() | |||
bbs_foot() | |||
print("</div></div>") | |||
def bbs_header(): | |||
with open("./html/head.html", "r") as head: | |||
print(head.read().format(b_conf[0])) | |||
def bbs_main(): | |||
print("<div class='front'>") | |||
print("""Styles: [<a href="javascript:setActiveStyleSheet('4x13');">4x13</a>] | |||
[<a href="javascript:setActiveStyleSheet('0ch');">0ch</a>]""") | |||
print("<h2>{0}</h2>".format(b_conf[0])) | |||
with open('./html/motd.html', 'r') as motd: | |||
print(motd.read()) | |||
bbs_list(prev='1') | |||
print("<p><hr>") | |||
do_prev() | |||
print("<hr>") | |||
bbs_create() | |||
# print("</div>") | |||
def bbs_thread(t_id='', prev=0): | |||
if not t_id and wt.get_form('t'): | |||
t_id = wt.get_form('t') | |||
if t_id: | |||
if t_id.isdigit(): | |||
t_fn = b_conf[5] + t_id + ".txt" | |||
else: | |||
bbs_list() | |||
return | |||
else: | |||
bbs_list() | |||
return | |||
if not os.path.isfile(t_fn): | |||
bbs_list() | |||
return | |||
with open(t_fn, "r") as the_thread: | |||
the_thread = the_thread.readlines() | |||
r_cnt = str(len(the_thread) - 1) | |||
t_m = '' | |||
if "><" in the_thread[0]: | |||
t_m = the_thread[0].split("><")[1].strip() | |||
if t_m in t_modes.keys(): | |||
t_m = t_modes[t_m] | |||
else: | |||
t_m = '' | |||
the_thread[0] = the_thread[0].split("><")[0] | |||
elif int(r_cnt) >= b_conf[7]: | |||
t_m = t_modes['1'] | |||
bld = {} | |||
if prev == 0: | |||
bld = backlink.do_backlink(t_id) | |||
print("<div class='thread'>") | |||
print("<h3>", t_m, the_thread[0] + "[" + r_cnt + "]", "</h3>") | |||
p_n = 0 | |||
replies=[] | |||
for reply in the_thread[1:]: | |||
p_n += 1 | |||
reply = reply.split(' >< ') | |||
if len(reply) > 4: | |||
reply[3] = " >< ".join(reply[3:]) | |||
if reply[2]: | |||
reply.pop(2) | |||
reply[0] = "<span class='sage'>" \ | |||
+ reply[0] + "</span>" | |||
else: | |||
reply.pop(2) #30c | |||
reply[0] = "<span class='bump'>" \ | |||
+ reply[0] + "</span>" | |||
if prev == 0: | |||
if re.compile(r'>>[\d]').search(reply[2]): | |||
reply[2] = re.sub(r'>>([\d]+)', \ | |||
r'<a href="#\1">>>\1</a>', \ | |||
reply[2]) | |||
reply[2] = do_format(reply[2]) | |||
print("<div class='reply'><div class='title'>") | |||
print("<a name='{0}' href='#reply'".format(p_n)) | |||
print("onclick='addText(\"{1}\", \"{0}\")'>"\ | |||
.format(p_n, t_id)) | |||
print("#{0}</a> //".format(p_n)) | |||
print("Name: {0} :\nDate: {1} \n"\ | |||
.format(reply[0], reply[1])) | |||
if str(p_n) in bld.keys(): | |||
print("<div class='bl'>Replies: ") | |||
aref = "<a href='#{0}'><i>>>{0}</a></i>" | |||
print(", ".join([aref.format(r) \ | |||
for r in bld[str(p_n)]])) | |||
print("</div>") | |||
print("</div>") | |||
print("<p>", reply[2], "</p></div>") | |||
else: | |||
if re.compile(r'>>[\d]').search(reply[2]): | |||
reply[2] = re.sub(r'>>([\d]+)', \ | |||
r'<a href="?m=thread;t={0}#\1">>>\1</a>'.format(t_id), reply[2]) | |||
reply[2] = do_format(reply[2]) | |||
if len(reply[2].split('<br>')) > 8: | |||
reply[2] = '<br>'.join(reply[2].split('<br>')[:9])[:850] | |||
if "<pre" and not "</pre>" in reply[2]: | |||
reply[2] += "</pre>" | |||
if "<code" and not "</code>" in reply[2]: | |||
reply[2] += "</code>" | |||
reply[2] += "</p><div class='rmr'>Post shortened. " \ | |||
+ "<a href='?m=thread;t={0}'>[".format(t_id) \ | |||
+ "View full thread]</a></div>" | |||
elif len(reply[2].split('<span class="youtube" ')) > 2: | |||
reply[2] = '<span class="youtube"'.join(reply[2].split('<span class="youtube"')[:2]) | |||
if "<pre" and not "</pre>" in reply[2]: | |||
reply[2] += "</pre>" | |||
if "<code" and not "</code>" in reply[2]: | |||
reply[2] += "</code>" | |||
reply[2] += "</p><div class='rmr'>Post shortened. " \ | |||
+ "<a href='?m=thread;t={0}'>[".format(t_id) \ | |||
+ "View full thread]</a></div>" | |||
elif len(reply[2]) > 850: | |||
reply[2] = reply[2][:850] | |||
if "<pre" and not "</pre>" in reply[2]: | |||
reply[2] += "</pre>" | |||
if "<code" and not "</code>" in reply[2]: | |||
reply[2] += "</code>" | |||
reply[2] += "</p><div class='rmr'>Post shortened. " \ | |||
+ "<a href='?m=thread;t={0}'>".format(t_id) \ | |||
+ "[View full thread]</a></div>" | |||
show_r = b_conf[13] | |||
if int(r_cnt) > show_r and p_n == int(r_cnt): | |||
reply[2] = reply[2] + "</p><br><div class='rmr'>" \ | |||
+"<a href='?m=thread;t={0}'".format(t_id) \ | |||
+">[Read all posts]</a></div><br>" | |||
reply[2] += "</div>" | |||
replies.append(reply) | |||
if "lock" in t_m.strip() and prev == 0: | |||
print("<div class='rmr'>") | |||
print(t_m.strip()) | |||
print("<b>This thread is locked.</b>") | |||
print("<p>No more replies can be added.") | |||
print("</div>") | |||
elif int(r_cnt) >= b_conf[7]: | |||
print("<div class='closed'>", t_modes['1']) | |||
print("This thread is locked. Reply limit hit.") | |||
print("</div>") | |||
elif prev == 0: | |||
bbs_reply(t_fn, t_id) | |||
return replies | |||
def bbs_create(): | |||
thread_attrs = {'title':'', 'name':'', 'content':''} | |||
for key in thread_attrs.keys(): | |||
if wt.get_form(key): | |||
thread_attrs[key] = wt.get_form(key) | |||
if thread_attrs['title'] and thread_attrs['content']: | |||
thread_attrs['title'] = thread_attrs['title'][:30].strip() | |||
if thread_attrs['name']: | |||
thread_attrs['name'] = \ | |||
thread_attrs['name'][:18].strip() | |||
if '#' in thread_attrs['name']: | |||
namentrip = thread_attrs['name'].split('#')[:2] | |||
namentrip[1] = tripcode(namentrip[1]) | |||
thread_attrs['name'] = '</span> <span class="trip">'.join(namentrip) | |||
thread_attrs['content'] = thread_attrs['content'].strip().replace('\r\n', "<br>")[:2000] | |||
thread_attrs['dt'] = str(time.time())[:10] | |||
if not thread_attrs['name']: | |||
thread_attrs['name'] = 'Anonymous' | |||
local_dt = time.localtime(int(thread_attrs['dt'])) | |||
date_str = "%Y-%m-%d [%a] %H:%M" | |||
thread_attrs['ldt'] = time.strftime(date_str, local_dt) | |||
t_fn = thread_attrs['dt'] + ".txt" | |||
with open(b_conf[5] + t_fn, "x") as new_thread: | |||
new_thread.write(thread_attrs['title'] + "\n" \ | |||
+ thread_attrs['name'] + " >< " \ | |||
+ thread_attrs['ldt'] + " >< >< " \ | |||
+ thread_attrs['content'] + "\n" ) | |||
print("Thread <i>{0}</i> posted successfully!".format(thread_attrs['title'])) | |||
with open(b_conf[11], "a") as log: | |||
ip = os.environ["REMOTE_ADDR"] | |||
# IP | location | filename | ldt | comment | |||
log_data = " | ".join([ip, t_fn, thread_attrs['ldt'], \ | |||
thread_attrs['title'], thread_attrs['name'], thread_attrs['content']+"\n"]) | |||
log.write(log_data) | |||
print("Redirecting you in 5 seconds...") | |||
print("<meta http-equiv='refresh' content='5;.'") | |||
with open(b_conf[6], "r") as t_list: | |||
t_list = t_list.read().splitlines() | |||
new_t = " >< ".join([thread_attrs['dt'], \ | |||
thread_attrs['ldt'], thread_attrs['title'], \ | |||
"1", "0"]) | |||
for n, t in enumerate(t_list): | |||
t = t.split(" >< ") | |||
if len(t) == 5 and t[4] not in ["2", "3"] or len(t) == 4: | |||
t_list.insert(n, new_t) | |||
break | |||
else: | |||
pass | |||
else: | |||
t_list.insert(0, new_t) | |||
with open(b_conf[6], "w") as upd_list: | |||
upd_list.write('\n'.join(t_list)) | |||
else: | |||
if not thread_attrs['title']: | |||
if thread_attrs['content']: | |||
print("You need to enter a title to post a new thread.<br>") | |||
elif not thread_attrs['content']: | |||
print("You need to write a message to post a new thread.<br>") | |||
with open("./html/create.html") as c_thread: | |||
print(c_thread.read()) | |||
def bbs_list(prev='0', rss=False): | |||
if prev == '0': | |||
s_ts = None | |||
else: | |||
s_ts = b_conf[12] | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines() | |||
t_cnt = len(t_list) | |||
cnt = 1 | |||
if not rss: | |||
print("<a name='tbox'></a><table>") | |||
print("<th>{0} <th>Title <th>Posts <th>Last post".format(t_cnt)) | |||
else: | |||
rss_list = [] | |||
for t in t_list[:s_ts]: | |||
t = t.split(" >< ") | |||
if not rss: | |||
print("<tr><td><a href='?m=thread;t={0}'>{1}.".format(t[0], cnt)) | |||
if int(t[3]) >= b_conf[7] and t[4] not in ["1", "3"]: | |||
t[2] = t_modes['1'] + t[2] | |||
elif int(t[3]) >= b_conf[7] and t[4] == "2": | |||
t[2] = t_modes['3'] + t[2] | |||
elif t[4] in t_modes.keys(): | |||
t[2] = t_modes[t[4]] + t[2] | |||
if not rss: | |||
print("</a><td><a href='#" \ | |||
+ "{0}'>{2}</a> <td>{3} <td>{1} ".format(*t)) | |||
else: | |||
rss_list.append(t) | |||
cnt += 1 | |||
if rss: | |||
return rss_list | |||
elif prev == "1": | |||
print("<tr><th>") | |||
if (t_cnt - s_ts) > 0: | |||
print("<td colspan='2'><a href='?m=list'>") | |||
print("View all threads</a> ({0} hidden)".format(t_cnt - s_ts)) | |||
print("<td>") | |||
else: | |||
print("<td colspan='3'>") | |||
print("<a href='#create'>Create new thread</a>") | |||
print("</table>") | |||
def bbs_reply(t_fn='', t_id=''): | |||
with open("./html/reply.html") as r_thread: | |||
print(r_thread.read().format(t_fn, t_id)) | |||
def bbs_atom(m='t'): | |||
amode = wt.get_form('r') | |||
if amode not in ['p', 't']: | |||
bbs_header() | |||
print(b_conf[8]) | |||
return | |||
print("""Content-type: application/atom+xml\r\n | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<feed xmlns="http://www.w3.org/2005/Atom">""") | |||
if amode == 'p': | |||
print("<title>{0}: 50 latest posts</title>".format(b_conf[0])) | |||
print("<link rel='self' href='" + b_conf[8] + "?m=atom;r=p' />") | |||
print("<id>{0}#posts</id>".format(b_conf[8])) | |||
isot = "%Y-%m-%dT%H:%M:00%z" | |||
with open(b_conf[6], 'r') as tlist: | |||
tlist = tlist.read().splitlines() | |||
tdict = {} | |||
for t in tlist: | |||
t = t.split(" >< ") | |||
tdict[t[0]] = t[2] | |||
with open(b_conf[10]) as ip_l: | |||
ip_l = ip_l.read().splitlines()[::-1][:50] | |||
l_upd = ip_l[0].split(" >< ")[1].replace(" ", "").replace(".", "-") | |||
l_upd = re.sub(r'\[(.*?)\]', 'T', l_upd) | |||
print("<updated>" + l_upd + b_conf[9] + "</updated>") | |||
for p in ip_l: | |||
print("\n<entry>") | |||
# 0: thread fn, 1: time, 2: post, | |||
# 3: thread title, 4: link | |||
p = p.split(" >< ") | |||
p = [p[0].split(" | ")[1], p[1], cgi.escape(p[3])] | |||
p[0] = p[0].split(".")[0] | |||
p[1] = p[1].replace(" ", "").replace(".", "-") | |||
p[1] = re.sub(r'\[(.*?)\]', 'T', p[1]) + b_conf[9] | |||
p.append('"' + cgi.escape(tdict[p[0]]) + '"') | |||
p.append(b_conf[8] + "?m=thread;t=" + p[0]) | |||
print("<updated>" + p[1] + "</updated>") | |||
print("<id>" + p[4] + "#" + p[1] + "</id>") | |||
print("<title>reply in thread", p[3], "</title>") | |||
print("<link rel='alternate' href='" + p[4] + "'/>") | |||
print("<content type='html'>", p[2], "</content>") | |||
print("</entry>\n") | |||
print("</feed>") | |||
elif amode == 't': | |||
print("<title>{0}: 50 latest threads</title>".format(b_conf[0])) | |||
print("<link rel='self' href='" + b_conf[8] + "?m=atom;r=t' />") | |||
print("<id>{0}#threads</id>".format(b_conf[8])) | |||
t_list = bbs_list('0', "1") | |||
t_list.sort(key=lambda t_list:t_list[0]) | |||
upd = t_list[-1][0] | |||
# print(t_list) | |||
l_upd = time.localtime(int(upd)) | |||
isot = "%Y-%m-%dT%H:%M" + b_conf[9] | |||
l_upd = time.strftime(isot, l_upd) | |||
print("<updated>" + l_upd + "</updated>") | |||
for t in t_list[::-1]: | |||
print("\n<entry>") | |||
upd = time.localtime(int(t[0])) | |||
t_url = b_conf[8] + "?m=thread;t=" + t[0] | |||
print("<updated>" + time.strftime(isot, upd) + "</updated>") | |||
print("<id>" + t_url + "</id>") | |||
print("<link rel='alternate' href='" + t_url + "' />") | |||
print("<title>", cgi.escape(t[2]), "</title>") | |||
with open(b_conf[5] + t[0] + ".txt", "r") as tt: | |||
tt = tt.read().splitlines()[1] | |||
tt = " >< ".join(tt.split(" >< ")[3:]) | |||
tt = cgi.escape(tt) | |||
print("<content type='html'>", tt, "</content>") | |||
print("</entry>") | |||
print("</feed>") | |||
def bbs_foot(): | |||
with open("./html/foot.html") as b_foot: | |||
print(b_foot.read()) | |||
def do_reply(): | |||
reply_attrs = {'name':'', 'bump':'', 'comment':'', 't':''} | |||
for key in reply_attrs.keys(): | |||
if wt.get_form(key): | |||
reply_attrs[key] = wt.get_form(key) | |||
# Comment and thread are necessary params | |||
if not reply_attrs['comment']: | |||
print("You need to write something to post a comment.") | |||
return None | |||
elif not reply_attrs['t']: | |||
return None | |||
reply_attrs['comment'] = reply_attrs['comment'].\ | |||
strip().replace('\r\n', "<br>")[:5000] | |||
# Get the name, generating trip / capcoding admin as needed | |||
if not reply_attrs['name']: | |||
reply_attrs['name'] = "Anonymous" | |||
elif '#' in reply_attrs['name']: | |||
namentrip = reply_attrs['name'][:18].split('#')[:2] | |||
namentrip[1] = tripcode(namentrip[1]) | |||
if not namentrip[0]: | |||
namentrip[0] = "Anonymous" | |||
if b_conf[3] == namentrip[1][2:]: | |||
namentrip = ['', b_conf[2]] | |||
reply_attrs['name'] = '</span> <span class="admin">'\ | |||
.join(namentrip) | |||
else: | |||
reply_attrs['name'] = '</span> <span clas="trip">'\ | |||
.join(namentrip) | |||
else: | |||
reply_attrs['name'] = reply_attrs['name'][:18] | |||
# Check if sage or not | |||
if not reply_attrs['bump']: | |||
reply_attrs['bump'] = "1" | |||
elif reply_attrs['bump'] != "1": | |||
reply_attrs['bump'] = '' | |||
reply_attrs['ldt'] = wt.fancy_time(None, "human") | |||
reply_string = " >< ".join([reply_attrs['name'], \ | |||
reply_attrs['ldt'], reply_attrs['bump'], \ | |||
reply_attrs['comment'] + "\n"]) | |||
fale = 0 | |||
with open(reply_attrs['t'], "r") as the_thread: | |||
ter = the_thread.read().splitlines() | |||
num_replies = len(ter) - 1 | |||
if "><" in ter[0] and ter[0].split(">< ")[1] in ["1", "3"]: | |||
fale = 3 | |||
elif num_replies >= b_conf[7]: | |||
fale = 1 | |||
else: | |||
ter = ter[-1].split(' >< ') | |||
if ter[-1] == reply_string.split(' >< ')[-1][:-1]: | |||
fale = 2 | |||
f_mesg = { | |||
1:"Sorry, thread limit reached.", | |||
2:"Sorry, you already posted that.", | |||
3:"Sorry, the thread is locked."} | |||
# Update thread with latest post by appending to the file. | |||
if fale == 0: | |||
with open(reply_attrs['t'], "a+") as the_thread: | |||
the_thread.write(reply_string) | |||
else: | |||
print(f_mesg[fale]) | |||
return None | |||
ip = os.environ["REMOTE_ADDR"] | |||
reply_attrs['t'] = reply_attrs['t'][len(b_conf[5]):] | |||
log_data = " | ".join([ip, reply_attrs['t'], | |||
f"#{num_replies} | {reply_string}"]) | |||
# update ips.txt (log of posts + ips) | |||
with open(b_conf[10], "a") as log: | |||
log.write(log_data) | |||
print("comment successfully posted<p>Redirecting you in" | |||
" 5 seconds...", wt.redirect()) | |||
reply_attrs['t'] = ''.join([i for i in reply_attrs['t'] \ | |||
if i.isdigit()]) | |||
t_line = [reply_attrs['t'], reply_attrs['ldt'], reply_attrs['bump']] | |||
nt_list = [] | |||
new_t = [] | |||
sage = 0 | |||
# load list.txt | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines() | |||
for n, t in enumerate(t_list): | |||
t = t.split(' >< ') | |||
nt_list.append(t) | |||
if t[0] == t_line[0]: | |||
if len(t) is 4 and t[4] in ["1", "3"]: | |||
print("you should not be posting in a locked thread") | |||
return None | |||
elif t_line[2] == "1" or t[4] in ("2", "4"): | |||
# Sage, posting in sticky, posting in permasage | |||
# does not affect the thread's position | |||
sage = 1 | |||
t_line.pop(2) | |||
t_line.insert(2, t[2]) | |||
t_line.insert(3, str(int(t[3])+1)) | |||
t_line.insert(4, t[4]) | |||
new_t = [' >< '.join(t), ' >< '.join(t_line)] | |||
# Update list.txt | |||
posted = 0 | |||
for n, t in enumerate(nt_list): | |||
# Maintain position of sage/stickies | |||
if sage: | |||
if t[0] == new_t[0].split(" >< ")[0]: | |||
nt_list[n] = new_t[1].split(" >< ") | |||
break | |||
# Do not move a bumped thread above stickied threads | |||
elif posted == 0 and t[4] not in ["2", "3"]: | |||
nt_list.insert(n, new_t[1].split(" >< ")) | |||
posted = 1 | |||
elif t == new_t[0].split(" >< "): | |||
nt_list.remove(t) | |||
break | |||
for n, l in enumerate(nt_list): | |||
nt_list[n] = " >< ".join(l) | |||
with open(b_conf[6], "w") as new_tl: | |||
new_tl.write('\n'.join(nt_list)) | |||
def do_prev(bbt=[]): | |||
if not bbt: | |||
with open(b_conf[6]) as t_list: | |||
t_list = t_list.read().splitlines()[:b_conf[12]] | |||
for n, t in enumerate(t_list): | |||
t = t.split(" >< ") | |||
bbs = bbs_thread(t[0], 1) | |||
print("<div class='thread'><a name={0}>".format(t[0])) | |||
print("<h3><a>" + str(n+1)+".</a>") | |||
do_prev([bbs, t[0]]) | |||
return | |||
pstcnt = 0 | |||
bbn = len(bbt[0]) | |||
if bbn > b_conf[13]: | |||
bbn = len(bbt[0]) - b_conf[13] + 1 | |||
else: | |||
bbn = 1 | |||
with open(b_conf[5] + str(bbt[1]) + ".txt") as t: | |||
t_t = t.readline()[:-1] | |||
t_r = len(t.read().splitlines()) | |||
t_m = '' | |||
if "><" in t_t: | |||
print(t_modes[t_t.split(">< ")[1]]) | |||
t_m = t_modes[t_t.split(">< ")[1]] | |||
if t_m in t_modes.keys(): | |||
t_t = t_t.split(" >< ")[0] + t_m | |||
else: | |||
t_t = t_t.split(" >< ")[0] | |||
print("<a href='?m=thread;t={0}'>{1} [{2}]"\ | |||
.format(bbt[1], t_t, len(bbt[0]))) | |||
print("</a></h3>") | |||
for replies in bbt[0]: | |||
pstcnt += 1 | |||
if pstcnt == 1 or pstcnt >= bbn: | |||
print("<div class='reply'><div class='title'>") | |||
print("#{0} //".format(pstcnt)) | |||
print("Name: {0} \n: Date: {1} \n</div><p>{2}".format(*replies)) | |||
elif pstcnt == (bbn - 1): | |||
print("<hr>") | |||
if "lock" in t_m or t_r >= b_conf[7]: | |||
print("""<div class='reply'> | |||
<div class='rmr'><br>{0} | |||
Thread locked.<p> | |||
No more comments allowed | |||
</div><br></div></div>""".format(t_m)) | |||
elif t_r < b_conf[7]: | |||
# print("<hr width='420px' align='left'>") | |||
bbs_reply(b_conf[5] + bbt[1]+".txt") | |||
def do_format(urp=''): | |||
x = "(text omitted)<br>" + urp.split('[/yt]')[-1] \ | |||
if len(urp.split('[yt]')) > 3 \ | |||
else '' | |||
urp = '[yt]'.join(urp.split('[yt]')[:3]) | |||
urp += x | |||
# urp += if len(urpl.split('[yt]')) > 3 urp.split('[/yt]')[-1] | |||
urp = re.sub(r'\[yt\]http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?\[/yt\]', | |||
r'<div style="width:480; height:320" class="youtube" id="\1"></div><p>', urp) | |||
urp = re.sub(r'\[spoiler\](.*?)\[/spoiler\]', | |||
r'<span class="spoiler">\1</span>', urp) | |||
urp = urp.split("<br>") | |||
for n, l in enumerate(urp): | |||
if l[:4] == '>': | |||
urp[n] = "<span class='quote'>" + l + "</span>" | |||
urp = "<br>".join(urp) | |||
urp = re.sub(r'(<br>{4,})', r'<br>', urp) | |||
urp = re.sub(r'\[code\](.*?)\[/code\]', r'<pre><b>Code:</b><hr><code>\1</code></pre><p>', urp) | |||
urp = urp.replace('&', '&').encode('ascii', 'xmlcharrefreplace').decode() | |||
return urp | |||
def tripcode(pw): | |||
pw = pw[:8] | |||
salt = (pw + "H..")[1:3] | |||
trip = crypt.crypt(pw, salt) | |||
return (" !" + trip[-10:]) | |||
if __name__ == "__main__": | |||
main() |
@@ -64,7 +64,7 @@ var title = cookie ? cookie : getPreferredStyleSheet(); | |||
setActiveStyleSheet(title); | |||
function addText(elId,text) { | |||
text = ">>" + text + " \\r"; | |||
text = ">>" + text + " \r"; | |||
document.getElementById(elId).value += text; | |||
} | |||
@@ -1 +1 @@ | |||
00 >< 1993-09-01 16:20 >< hello world! >< 1 >< 2 | |||
0 >< 2017-03-22 [Wed] 04:03 >< hello world! >< 1 >< 3 |
@@ -0,0 +1,18 @@ | |||
* moderate board | |||
- set title | |||
- set URL | |||
- select timezone | |||
- modify MOTD / foot | |||
- select style/s | |||
- set Admin name | |||
- set tripcode (password) | |||
- set reply limit (per-thread; board rule) | |||
- set show-threads (frontpage) | |||
- set show-replies (frontpage) | |||
- view post log (delete/ban/filter) | |||
- view thread log (delete/ban/filter/mode) | |||
- view thread directory | |||
- view thread list (formatted list.txt) |
@@ -0,0 +1,27 @@ | |||
* head | |||
** title | |||
** stylesheet | |||
** atom feed | |||
** javascript | |||
* body | |||
** div.front | |||
*** styles | |||
*** h2 | |||
*** div.thread#motd | |||
On the front page of a board, show a pane for | |||
general news / info / links | |||
*** div.thread | |||
**** anchor | |||
**** h3 title | |||
**** div.reply | |||
One for each comment in a thread | |||
***** div.title | |||
(#, name, date) | |||
***** p (post content) | |||
**** div.reply | |||
***** input (name, bump, comment) | |||
@@ -0,0 +1,28 @@ | |||
* settings | |||
** settings.txt / set.txt | |||
* script modes | |||
** main thread list create reply atom | |||
* thread modes | |||
** none lock sticky stickylock nobump | |||
* main | |||
** get function | |||
*** atom if atom | |||
** header | |||
** function if function | |||
** foot | |||
* thread | |||
** get thread id | |||
** if not thread: | |||
list | |||
** open thread | |||
** set mode | |||
** if not preview: backlink.do_backlink() | |||
** for replies: | |||
*** set reply mode | |||
*** if not preview do backlinks | |||
*** else do previewed replies | |||
** if lock / postlimit dont reply |
@@ -0,0 +1,12 @@ | |||
todo | |||
- refactor post formatting / markup | |||
- make more HTML/ATOM templates | |||
- add in blacklist aka wordfilter | |||
- add custom date format | |||
- use unix dates rather than ISO 8601 | |||
- migrate to settings dictionary | |||
- create setup wizard | |||
- recruit designers | |||
- mobile theme | |||
- poster ID feature ? |
@@ -0,0 +1,27 @@ | |||
# bname = board title (iyagi bbs) | |||
# burl = board url (/bbs/) | |||
# aun = admin username (Admin) | |||
# apw = admin password (.CzKQna1OU) | |||
# thrdb = thread database (./threads/) | |||
# tlist = thread list (./list.txt) | |||
# rlim = reply limit (100) | |||
# furl = full url (http://127.0.0.1/bbs/) | |||
# tz = time zone (:00-0700) | |||
# tip = thread ips (ips2.txt) | |||
# frec = front page recent threads index (4) | |||
# frep = front page replies shown (4) | |||
# ytlim = youtube's per post (to prevent spam) 2 | |||
bname:iyagi bbs | |||
burl:/bbs/ | |||
aun:Admin | |||
apw:.CzKQna1OU | |||
thrdb:./threads/ | |||
tlist:./list.txt | |||
rlim:100 | |||
furl:http://127.0.0.1/bbs/ | |||
tz::00-0700 | |||
tip:ips2.txt | |||
frec:4 | |||
frep:4 | |||
ytlim:2 |
@@ -9,7 +9,8 @@ Mod username: Admin | |||
#3 The tripcoded version of your password. | |||
# Use tripcode.py3 to generate this code. | |||
Mod pw: tyF/EWEkIY | |||
# default is test. | |||
Mod pw: .CzKQna1OU | |||
#4 Unused | |||
Theme: alpha | |||
@@ -22,7 +23,7 @@ Thread list: ./list.txt | |||
#7 Maximum number of replies a thread can have | |||
# (suggested: 100 - 1000) | |||
Reply limit: 3 | |||
Reply limit: 100 | |||
#8 Used for the RSS | |||
Full board URL: http://127.0.0.1/bbs/ | |||
@@ -35,7 +36,7 @@ Post IP log: ips.txt | |||
Thread IP log: ips2.txt | |||
#12 Threads to display on front page | |||
Show recent: 7 | |||
Show recent: 5 | |||
#13 Replies to show in the front page's threads | |||
Show replies: 3 |
@@ -0,0 +1,2 @@ | |||
hello world! >< 3 | |||
</span><span class='admin'>Admin >< 1993-09-01 16:20 >< >< Welcome to iyagi! Have you configured ./settings.txt yet?<p>To remove a thread, remove its entry from list.txt, then delete its file in ./threads/ . Then, remove its entry from ips2.txt<p>To remove a post, delete its line in the /threads/#thread.txt and remove its entry from ips.txt<p>To lock a thread, add " >< 1" to its title in its file. Then, change the last value to its row in list.txt to "1".<br><br>2 stickies, 3 stickylocks, 4 permasages |
@@ -1,3 +1,4 @@ | |||
#!/usr/bin/env python3 | |||
import crypt | |||
def mktripcode(pw): | |||
@@ -0,0 +1,97 @@ | |||
#!/usr/bin/env python3 | |||
import cgi, os, time | |||
form = cgi.FieldStorage() | |||
time_form = '%Y.%m.%d [%a] %H:%M' | |||
long_time = '%A, %B %d @ %H:%M' | |||
short_time = '%m-%d @ %H:%M' | |||
# print headers, receive CGI form info, create CGI | |||
# forms, basic cookie handling, and translate UNIX time | |||
# to the preferred timestamp format. | |||
def head(title=''): | |||
header = ["Content-type: text/html\n\n"] | |||
header.append("<title>{0}</title>".format(title)) | |||
return "\n".join(header) | |||
def get_form(val): | |||
if form.getvalue(val): | |||
if len(form.getvalue(val)[0]) > 1: | |||
# print('\n'.join(form.getlist(val))) | |||
return cgi.escape('\n'.join(form.getlist(val))).strip() | |||
return cgi.escape(form.getvalue(val)).strip() | |||
else: | |||
return '' | |||
def put_form(ty='', na='', va='', re=''): | |||
inps = [ty, na, va] | |||
if ty != "textarea": | |||
exform = "<input type='{0}' name='{1}' value='{2}'>".format(*inps) | |||
else: | |||
exform = "<textarea name='{1}'>{2}</textarea>".format(*inps) | |||
if re: | |||
exform.replace(">", " required>") | |||
return exform | |||
def dropdown(na='', val=[], nam=[]): | |||
dropdown = ["<select name='{0}'>".format(na)] | |||
if len(val) > len(nam) or not nam: | |||
nam = val | |||
for n, i in enumerate(val): | |||
dropdown.append("<option value='{0}'>{1}</option>".format(val[n], \ | |||
nam[n])) | |||
dropdown.append("</select>") | |||
return "\n".join(dropdown) | |||
def new_form(act='.', met='post'): | |||
formhead = "<form action='{0}' method='{1}'>".format(act, met) | |||
return formhead | |||
def put_cookie(name='', data=''): | |||
content = name+"="+data+";" | |||
c_string = "<meta http-equiv='set-cookie' content='{0}'>".format(content) | |||
return c_string | |||
def get_cookie(): | |||
c_dict = {} | |||
if "HTTP_COOKIE" in os.environ: | |||
cookies = os.environ["HTTP_COOKIE"] | |||
cookies = cookies.split("; ") | |||
for c in cookies: | |||
c = c.split("=") | |||
c_dict[c[0]] = "=".join(c[1:]) | |||
return c_dict | |||
def get_ip(): | |||
return os.environ["REMOTE_ADDR"] | |||
def fancy_time(utime='', mode=''): | |||
if not utime: | |||
utime = int(time.time()) | |||
else: | |||
utime = int(utime) | |||
if mode == 'unix': | |||
return str(utime) | |||
htime = time.localtime(utime) | |||
if mode == "human": | |||
htime = time.strftime(time_form, htime) | |||
return htime | |||
elif mode == "lt": | |||
return time.strftime(long_time, htime) | |||
elif mode == "st": | |||
return time.strftime(short_time, htime) | |||
else: | |||
htime = time.strftime(time_form, htime) | |||
return [utime, htime] | |||
def grab_html(fn=''): | |||
with open('./html/' + fn + '.html', 'r') as html: | |||
html = html.read() | |||
return html | |||
def redirect(sec=5, loc='.'): | |||
return "<meta http-equiv='refresh' content='" \ | |||
+ str(sec) + ";" + loc + "'>" |