#!/usr/bin/env python3 # coding=utf-8 # Remove the first line to use the env command to locate python import os import time import datetime import random import cgi import logging from MySQLdb import _mysql from http.cookies import SimpleCookie import tenjin import manage import oekaki import gettext from database import * from settings import Settings from framework import * from formatting import * from post import * from img import * __version__ = "0.10.9" # Set to True to disable weabot's exception routing and enable profiling _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 logging.basicConfig(filename='weabot.log', format='%(asctime)s %(levelname)s %(message)s', level=logging.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() if _DEBUG: import cProfile prof = cProfile.Profile() prof.runcall(self.run) prof.dump_stats('stats.prof') else: try: self.run() except UserError as message: self.error(message) except Exception as inst: logging.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() def __iter__(self): self.handleResponse() self.start("200 OK", self.headers) self.output = self.output.encode('utf-8') 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 += 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 list(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): clearCache() 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) # 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_ib = getLastAge(0, Settings.HOME_LASTPOSTS) latest_age_bbs = getLastAge(1, Settings.HOME_LASTPOSTS) for threads in latest_age_ib: content = threads['url'] content = content.replace('/read/', '/') content = content.replace('/res/', '/') content = content.replace('.html', '') threads['url'] = content for threads in latest_age_bbs: 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_ib': latest_age_ib, 'latest_age_bbs': latest_age_bbs}, 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 # Redirect to ban page if user is banned if Settings.ENABLE_BANS and addressIsBanned(self.environ['REMOTE_ADDR'], board["dir"], blind_only=True): raise UserError('' % board["dir"]) if len(path_split) > 4 and path_split[4] and board['board_type'] == 1: self.output = dynamicRead(int(path_split[3]), path_split[4], True) elif board['board_type'] == 1: self.output = threadPage(0, True, int(path_split[3])) else: self.output = threadPage(int(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 = f"{Settings.HOME_URL}{board['dir']}/read/{thread['timestamp']}/" else: url = f"{Settings.HOME_URL}{board['dir']}/res/{thread['id']}.html" self.output += '

...

' % 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 = f"{Settings.HOME_URL}/zonavip/read/{thread['timestamp']}/" self.output += '

...

' % url elif path_split[1] == "banned": OpenDb() bans = FetchAll("SELECT * FROM `bans` WHERE INET6_ATON(%s) BETWEEN `ipstart` AND `ipend`", (self.environ["REMOTE_ADDR"],)) if bans: for ban in bans: if ban["boards"]: boards = str2boards(ban["boards"]) if not ban["boards"] or path_split[2] in boards: caught = True if ban["boards"]: boards_str = '/' + '/, /'.join(boards) + '/' else: boards_str = 'todas' if ban["until"]: 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"], 'ipstr': ban['ipstr'], } self.output = renderTemplate( 'banned.html', template_values) else: if len(path_split) > 2: caught = True self.output += '

%s

' % ( 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]) # Redirect to ban page if user is banned if Settings.ENABLE_BANS and addressIsBanned(self.environ['REMOTE_ADDR'], board["dir"], blind_only=True): raise UserError('' % board["dir"]) 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 as messagez: self.output = "Error: " + \ str(messagez) + " : " + str(self.formdata) elif path_split[1] == "mod": import modapi 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() modapi.api(self, path_split) if not caught: # Redirect the user back to the front page self.output += '

--> --> -->

' % Settings.HOME_URL CloseDb() def make_post(self, ip, boarddir, parent, trap1, trap2, name, email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile): _STARTTIME = time.process_time() # Comment if not debug if Settings.PROXY_BANS and ip not in Settings.PROXY_WHITELIST and (boarddir not in Settings.EXCLUDE_GLOBAL_BANS): if addressIsTor(ip): logging.warn("Proxy: Tor %s" % ip) raise UserError("Proxy prohibido.") if addressIsProxy(ip): logging.warn("Proxy: Proxy %s" % ip) raise UserError("Proxy prohibido.") if addressIsBannedCountry(ip): logging.warn("Proxy: Banned country %s" % ip) raise UserError("Proxy prohibido.") if not addressIsES(ip): logging.warn("Proxy: Not ES %s" % ip) raise UserError("Proxy prohibido.") if hostIsBanned(ip): logging.warn("Proxy: Blacklisted host %s" % ip) raise UserError("Sufijo de host en lista negra.") # open database OpenDb() # set the board board = setBoard(boarddir) # 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 Settings.ENABLE_BANS and addressIsBanned(ip, board["dir"]): raise UserError('' % board["dir"]) # Disallow posting if the site OR board is in maintenance if Settings.MAINTENANCE and board["dir"] != 'polka': 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"] = 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 not board["disable_name"]: post["name"] = cleanString(name) post["email"] = cleanString(email, quote=True) if not board["disable_subject"]: 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 = None if not post["parentid"] and board["dir"] not in ['bai', 'world']: # creating thread __extend = re.compile(r"!extend(:\w+)(:\w+)?\n", re.IGNORECASE) res = __extend.match(message) if res: extend = res.groups() # truncate extend extend_str = res.group(0) 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[1:] extend_str = extend_str.replace('extend', 'EXTEND') post["message"] += '
' + extend_str + ' configurado.' 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 = use_icon = 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] cap_id, hide_end, use_icon = capcode[3], capcode[4], capcode[5] # hide ip if necessary if hide_end: post["ip"] = 0 # insert icon if use_icon: img_src = 'ico
' % use_icon post["message"] = img_src + post["message"] # use password post["password"] = password # EXTEND feature if post["parentid"] and board["dir"] not in ['bai', 'world']: # replying __extend = re.compile(r"
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"]: 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 not board['allow_image_replies']: raise UserError(_("Image replies not allowed.")) else: if file and not board['allow_images']: 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'])) 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"]: 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.items(): 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((getMD5(ip + post["name"])[:4], '****'))) 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 = '★' 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 elif ':' in ip: iprs = ip.split(':') host = '%s:%s:*:*:*.*.*.*' % (iprs[0], iprs[1]) 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) or '??' post["name"] += " [%s]" % 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) + (board["maxage"] * 86400) if 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 "' + \ '
'.join(msgs)) # redirect if imageonly: self.output += '

%s

' % ( ("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("File deleted successfully.")) else: self.output += '

%s

' % ( ("/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 Settings.ENABLE_BANS and 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", (postid, 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 = html.escape(self.formdata["reason"]).strip()[0:800] message = message.replace("\n", "
") UpdateDb("INSERT INTO `reports` (board, postid, parentid, link, ip, reason, repip, timestamp, timestamp_formatted) " + "VALUES (%s, %s, %s, %s, %s, %s, INET6_ATON(%s), %s, %s)", (board["dir"], post['id'], post['parentid'], link, post['ip'], message, self.environ["REMOTE_ADDR"], t, formatTimestamp(t))) self.output = renderTemplate("report.html", {'finished': True}) def stats(self): import json import math import 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\") AS date, COUNT(1) AS count, COUNT(IF(parentid=0, 1, NULL)) AS threads " "FROM posts " "WHERE (timestamp-10800) > (UNIX_TIMESTAMP()-604800) AND (IS_DELETED = 0) " "GROUP BY FLOOR((timestamp-10800)/86400) " "ORDER BY FLOOR((timestamp-10800)/86400)") query_count = FetchOne("SELECT COUNT(1) AS posts, COUNT(NULLIF(file, '')) AS files, VERSION() AS version FROM posts") total = query_count["posts"] total_files = query_count["files"] mysql_ver = query_count["version"] archive_count = FetchOne("SELECT SUM(length) AS sum FROM archive") total_archived = int(archive_count["sum"]) days = [] for r in query_day[1:]: days.append((r["date"], r["count"], r["threads"])) query_b = FetchAll("SELECT id, dir, name FROM boards WHERE boards.secret = 0") boards = [] totalp = 0 for b in query_b: bposts = FetchOne("SELECT COUNT(1) AS count FROM posts " "WHERE posts.boardid = %s AND timestamp > ( UNIX_TIMESTAMP(DATE(NOW())) - 2419200 )", (b['id'],)) boards.append((b['dir'], b['name'], bposts["count"])) totalp += bposts["count"] 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 flup.server.fcgi import WSGIServer # Psyco is not required, however it will be used if available try: import psyco logging.debug("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 app = WSGIServer(weabot, debug=True, forceCGI=False) app.run()