Browse Source

big overhaul

tags/v1.6
None 3 years ago
parent
commit
cb4f28b019
24 changed files with 2268 additions and 522 deletions
  1. +8
    -0
      .gitignore
  2. BIN
      __pycache__/backlink.cpython-36.pyc
  3. +152
    -0
      admin.py
  4. +22
    -0
      admin/op.html
  5. +49
    -0
      backlink.py
  6. +75
    -35
      css/0ch.css
  7. +78
    -38
      css/4x13.css
  8. +1
    -1
      html/reply.html
  9. +601
    -0
      index-4x13.py3
  10. +462
    -442
      index.py3
  11. +1
    -0
      ips.txt
  12. +0
    -1
      ips2.txt
  13. +601
    -0
      iyagi-index.py3
  14. +1
    -1
      js/style.js
  15. +1
    -1
      list.txt
  16. +18
    -0
      notes/admin.org
  17. +27
    -0
      notes/html.org
  18. +28
    -0
      notes/iyagi.org
  19. +12
    -0
      notes/todo.txt
  20. +27
    -0
      set.txt
  21. +4
    -3
      settings.txt
  22. +2
    -0
      threads/0.txt
  23. +1
    -0
      tripcode.py3
  24. +97
    -0
      webtools.py

+ 8
- 0
.gitignore View File

@@ -0,0 +1,8 @@
*~
\#*
__pycache__/*
*\\\.*
*pyc
./threads/*
*list*
*ips*

BIN
__pycache__/backlink.cpython-36.pyc View File


+ 152
- 0
admin.py View File

@@ -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>&emsp;", 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()

+ 22
- 0
admin/op.html View File

@@ -0,0 +1,22 @@
<hr><form action='admin.py' method='get'>
<table>
<tr>
<td>{2} {1[3]}
<td>&emsp;&emsp;
<input type='submit' name='more' value='More &#x203A;'>
<input type='hidden' name='thread' value='{0}.txt'>
&emsp; <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>

+ 49
- 0
backlink.py View File

@@ -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'\&gt;\&gt;[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()

+ 75
- 35
css/0ch.css View File

@@ -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);
}
}



+ 78
- 38
css/4x13.css View File

@@ -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);
}


+ 1
- 1
html/reply.html View File

@@ -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>


+ 601
- 0
index-4x13.py3 View File

@@ -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='.'>&lt;&lt; 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'&gt;&gt;[\d]').search(reply[2]):
reply[2] = re.sub(r'&gt;&gt;([\d]+)', \
r'<a href="#\1">&gt;&gt;\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>&gt;&gt;{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'&gt;&gt;[\d]').search(reply[2]):
reply[2] = re.sub(r'&gt;&gt;([\d]+)', \
r'<a href="?m=thread;t={0}#\1">&gt;&gt;\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>&nbsp; <td>{3} <td>{1} &nbsp;".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] == '&gt;':
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('&amp;', '&').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()

+ 462
- 442
index.py3
File diff suppressed because it is too large
View File


+ 1
- 0
ips.txt View File

@@ -0,0 +1 @@

+ 0
- 1
ips2.txt View File

@@ -1 +0,0 @@


+ 601
- 0
iyagi-index.py3 View File

@@ -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='.'>&lt;&lt; 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'&gt;&gt;[\d]').search(reply[2]):
reply[2] = re.sub(r'&gt;&gt;([\d]+)', \
r'<a href="#\1">&gt;&gt;\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>&gt;&gt;{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'&gt;&gt;[\d]').search(reply[2]):
reply[2] = re.sub(r'&gt;&gt;([\d]+)', \
r'<a href="?m=thread;t={0}#\1">&gt;&gt;\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>&nbsp; <td>{3} <td>{1} &nbsp;".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] == '&gt;':
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('&amp;', '&').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()

+ 1
- 1
js/style.js View File

@@ -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
list.txt View File

@@ -1 +1 @@
00 >< 1993-09-01 16:20 >< hello world! >< 1 >< 2
0 >< 2017-03-22 [Wed] 04:03 >< hello world! >< 1 >< 3

+ 18
- 0
notes/admin.org View File

@@ -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)

+ 27
- 0
notes/html.org View File

@@ -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)


+ 28
- 0
notes/iyagi.org View File

@@ -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

+ 12
- 0
notes/todo.txt View File

@@ -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 ?

+ 27
- 0
set.txt View File

@@ -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

+ 4
- 3
settings.txt View File

@@ -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

+ 2
- 0
threads/0.txt View File

@@ -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
- 0
tripcode.py3 View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import crypt

def mktripcode(pw):


+ 97
- 0
webtools.py View File

@@ -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 + "'>"

Loading…
Cancel
Save