diff options
Diffstat (limited to 'cgi/weabot.py')
-rwxr-xr-x | cgi/weabot.py | 2014 |
1 files changed, 1038 insertions, 976 deletions
diff --git a/cgi/weabot.py b/cgi/weabot.py index 398ebfa..1406707 100755 --- a/cgi/weabot.py +++ b/cgi/weabot.py @@ -30,995 +30,1057 @@ _DEBUG = False # Set to True to save performance data to weabot.txt _LOG = False + class weabot(object): - def __init__(self, environ, start_response): - global _DEBUG - self.environ = environ - if self.environ["PATH_INFO"].startswith("/weabot.py/"): - self.environ["PATH_INFO"] = self.environ["PATH_INFO"][11:] - - self.start = start_response - self.formdata = getFormData(self) - self.output = "" - - self.handleRequest() - - # Localization Code - lang = gettext.translation('weabot', './locale', languages=[Settings.LANG]) - lang.install() - - logTime("**Start**") - if _DEBUG: - import cProfile - - prof = cProfile.Profile() - prof.runcall(self.run) - prof.dump_stats('stats.prof') - else: - try: - self.run() - except UserError, message: - self.error(message) - except Exception, inst: - import sys, traceback - exc_type, exc_value, exc_traceback = sys.exc_info() - detail = ((os.path.basename(o[0]),o[1],o[2],o[3]) for o in traceback.extract_tb(exc_traceback)) - self.exception(type(inst), inst, detail) - - # close database and finish - CloseDb() - logTime("**End**") - - if _LOG: - logfile = open(Settings.ROOT_DIR + "weabot.txt", "w") - logfile.write(logTimes()) - logfile.close() - - def __iter__(self): - self.handleResponse() - self.start("200 OK", self.headers) - yield self.output - - def error(self, message): - board = Settings._.BOARD - if board: - if board['board_type'] == '1': - info = {} - info['host'] = self.environ["REMOTE_ADDR"] - info['name'] = self.formdata.get('fielda', '') - info['email'] = self.formdata.get('fieldb', '') - info['message'] = self.formdata.get('message', '') - - self.output += renderTemplate("txt_error.html", {"info": info, "error": message}) - else: - mobile = self.formdata.get('mobile', '') - if mobile: - self.output += renderTemplate("mobile/error.html", {"error": message}) + def __init__(self, environ, start_response): + global _DEBUG + self.environ = environ + if self.environ["PATH_INFO"].startswith("/weabot.py/"): + self.environ["PATH_INFO"] = self.environ["PATH_INFO"][11:] + + self.start = start_response + self.formdata = getFormData(self) + self.output = "" + + self.handleRequest() + + # Localization Code + lang = gettext.translation( + 'weabot', './locale', languages=[Settings.LANG]) + lang.install() + + logTime("**Start**") + if _DEBUG: + import cProfile + + prof = cProfile.Profile() + prof.runcall(self.run) + prof.dump_stats('stats.prof') else: - self.output += renderTemplate("error.html", {"error": message, "boards_url": Settings.BOARDS_URL, "board": board["dir"]}) - else: - self.output += renderTemplate("exception.html", {"exception": None, "error": message}) - - def exception(self, type, message, detail): - self.output += renderTemplate("exception.html", {"exception": type, "error": message, "detail": detail}) - - def handleRequest(self): - self.headers = [("Content-Type", "text/html")] - self.handleCookies() - - def handleResponse(self): - if self._newcookies: - for newcookie in self._newcookies.values(): - self.headers.append(("Set-Cookie", newcookie.output(header=""))) - - def handleCookies(self): - self._cookies = SimpleCookie() - self._cookies.load(self.environ.get("HTTP_COOKIE", "")) - self._newcookies = None - - def run(self): - path_split = self.environ["PATH_INFO"].split("/") - caught = False - - if Settings.FULL_MAINTENANCE: - raise UserError, _("%s is currently under maintenance. We'll be back.") % Settings.SITE_TITLE - - if len(path_split) > 1: - if path_split[1] == "post": - # Making a post - caught = True - - if 'password' not in self.formdata: - raise UserError, "El request está incompleto." - - # let's get all the POST data we need - ip = self.environ["REMOTE_ADDR"] - boarddir = self.formdata.get('board') - parent = self.formdata.get('parent') - trap1 = self.formdata.get('name', '') - trap2 = self.formdata.get('email', '') - name = self.formdata.get('fielda', '') - email = self.formdata.get('fieldb', '') - subject = self.formdata.get('subject', '') - message = self.formdata.get('message', '') - file = self.formdata.get('file') - file_original = self.formdata.get('file_original') - spoil = self.formdata.get('spoil') - oek_file = self.formdata.get('oek_file') - password = self.formdata.get('password', '') - noimage = self.formdata.get('noimage') - mobile = ("mobile" in self.formdata.keys()) - - # call post function - (post_url, ttaken, unused) = self.make_post(ip, boarddir, parent, trap1, trap2, name, email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile) - - # make redirect - self.output += make_redirect(post_url, ttaken) - elif path_split[1] == "environ": - caught = True - - self.output += repr(self.environ) - elif path_split[1] == "delete": - # Deleting a post - caught = True - - boarddir = self.formdata.get('board') - postid = self.formdata.get('delete') - imageonly = self.formdata.get('imageonly') - password = self.formdata.get('password') - mobile = self.formdata.get('mobile') - - # call delete function - self.delete_post(boarddir, postid, imageonly, password, mobile) - elif path_split[1] == "anarkia": - import anarkia - caught = True - OpenDb() - anarkia.anarkia(self, path_split) - elif path_split[1] == "manage": - caught = True - OpenDb() - manage.manage(self, path_split) - elif path_split[1] == "api": - import api - caught = True - self.headers = [("Content-Type", "application/json"), ("Access-Control-Allow-Origin", "*"), ("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS"), ("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")] - OpenDb() - api.api(self, path_split) - elif path_split[1] == "threadlist": - OpenDb() - board = setBoard(path_split[2]) - caught = True - if board['board_type'] != '1': - raise UserError, "No disponible para esta sección." - self.output = threadList(0) - elif path_split[1] == "mobile": - OpenDb() - board = setBoard(path_split[2]) - caught = True - self.output = threadList(1) - elif path_split[1] == "mobilelist": - OpenDb() - board = setBoard(path_split[2]) - caught = True - self.output = threadList(2) - elif path_split[1] == "mobilecat": - OpenDb() - board = setBoard(path_split[2]) - caught = True - self.output = threadList(3) - elif path_split[1] == "mobilenew": - OpenDb() - board = setBoard(path_split[2]) - caught = True - self.output = renderTemplate('txt_newthread.html', {}, True) - elif path_split[1] == "mobilehome": - OpenDb() - latest_age = getLastAge(Settings.HOME_LASTPOSTS) - for threads in latest_age: - content = threads['url'] - content = content.replace('/read/', '/') - content = content.replace('/res/', '/') - content = content.replace('.html', '') - threads['url'] = content - caught = True - self.output = renderTemplate('latest.html', {'latest_age': latest_age}, True) - elif path_split[1] == "mobilenewest": - OpenDb() - newthreads = getNewThreads(Settings.HOME_LASTPOSTS) - for threads in newthreads: - content = threads['url'] - content = content.replace('/read/', '/') - content = content.replace('/res/', '/') - content = content.replace('.html', '') - threads['url'] = content - caught = True - self.output = renderTemplate('newest.html', {'newthreads': newthreads}, True) - elif path_split[1] == "mobileread": - OpenDb() - board = setBoard(path_split[2]) - caught = True - if len(path_split) > 4 and path_split[4] and board['board_type'] == '1': - #try: - self.output = dynamicRead(int(path_split[3]), path_split[4], True) - #except: - # self.output = threadPage(path_split[3], True) - elif board['board_type'] == '1': - self.output = threadPage(0, True, path_split[3]) + try: + self.run() + except UserError, message: + self.error(message) + except Exception, inst: + import sys + import traceback + exc_type, exc_value, exc_traceback = sys.exc_info() + detail = ((os.path.basename(o[0]), o[1], o[2], o[3]) + for o in traceback.extract_tb(exc_traceback)) + self.exception(type(inst), inst, detail) + + # close database and finish + CloseDb() + logTime("**End**") + + if _LOG: + logfile = open(Settings.ROOT_DIR + "weabot.txt", "w") + logfile.write(logTimes()) + logfile.close() + + def __iter__(self): + self.handleResponse() + self.start("200 OK", self.headers) + yield self.output + + def error(self, message): + board = Settings._.BOARD + if board: + if board['board_type'] == '1': + info = {} + info['host'] = self.environ["REMOTE_ADDR"] + info['name'] = self.formdata.get('fielda', '') + info['email'] = self.formdata.get('fieldb', '') + info['message'] = self.formdata.get('message', '') + + self.output += renderTemplate("txt_error.html", + {"info": info, "error": message}) + else: + mobile = self.formdata.get('mobile', '') + if mobile: + self.output += renderTemplate("mobile/error.html", + {"error": message}) + else: + self.output += renderTemplate("error.html", { + "error": message, "boards_url": Settings.BOARDS_URL, "board": board["dir"]}) else: - self.output = threadPage(path_split[3], True) - elif path_split[1] == "catalog": - OpenDb() - board = setBoard(path_split[2]) - caught = True - sort = self.formdata.get('sort', '') - self.output = catalog(sort) - elif path_split[1] == "oekaki": - caught = True - OpenDb() - oekaki.oekaki(self, path_split) - elif path_split[1] == "play": - # Module player - caught = True - boarddir = path_split[2] - modfile = path_split[3] - self.output = renderTemplate('mod.html', {'board': boarddir, 'modfile': modfile}) - elif path_split[1] == "report": - # Report post, check if they are enabled - # Can't report if banned - caught = True - ip = self.environ["REMOTE_ADDR"] - boarddir = path_split[2] - postid = int(path_split[3]) - reason = self.formdata.get('reason') - try: - txt = True - postshow = int(path_split[4]) - except: - txt = False - postshow = postid - - self.report(ip, boarddir, postid, reason, txt, postshow) - elif path_split[1] == "stats": - caught = True - self.stats() - elif path_split[1] == "random": - caught = True + self.output += renderTemplate("exception.html", + {"exception": None, "error": message}) + + def exception(self, type, message, detail): + self.output += renderTemplate("exception.html", + {"exception": type, "error": message, "detail": detail}) + + def handleRequest(self): + self.headers = [("Content-Type", "text/html")] + self.handleCookies() + + def handleResponse(self): + if self._newcookies: + for newcookie in self._newcookies.values(): + self.headers.append( + ("Set-Cookie", newcookie.output(header=""))) + + def handleCookies(self): + self._cookies = SimpleCookie() + self._cookies.load(self.environ.get("HTTP_COOKIE", "")) + self._newcookies = None + + def run(self): + path_split = self.environ["PATH_INFO"].split("/") + caught = False + + if Settings.FULL_MAINTENANCE: + raise UserError, _( + "%s is currently under maintenance. We'll be back.") % Settings.SITE_TITLE + + if len(path_split) > 1: + if path_split[1] == "post": + # Making a post + caught = True + + if 'password' not in self.formdata: + raise UserError, "El request está incompleto." + + # let's get all the POST data we need + ip = self.environ["REMOTE_ADDR"] + boarddir = self.formdata.get('board') + parent = self.formdata.get('parent') + trap1 = self.formdata.get('name', '') + trap2 = self.formdata.get('email', '') + name = self.formdata.get('fielda', '') + email = self.formdata.get('fieldb', '') + subject = self.formdata.get('subject', '') + message = self.formdata.get('message', '') + file = self.formdata.get('file') + file_original = self.formdata.get('file_original') + spoil = self.formdata.get('spoil') + oek_file = self.formdata.get('oek_file') + password = self.formdata.get('password', '') + noimage = self.formdata.get('noimage') + mobile = ("mobile" in self.formdata.keys()) + + # call post function + (post_url, ttaken, unused) = self.make_post(ip, boarddir, parent, trap1, trap2, name, + email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile) + + # make redirect + self.output += make_redirect(post_url, ttaken) + elif path_split[1] == "environ": + caught = True + + self.output += repr(self.environ) + elif path_split[1] == "delete": + # Deleting a post + caught = True + + boarddir = self.formdata.get('board') + postid = self.formdata.get('delete') + imageonly = self.formdata.get('imageonly') + password = self.formdata.get('password') + mobile = self.formdata.get('mobile') + + # call delete function + self.delete_post(boarddir, postid, imageonly, password, mobile) + elif path_split[1] == "anarkia": + import anarkia + caught = True + OpenDb() + anarkia.anarkia(self, path_split) + elif path_split[1] == "manage": + caught = True + OpenDb() + manage.manage(self, path_split) + elif path_split[1] == "api": + import api + caught = True + self.headers = [("Content-Type", "application/json"), ("Access-Control-Allow-Origin", "*"), ("Access-Control-Allow-Methods", + "PUT, GET, POST, DELETE, OPTIONS"), ("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")] + OpenDb() + api.api(self, path_split) + elif path_split[1] == "threadlist": + OpenDb() + board = setBoard(path_split[2]) + caught = True + if board['board_type'] != '1': + raise UserError, "No disponible para esta sección." + self.output = threadList(0) + elif path_split[1] == "mobile": + OpenDb() + board = setBoard(path_split[2]) + caught = True + self.output = threadList(1) + elif path_split[1] == "mobilelist": + OpenDb() + board = setBoard(path_split[2]) + caught = True + self.output = threadList(2) + elif path_split[1] == "mobilecat": + OpenDb() + board = setBoard(path_split[2]) + caught = True + self.output = threadList(3) + elif path_split[1] == "mobilenew": + OpenDb() + board = setBoard(path_split[2]) + caught = True + self.output = renderTemplate('txt_newthread.html', {}, True) + elif path_split[1] == "mobilehome": + OpenDb() + latest_age = getLastAge(Settings.HOME_LASTPOSTS) + for threads in latest_age: + content = threads['url'] + content = content.replace('/read/', '/') + content = content.replace('/res/', '/') + content = content.replace('.html', '') + threads['url'] = content + caught = True + self.output = renderTemplate( + 'latest.html', {'latest_age': latest_age}, True) + elif path_split[1] == "mobilenewest": + OpenDb() + newthreads = getNewThreads(Settings.HOME_LASTPOSTS) + for threads in newthreads: + content = threads['url'] + content = content.replace('/read/', '/') + content = content.replace('/res/', '/') + content = content.replace('.html', '') + threads['url'] = content + caught = True + self.output = renderTemplate( + 'newest.html', {'newthreads': newthreads}, True) + elif path_split[1] == "mobileread": + OpenDb() + board = setBoard(path_split[2]) + caught = True + if len(path_split) > 4 and path_split[4] and board['board_type'] == '1': + # try: + self.output = dynamicRead( + int(path_split[3]), path_split[4], True) + # except: + # self.output = threadPage(path_split[3], True) + elif board['board_type'] == '1': + self.output = threadPage(0, True, path_split[3]) + else: + self.output = threadPage(path_split[3], True) + elif path_split[1] == "catalog": + OpenDb() + board = setBoard(path_split[2]) + caught = True + sort = self.formdata.get('sort', '') + self.output = catalog(sort) + elif path_split[1] == "oekaki": + caught = True + OpenDb() + oekaki.oekaki(self, path_split) + elif path_split[1] == "play": + # Module player + caught = True + boarddir = path_split[2] + modfile = path_split[3] + self.output = renderTemplate( + 'mod.html', {'board': boarddir, 'modfile': modfile}) + elif path_split[1] == "report": + # Report post, check if they are enabled + # Can't report if banned + caught = True + ip = self.environ["REMOTE_ADDR"] + boarddir = path_split[2] + postid = int(path_split[3]) + reason = self.formdata.get('reason') + try: + txt = True + postshow = int(path_split[4]) + except: + txt = False + postshow = postid + + self.report(ip, boarddir, postid, reason, txt, postshow) + elif path_split[1] == "stats": + caught = True + self.stats() + elif path_split[1] == "random": + caught = True + OpenDb() + board = FetchOne( + "SELECT `id`, `dir`, `board_type` FROM `boards` WHERE `secret` = 0 AND `id` <> 1 AND `id` <> 13 AND `id` <> 34 ORDER BY RAND() LIMIT 1") + thread = FetchOne( + "SELECT `id`, `timestamp` FROM `posts` WHERE `parentid` = 0 AND `boardid` = %s ORDER BY RAND() LIMIT 1" % board['id']) + if board['board_type'] == '1': + url = Settings.HOME_URL + \ + board['dir'] + '/read/' + thread['timestamp'] + '/' + else: + url = Settings.HOME_URL + \ + board['dir'] + '/res/' + thread['id'] + '.html' + self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><meta http-equiv="refresh" content="0;url=%s" /><body><p>...</p></body></html>' % url + elif path_split[1] == "nostalgia": + caught = True + OpenDb() + thread = FetchOne( + "SELECT `timestamp` FROM `archive` WHERE `boardid` = 9 AND `timestamp` < 1462937230 ORDER BY RAND() LIMIT 1") + url = Settings.HOME_URL + '/zonavip/read/' + \ + thread['timestamp'] + '/' + self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><meta http-equiv="refresh" content="0;url=%s" /><body><p>...</p></body></html>' % url + elif path_split[1] == "banned": + OpenDb() + packed_ip = inet_aton(self.environ["REMOTE_ADDR"]) + bans = FetchAll("SELECT * FROM `bans` WHERE (`netmask` IS NULL AND `ip` = '"+str( + packed_ip)+"') OR (`netmask` IS NOT NULL AND '"+str(packed_ip)+"' & `netmask` = `ip`)") + if bans: + for ban in bans: + if ban["boards"] != "": + boards = pickle.loads(ban["boards"]) + if ban["boards"] == "" or path_split[2] in boards: + caught = True + if ban["boards"]: + boards_str = '/' + '/, /'.join(boards) + '/' + else: + boards_str = _("all boards") + if ban["until"] != "0": + expire = formatTimestamp(ban["until"]) + else: + expire = "" + + template_values = { + # 'return_board': path_split[2], + 'boards_str': boards_str, + 'reason': ban['reason'], + 'added': formatTimestamp(ban["added"]), + 'expire': expire, + 'ip': self.environ["REMOTE_ADDR"], + } + self.output = renderTemplate( + 'banned.html', template_values) + else: + if len(path_split) > 2: + caught = True + self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s" /><p>%s</p></body></html>' % ( + Settings.HOME_URL + path_split[2], _("Your ban has expired. Redirecting...")) + elif path_split[1] == "read": + # Textboard read: + if len(path_split) > 4: + caught = True + # 2: board + # 3: thread + # 4: post(s) + OpenDb() + board = setBoard(path_split[2]) + self.output = dynamicRead( + int(path_split[3]), path_split[4]) + elif path_split[1] == "preview": + caught = True + OpenDb() + try: + board = setBoard(self.formdata["board"]) + message = format_post( + self.formdata["message"], self.environ["REMOTE_ADDR"], self.formdata["parentid"]) + self.output = message + except Exception, messagez: + self.output = "Error: " + \ + str(messagez) + " : " + str(self.formdata) + if not caught: + # Redirect the user back to the front page + self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s" /><p>--> --> --></p></body></html>' % Settings.HOME_URL + + def make_post(self, ip, boarddir, parent, trap1, trap2, name, email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile): + _STARTTIME = time.clock() # Comment if not debug + + if addressIsUS(ip): + raise UserError, "Host en lista negra." + + # open database OpenDb() - board = FetchOne("SELECT `id`, `dir`, `board_type` FROM `boards` WHERE `secret` = 0 AND `id` <> 1 AND `id` <> 13 AND `id` <> 34 ORDER BY RAND() LIMIT 1") - thread = FetchOne("SELECT `id`, `timestamp` FROM `posts` WHERE `parentid` = 0 AND `boardid` = %s ORDER BY RAND() LIMIT 1" % board['id']) + + # set the board + board = setBoard(boarddir) + + if board["dir"] != ["anarkia"]: + if addressIsProxy(ip): + raise UserError, "Proxy prohibido en esta sección." + + # check length of fields + if len(name) > 50: + raise UserError, "El campo de nombre es muy largo." + if len(email) > 50: + raise UserError, "El campo de e-mail es muy largo." + if len(subject) > 100: + raise UserError, "El campo de asunto es muy largo." + if len(message) > 8000: + raise UserError, "El campo de mensaje es muy largo." + if message.count('\n') > 50: + raise UserError, "El mensaje tiene muchos saltos de línea." + + # anti-spam trap + if trap1 or trap2: + raise UserError, "Te quedan tres días de vida." + + # Create a single datetime now so everything syncs up + t = time.time() + + # Delete expired bans + deletedBans = UpdateDb( + "DELETE FROM `bans` WHERE `until` != 0 AND `until` < " + str(timestamp())) + if deletedBans > 0: + regenerateAccess() + + # Redirect to ban page if user is banned + if addressIsBanned(ip, board["dir"]): + #raise UserError, 'Tu host está en la lista negra.' + raise UserError, '<meta http-equiv="refresh" content="0; url=/cgi/banned/%s">' % board["dir"] + + # Disallow posting if the site OR board is in maintenance + if Settings.MAINTENANCE: + raise UserError, _( + "%s is currently under maintenance. We'll be back.") % Settings.SITE_TITLE + if board["locked"] == '1': + raise UserError, _("This board is closed. You can't post in it.") + + # create post object + post = Post(board["id"]) + post["ip"] = inet_aton(ip) + post["timestamp"] = post["bumped"] = int(t) + post["timestamp_formatted"] = formatTimestamp(t) + + # load parent info if we are replying + parent_post = None + parent_timestamp = post["timestamp"] + if parent: + parent_post = get_parent_post(parent, board["id"]) + parent_timestamp = parent_post['timestamp'] + post["parentid"] = parent_post['id'] + post["bumped"] = parent_post['bumped'] + if parent_post['locked'] == '1': + raise UserError, _( + "The thread is closed. You can't post in it.") + + # check if the user is flooding + flood_check(t, post, board["id"]) + + # use fields only if enabled + if board["disable_name"] != '1': + post["name"] = cleanString(name) + post["email"] = cleanString(email, quote=True) + if board["disable_subject"] != '1': + post["subject"] = cleanString(subject) + + # process tripcodes + post["name"], post["tripcode"] = tripcode(post["name"]) + + # Remove carriage return, they're useless + message = message.replace("\r", "") + + # check ! functions before + extend = extend_str = dice = ball = None + + if not post["parentid"] and board["dir"] not in ['bai', 'world']: + # creating thread + __extend = re.compile(r"^!extend(:\w+)(:\w+)?\n") + res = __extend.match(message) + if res: + extend = res.groups() + # truncate extend + extend_str = res.group(0) + message = message[res.end(0):] + + if board["dir"] in ['juegos', '0', 'polka']: + __dice = re.compile(r"^!dado(:\w+)(:\w+)?\n") + res = __dice.match(message) + if res: + dice = res.groups() + message = message[res.end(0):] + + if board["dir"] in ['zonavip', '0', 'polka']: + __ball = re.compile(r"^!bola8\n") + res = __ball.match(message) + if res: + ball = True + message = message[res.end(0):] + + # use and format message + if message.strip(): + post["message"] = format_post( + message, ip, post["parentid"], parent_timestamp) + + # add function messages + if extend_str: + extend_str = extend_str.replace('!extend', 'EXTEND') + post["message"] += '<hr />' + extend_str + ' configurado.' + if dice: + post["message"] += '<hr />' + throw_dice(dice) + if ball: + post["message"] += '<hr />' + magic_ball() + + if not post["parentid"] and post["email"].lower() == 'sage': + post["email"] = "" + + # disallow illegal characters + if post["name"]: + post["name"] = post["name"].replace('★', '☆') + post["name"] = post["name"].replace('◆', '◇') + + # process capcodes + cap_id = hide_end = None + if post["name"] in Settings.CAPCODES: + capcode = Settings.CAPCODES[post["name"]] + if post["tripcode"] == (Settings.TRIP_CHAR + capcode[0]): + post["name"], post["tripcode"] = capcode[1], capcode[2] + # if board['board_type'] == '1': + # post["name"], post["tripcode"] = capcode[1], '' + # else: + # post["name"] = post["tripcode"] = '' + # post["message"] = ('[<span style="color:red">%s</span>]<br />' % capcode[2]) + post["message"] + + cap_id, hide_end = capcode[3], capcode[4] + + # hide ip if necessary + if hide_end: + post["ip"] = 0 + + # use password + post["password"] = password + + # EXTEND feature + if post["parentid"] and board["dir"] not in ['bai', 'world']: + # replying + __extend = re.compile(r"<hr />EXTEND(:\w+)(:\w+)?\b") + res = __extend.search(parent_post["message"]) + if res: + extend = res.groups() + + # compatibility : old id function + if 'id' in parent_post["email"]: + board["useid"] = '3' + + if 'id' in post["email"]: + board["useid"] = '3' + + if extend: + try: + # 1: ID + if extend[0] == ':no': + board["useid"] = '0' + elif extend[0] == ':yes': + board["useid"] = '1' + elif extend[0] == ':force': + board["useid"] = '2' + elif extend[0] == ':extra': + board["useid"] = '3' + + # 2: Slip + if extend[1] == ':no': + board["slip"] = '0' + elif extend[1] == ':yes': + board["slip"] = '1' + elif extend[1] == ':domain': + board["slip"] = '2' + elif extend[1] == ':verbose': + board["slip"] = '3' + elif extend[1] == ':country': + board["countrycode"] = '1' + elif extend[1] == ':all': + board["slip"] = '3' + board["countrycode"] = '1' + except IndexError: + pass + + # if we are replying, use first post's time + if post["parentid"]: + tim = parent_post["timestamp"] + else: + tim = post["timestamp"] + + # make ID hash + if board["useid"] != '0': + post["timestamp_formatted"] += ' ID:' + iphash(ip, post, tim, board["useid"], mobile, + self.environ["HTTP_USER_AGENT"], cap_id, hide_end, (board["countrycode"] in ['1', '2'])) + + # use for future file checks + xfile = (file or oek_file) + + # textboard inforcements (change it to settings maybe?) if board['board_type'] == '1': - url = Settings.HOME_URL + board['dir'] + '/read/' + thread['timestamp'] + '/' + if not post["parentid"] and not post["subject"]: + raise UserError, _( + "You must enter a title to create a thread.") + if not post["message"]: + raise UserError, _("Please enter a message.") else: - url = Settings.HOME_URL + board['dir'] + '/res/' + thread['id'] + '.html' - self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><meta http-equiv="refresh" content="0;url=%s" /><body><p>...</p></body></html>' % url - elif path_split[1] == "nostalgia": - caught = True - OpenDb() - thread = FetchOne("SELECT `timestamp` FROM `archive` WHERE `boardid` = 9 AND `timestamp` < 1462937230 ORDER BY RAND() LIMIT 1") - url = Settings.HOME_URL + '/zonavip/read/' + thread['timestamp'] + '/' - self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><meta http-equiv="refresh" content="0;url=%s" /><body><p>...</p></body></html>' % url - elif path_split[1] == "banned": - OpenDb() - packed_ip = inet_aton(self.environ["REMOTE_ADDR"]) - bans = FetchAll("SELECT * FROM `bans` WHERE (`netmask` IS NULL AND `ip` = '"+str(packed_ip)+"') OR (`netmask` IS NOT NULL AND '"+str(packed_ip)+"' & `netmask` = `ip`)") - if bans: - for ban in bans: - if ban["boards"] != "": - boards = pickle.loads(ban["boards"]) - if ban["boards"] == "" or path_split[2] in boards: - caught = True - if ban["boards"]: - boards_str = '/' + '/, /'.join(boards) + '/' - else: - boards_str = _("all boards") - if ban["until"] != "0": - expire = formatTimestamp(ban["until"]) - else: - expire = "" - - template_values = { - #'return_board': path_split[2], - 'boards_str': boards_str, - 'reason': ban['reason'], - 'added': formatTimestamp(ban["added"]), - 'expire': expire, - 'ip': self.environ["REMOTE_ADDR"], - } - self.output = renderTemplate('banned.html', template_values) + if not post["parentid"] and not xfile and not noimage: + raise UserError, _( + "You must upload an image first to create a thread.") + if not xfile and not post["message"]: + raise UserError, _( + "Please enter a message or upload an image to reply.") + + # check if this post is allowed + if post["parentid"]: + if file and board['allow_image_replies'] == '0': + raise UserError, _("Image replies not allowed.") else: - if len(path_split) > 2: - caught = True - self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s" /><p>%s</p></body></html>' % (Settings.HOME_URL + path_split[2], _("Your ban has expired. Redirecting...")) - elif path_split[1] == "read": - # Textboard read: - if len(path_split) > 4: - caught = True - # 2: board - # 3: thread - # 4: post(s) - OpenDb() - board = setBoard(path_split[2]) - self.output = dynamicRead(int(path_split[3]), path_split[4]) - elif path_split[1] == "preview": - caught = True + if file and board['allow_images'] == '0': + raise UserError, _("No images allowed.") + + # use default values when missing / remove sage from wrong fields + if (not post["name"] and not post["tripcode"]) or (post["name"].lower() == 'sage'): + post["name"] = random.choice(board["anonymous"].split('|')) + if (not post["subject"] and not post["parentid"]) or (post["subject"].lower() == 'sage'): + post["subject"] = board["subject"] + if not post["message"]: + post["message"] = board["message"] + + # process files + if oek_file: + try: + fname = os.path.join(Settings.IMAGES_DIR, + board['dir'], "temp", oek_file + ".png") + with open(fname, 'rb') as f: + file = f.read() + except: + raise UserError, "Imposible leer la imagen oekaki." + + if file and not noimage: + post = processImage(post, file, t, file_original, + (spoil and board['allow_spoilers'] == '1')) + + if oek_file: + # Remove temporary oekaki file if everything went right + # os.remove(fname) + # TODO: We will rename the file for now. We don't want lost work. + try: + os.rename(fname, fname + ".bak") + except: + pass # Just keep it if anything went wrong + + # slip + if board["slip"] != '0': + slips = [] + + # name + if board["slip"] in ['1', '3']: + if time.strftime("%H") in ['00', '24'] and time.strftime("%M") == '00' and time.strftime("%S") == '00': + host_nick = '000000' + else: + host_nick = 'sarin' + + if hide_end: + host_nick = '★' + elif addressIsTor(ip): + host_nick = 'onion' + else: + isps = {'cablevision': 'easy', + 'cantv': 'warrior', + 'claro': 'america', + 'cnet': 'nova', + 'copelnet': 'cisneros', + 'cps.com': 'silver', + 'cybercable': 'bricklayer', + 'entel': 'matte', + 'eternet': 'stream', + 'fibertel': 'roughage', + 'geonet': 'thunder', + 'gtdinternet': 'casanueva', + 'ifxnw': 'effect', + 'infinitum': 'telegraph', + 'intercable': 'easy', + 'intercity': 'cordoba', + 'iplannet': 'conquest', + 'itcsa.net': 'sarmiento', + 'megared': 'clear', + 'movistar': 'bell', + 'nextel': 'fleet', + 'speedy': 'oxygen', + 'telecom': 'license', + 'telmex': 'slender', + 'telnor': 'compass', + 'tie.cl': 'bell', + 'vtr.net': 'liberty', + 'utfsm': 'virgin', + } + host = getHost(ip) + + if host: + for k, v in isps.iteritems(): + if k in host: + host_nick = v + break + + slips.append(host_nick) + + # hash + if board["slip"] in ['1', '3']: + if hide_end: + slips.append('-'.join(('****', '****'))) + elif addressIsTor(ip): + slips.append( + '-'.join(('****', getMD5(self.environ["HTTP_USER_AGENT"])[:4]))) + else: + slips.append( + '-'.join((getMD5(ip)[:4], getMD5(self.environ["HTTP_USER_AGENT"])[:4]))) + + # host + if board["slip"] == '2': + if hide_end: + host = '★' + elif addressIsTor(ip): + host = 'onion' + else: + host = getHost(ip) + if host: + hosts = host.split('.') + if len(hosts) > 2: + if hosts[-2] in ['ne', 'net', 'com', 'co']: + host = '.'.join( + (hosts[-3], hosts[-2], hosts[-1])) + else: + host = '.'.join((hosts[-2], hosts[-1])) + host = '*.' + host + else: + iprs = ip.split('.') + host = '%s.%s.*.*' % (iprs[0], iprs[1]) + slips.append(host) + + # IP + if board["slip"] == '3': + if hide_end: + host = '[*.*.*.*]' + else: + iprs = ip.split('.') + host = '[%s.%s.*.*]' % (iprs[0], iprs[1]) + slips.append(host) + + if slips: + post["tripcode"] += " (%s)" % ' '.join(slips) + + # country code + if board["countrycode"] == '1': + if hide_end or addressIsTor(ip): + country = '??' + else: + country = getCountry(ip) + post["name"] += " <em>[%s]</em>" % country + + # set expiration date if necessary + if board["maxage"] != '0' and not post["parentid"]: + if board["dir"] == '2d': + date_format = '%m月%d日' + date_format_y = '%Y年%m月' + else: + date_format = '%d/%m' + date_format_y = '%m/%Y' + post["expires"] = int(t) + (int(board["maxage"]) * 86400) + if int(board["maxage"]) >= 365: + date_format = date_format_y + post["expires_formatted"] = datetime.datetime.fromtimestamp( + post["expires"]).strftime(date_format) + + if not post["parentid"]: + # fill with default values if creating a new thread + post["length"] = 1 + post["last"] = post["timestamp"] + + if board["dir"] == 'noticias': + # check if there's at least one link + if "<a href" not in post["message"]: + raise UserError, "Al momento de crear un hilo en esta sección necesitas incluír al menos 1 link como fuente en tu mensaje." + + # insert icon if needed + img_src = '<img src="%s" alt="ico" /><br />' % getRandomIco() + post["message"] = img_src + post["message"] + + # insert post, then run timThreads to make sure the board doesn't exceed the page limit + postid = post.insert() + + # delete threads that have crossed last page + trimThreads() + + # fix null references when creating thread + if board["board_type"] == '1' and not post["parentid"]: + post["message"] = re.compile(r'<a href="/(\w+)/res/0.html/(.+)"').sub( + r'<a href="/\1/res/'+str(postid)+r'.html/\2"', post["message"]) + UpdateDb("UPDATE `posts` SET message = '%s' WHERE boardid = '%s' AND id = '%s'" % (_mysql.escape_string( + post["message"]), _mysql.escape_string(board["id"]), _mysql.escape_string(str(postid)))) + + # do operations if replying to a thread (bump, autoclose, update cache) + logTime("Updating thread") + thread_length = None + if post["parentid"]: + # get length of the thread + thread_length = threadNumReplies(post["parentid"]) + + # bump if not saged + if 'sage' not in post["email"].lower() and parent_post['locked'] != '2': + UpdateDb("UPDATE `posts` SET bumped = %d WHERE (`id` = '%s' OR `parentid` = '%s') AND `boardid` = '%s'" % ( + post["timestamp"], post["parentid"], post["parentid"], board["id"])) + + # check if thread must be closed + autoclose_thread(post["parentid"], t, thread_length) + + # update final attributes (length and last post) + UpdateDb("UPDATE `posts` SET length = %d, last = %d WHERE `id` = '%s' AND `boardid` = '%s'" % ( + thread_length, post["timestamp"], post["parentid"], board["id"])) + + # update cache + threadUpdated(post["parentid"]) + else: + # create cache for new thread + threadUpdated(postid) + + regenerateHome() + + # make page redirect + ttaken = timeTaken(_STARTTIME, time.clock()) + noko = 'noko' in email.lower() or (board["board_type"] == '1') + + # get new post url + post_url = make_url(postid, post, parent_post or post, noko, mobile) + + if board['secret'] == '0': + # add to recent posts + if Settings.ENABLE_RSS: + latestAdd(post, thread_length, postid, parent_post) + # call discord hook + if Settings.ENABLE_DISCORD_HOOK and not post["parentid"]: + hook_url = make_url( + postid, post, parent_post or post, True, False) + discord_hook(post, hook_url) + + return (post_url, ttaken, postid) + + def delete_post(self, boarddir, postid, imageonly, password, mobile=False): OpenDb() - try: - board = setBoard(self.formdata["board"]) - message = format_post(self.formdata["message"], self.environ["REMOTE_ADDR"], self.formdata["parentid"]) - self.output = message - except Exception, messagez: - self.output = "Error: " + str(messagez) + " : " + str(self.formdata) - if not caught: - # Redirect the user back to the front page - self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s" /><p>--> --> --></p></body></html>' % Settings.HOME_URL - - def make_post(self, ip, boarddir, parent, trap1, trap2, name, email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile): - _STARTTIME = time.clock() # Comment if not debug - - if addressIsUS(ip): - raise UserError, "Host en lista negra." - - # open database - OpenDb() - - # set the board - board = setBoard(boarddir) - - if board["dir"] != ["anarkia"]: - if addressIsProxy(ip): - raise UserError, "Proxy prohibido en esta sección." - - # check length of fields - if len(name) > 50: - raise UserError, "El campo de nombre es muy largo." - if len(email) > 50: - raise UserError, "El campo de e-mail es muy largo." - if len(subject) > 100: - raise UserError, "El campo de asunto es muy largo." - if len(message) > 8000: - raise UserError, "El campo de mensaje es muy largo." - if message.count('\n') > 50: - raise UserError, "El mensaje tiene muchos saltos de línea." - - # anti-spam trap - if trap1 or trap2: - raise UserError, "Te quedan tres días de vida." - - # Create a single datetime now so everything syncs up - t = time.time() - - # Delete expired bans - deletedBans = UpdateDb("DELETE FROM `bans` WHERE `until` != 0 AND `until` < " + str(timestamp())) - if deletedBans > 0: - regenerateAccess() - - # Redirect to ban page if user is banned - if addressIsBanned(ip, board["dir"]): - #raise UserError, 'Tu host está en la lista negra.' - raise UserError, '<meta http-equiv="refresh" content="0; url=/cgi/banned/%s">' % board["dir"] - - # Disallow posting if the site OR board is in maintenance - if Settings.MAINTENANCE: - raise UserError, _("%s is currently under maintenance. We'll be back.") % Settings.SITE_TITLE - if board["locked"] == '1': - raise UserError, _("This board is closed. You can't post in it.") - - # create post object - post = Post(board["id"]) - post["ip"] = inet_aton(ip) - post["timestamp"] = post["bumped"] = int(t) - post["timestamp_formatted"] = formatTimestamp(t) - - # load parent info if we are replying - parent_post = None - parent_timestamp = post["timestamp"] - if parent: - parent_post = get_parent_post(parent, board["id"]) - parent_timestamp = parent_post['timestamp'] - post["parentid"] = parent_post['id'] - post["bumped"] = parent_post['bumped'] - if parent_post['locked'] == '1': - raise UserError, _("The thread is closed. You can't post in it.") - - # check if the user is flooding - flood_check(t, post, board["id"]) - - # use fields only if enabled - if board["disable_name"] != '1': - post["name"] = cleanString(name) - post["email"] = cleanString(email, quote=True) - if board["disable_subject"] != '1': - post["subject"] = cleanString(subject) - - # process tripcodes - post["name"], post["tripcode"] = tripcode(post["name"]) - - # Remove carriage return, they're useless - message = message.replace("\r", "") - - # check ! functions before - extend = extend_str = dice = ball = None - - if not post["parentid"] and board["dir"] not in ['bai', 'world']: - # creating thread - __extend = re.compile(r"^!extend(:\w+)(:\w+)?\n") - res = __extend.match(message) - if res: - extend = res.groups() - # truncate extend - extend_str = res.group(0) - message = message[res.end(0):] - - if board["dir"] in ['juegos', '0', 'polka']: - __dice = re.compile(r"^!dado(:\w+)(:\w+)?\n") - res = __dice.match(message) - if res: - dice = res.groups() - message = message[res.end(0):] - - if board["dir"] in ['zonavip', '0', 'polka']: - __ball = re.compile(r"^!bola8\n") - res = __ball.match(message) - if res: - ball = True - message = message[res.end(0):] - - # use and format message - if message.strip(): - post["message"] = format_post(message, ip, post["parentid"], parent_timestamp) - - # add function messages - if extend_str: - extend_str = extend_str.replace('!extend', 'EXTEND') - post["message"] += '<hr />' + extend_str + ' configurado.' - if dice: - post["message"] += '<hr />' + throw_dice(dice) - if ball: - post["message"] += '<hr />' + magic_ball() - - if not post["parentid"] and post["email"].lower() == 'sage': - post["email"] = "" - - # disallow illegal characters - if post["name"]: - post["name"] = post["name"].replace('★', '☆') - post["name"] = post["name"].replace('◆', '◇') - - # process capcodes - cap_id = hide_end = None - if post["name"] in Settings.CAPCODES: - capcode = Settings.CAPCODES[post["name"]] - if post["tripcode"] == (Settings.TRIP_CHAR + capcode[0]): - post["name"], post["tripcode"] = capcode[1], capcode[2] - #if board['board_type'] == '1': - # post["name"], post["tripcode"] = capcode[1], '' - #else: - # post["name"] = post["tripcode"] = '' - # post["message"] = ('[<span style="color:red">%s</span>]<br />' % capcode[2]) + post["message"] - - cap_id, hide_end = capcode[3], capcode[4] - - # hide ip if necessary - if hide_end: - post["ip"] = 0 - - # use password - post["password"] = password - - # EXTEND feature - if post["parentid"] and board["dir"] not in ['bai', 'world']: - # replying - __extend = re.compile(r"<hr />EXTEND(:\w+)(:\w+)?\b") - res = __extend.search(parent_post["message"]) - if res: - extend = res.groups() - - # compatibility : old id function - if 'id' in parent_post["email"]: - board["useid"] = '3' - - if 'id' in post["email"]: - board["useid"] = '3' - - if extend: - try: - # 1: ID - if extend[0] == ':no': - board["useid"] = '0' - elif extend[0] == ':yes': - board["useid"] = '1' - elif extend[0] == ':force': - board["useid"] = '2' - elif extend[0] == ':extra': - board["useid"] = '3' - - # 2: Slip - if extend[1] == ':no': - board["slip"] = '0' - elif extend[1] == ':yes': - board["slip"] = '1' - elif extend[1] == ':domain': - board["slip"] = '2' - elif extend[1] == ':verbose': - board["slip"] = '3' - elif extend[1] == ':country': - board["countrycode"] = '1' - elif extend[1] == ':all': - board["slip"] = '3' - board["countrycode"] = '1' - except IndexError: - pass - - # if we are replying, use first post's time - if post["parentid"]: - tim = parent_post["timestamp"] - else: - tim = post["timestamp"] - - # make ID hash - if board["useid"] != '0': - post["timestamp_formatted"] += ' ID:' + iphash(ip, post, tim, board["useid"], mobile, self.environ["HTTP_USER_AGENT"], cap_id, hide_end, (board["countrycode"] in ['1', '2'])) - - # use for future file checks - xfile = (file or oek_file) - - # textboard inforcements (change it to settings maybe?) - if board['board_type'] == '1': - if not post["parentid"] and not post["subject"]: - raise UserError, _("You must enter a title to create a thread.") - if not post["message"]: - raise UserError, _("Please enter a message.") - else: - if not post["parentid"] and not xfile and not noimage: - raise UserError, _("You must upload an image first to create a thread.") - if not xfile and not post["message"]: - raise UserError, _("Please enter a message or upload an image to reply.") - - # check if this post is allowed - if post["parentid"]: - if file and board['allow_image_replies'] == '0': - raise UserError, _("Image replies not allowed.") - else: - if file and board['allow_images'] == '0': - raise UserError, _("No images allowed.") - - # use default values when missing / remove sage from wrong fields - if (not post["name"] and not post["tripcode"]) or (post["name"].lower() == 'sage'): - post["name"] = random.choice(board["anonymous"].split('|')) - if (not post["subject"] and not post["parentid"]) or (post["subject"].lower() == 'sage'): - post["subject"] = board["subject"] - if not post["message"]: - post["message"] = board["message"] - - # process files - if oek_file: - try: - fname = os.path.join(Settings.IMAGES_DIR, board['dir'], "temp", oek_file + ".png") - with open(fname, 'rb') as f: - file = f.read() - except: - raise UserError, "Imposible leer la imagen oekaki." - - if file and not noimage: - post = processImage(post, file, t, file_original, (spoil and board['allow_spoilers'] == '1')) - - if oek_file: - # Remove temporary oekaki file if everything went right - #os.remove(fname) - # TODO: We will rename the file for now. We don't want lost work. - try: - os.rename(fname, fname + ".bak") - except: - pass # Just keep it if anything went wrong - - # slip - if board["slip"] != '0': - slips = [] - - # name - if board["slip"] in ['1', '3']: - if time.strftime("%H") in ['00', '24'] and time.strftime("%M") == '00' and time.strftime("%S") == '00': - host_nick = '000000' + + # set the board + board = setBoard(boarddir) + + if board["dir"] == '0': + raise UserError, "No se pueden eliminar mensajes en esta sección." + + # check if we have a post id and check it's numeric + if not postid: + raise UserError, "Selecciona uno o más mensajes a eliminar." + + # make sure we have a password + if not password: + raise UserError, _("Please enter a password.") + + to_delete = [] + if isinstance(postid, list): + to_delete = [n.value for n in postid] else: - host_nick = 'sarin' - - if hide_end: - host_nick = '★' - elif addressIsTor(ip): - host_nick = 'onion' - else: - isps = {'cablevision': 'easy', - 'cantv': 'warrior', - 'claro': 'america', - 'cnet': 'nova', - 'copelnet': 'cisneros', - 'cps.com': 'silver', - 'cybercable': 'bricklayer', - 'entel': 'matte', - 'eternet': 'stream', - 'fibertel': 'roughage', - 'geonet': 'thunder', - 'gtdinternet': 'casanueva', - 'ifxnw': 'effect', - 'infinitum': 'telegraph', - 'intercable': 'easy', - 'intercity': 'cordoba', - 'iplannet': 'conquest', - 'itcsa.net': 'sarmiento', - 'megared': 'clear', - 'movistar': 'bell', - 'nextel': 'fleet', - 'speedy': 'oxygen', - 'telecom': 'license', - 'telmex': 'slender', - 'telnor': 'compass', - 'tie.cl': 'bell', - 'vtr.net': 'liberty', - 'utfsm': 'virgin', - } - host = getHost(ip) - - if host: - for k, v in isps.iteritems(): - if k in host: - host_nick = v - break - - slips.append(host_nick) - - # hash - if board["slip"] in ['1', '3']: - if hide_end: - slips.append('-'.join(('****', '****'))) - elif addressIsTor(ip): - slips.append('-'.join(('****', getMD5(self.environ["HTTP_USER_AGENT"])[:4]))) + to_delete = [postid] + + # delete posts + if board['board_type'] == '1' and len(to_delete) == 1: + # we should be deleting only one (textboard) + # check if it's the last post and delete permanently if so + deltype = '0' + post = FetchOne("SELECT `id`, `timestamp`, `parentid` FROM `posts` WHERE `boardid` = %s AND `id` = %s LIMIT 1" % ( + board["id"], str(to_delete[0]))) + if post['parentid'] != '0': + op = get_parent_post(post['parentid'], board['id']) + if op['last'] != post['timestamp']: + deltype = '1' + + deletePost(to_delete[0], password, deltype, imageonly) + latestRemove(post['id']) + regenerateHome() else: - slips.append('-'.join((getMD5(ip)[:4], getMD5(self.environ["HTTP_USER_AGENT"])[:4]))) - - # host - if board["slip"] == '2': - if hide_end: - host = '★' - elif addressIsTor(ip): - host = 'onion' + # delete all checked posts (IB) + deleted = 0 + errors = 0 + msgs = [] + + for pid in to_delete: + try: + deletePost(pid, password, board['recyclebin'], imageonly) + latestRemove(pid) + deleted += 1 + msgs.append('No.%s: Eliminado' % pid) + except UserError, message: + errors += 1 + msgs.append('No.%s: %s' % (pid, message)) + + # regenerate home + if deleted: + regenerateHome() + + # show errors, if any + if errors: + raise UserError, 'No todos los mensajes pudieron ser eliminados.<br />' + \ + '<br />'.join(msgs) + + # redirect + if imageonly: + self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s/" /><p>%s</p></body></html>' % ( + ("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("File deleted successfully.")) else: - host = getHost(ip) - if host: - hosts = host.split('.') - if len(hosts) > 2: - if hosts[-2] in ['ne', 'net', 'com', 'co']: - host = '.'.join((hosts[-3], hosts[-2], hosts[-1])) - else: - host = '.'.join((hosts[-2], hosts[-1])) - host = '*.' + host - else: - iprs = ip.split('.') - host = '%s.%s.*.*' % (iprs[0], iprs[1]) - slips.append(host) - - # IP - if board["slip"] == '3': - if hide_end: - host = '[*.*.*.*]' + self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s/" /><p>%s</p></body></html>' % ( + ("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("Post deleted successfully.")) + + def report(self, ip, boarddir, postid, reason, txt, postshow): + # don't allow if the report system is off + if not Settings.REPORTS_ENABLE: + raise UserError, _('Report system is deactivated.') + + # if there's not a reason, show the report page + if reason is None: + self.output += renderTemplate("report.html", + {'finished': False, 'postshow': postshow, 'txt': txt}) + return + + # check reason + if not reason: + raise UserError, _("Enter a reason.") + if len(reason) > 100: + raise UserError, _("Text too long.") + + # open database + OpenDb() + + # set the board we're in + board = setBoard(boarddir) + + # check if he's banned + if addressIsBanned(ip, board["dir"]): + raise UserError, _("You're banned.") + + # check if post exists + post = FetchOne("SELECT `id`, `parentid`, `ip` FROM `posts` WHERE `id` = '%s' AND `boardid` = '%s'" % ( + _mysql.escape_string(str(postid)), _mysql.escape_string(board['id']))) + if not post: + raise UserError, _("Post doesn't exist.") + + # generate link + if board["board_type"] == '1': + parent_post = get_parent_post(post["parentid"], board["id"]) + link = "/%s/read/%s/%s" % (board["dir"], + parent_post["timestamp"], postshow) else: - iprs = ip.split('.') - host = '[%s.%s.*.*]' % (iprs[0], iprs[1]) - slips.append(host) - - if slips: - post["tripcode"] += " (%s)" % ' '.join(slips) - - # country code - if board["countrycode"] == '1': - if hide_end or addressIsTor(ip): - country = '??' - else: - country = getCountry(ip) - post["name"] += " <em>[%s]</em>" % country - - # set expiration date if necessary - if board["maxage"] != '0' and not post["parentid"]: - if board["dir"] == '2d': - date_format = '%m月%d日' - date_format_y = '%Y年%m月' - else: - date_format = '%d/%m' - date_format_y = '%m/%Y' - post["expires"] = int(t) + (int(board["maxage"]) * 86400) - if int(board["maxage"]) >= 365: - date_format = date_format_y - post["expires_formatted"] = datetime.datetime.fromtimestamp(post["expires"]).strftime(date_format) - - if not post["parentid"]: - # fill with default values if creating a new thread - post["length"] = 1 - post["last"] = post["timestamp"] - - if board["dir"] == 'noticias': - # check if there's at least one link - if "<a href" not in post["message"]: - raise UserError, "Al momento de crear un hilo en esta sección necesitas incluír al menos 1 link como fuente en tu mensaje." - - # insert icon if needed - img_src = '<img src="%s" alt="ico" /><br />' % getRandomIco() - post["message"] = img_src + post["message"] - - # insert post, then run timThreads to make sure the board doesn't exceed the page limit - postid = post.insert() - - # delete threads that have crossed last page - trimThreads() - - # fix null references when creating thread - if board["board_type"] == '1' and not post["parentid"]: - post["message"] = re.compile(r'<a href="/(\w+)/res/0.html/(.+)"').sub(r'<a href="/\1/res/'+str(postid)+r'.html/\2"', post["message"]) - UpdateDb("UPDATE `posts` SET message = '%s' WHERE boardid = '%s' AND id = '%s'" % (_mysql.escape_string(post["message"]), _mysql.escape_string(board["id"]), _mysql.escape_string(str(postid)))) - - # do operations if replying to a thread (bump, autoclose, update cache) - logTime("Updating thread") - thread_length = None - if post["parentid"]: - # get length of the thread - thread_length = threadNumReplies(post["parentid"]) - - # bump if not saged - if 'sage' not in post["email"].lower() and parent_post['locked'] != '2': - UpdateDb("UPDATE `posts` SET bumped = %d WHERE (`id` = '%s' OR `parentid` = '%s') AND `boardid` = '%s'" % (post["timestamp"], post["parentid"], post["parentid"], board["id"])) - - # check if thread must be closed - autoclose_thread(post["parentid"], t, thread_length) - - # update final attributes (length and last post) - UpdateDb("UPDATE `posts` SET length = %d, last = %d WHERE `id` = '%s' AND `boardid` = '%s'" % (thread_length, post["timestamp"], post["parentid"], board["id"])) - - # update cache - threadUpdated(post["parentid"]) - else: - # create cache for new thread - threadUpdated(postid) - - regenerateHome() - - # make page redirect - ttaken = timeTaken(_STARTTIME, time.clock()) - noko = 'noko' in email.lower() or (board["board_type"] == '1') - - # get new post url - post_url = make_url(postid, post, parent_post or post, noko, mobile) - - if board['secret'] == '0': - # add to recent posts - if Settings.ENABLE_RSS: - latestAdd(post, thread_length, postid, parent_post) - # call discord hook - if Settings.ENABLE_DISCORD_HOOK and not post["parentid"]: - hook_url = make_url(postid, post, parent_post or post, True, False) - discord_hook(post, hook_url) - - return (post_url, ttaken, postid) - - def delete_post(self, boarddir, postid, imageonly, password, mobile=False): - OpenDb() - - # set the board - board = setBoard(boarddir) - - if board["dir"] == '0': - raise UserError, "No se pueden eliminar mensajes en esta sección." - - # check if we have a post id and check it's numeric - if not postid: - raise UserError, "Selecciona uno o más mensajes a eliminar." - - # make sure we have a password - if not password: - raise UserError, _("Please enter a password.") - - to_delete = [] - if isinstance(postid, list): - to_delete = [n.value for n in postid] - else: - to_delete = [postid] - - # delete posts - if board['board_type'] == '1' and len(to_delete) == 1: - # we should be deleting only one (textboard) - # check if it's the last post and delete permanently if so - deltype = '0' - post = FetchOne("SELECT `id`, `timestamp`, `parentid` FROM `posts` WHERE `boardid` = %s AND `id` = %s LIMIT 1" % (board["id"], str(to_delete[0]))) - if post['parentid'] != '0': - op = get_parent_post(post['parentid'], board['id']) - if op['last'] != post['timestamp']: - deltype = '1' - - deletePost(to_delete[0], password, deltype, imageonly) - latestRemove(post['id']) - regenerateHome() - else: - # delete all checked posts (IB) - deleted = 0 - errors = 0 - msgs = [] - - for pid in to_delete: + link = "/%s/res/%d.html#%d" % (board["dir"], int( + post["parentid"]) or int(post["id"]), int(post["id"])) + + # insert report + t = time.time() + message = cgi.escape(self.formdata["reason"]).strip()[0:8000] + message = message.replace("\n", "<br />") + + UpdateDb("INSERT INTO `reports` (board, postid, parentid, link, ip, reason, reporterip, timestamp, timestamp_formatted) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % ( + board["dir"], post['id'], post['parentid'], link, post['ip'], _mysql.escape_string(message), _mysql.escape_string(self.environ["REMOTE_ADDR"]), str(t), formatTimestamp(t))) + self.output = renderTemplate("report.html", {'finished': True}) + + def stats(self): + import json + import math + import platform try: - deletePost(pid, password, board['recyclebin'], imageonly) - latestRemove(pid) - deleted += 1 - msgs.append('No.%s: Eliminado' % pid) - except UserError, message: - errors += 1 - msgs.append('No.%s: %s' % (pid, message)) - - # regenerate home - if deleted: - regenerateHome() - - # show errors, if any - if errors: - raise UserError, 'No todos los mensajes pudieron ser eliminados.<br />' + '<br />'.join(msgs) - - # redirect - if imageonly: - self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s/" /><p>%s</p></body></html>' % (("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("File deleted successfully.")) - else: - self.output += '<html xmlns="http://www.w3.org/1999/xhtml"><body><meta http-equiv="refresh" content="0;url=%s/" /><p>%s</p></body></html>' % (("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("Post deleted successfully.")) - - def report(self, ip, boarddir, postid, reason, txt, postshow): - # don't allow if the report system is off - if not Settings.REPORTS_ENABLE: - raise UserError, _('Report system is deactivated.') - - # if there's not a reason, show the report page - if reason is None: - self.output += renderTemplate("report.html", {'finished': False, 'postshow': postshow, 'txt': txt}) - return - - # check reason - if not reason: - raise UserError, _("Enter a reason.") - if len(reason) > 100: - raise UserError, _("Text too long.") - - # open database - OpenDb() - - # set the board we're in - board = setBoard(boarddir) - - # check if he's banned - if addressIsBanned(ip, board["dir"]): - raise UserError, _("You're banned.") - - # check if post exists - post = FetchOne("SELECT `id`, `parentid`, `ip` FROM `posts` WHERE `id` = '%s' AND `boardid` = '%s'" % (_mysql.escape_string(str(postid)), _mysql.escape_string(board['id']))) - if not post: - raise UserError, _("Post doesn't exist.") - - # generate link - if board["board_type"] == '1': - parent_post = get_parent_post(post["parentid"], board["id"]) - link = "/%s/read/%s/%s" % (board["dir"], parent_post["timestamp"], postshow) - else: - link = "/%s/res/%d.html#%d" % (board["dir"], int(post["parentid"]) or int(post["id"]), int(post["id"])) - - # insert report - t = time.time() - message = cgi.escape(self.formdata["reason"]).strip()[0:8000] - message = message.replace("\n", "<br />") - - UpdateDb("INSERT INTO `reports` (board, postid, parentid, link, ip, reason, reporterip, timestamp, timestamp_formatted) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % (board["dir"], post['id'], post['parentid'], link, post['ip'], _mysql.escape_string(message), _mysql.escape_string(self.environ["REMOTE_ADDR"]), str(t), formatTimestamp(t))) - self.output = renderTemplate("report.html", {'finished': True}) - - def stats(self): - import json, math, platform - try: - with open('stats.json', 'r') as f: - out = json.load(f) - except ValueError: - out = {'t': 0} - - regenerated = False - if (time.time() - out['t']) > 3600: - regenerated = True - - # open database - OpenDb() - - # 1 week = 604800 - query_day = FetchAll("SELECT DATE_FORMAT(FROM_UNIXTIME(FLOOR((timestamp-10800)/86400)*86400+86400), \"%Y-%m-%d\"), COUNT(1), COUNT(IF(parentid=0, 1, NULL)) " - "FROM posts " - "WHERE (timestamp-10800) > (UNIX_TIMESTAMP()-604800) AND (IS_DELETED = 0 OR IS_DELETED = 3) " - "GROUP BY FLOOR((timestamp-10800)/86400) " - "ORDER BY FLOOR((timestamp-10800)/86400)", 0) - - query_count = FetchOne("SELECT COUNT(1), COUNT(NULLIF(file, '')), VERSION() FROM posts", 0) - total = int(query_count[0]) - total_files = int(query_count[1]) - mysql_ver = query_count[2] - - archive_count = FetchOne("SELECT SUM(length) FROM archive", 0) - total_archived = int(archive_count[0]) - - days = [] - for date, count, threads in query_day[1:]: - days.append( (date, count, threads) ) - - query_b = FetchAll("SELECT id, dir, name FROM boards WHERE boards.secret = 0", 0) - - boards = [] - totalp = 0 - for id, dir, longname in query_b: - bposts = FetchOne("SELECT COUNT(1) FROM posts " - "WHERE '"+str(id)+"' = posts.boardid AND timestamp > ( UNIX_TIMESTAMP(DATE(NOW())) - 2419200 )", 0) - boards.append( (dir, longname, int(bposts[0])) ) - totalp += int(bposts[0]) - - boards = sorted(boards, key=lambda boards: boards[2], reverse=True) - - boards_percent = [] - for dir, longname, bposts in boards: - if bposts > 0: - boards_percent.append( (dir, longname, '{0:.2f}'.format( float(bposts)*100/totalp ), int(bposts) ) ) - else: - boards_percent.append( (dir, longname, '0.00', '0' ) ) - - #posts = FetchAll("SELECT `parentid`, `boardid` FROM `posts` INNER JOIN `boards` ON posts.boardid = boards.id WHERE posts.parentid<>0 AND posts.timestamp>(UNIX_TIMESTAMP()-86400) AND boards.secret=0 ORDER BY `parentid`") - #threads = {} - #for post in posts: - # if post["parentid"] in threads: - # threads[post["parentid"]] += 1 - # else: - # threads[post["parentid"]] = 1 - - python_version = platform.python_version() - if self.environ.get('FCGI_FORCE_CGI', 'N').upper().startswith('Y'): - python_version += " (CGI)" - else: - python_version += " (FastCGI)" - - out = { - "uname": platform.uname(), - "python_ver": python_version, - "python_impl": platform.python_implementation(), - "python_build": platform.python_build()[1], - "python_compiler": platform.python_compiler(), - "mysql_ver": mysql_ver, - "tenjin_ver": tenjin.__version__, - "weabot_ver": __version__, - "days": days, - "boards": boards, - "boards_percent": boards_percent, - "total": total, - "total_files": total_files, - "total_archived": total_archived, - "t": timestamp(), - "tz": Settings.TIME_ZONE, - } - with open('stats.json', 'w') as f: - json.dump(out, f) - - out['timestamp'] = re.sub(r"\(...\)", " ", formatTimestamp(out['t'])) - out['regenerated'] = regenerated - self.output = renderTemplate("stats.html", out) - #self.headers = [("Content-Type", "application/json")] + with open('stats.json', 'r') as f: + out = json.load(f) + except ValueError: + out = {'t': 0} + + regenerated = False + if (time.time() - out['t']) > 3600: + regenerated = True + + # open database + OpenDb() + + # 1 week = 604800 + query_day = FetchAll("SELECT DATE_FORMAT(FROM_UNIXTIME(FLOOR((timestamp-10800)/86400)*86400+86400), \"%Y-%m-%d\"), COUNT(1), COUNT(IF(parentid=0, 1, NULL)) " + "FROM posts " + "WHERE (timestamp-10800) > (UNIX_TIMESTAMP()-604800) AND (IS_DELETED = 0 OR IS_DELETED = 3) " + "GROUP BY FLOOR((timestamp-10800)/86400) " + "ORDER BY FLOOR((timestamp-10800)/86400)", 0) + + query_count = FetchOne( + "SELECT COUNT(1), COUNT(NULLIF(file, '')), VERSION() FROM posts", 0) + total = int(query_count[0]) + total_files = int(query_count[1]) + mysql_ver = query_count[2] + + archive_count = FetchOne("SELECT SUM(length) FROM archive", 0) + total_archived = int(archive_count[0]) + + days = [] + for date, count, threads in query_day[1:]: + days.append((date, count, threads)) + + query_b = FetchAll( + "SELECT id, dir, name FROM boards WHERE boards.secret = 0", 0) + + boards = [] + totalp = 0 + for id, dir, longname in query_b: + bposts = FetchOne("SELECT COUNT(1) FROM posts " + "WHERE '"+str(id)+"' = posts.boardid AND timestamp > ( UNIX_TIMESTAMP(DATE(NOW())) - 2419200 )", 0) + boards.append((dir, longname, int(bposts[0]))) + totalp += int(bposts[0]) + + boards = sorted(boards, key=lambda boards: boards[2], reverse=True) + + boards_percent = [] + for dir, longname, bposts in boards: + if bposts > 0: + boards_percent.append((dir, longname, '{0:.2f}'.format( + float(bposts)*100/totalp), int(bposts))) + else: + boards_percent.append((dir, longname, '0.00', '0')) + + #posts = FetchAll("SELECT `parentid`, `boardid` FROM `posts` INNER JOIN `boards` ON posts.boardid = boards.id WHERE posts.parentid<>0 AND posts.timestamp>(UNIX_TIMESTAMP()-86400) AND boards.secret=0 ORDER BY `parentid`") + #threads = {} + # for post in posts: + # if post["parentid"] in threads: + # threads[post["parentid"]] += 1 + # else: + # threads[post["parentid"]] = 1 + + python_version = platform.python_version() + if self.environ.get('FCGI_FORCE_CGI', 'N').upper().startswith('Y'): + python_version += " (CGI)" + else: + python_version += " (FastCGI)" + + out = { + "uname": platform.uname(), + "python_ver": python_version, + "python_impl": platform.python_implementation(), + "python_build": platform.python_build()[1], + "python_compiler": platform.python_compiler(), + "mysql_ver": mysql_ver, + "tenjin_ver": tenjin.__version__, + "weabot_ver": __version__, + "days": days, + "boards": boards, + "boards_percent": boards_percent, + "total": total, + "total_files": total_files, + "total_archived": total_archived, + "t": timestamp(), + "tz": Settings.TIME_ZONE, + } + with open('stats.json', 'w') as f: + json.dump(out, f) + + out['timestamp'] = re.sub(r"\(...\)", " ", formatTimestamp(out['t'])) + out['regenerated'] = regenerated + self.output = renderTemplate("stats.html", out) + #self.headers = [("Content-Type", "application/json")] + if __name__ == "__main__": - from fcgi import WSGIServer - - # Psyco is not required, however it will be used if available - try: - import psyco - logTime("Psyco se ha instalado") - psyco.bind(tenjin.helpers.to_str) - psyco.bind(weabot.run, 2) - psyco.bind(getFormData) - psyco.bind(setCookie) - psyco.bind(threadUpdated) - psyco.bind(processImage) - except: - pass - - WSGIServer(weabot).run() + from fcgi import WSGIServer + + # Psyco is not required, however it will be used if available + try: + import psyco + logTime("Psyco se ha instalado") + psyco.bind(tenjin.helpers.to_str) + psyco.bind(weabot.run, 2) + psyco.bind(getFormData) + psyco.bind(setCookie) + psyco.bind(threadUpdated) + psyco.bind(processImage) + except: + pass + WSGIServer(weabot).run() |