Python web board system (textboard, CGI, flatfile) http://4x13.net/bbs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

658 lines
24 KiB

  1. #!/usr/bin/env python3
  2. import os, cgi, cgitb
  3. import time, re, crypt
  4. import webtools as wt
  5. import backlink, bans
  6. form = cgi.FieldStorage()
  7. settings = "./settings.txt"
  8. cgitb.enable()
  9. with open("spam.txt", "r") as spam:
  10. spam = spam.read().splitlines()
  11. bad_words = spam
  12. # To generate a mod password, use tripcode.py3 to generate a tripkey.
  13. # What comes out is the result of a code that generates something like
  14. # a public key. Type #password in the name field to have your post
  15. # render as the mod_un using a special color. Save your !tripcode in the
  16. # mod_pw field to do so, without an exclamation mark in front. Your tripkey
  17. # should be 10 letters long, the result of an 8 character secure password
  18. # that you know others can't guess easily.
  19. # list(conf) settings are:
  20. # 0 - name, 1 - web path, 2 - admin name,
  21. # 3 - mod pw hash, 5 - thread storage,
  22. # 6 - thread list, 7 - reply limit,
  23. # 8 - full web path, # 9 - timezone
  24. # 10 - post IPs log, # 11 - thread IP log
  25. # 12 - threads to show, # 13 - replies to show
  26. with open(settings, "r") as settings:
  27. conf = []
  28. settings = settings.read().splitlines()
  29. for s in settings:
  30. if len(s) == 0 or s[0] == "#" or ": " not in s:
  31. continue
  32. elif "#" in s:
  33. s = s.split("#")[0]
  34. s = s.split(": ")
  35. if len(s) > 2:
  36. s[1] = ": ".join(s[1:])
  37. try:
  38. s[1] = int(s[1])
  39. except:
  40. pass
  41. conf.append(s[1])
  42. f = {"main":"bbs_main()", "thread":"bbs_thread()",
  43. "list":"bbs_list()", "create":"bbs_create()",
  44. "reply":"do_reply()", "atom":"bbs_atom()"
  45. }
  46. t_modes = {"0":"", \
  47. "1":"<img src='./img/lock.png' alt='Lock'>",
  48. "2":"<img src='./img/sticky.png' alt='Sticky'>",
  49. "3":"<img src='./img/sticky.png'><img src='./img/lock.png'>",
  50. "4":"<img src='./img/ghost.png' alt='Nobump'>"}
  51. def main():
  52. select_func = wt.get_form('m')
  53. if select_func == 'atom':
  54. bbs_atom()
  55. return
  56. bbs_header()
  57. if select_func in f.keys():
  58. print("<a href='.'>&lt;&lt; back</a><br>")
  59. print("----"*10, "<p>")
  60. eval(f[select_func])
  61. else:
  62. try:
  63. x = 1 / int(wt.raw_query())
  64. print("<a href='.'>&lt;&lt; back</a><br>")
  65. print("----"*10, "<p>")
  66. bbs_thread(wt.raw_query())
  67. except:
  68. bbs_main()
  69. bbs_foot()
  70. print("</div></div>")
  71. def bbs_header():
  72. with open("./html/head.html", "r") as head:
  73. print(head.read().format(conf[0]))
  74. print("""<style>
  75. .x {visibility: hidden;width:0;height:0;display:none}
  76. </style>""")
  77. def bbs_main():
  78. print("<div class='front'>")
  79. print("""Styles:
  80. [<a href="javascript:setActiveStyleSheet('4x13');">4x13</a>]
  81. [<a href="javascript:setActiveStyleSheet('7ch');">7ch</a>]
  82. [<a href="javascript:setActiveStyleSheet('vba');">vba</a>]
  83. [<a href="javascript:setActiveStyleSheet('geocities');">Geocities</a>]
  84. [<a href="javascript:setActiveStyleSheet('0ch');">0ch</a>]""")
  85. print(f"<h2><a href='#tbox'>&#9632;</a> {conf[0]}</h2>")
  86. with open('./html/motd.html', 'r') as motd:
  87. print(motd.read())
  88. bbs_list(1)
  89. print("<p><hr>")
  90. do_prev()
  91. print("<hr>")
  92. bbs_create()
  93. print("</div>")
  94. def bbs_thread(t_id='', prev=0):
  95. if not t_id and wt.get_form('t'):
  96. t_id = wt.get_form('t')
  97. if t_id:
  98. if t_id.isdigit():
  99. t_fn = conf[5] + t_id + ".txt"
  100. else:
  101. bbs_list(0)
  102. return
  103. else:
  104. bbs_list(0)
  105. return
  106. if not os.path.isfile(t_fn):
  107. bbs_list(0)
  108. return
  109. with open(t_fn, "r") as the_thread:
  110. the_thread = the_thread.readlines()
  111. r_cnt = str(len(the_thread) - 1)
  112. t_m = ''
  113. if "><" in the_thread[0]:
  114. t_m = the_thread[0].split("><")[1].strip()
  115. if t_m in t_modes.keys():
  116. t_m = t_modes[t_m]
  117. else:
  118. t_m = ''
  119. the_thread[0] = the_thread[0].split("><")[0]
  120. elif int(r_cnt) >= conf[7]:
  121. t_m = t_modes['1']
  122. bld = {}
  123. if prev == 0:
  124. bld = backlink.do_backlink(t_id)
  125. print("<div class='thread'>")
  126. print(f"<h3> {t_m} {the_thread[0]} [{r_cnt}]</h3>")
  127. p_n = 0
  128. replies=[]
  129. for reply in the_thread[1:]:
  130. p_n += 1
  131. reply = reply.split(' >< ')
  132. if len(reply) > 4:
  133. reply[3] = " >< ".join(reply[3:])
  134. if reply[2]:
  135. reply.pop(2)
  136. reply[0] = f"<span class='sage'>{reply[0]}</span>"
  137. else:
  138. reply.pop(2) #30c
  139. reply[0] = f"<span class='bump'>{reply[0]}</span>"
  140. # Viewing thread in threadview
  141. if prev == 0:
  142. if re.compile(r'&gt;&gt;[\d]').search(reply[2]):
  143. reply[2] = re.sub(r'&gt;&gt;([\d]+)', \
  144. r'<a href="#\1">&gt;&gt;\1</a>', \
  145. reply[2])
  146. reply[2] = do_format(reply[2])
  147. print("<div class='reply'><div class='title'>")
  148. print("<a name='{0}' href='#reply'".format(p_n))
  149. print("onclick='addText(\"{1}\", \"{0}\")'>"\
  150. .format(p_n, t_id))
  151. print("#{0}</a> //".format(p_n))
  152. print("Name: {0} :\nDate: {1} \n"\
  153. .format(reply[0], reply[1]))
  154. if str(p_n) in bld.keys():
  155. print("<div class='bl'>Replies: ")
  156. aref = "<a href='#{0}'><i>&gt;&gt;{0}</a></i>"
  157. print(", ".join([aref.format(r) \
  158. for r in bld[str(p_n)]]))
  159. print("</div>")
  160. print("</div><div class='comment'>")
  161. print("<p>", reply[2], "</p></div></div>")
  162. # Viewing preview on front page
  163. else:
  164. if re.compile(r'&gt;&gt;[\d]').search(reply[2]):
  165. reply[2] = re.sub(r'&gt;&gt;([\d]+)',
  166. r'<a href="?{0}#\1">'\
  167. .format(t_id) + r'&gt;&gt;\1</a>',
  168. reply[2])
  169. reply[2] = do_format(reply[2])
  170. if len(reply[2].split('<br>')) > 12:
  171. reply[2] = '<br>'.join(reply[2].split('<br>')[:12])[:850]
  172. if "<pre" and not "</pre>" in reply[2]:
  173. reply[2] += "</pre>"
  174. if "<code" and not "</code>" in reply[2]:
  175. reply[2] += "</code>"
  176. reply[2] += "</p><div class='rmr'>Post shortened. "
  177. reply[2] += f"<a href='?{t_id}'>["
  178. reply[2] += "View full thread]</a></div>"
  179. elif len(reply[2].split('<span class="youtube" ')) > 2:
  180. reply[2] = '<span class="youtube"'.join(reply[2]\
  181. .split('<span class="youtube"')[:2])
  182. if "<pre" and not "</pre>" in reply[2]:
  183. reply[2] += "</pre>"
  184. if "<code" and not "</code>" in reply[2]:
  185. reply[2] += "</code>"
  186. reply[2] += "</p><div class='rmr'>Post shortened. "
  187. reply[2] += "<a href='?{t_id}'>["
  188. reply[2] += "View full thread]</a></div>"
  189. elif len(reply[2]) > 1400:
  190. reply[2] = reply[2][:1400]
  191. if "<pre" and not "</pre>" in reply[2]:
  192. reply[2] += "</pre>"
  193. if "<code" and not "</code>" in reply[2]:
  194. reply[2] += "</code>"
  195. reply[2] += "</p><div class='rmr'>Post shortened. "
  196. reply[2] += f"<a href='?{t_id}'>"
  197. reply[2] += "[View full thread]</a></div>"
  198. show_r = conf[13]
  199. if (int(r_cnt) - 1) > show_r and p_n == int(r_cnt):
  200. reply[2] += "</p><br><div class='rmr'>"
  201. reply[2] += f"<a href='?{t_id}'"
  202. reply[2] += ">[Read all posts]</a></div><br>"
  203. reply[2] += "</div>"
  204. replies.append(reply)
  205. if "lock" in t_m.strip() and prev == 0:
  206. print("<div class='rmr'>")
  207. print(t_m.strip())
  208. print("<b>This thread is locked.</b>")
  209. print("<p>No more replies can be added.")
  210. print("</div>")
  211. elif int(r_cnt) >= conf[7]:
  212. print("<div class='closed'>", t_modes['1'])
  213. print("This thread is locked. Reply limit hit.")
  214. print("</div>")
  215. elif prev == 0:
  216. bbs_reply(t_fn, t_id)
  217. return replies
  218. def bbs_create(prev=0):
  219. thread_attrs = {'title':'', 'name':'', 'content':''}
  220. thread_attrs = {k: wt.get_form(k) for k in thread_attrs.keys()}
  221. if wt.get_form('m') == "create" \
  222. and None in [thread_attrs['title'], thread_attrs['content']]:
  223. if not thread_attrs['title']:
  224. print("You need to enter a title to post a new thread.<br>")
  225. if not thread_attrs['content']:
  226. print("You need to write a message to post a new thread.<br>")
  227. with open("./html/create.html") as c_thread:
  228. print(c_thread.read())
  229. return
  230. elif wt.get_form('m') != "create":
  231. with open("./html/create.html") as c_thread:
  232. print(c_thread.read())
  233. return
  234. if bans.is_banned(wt.get_ip()):
  235. print("<center>")
  236. print("<h1>You are banned!</h1>")
  237. print("<h3>", bans.is_banned(wt.get_ip()), "</h3>")
  238. return
  239. for word in bad_words:
  240. if word in [thread_attrs["title"].lower(),
  241. thread_attrs["content"].lower()]:
  242. print("<center><h2>Bad word error.</h2></center>")
  243. return
  244. thread_attrs['title'] = thread_attrs['title'][:30].strip()
  245. if thread_attrs['name']:
  246. thread_attrs['name'] = thread_attrs['name'][:18].strip()
  247. if '#' in thread_attrs['name']:
  248. namentrip = thread_attrs['name'].split('#')[:2]
  249. namentrip[1] = tripcode(namentrip[1])
  250. thread_attrs['name'] = '</span> <span class="trip">'\
  251. .join(namentrip)
  252. else:
  253. thread_attrs['name'] = 'Anonymous'
  254. thread_attrs['content'] = thread_attrs['content']\
  255. .strip().replace('\r\n', "<br>")[:2000] +'\n'
  256. dt = wt.fancy_time('', 'unix')
  257. ldt = wt.fancy_time(dt, 'human')
  258. t_fn = dt + ".txt"
  259. with open(conf[5] + t_fn, "x") as new_thread:
  260. new_thread.write(thread_attrs['title'] + "\n" \
  261. + thread_attrs['name'] + " >< " \
  262. + ldt + " >< >< " \
  263. + thread_attrs['content'])
  264. print("Thread <i>{0}</i> posted successfully!"\
  265. .format(thread_attrs['title']))
  266. with open(conf[11], "a") as log:
  267. ip = os.environ["REMOTE_ADDR"]
  268. # IP | location | filename | ldt | comment
  269. log_data = " | ".join([ip, t_fn, ldt, thread_attrs['name'],
  270. thread_attrs['title'],
  271. thread_attrs['content']])
  272. log.write(log_data)
  273. print("Redirecting you in 5 seconds...", wt.redirect())
  274. new_t = " >< ".join([dt, ldt, thread_attrs['title'], "1", "0"])
  275. # load list.txt
  276. with open(conf[6], "r") as t_list:
  277. t_list = t_list.read().splitlines()
  278. for n, t in enumerate(t_list):
  279. t = t.split(" >< ")
  280. if len(t) == 5 and t[4] not in ["2", "3"] or len(t) == 4:
  281. t_list.insert(n, new_t)
  282. break
  283. else:
  284. pass
  285. else:
  286. t_list.insert(len(t_list), new_t)
  287. with open(conf[6], "w") as upd_list:
  288. upd_list.write('\n'.join(t_list))
  289. def bbs_list(prev=0, rss=False):
  290. with open(conf[6]) as t_list:
  291. t_list = t_list.read().splitlines()
  292. t_cnt = len(t_list)
  293. if not prev:
  294. s_ts = t_cnt
  295. else:
  296. s_ts = conf[12]
  297. cnt = 1
  298. if not rss:
  299. print("<a name='tbox'></a><table class='sortable'><thead>")
  300. print("<th>{0} <th>Title <th>Posts <th>Last post</thead>".format(t_cnt))
  301. else:
  302. rss_list = []
  303. for t in t_list[:s_ts]:
  304. t = t.split(" >< ")
  305. if not rss:
  306. print("<tr><td><a href='?{0}'>{1}.".format(t[0], cnt))
  307. if int(t[3]) >= conf[7] and t[4] not in ["1", "3"]:
  308. t[2] = t_modes['1'] + t[2]
  309. elif int(t[3]) >= conf[7] and t[4] == "2":
  310. t[2] = t_modes['3'] + t[2]
  311. elif t[4] in t_modes.keys():
  312. t[2] = t_modes[t[4]] + t[2]
  313. if not rss and prev:
  314. print("</a><td><a href='#" \
  315. + "{0}'>{2}</a>&nbsp; <td>{3} <td>{1} &nbsp;".format(*t))
  316. elif not rss:
  317. print("</a><td><a href='?" \
  318. + "{0}'>{2}</a>&nbsp; <td>{3} <td>{1} &nbsp;".format(*t))
  319. else:
  320. rss_list.append(t)
  321. cnt += 1
  322. if rss:
  323. return rss_list
  324. print("<tr><td>")
  325. if prev and (t_cnt - s_ts) > 0:
  326. print("<td colspan='2'>")
  327. print(f"<a href='?m=list'>View all threads</a> ({t_cnt - s_ts} hidden)<td>")
  328. else:
  329. print("<td colspan='3'>")
  330. print("<a href='#create'>Create new thread</a>")
  331. print("</table>")
  332. def bbs_reply(t_fn='', t_id=''):
  333. with open("./html/reply.html") as r_thread:
  334. print(r_thread.read().format(t_fn, t_id))
  335. def bbs_atom(m='t'):
  336. amode = wt.get_form('r')
  337. if amode not in ['p', 't']:
  338. bbs_header()
  339. print(conf[8])
  340. return
  341. print("""Content-type: application/atom+xml\r\n
  342. <?xml version="1.0" encoding="utf-8"?>
  343. <feed xmlns="http://www.w3.org/2005/Atom">""")
  344. if amode == 'p':
  345. print("<title>{0}: 50 latest posts</title>".format(conf[0]))
  346. print(f"<link rel='self' href='{conf[8]}?m=atom;r=p' />")
  347. print("<id>{0}#posts</id>".format(conf[8]))
  348. isot = "%Y-%m-%dT%H:%M:00%z"
  349. with open(conf[6], 'r') as tlist:
  350. tlist = tlist.read().splitlines()
  351. tdict = {}
  352. for t in tlist:
  353. t = t.split(" >< ")
  354. tdict[t[0]] = t[2]
  355. with open(conf[10]) as ip_l:
  356. ip_l = ip_l.read().splitlines()[::-1][:50]
  357. l_upd = ip_l[0].split(" >< ")[1].replace(" ", "").replace(".", "-")
  358. l_upd = re.sub(r'\[(.*?)\]', 'T', l_upd)
  359. print("<updated>" + l_upd + conf[9] + "</updated>")
  360. for p in ip_l:
  361. print("\n<entry>")
  362. # 0: thread fn, 1: time, 2: post,
  363. # 3: thread title, 4: link
  364. p = p.split(" >< ")
  365. p = [p[0].split(" | ")[1], p[1], cgi.escape(p[3])]
  366. p[0] = p[0].split(".")[0]
  367. p[1] = p[1].replace(" ", "").replace(".", "-")
  368. p[1] = re.sub(r'\[(.*?)\]', 'T', p[1]) + conf[9]
  369. p.append('"' + cgi.escape(tdict[p[0]]) + '"')
  370. p.append(conf[8] + "?" + p[0])
  371. print(f"<updated>{p[1]}</updated>",
  372. f"<id>{p[4]}#{p[1]}</id>",
  373. f"<title>reply in thread {p[3]}</title>",
  374. f"<link rel='alternate' href='{p[4]}'/>",
  375. f"<content type='html'>{p[2]}</content>\n</entry>\n")
  376. print("</feed>")
  377. elif amode == 't':
  378. print("<title>{0}: 50 latest threads</title>".format(conf[0]))
  379. print("<link rel='self' href='" + conf[8] + "?m=atom;r=t' />")
  380. print("<id>{0}#threads</id>".format(conf[8]))
  381. t_list = bbs_list(0, 1)
  382. t_list.sort(key=lambda t_list:t_list[0])
  383. upd = t_list[-1][0]
  384. # print(t_list)
  385. l_upd = time.localtime(int(upd))
  386. isot = "%Y-%m-%dT%H:%M" + conf[9]
  387. l_upd = time.strftime(isot, l_upd)
  388. print(f"<updated>{l_upd}</updated>")
  389. for t in t_list[::-1]:
  390. print("\n<entry>")
  391. upd = time.localtime(int(t[0]))
  392. t_url = conf[8] + "?" + t[0]
  393. print(f"<updated>{time.strftime(isot, upd)}</updated>",
  394. f"<id>{t_url}</id>",
  395. f"<link rel='alternate' href='{t_url}' />",
  396. f"<title>{cgi.escape(t[2])}</title>")
  397. with open(conf[5] + t[0] + ".txt", "r") as tt:
  398. tt = tt.read().splitlines()[1]
  399. tt = " >< ".join(tt.split(" >< ")[3:])
  400. tt = cgi.escape(tt)
  401. print(f"<content type='html'>{tt}</content>\n</entry>")
  402. print("</feed>")
  403. def bbs_foot():
  404. with open("./html/foot.html") as b_foot:
  405. print(b_foot.read())
  406. def do_reply():
  407. reply_attrs = {'name':'', 'bump':'', 'comment':'', 't':''}
  408. if bans.is_banned(wt.get_ip()):
  409. print("<center>")
  410. print("<h1>You are banned!</h1>")
  411. print("<h3>", bans.is_banned(wt.get_ip()), "</h3>")
  412. return
  413. for key in reply_attrs.keys():
  414. if wt.get_form(key):
  415. reply_attrs[key] = wt.get_form(key)
  416. for word in bad_words:
  417. if word in reply_attrs["comment"].lower():
  418. print("<center><h2>Bad word error.</h2></center>")
  419. return
  420. if wt.get_form('rname') or wt.get_form('email'):
  421. return reply_attrs[comment]
  422. # Comment and thread are necessary params
  423. if not reply_attrs['comment']:
  424. print("You need to write something to post a comment.")
  425. return None
  426. elif not reply_attrs['t']:
  427. return None
  428. reply_attrs['comment'] = reply_attrs['comment']\
  429. .strip().replace('\r\n', "<br>")[:5000]\
  430. .encode("ascii", "ignore").decode()
  431. # Get the name, generating trip / capcoding admin as needed
  432. if not reply_attrs['name']:
  433. reply_attrs['name'] = "Anonymous"
  434. elif '#' in reply_attrs['name']:
  435. namentrip = reply_attrs['name'][:18].split('#')[:2]
  436. namentrip[1] = tripcode(namentrip[1])
  437. if not namentrip[0]:
  438. namentrip[0] = "Anonymous"
  439. if conf[3] == namentrip[1][2:]:
  440. namentrip = ['', conf[2]]
  441. reply_attrs['name'] = '</span> <span class="admin">'\
  442. .join(namentrip)
  443. else:
  444. reply_attrs['name'] = '</span> <span clas="trip">'\
  445. .join(namentrip)
  446. else:
  447. reply_attrs['name'] = reply_attrs['name'][:18]
  448. # Check if sage or not
  449. if not reply_attrs['bump']:
  450. reply_attrs['bump'] = "1"
  451. elif reply_attrs['bump'] != "1":
  452. reply_attrs['bump'] = ''
  453. reply_attrs['ldt'] = wt.fancy_time(None, "human")
  454. reply_string = " >< ".join([reply_attrs['name'], \
  455. reply_attrs['ldt'], reply_attrs['bump'], \
  456. reply_attrs['comment'] + "\n"])
  457. fale = 0
  458. with open(reply_attrs['t'], "r") as the_thread:
  459. ter = the_thread.read().splitlines()
  460. num_replies = len(ter) - 1
  461. if "><" in ter[0] and ter[0].split(">< ")[1] in ["1", "3"]:
  462. fale = 3
  463. elif num_replies >= conf[7]:
  464. fale = 1
  465. else:
  466. ter = ter[-1].split(' >< ')
  467. if ter[-1] == reply_string.split(' >< ')[-1][:-1]:
  468. fale = 2
  469. f_mesg = {1:"Sorry, thread limit reached.",
  470. 2:"Sorry, you already posted that.",
  471. 3:"Sorry, the thread is locked."}
  472. # Update thread with latest post by appending to the file.
  473. if fale == 0:
  474. with open(reply_attrs['t'], "a+") as the_thread:
  475. the_thread.write(reply_string)
  476. else:
  477. print(f_mesg[fale])
  478. return None
  479. ip = os.environ["REMOTE_ADDR"]
  480. reply_attrs['t'] = reply_attrs['t'][len(conf[5]):]
  481. log_data = " | ".join([ip, reply_attrs['t'],
  482. f"#{num_replies} | {reply_string}"])
  483. # update ips.txt (log of posts + ips)
  484. with open(conf[10], "a") as log:
  485. log.write(log_data)
  486. print("comment successfully posted<p>Redirecting you in"
  487. " 5 seconds...", wt.redirect())
  488. reply_attrs['t'] = ''.join([i for i in reply_attrs['t'] \
  489. if i.isdigit()])
  490. t_line = [reply_attrs['t'], reply_attrs['ldt'], reply_attrs['bump']]
  491. nt_list = []
  492. new_t = []
  493. sage = 0
  494. # load list.txt
  495. with open(conf[6]) as t_list:
  496. t_list = t_list.read().splitlines()
  497. for n, t in enumerate(t_list):
  498. t = t.split(' >< ')
  499. nt_list.append(t)
  500. if t[0] == t_line[0]:
  501. if t[4] in ["1", "3"]:
  502. print("you should not be posting in a locked thread")
  503. return None
  504. elif t_line[2] == "1" or t[4] in ("2", "4"):
  505. # Sage, posting in sticky, posting in permasage
  506. # does not affect the thread's position
  507. sage = 1
  508. t_line.pop(2)
  509. t_line.insert(2, t[2])
  510. t_line.insert(3, str(int(t[3])+1))
  511. t_line.insert(4, t[4])
  512. new_t = [' >< '.join(t), ' >< '.join(t_line)]
  513. # Update list.txt
  514. posted = 0
  515. for n, t in enumerate(nt_list):
  516. # Maintain position of sage/stickies
  517. if sage:
  518. if t[0] == new_t[0].split(" >< ")[0]:
  519. nt_list[n] = new_t[1].split(" >< ")
  520. break
  521. # Do not move a bumped thread above stickied threads
  522. elif posted == 0 and t[4] not in ["2", "3"]:
  523. nt_list.insert(n, new_t[1].split(" >< "))
  524. posted = 1
  525. elif t == new_t[0].split(" >< "):
  526. nt_list.remove(t)
  527. break
  528. for n, l in enumerate(nt_list):
  529. nt_list[n] = " >< ".join(l)
  530. with open(conf[6], "w") as new_tl:
  531. new_tl.write('\n'.join(nt_list))
  532. def do_prev(bbt=[]):
  533. if not bbt:
  534. with open(conf[6]) as t_list:
  535. t_list = t_list.read().splitlines()[:conf[12]]
  536. for n, t in enumerate(t_list):
  537. t = t.split(" >< ")
  538. bbs = bbs_thread(t[0], 1)
  539. print("<div class='thread'><a name={0}>".format(t[0]))
  540. print("<h3><a>" + str(n+1)+".</a>")
  541. do_prev([bbs, t[0]])
  542. return
  543. pstcnt = 0
  544. bbn = len(bbt[0])
  545. if bbn > conf[13]:
  546. bbn = len(bbt[0]) - conf[13] + 1
  547. else:
  548. bbn = 1
  549. with open(conf[5] + str(bbt[1]) + ".txt") as t:
  550. t_t = t.readline()[:-1]
  551. t_r = len(t.read().splitlines())
  552. t_m = ''
  553. if "><" in t_t:
  554. print(t_modes[t_t.split(">< ")[1]])
  555. t_m = t_modes[t_t.split(">< ")[1]]
  556. if t_m in t_modes.keys():
  557. t_t = t_t.split(" >< ")[0] + t_m
  558. else:
  559. t_t = t_t.split(" >< ")[0]
  560. print("<a href='?{0}'>{1} [{2}]"\
  561. .format(bbt[1], t_t, len(bbt[0])))
  562. print("</a></h3>")
  563. for replies in bbt[0]:
  564. pstcnt += 1
  565. if pstcnt == 1 or pstcnt >= bbn:
  566. print("<div class='reply'><div class='title'>")
  567. print("#{0} //".format(pstcnt))
  568. print("Name: {0} \n: Date: {1} \n</div>"
  569. "<div class='comment'>{2}</div>".format(*replies))
  570. elif pstcnt == (bbn - 1):
  571. print("<hr>")
  572. if "lock" in t_m or t_r >= conf[7]:
  573. print("""<div class='reply'>
  574. <div class='rmr'><br>{0}
  575. Thread locked.<p>
  576. No more comments allowed
  577. </div><br></div></div>""".format(t_m))
  578. elif t_r < conf[7]:
  579. print("<p>")
  580. # print("<hr width='420px' align='left'>")
  581. bbs_reply(conf[5] + bbt[1]+".txt")
  582. def do_format(urp=''):
  583. x = "(text omitted)<br>" + urp.split('[/yt]')[-1] \
  584. if len(urp.split('[yt]')) > 3 \
  585. else ''
  586. urp = '[yt]'.join(urp.split('[yt]')[:3])
  587. urp += x
  588. # urp += if len(urpl.split('[yt]')) > 3 urp.split('[/yt]')[-1]
  589. urp = re.sub(r'\[yt\]http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?\[/yt\]',
  590. r'<div style="width:480; height:320" class="youtube" id="\1"></div><p>', urp)
  591. urp = re.sub(r'\[spoiler\](.*?)\[/spoiler\]',
  592. r'<span class="spoiler">\1</span>', urp)
  593. urp = urp.split("<br>")
  594. for n, l in enumerate(urp):
  595. if l[:4] == '&gt;':
  596. urp[n] = "<span class='quote'>" + l + "</span>"
  597. urp = "<br>".join(urp)
  598. urp = re.sub(r'(<br>{4,})', r'<br>', urp)
  599. urp = re.sub(r'\[code\](.*?)\[/code\]', r'<pre><b>Code:</b><hr><code>\1</code></pre><p>', urp)
  600. urp = urp.replace('&amp;', '&').encode('ascii', 'xmlcharrefreplace').decode()
  601. return urp
  602. def tripcode(pw):
  603. pw = pw[:8]
  604. salt = (pw + "H..")[1:3]
  605. trip = crypt.crypt(pw, salt)
  606. return (" !" + trip[-10:])
  607. if __name__ == "__main__":
  608. main()