# coding=utf-8 import os import cgi import html import shutil import datetime import logging from database import * from settings import Settings from framework import * from formatting import * from template import * from post import * def manage(self, path_split): page = '' validated = False administrator = False moderator = True skiptemplate = False staff_account = None if 'username' in self.formdata and 'password' in self.formdata: # If no admin accounts available, create admin:admin first_admin = FetchOne("SELECT 1 FROM `staff` WHERE `rights` = 0 LIMIT 1") if not first_admin: InsertDb("INSERT INTO `staff` (`username`, `password`, `added`, `rights`) VALUES ('admin', %s, 0, 0)", (genPasswdHash("admin"),)) staff_account = verifyPasswd(self.formdata['username'], self.formdata['password']) if staff_account: session_uuid = newSession(staff_account['id']) setCookie(self, 'weabot_manage', session_uuid) UpdateDb("DELETE FROM `logs` WHERE `timestamp` < %s", (timestamp() - Settings.MANAGE_LOG_TIME,)) else: page += _('Incorrect username/password.') logAction('', 'Failed login. U:'+self.formdata['username']+' IP logged.') logging.warn("Failed login. U:{} IP:{}".format(self.formdata['username'], self.ip)) else: # Validate existing session manage_cookie = getCookie(self, 'weabot_manage') if manage_cookie: staff_account = validateSession(manage_cookie) if not staff_account: page += "La sesión ha expirado. Por favor ingresa tus credenciales nuevamente." deleteCookie(self, 'weabot_manage') if staff_account: validated = True if 'session_id' in staff_account: renewSession(staff_account['session_id']) if staff_account['rights'] in [0, 1, 2]: administrator = True if staff_account['rights'] == 2: moderator = False UpdateDb('UPDATE `staff` SET `lastactive` = %s WHERE `id` = %s LIMIT 1', (timestamp(), staff_account['id'])) if not validated: template_filename = "login.html" template_values = {} else: if len(path_split) > 2: if path_split[2] == 'rebuild': if not administrator: return try: board_dir = path_split[3] except: board_dir = '' if board_dir == '': template_filename = "rebuild.html" template_values = {'boards': boardlist()} else: everything = ("everything" in self.formdata) if board_dir == '!ALL': t1 = time.time() boards = FetchAll( 'SELECT `dir` FROM `boards` WHERE secret = 0') for board in boards: board = setBoard(board['dir']) regenerateBoard(everything) message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('all boards'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('all boards')) elif board_dir == '!BBS': t1 = time.time() boards = FetchAll( 'SELECT `dir` FROM `boards` WHERE `board_type` = 1') for board in boards: board = setBoard(board['dir']) regenerateBoard(everything) message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('all boards'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('all boards')) elif board_dir == '!IB': t1 = time.time() boards = FetchAll( 'SELECT `dir` FROM `boards` WHERE `board_type` = 1') for board in boards: board = setBoard(board['dir']) regenerateBoard(everything) message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('all boards'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('all boards')) elif board_dir == '!HOME': t1 = time.time() regenerateHome() message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('home'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('home')) elif board_dir == '!NEWS': t1 = time.time() regenerateNews() message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('news'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('news')) elif board_dir == '!TRASH': t1 = time.time() regenerateTrash() message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('trash'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('trash')) elif board_dir == '!KAKO': t1 = time.time() boards = FetchAll( 'SELECT `dir` FROM `boards` WHERE archive = 1') for board in boards: board = setBoard(board['dir']) regenerateKako() message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': 'kako', 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % 'kako') elif board_dir == '!HTACCESS': t1 = time.time() if regenerateAccess(): message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': _('htaccess'), 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], _( 'Rebuilt %s') % _('htaccess')) else: message = _( 'htaccess regeneration deactivated by sysop.') else: t1 = time.time() board = setBoard(board_dir) regenerateBoard(everything) message = _('Rebuilt %(board)s in %(time)s seconds.') % { 'board': '/' + board['dir'] + '/', 'time': timeTaken(t1, time.time())} logAction(staff_account['username'], 'Rebuilt /' + board['dir'] + '/') template_filename = "message.html" elif path_split[2] == 'mod': if not moderator: return try: board = setBoard(path_split[3]) except: board = "" if not board: template_filename = "mod.html" template_values = {"mode": 1, 'boards': boardlist()} elif len(path_split) > 4: parentid = int(path_split[4]) # make sure it's the full thread check = FetchOne("SELECT `parentid` FROM `posts` WHERE `id` = %s AND `boardid` = %s LIMIT 1", (parentid, board['id'])) if check['parentid']: parentid = int(check['parentid']) posts = FetchAll('SELECT id, timestamp, timestamp_formatted, name, message, file, thumb, IS_DELETED, locked, subject, length, INET6_NTOA(ip) AS ip FROM `posts` WHERE (parentid = %s OR id = %s) AND boardid = %s ORDER BY `id` ASC', (parentid, parentid, board['id'])) template_filename = "mod.html" template_values = {"mode": 3, "dir": board["dir"], "posts": posts} else: threads = FetchAll("SELECT * FROM `posts` WHERE boardid = %s AND parentid = 0 ORDER BY `bumped` DESC" % board["id"]) template_filename = "mod.html" template_values = {"mode": 2, "dir": board["dir"], "threads": threads} elif path_split[2] == 'recent': posts = FetchAll("SELECT posts.id, posts.subject, dir, boards.board_type, CASE parentid WHEN '0' THEN posts.id ELSE parentid END AS parentid, file, thumb, timestamp_formatted, timestamp, posts.message, INET6_NTOA(ip) AS ip, posts.name, email, tripcode, posts.IS_DELETED, boards.name AS board_name FROM posts INNER JOIN boards ON posts.boardid = boards.id WHERE posts.timestamp > UNIX_TIMESTAMP() - 86400 ORDER BY timestamp DESC") template_filename = "recent.html" template_values = {"posts": posts} elif path_split[2] == 'staff': if staff_account['rights'] != 0: return action_taken = False if len(path_split) > 3: if path_split[3] == 'add' or path_split[3] == 'edit': member = None member_username = '' member_rights = 3 if path_split[3] == 'edit': if len(path_split) > 4: member = FetchOne('SELECT * FROM `staff` WHERE `id` = %s LIMIT 1', (path_split[4],)) if member: member_username = member['username'] member_rights = member['rights'] action = 'edit/' + str(member['id']) try: if self.formdata.get('user'): if self.formdata['rights'] in [0, 1, 2, 3]: action_taken = True UpdateDb("UPDATE `staff` SET `username` = %s, `rights` = %s WHERE `id` = LIMIT 1", (self.formdata['user'], self.formdata['rights'], member['id'])) message = _( 'Staff member updated.') logAction(staff_account['username'], _( 'Updated staff account for %s') % self.formdata['user']) template_filename = "message.html" except: pass else: action = 'add' if self.formdata.get('user') and self.formdata.get('pass'): username_taken = FetchOne( 'SELECT COUNT(1) as count FROM `staff` WHERE `username` = %s LIMIT 1', (self.formdata['user'],)) if not username_taken['count']: if self.formdata['rights'] in [0, 1, 2, 3]: action_taken = True pass_hash = genPasswdHash( self.formdata['pass']) InsertDb("INSERT INTO `staff` (`username`, `password`, `added`, `rights`) VALUES (%s, %s, %s, %s)", (self.formdata['user'], pass_hash, timestamp(), self.formdata['rights'])) message = _('Staff member added.') logAction( staff_account['username'], 'Added staff account for ' + self.formdata['user']) template_filename = "message.html" else: action_taken = True message = _( 'That username is already in use.') template_filename = "message.html" if not action_taken: action_taken = True if action == 'add': submit = 'Agregar' else: submit = 'Editar' template_filename = "staff.html" template_values = {'mode': 1, 'action': action, 'member': member, 'member_username': member_username, 'member_rights': member_rights, 'submit': submit} elif path_split[3] == 'delete': if not moderator: return action_taken = True message = '' + _('Click here to confirm the deletion of that staff member') + '' template_filename = "message.html" elif path_split[3] == 'delete_confirmed': if not moderator: return action_taken = True member = FetchOne('SELECT `username` FROM `staff` WHERE `id` = %s LIMIT 1', (path_split[4],)) if member: UpdateDb('DELETE FROM `staff` WHERE `id` = %s LIMIT 1', (path_split[4],)) message = 'Staff member deleted.' template_filename = "message.html" logAction(staff_account['username'], _( 'Deleted staff account for %s') % member['username']) else: message = _( 'Unable to locate a staff account with that ID.') template_filename = "message.html" if not action_taken: staff = FetchAll('SELECT * FROM `staff` ORDER BY `rights`') for member in staff: if member['rights'] == 0: member['rights'] = _('Super-administrator') elif member['rights'] == 1: member['rights'] = _('Administrator') elif member['rights'] == 2: member['rights'] = _('Developer') elif member['rights'] == 3: member['rights'] = _('Moderator') if member['lastactive']: member['lastactivestamp'] = member['lastactive'] member['lastactive'] = formatTimestamp( member['lastactive']) else: member['lastactive'] = _('Never') member['lastactivestamp'] = 0 template_filename = "staff.html" template_values = {'mode': 0, 'staff': staff} elif path_split[2] == 'delete': if not moderator: return do_ban = False try: if self.formdata['ban'] == 'true': do_ban = True except: pass board = setBoard(path_split[3]) post = FetchOne('SELECT id, message, thumb, name, tripcode FROM posts WHERE boardid = %s AND id = %s LIMIT 1' % (board['id'], path_split[4])) template_filename = "delete.html" template_values = {'do_ban': do_ban, 'curboard': board, 'post': post } elif path_split[2] == 'delete_confirmed': if not moderator: return do_ban = self.formdata.get('ban') permanently = self.formdata.get('perma') imageonly = self.formdata.get('imageonly') board = setBoard(path_split[3]) postid = int(path_split[4]) post = FetchOne('SELECT id, message, parentid, INET6_NTOA(ip) AS ip FROM posts WHERE boardid = %s AND id = %s' % (board['id'], postid)) if not permanently: deletePost(path_split[4], None, 2, imageonly) regenerateTrash() else: deletePost(path_split[4], None, 0, imageonly) regenerateHome() # Borrar denuncias UpdateDb("DELETE FROM `reports` WHERE `postid` = %s", (int(path_split[4]),)) boards = FetchAll('SELECT `name`, `dir` FROM `boards` ORDER BY `dir`') if imageonly: message = 'Archivo de post /%s/%s eliminado.' % ( board['dir'], post['id']) elif permanently or post["parentid"] == 0: message = 'Post /%s/%s eliminado permanentemente.' % ( board['dir'], post['id']) else: message = 'Post /%s/%s enviado a la papelera.' % ( board['dir'], post['id']) template_filename = "message.html" logAction(staff_account['username'], message + ' Contenido: ' + post['message'] + ' IP: ' + post['ip']) if do_ban: message = _('Redirecting to ban page...') + '' template_filename = "message.html" elif path_split[2] == 'lock': setLocked = 0 # Nos vamos al board y ubicamos el post board = setBoard(path_split[3]) pid = int(path_split[4]) post = FetchOne('SELECT id, parentid, locked FROM posts WHERE boardid = %s AND id = %s LIMIT 1', (board['id'], pid) ) if not post: message = _('Unable to locate a post with that ID.') template_filename = "message.html" else: if post['parentid'] != 0: message = _('Post is not a thread opener.') template_filename = "message.html" else: if post['locked'] == 0: # Cerrar si esta abierto setLocked = 1 else: # Abrir si esta cerrado setLocked = 0 UpdateDb("UPDATE `posts` SET `locked` = %s WHERE `boardid` = %s AND `id` = %s LIMIT 1", (setLocked, board["id"], post["id"])) threadUpdated(pid) if setLocked == 1: message = _('Thread successfully closed.') logAction(staff_account['username'], _('Closed thread %s') % ('/' + board['dir'] + '/' + str(pid)) ) else: message = _('Thread successfully opened.') logAction(staff_account['username'], _('Opened thread %s') % ('/' + board['dir'] + '/' + str(pid)) ) template_filename = "message.html" elif path_split[2] == 'permasage': setPermasaged = 0 # Nos vamos al board y ubicamos el post board = setBoard(path_split[3]) pid = int(path_split[4]) post = FetchOne('SELECT `parentid`, `locked` FROM `posts` WHERE `boardid` = %s AND `id` = %s LIMIT 1', (board['id'], pid) ) if not post: message = 'No se encuentra un hilo con ese ID.' template_filename = "message.html" elif post['locked'] == 1: message = 'Solo se puede aplicar permasage en un hilo abierto.' template_filename = "message.html" else: if post['parentid']: message = 'El post indicado es una respuesta a un hilo.' template_filename = "message.html" else: if post['locked'] == 2: # Sacar permasage setPermasaged = 0 else: # Colocar permasage setPermasaged = 2 UpdateDb("UPDATE `posts` SET `locked` = %s WHERE `boardid` = '%s' AND `id` = '%s' LIMIT 1" % (setPermasaged, board["id"], pid) ) regenerateFrontPages() threadUpdated(pid) if setPermasaged == 2: message = 'Thread successfully permasaged.' logAction(staff_account['username'], 'Activado permasage en el hilo /%s/%s' % (board['dir'], pid) ) else: message = 'Thread successfully un-permasaged.' logAction(staff_account['username'], 'Desactivado permasage en el hilo /%s/%s' % (board['dir'], pid) ) template_filename = "message.html" elif path_split[2] == 'move': raise NotImplementedError #if not moderator: if not administrator: return oldboardid = "" oldthread = "" newboardid = "" try: oldboardid = path_split[3] oldthread = path_split[4] newboardid = path_split[5] except: pass try: oldboardid = self.formdata['oldboardid'] oldthread = self.formdata['oldthread'] newboardid = self.formdata['newboardid'] except: pass if oldboardid and oldthread and newboardid: message = "import" import shutil message += "ok" board = setBoard(oldboardid) oldboard = board['dir'] oldboardsubject = board['subject'] oldboardname = random.choice(board["anonymous"].split('|')) # get old posts posts = FetchAll( "SELECT * FROM `posts` WHERE (`id` = {0} OR `parentid` = {0}) AND `boardid` = {1} ORDER BY id ASC".format(oldthread, board['id'])) # switch to new board board = setBoard(newboardid) newboard = board['dir'] refs = {} moved_files = [] moved_thumbs = [] moved_cats = [] newthreadid = 0 newthread = 0 num = 1 message = "from total: %s
" % len(posts) template_filename = "message.html" for p in posts: # save old post ID old_id = p['id'] is_op = bool(p['parentid'] == '0') # copy post object but without ID and target boardid post = Post() post.post = p post.post.pop("id") post["boardid"] = board['id'] post["parentid"] = newthreadid # save the files we need to move if any if post['IS_DELETED'] == '0': if post['file']: moved_files.append(post['file']) if post['thumb']: moved_thumbs.append(post['thumb']) if is_op: moved_cats.append(post['thumb']) # fix subject if necessary if post['subject'] and post['subject'] == oldboardsubject: post['subject'] = board['subject'] # fix new default name if post['name'] == oldboardname: post['name'] = board['anonymous'] # fix date post['timestamp_formatted'] = formatTimestamp(post['timestamp']) #(re)add post ID if necessary if board["useid"] != '0': if post["parentid"]: tym = parent_time else: tym = post["timestamp"] post['posterid'] = iphash(post['ip'], post, tym, board["useid"], False, '', False, False, (board["countrycode"] in [1, 2])) # insert new post and get its new ID new_id = post.insert() # save the reference (BBS = post number, IB = new ID) refs[old_id] = num if board['board_type'] == 1 else new_id # this was an OP message += "newthread = %s parentid = %s
" % ( newthreadid, p['parentid']) if is_op: oldthread = old_id newthreadid = new_id oldbumped = post["bumped"] # BBS = new thread timestamp, IB = new thread ID newthread = post['timestamp'] if board['board_type'] == 1 else new_id parent_time = post['timestamp'] # log it message += "%s -> %s
" % (old_id, new_id) num += 1 # fix anchors for old, new in refs.items(): old_url = "/{oldboard}/res/{oldthread}.html#{oldpost}\">>>{oldpost}".format( oldboard=oldboard, oldthread=oldthread, oldpost=old) if board['board_type'] == 1: new_url = "/{newboard}/read/{newthread}/{newpost}\">>>{newpost}".format( newboard=newboard, newthread=newthread, newpost=new) else: new_url = "/{newboard}/res/{newthread}.html#{newpost}\">>>{newpost}".format( newboard=newboard, newthread=newthread, newpost=new) sql = "UPDATE `posts` SET `message` = REPLACE(message, '{old}', '{new}') WHERE `boardid` = {newboardid} AND (`id` = {newthreadid} OR `parentid` = {newthreadid})".format( old=old_url, new=new_url, newboardid=board['id'], newthreadid=newthreadid) message += sql + "
" UpdateDb(sql) # copy files for file in moved_files: if not os.path.isfile(Settings.IMAGES_DIR + newboard + "/src/" + file): shutil.copyfile(Settings.IMAGES_DIR + oldboard + "/src/" + file, Settings.IMAGES_DIR + newboard + "/src/" + file) for thumb in moved_thumbs: if not os.path.isfile(Settings.IMAGES_DIR + newboard + "/thumb/" + thumb): shutil.copyfile(Settings.IMAGES_DIR + oldboard + "/thumb/" + thumb, Settings.IMAGES_DIR + newboard + "/thumb/" + thumb) if not os.path.isfile(Settings.IMAGES_DIR + newboard + "/mobile/" + thumb): shutil.copyfile(Settings.IMAGES_DIR + oldboard + "/mobile/" + thumb, Settings.IMAGES_DIR + newboard + "/mobile/" + thumb) for cat in moved_cats: try: if not os.path.isfile(Settings.IMAGES_DIR + newboard + "/cat/" + thumb): shutil.copyfile(Settings.IMAGES_DIR + oldboard + "/cat/" + thumb, Settings.IMAGES_DIR + newboard + "/cat/" + thumb) except: pass # lock original, set expiration to 1 day exp = timestamp()+86400 exp_format = datetime.datetime.fromtimestamp(exp).strftime("%d/%m") sql = "UPDATE `posts` SET `locked`=1, `expires`={exp}, `expires_formatted`=\"{exp_format}\" WHERE `boardid`=\"{oldboard}\" AND id=\"{oldthread}\"".format(exp=exp, exp_format=exp_format, oldboard=oldboardid, oldthread=oldthread) UpdateDb(sql) # insert notice message if 'msg' in self.formdata: leavemsg = True board = setBoard(oldboard) if board['board_type'] == 1: thread_url = "/{newboard}/read/{newthread}".format(newboard=newboard, newthread=newthread) else: thread_url = "/{newboard}/res/{newthread}.html".format(newboard=newboard, newthread=newthread) notice_post = Post(board["id"]) notice_post["parentid"] = oldthread if board['board_type'] == "0": notice_post["subject"] = "Aviso" notice_post["name"] = "Sistema" notice_post["message"] = "El hilo ha sido movido a /{newboard}/{newthread}.".format( url=thread_url, newboard=newboard, newthread=newthread) notice_post["timestamp"] = timestamp()+1 notice_post["timestamp_formatted"] = "Hilo movido" notice_post["bumped"] = oldbumped notice_post.insert() regenerateFrontPages() regenerateThreadPage(oldthread) # regenerate again (fix?) board = setBoard(newboardid) regenerateFrontPages() regenerateThreadPage(newthreadid) message += "done" logAction(staff_account['username'], "Movido hilo %s/%s a %s/%s." % (oldboard, oldthread, newboard, newthread)) else: template_filename = "move.html" template_values = {'boards': boardlist( ), 'oldboardid': oldboardid, 'oldthread': oldthread} elif path_split[2] == 'ban': if not moderator: return if len(path_split) > 4: board = setBoard(path_split[3]) post = FetchOne('SELECT INET6_NTOA(`ip`) AS `ip` FROM `posts` WHERE `boardid` = %s AND `id` = %s LIMIT 1' % (board['id'], int(path_split[4])) ) if not post: message = _('Unable to locate a post with that ID.') template_filename = "message.html" else: message = 'Espere...' template_filename = "message.html" else: reason = self.formdata.get('reason') if reason is not None: # Start ban process import netaddr ip = self.formdata['ip'] # Parse CIDR or IP glob try: if netaddr.valid_ipv4(ip) or netaddr.valid_ipv6(ip): ipaddress = netaddr.IPAddress(ip) ipstart = ipend = ipstr = str(ipaddress) elif netaddr.valid_glob(ip): iprange = netaddr.glob_to_iprange(ip) ipstart, ipend = str(iprange[0]), str(iprange[-1]) cidrs = iprange.cidrs() if len(cidrs) == 1: ipstr = str(cidrs[0]) else: ipstr = str(iprange) elif '/' in ip: # Try with CIDR ipnetwork = netaddr.IPNetwork(ip) ipstart, ipend = str(ipnetwork[0]), str(ipnetwork[-1]) ipstr = str(ipnetwork) else: self.error("IP o rango inválido.") return except netaddr.core.AddrFormatError as e: self.error("Problema con el IP o rango ingresado: {}".format(e)) return if self.formdata['seconds'] != '0': until = timestamp() + int(self.formdata['seconds']) else: until = 0 where = '' if 'board_all' not in self.formdata: where = [] boards = FetchAll('SELECT `dir` FROM `boards`') for board in boards: keyname = 'board_' + board['dir'] if keyname in self.formdata: if self.formdata[keyname] == "1": where.append(board['dir']) if len(where) > 0: where = boards2str(where) else: self.error( _("You must select where the ban shall be placed")) return if 'edit' in self.formdata: UpdateDb("DELETE FROM `bans` WHERE `id` = '" + _mysql.escape_string(self.formdata['edit']) + "' LIMIT 1") else: # Duplicate check ban = addressIsBanned(ip) if ban: self.error(_('There is already an identical ban for this IP.') + '' + _('Edit') + '') return # Blind mode blind = self.formdata.get('blind', 0) #raise UserError, "{} {} {}".format(ipstart, ipend, ipstr) # Banear sin mensaje InsertDb("INSERT INTO `bans` (`ipstart`, `ipend`, `ipstr`, `boards`, `added`, `until`, `staff`, `reason`, `note`, `blind`) VALUES " "(INET6_ATON(%s), INET6_ATON(%s), %s, %s, %s, %s, %s, %s, %s, %s)", (ipstart, ipend, ipstr, where, timestamp(), until, staff_account['username'], self.formdata['reason'], self.formdata['note'], blind)) regenerateAccess() if 'edit' in self.formdata: message = _('Ban successfully edited.') action = 'Edited ban for ' + ip else: message = _('Ban successfully placed.') action = 'Banned ' + ip if until != 0: action += ' until ' + \ formatTimestamp(until) else: action += ' permanently' logAction(staff_account['username'], action) template_filename = 'message.html' else: startvalues = {'where': [], 'reason': '', 'note': '', 'message': '(GET OUT)', 'seconds': 0, 'blind': 1} edit_id = 0 if 'edit' in self.formdata: edit_id = self.formdata['edit'] ban = FetchOne("SELECT `id`, INET6_NTOA(`ip`) AS 'ip', CASE WHEN `netmask` IS NULL THEN '255.255.255.255' ELSE INET_NTOA(`netmask`) END AS 'netmask', boards, added, until, staff, reason, note, blind FROM `bans` WHERE `id` = %s ORDER BY `added` DESC", (edit_id) ) if ban: if ban['boards'] == '': where = '' else: where = boards2str(ban['boards']) if ban['until'] == '0': until = 0 else: until = int(ban['until']) - timestamp() startvalues = {'where': where, 'reason': ban['reason'], 'note': ban['note'], 'seconds': str(until), 'blind': ban['blind'] } else: edit_id = 0 template_filename = "bans.html" template_values = {'mode': 1, 'boards': boardlist(), 'ip': self.formdata.get('ip'), 'startvalues': startvalues, 'edit_id': edit_id} elif path_split[2] == 'bans': if not moderator: return action_taken = False if len(path_split) > 4: if path_split[3] == 'delete': ip = FetchOne("SELECT ipstr FROM `bans` WHERE `id` = %s LIMIT 1", (path_split[4],)) if ip: # Delete ban UpdateDb('DELETE FROM `bans` WHERE `id` = %s LIMIT 1', (path_split[4],)) regenerateAccess() message = _('Ban successfully deleted.') template_filename = "message.html" logAction(staff_account['username'], _( 'Deleted ban for %s') % ip['ipstr']) else: message = _( 'There was a problem while deleting that ban. It may have already been removed, or recently expired.') template_filename = "message.html" if not action_taken: bans = FetchAll( "SELECT `id`, `ipstr` AS 'ip', boards, added, until, staff, reason, note, blind FROM `bans` ORDER BY `added` DESC") if bans: for ban in bans: if ban['boards'] == '': ban['boards'] = _('All boards') else: where = str2boards(ban['boards']) if len(where) > 1: ban['boards'] = '/' + \ '/, /'.join(where) + '/' else: ban['boards'] = '/' + where[0] + '/' ban['added'] = formatTimestamp(ban['added']) if ban['until'] == 0: ban['until'] = _('Does not expire') else: ban['until'] = formatTimestamp(ban['until']) if ban['blind']: ban['blind'] = 'Sí' else: ban['blind'] = 'No' template_filename = "bans.html" template_values = {'mode': 0, 'bans': bans} elif path_split[2] == 'changepassword': form_submitted = False try: if self.formdata['oldpassword'] != '' and self.formdata['newpassword'] != '' and self.formdata['newpassword2'] != '': form_submitted = True except: pass if form_submitted: if verifyPasswd(staff_account['username'], self.formdata['oldpassword']): if self.formdata['newpassword'] == self.formdata['newpassword2']: UpdateDb('UPDATE `staff` SET `password` = %s WHERE `id` = %s LIMIT 1', (genPasswdHash(self.formdata['newpassword']), staff_account['id'])) message = _( 'Password successfully changed. Please log out and log back in.') template_filename = "message.html" else: message = _('Passwords did not match.') template_filename = "message.html" else: message = _('Current password incorrect.') template_filename = "message.html" else: template_filename = "changepassword.html" template_values = {} elif path_split[2] == 'board': if not administrator: return if len(path_split) > 3: board = setBoard(path_split[3]) form_submitted = False try: if self.formdata['name'] != '': form_submitted = True except: pass if form_submitted: rebuild = 0 RB_FRONT = BIT(0) RB_ALL = BIT(1) RB_KAKO = BIT(2) RB_HOME = BIT(3) # Update board settings if self.formdata['longname'] != board['longname']: board['longname'] = self.formdata['longname'] if board['board_type'] == 1: rebuild |= RB_FRONT else: rebuild |= RB_ALL if board['name'] != self.formdata['name']: board['name'] = self.formdata['name'] rebuild |= RB_ALL if board['board_type'] == 1: rebuild |= RB_KAKO if board['subname'] != self.formdata['subname']: board['subname'] = self.formdata['subname'] rebuild |= RB_HOME board['anonymous'] = self.formdata['anonymous'] board['subject'] = self.formdata['subject'] board['message'] = self.formdata['message'] board['useid'] = self.formdata['useid'] board['slip'] = self.formdata['slip'] board['countrycode'] = self.formdata['countrycode'] if 'recyclebin' in self.formdata: board['recyclebin'] = 1 else: board['recyclebin'] = 0 if 'disable_name' in self.formdata and not board['disable_name']: board['disable_name'] = 1 if board['board_type'] == 0: rebuild |= RB_ALL elif 'disable_name' not in self.formdata and board['disable_name']: board['disable_name'] = 0 if board['board_type'] == 0: rebuild |= RB_ALL if 'disable_subject' in self.formdata and not board['disable_subject']: board['disable_subject'] = 1 if board['board_type'] == 0: rebuild |= RB_ALL elif 'disable_subject' not in self.formdata and board['disable_subject']: board['disable_subject'] = 0 if board['board_type'] == 0: rebuild |= RB_ALL if 'secret' in self.formdata: board['secret'] = 1 else: board['secret'] = 0 if 'locked' in self.formdata: board['locked'] = 1 else: board['locked'] = 0 if board['postarea_desc'] != self.formdata['postarea_desc']: board['postarea_desc'] = self.formdata['postarea_desc'] if board['board_type'] == 1: rebuild |= RB_FRONT else: rebuild |= RB_ALL if board['postarea_extra'] != self.formdata['postarea_extra']: board['postarea_extra'] = self.formdata['postarea_extra'] if board['board_type'] == 1: rebuild |= RB_FRONT else: rebuild |= RB_ALL if board['force_css'] != self.formdata['force_css']: board['force_css'] = self.formdata['force_css'] rebuild |= RB_ALL if 'allow_noimage' in self.formdata and not board['allow_noimage']: board['allow_noimage'] = 1 if board['board_type'] == 0: rebuild |= RB_ALL elif 'allow_noimage' not in self.formdata and board['allow_noimage']: board['allow_noimage'] = 0 if board['board_type'] == 0: rebuild |= RB_ALL if 'allow_images' in self.formdata and not board['allow_images']: board['allow_images'] = 1 rebuild |= RB_FRONT elif 'allow_images' not in self.formdata and board['allow_images']: board['allow_images'] = 0 rebuild |= RB_FRONT if 'allow_image_replies' in self.formdata and not board['allow_image_replies']: board['allow_image_replies'] = 1 rebuild |= RB_ALL elif 'allow_image_replies' not in self.formdata and board['allow_image_replies']: board['allow_image_replies'] = 0 rebuild |= RB_ALL if 'allow_spoilers' in self.formdata and not board['allow_spoilers']: board['allow_spoilers'] = 1 if board['board_type'] == 0: rebuild |= RB_ALL elif 'allow_spoilers' not in self.formdata and board['allow_spoilers']: board['allow_spoilers'] = 0 if board['board_type'] == 0: rebuild |= RB_ALL if 'allow_oekaki' in self.formdata and not board['allow_oekaki']: board['allow_oekaki'] = 1 if board['board_type'] == 0: rebuild |= RB_ALL elif 'allow_oekaki' not in self.formdata and board['allow_oekaki']: board['allow_oekaki'] = 0 if board['board_type'] == 0: rebuild |= RB_ALL if 'archive' in self.formdata: board['archive'] = 1 else: board['archive'] = 0 # update file types new_filetypes = [(ft.replace('filetype', '')) for ft in self.formdata if ft.startswith('filetype')] if sorted(board['filetypes_ext']) != sorted(new_filetypes): UpdateDb("DELETE FROM boards_filetypes WHERE boardid = %s", (board['id'],) ) for filetype in filetypelist(): if 'filetype'+filetype['ext'] in self.formdata: UpdateDb("INSERT INTO boards_filetypes VALUES (%s, %s)", (board['id'], filetype['id']) ) if board['board_type'] == 0: rebuild |= RB_ALL try: if board['numthreads'] != int(self.formdata['numthreads']): board['numthreads'] = int(self.formdata['numthreads']) rebuild |= RB_FRONT except: raise UserError(_("Max threads shown must be numeric.")) try: if board['numcont'] != int(self.formdata['numcont']): board['numcont'] = int(self.formdata['numcont']) rebuild |= RB_FRONT except: raise UserError(_("Max replies shown must be numeric.")) try: if board['numline'] != int(self.formdata['numline']): board['numline'] = int(self.formdata['numline']) rebuild |= RB_FRONT except: raise UserError(_("Max lines shown must be numeric.")) try: if board['thumb_px'] != int(self.formdata['thumb_px']): board['thumb_px'] = int(self.formdata['thumb_px']) if board['board_type'] == 0: rebuild |= RB_ALL except: raise UserError(_("Max thumb dimensions must be numeric.")) try: if board['maxsize'] != int(self.formdata['maxsize']): board['maxsize'] = int(self.formdata['maxsize']) if board['board_type'] == 0: rebuild |= RB_ALL except: raise UserError(_("Max size must be numeric.")) try: if board['maxage'] != int(self.formdata['maxage']): board['maxage'] = int(self.formdata['maxage']) if board['board_type'] == 0: rebuild |= RB_ALL except: raise UserError(_("Max age must be numeric.")) try: if board['maxinactive'] != int(self.formdata['maxinactive']): board['maxinactive'] = int(self.formdata['maxinactive']) if board['board_type'] == 0: rebuild |= RB_ALL except: raise UserError(_("Max inactivity must be numeric.")) try: board['threadsecs'] = int(self.formdata['threadsecs']) except: raise UserError(_("Time between new threads must be numeric.")) try: board['postsecs'] = int(self.formdata['postsecs']) except: raise UserError(_("Time between replies must be numeric.")) new_type = int(self.formdata['type']) if new_type != board['board_type']: switchBoard(new_type) board['board_type'] = new_type rebuild |= RB_ALL updateBoardSettings() board = setBoard(board['dir']) if rebuild & RB_ALL: regenerateBoard(everything=True) elif rebuild & RB_FRONT: regenerateBoard() if rebuild & RB_KAKO: regenerateKako() if rebuild & RB_HOME: regenerateHome() message = _('Board options successfully updated.') template_filename = "message.html" logAction(staff_account['username'], _('Updated options for /%s/') % board['dir']) else: template_filename = "boardoptions.html" template_values = { 'mode': 1, 'boardopts': board, 'filetypes': filetypelist(), 'supported_filetypes': board['filetypes_ext'] } else: # List all boards template_filename = "boardoptions.html" template_values = {'mode': 0, 'boards': boardlist()} elif path_split[2] == 'boardlock': board = setBoard(path_split[3]) if int(board['locked']): # Si esta cerrado... abrir board['locked'] = 0 updateBoardSettings() message = _('Board opened successfully.') template_filename = "message.html" else: # Si esta abierta, cerrar board['locked'] = 1 updateBoardSettings() message = _('Board closed successfully.') template_filename = "message.html" elif path_split[2] == 'recyclebin': if not administrator: return message = None if len(path_split) > 5: if path_split[4] == 'restore': board = setBoard(path_split[5]) post = FetchOne('SELECT `parentid`, `IS_DELETED` FROM `posts` WHERE `boardid` = %s AND `id` = %s LIMIT 1', (board['id'], path_split[6])) if not post: message = _('Unable to locate a post with that ID.') + '
' template_filename = "message.html" else: UpdateDb('UPDATE `posts` SET `IS_DELETED` = 0 WHERE `boardid` = %s AND `id` = %s LIMIT 1', (board['id'], path_split[6])) if post['parentid'] != 0: threadUpdated(post['parentid']) else: regenerateFrontPages() if post['IS_DELETED'] == 2: regenerateTrash() message = _('Post successfully restored.') logAction(staff_account['username'], _('Restored post %s') % ('/' + path_split[5] + '/' + path_split[6]) ) if path_split[4] == 'delete': board = setBoard(path_split[5]) post = FetchOne('SELECT id, parentid, message, INET6_NTOA(ip) AS ip FROM `posts` WHERE `boardid` = %s AND `id` = %s LIMIT 1', (board['id'], path_split[6])) if not post: message = _('Unable to locate a post with that ID.') else: deletePost(post['id'], None) if post['parentid']: threadUpdated(post['parentid']) else: regenerateFrontPages() regenerateTrash() message = "Post %s eliminado permanentemente de la papelera" % ('/' + board['dir'] + '/' + str(post['id'])) logAction(staff_account['username'], message + ' desde papelera. Contenido: ' + post['message'] + ' IP: ' + post['ip']) # Delete more than 1 post if 'deleteall' in self.formdata: return # TODO deleted = 0 for key in self.formdata: if key[:2] == '!i': # Board where the post is dir = key[2:].split('/')[0] postid = key[2:].split('/')[1] # Post to delete # Delete post start post = FetchOne('SELECT `parentid`, `dir` FROM `posts` INNER JOIN `boards` ON posts.boardid = boards.id WHERE `dir` = \'' + _mysql.escape_string(dir) + '\' AND posts.id = \'' + _mysql.escape_string(postid) + '\' LIMIT 1') if not post: message = _('Unable to locate a post with that ID.') else: board = setBoard(dir) deletePost(int(postid), None) if post['parentid']: threadUpdated(post['parentid']) else: regenerateFrontPages() deleted += 1 # Delete post end if deleted > 0: regenerateTrash() logAction(staff_account['username'], _('Permadeleted %s post(s).') % str(deleted)) message = _('Permadeleted %s post(s).') % str(deleted) # Start import math pagesize = float(Settings.RECYCLEBIN_POSTS_PER_PAGE) try: currentpage = int(path_split[3]) except: currentpage = 0 skip = False if 'type' in self.formdata: type = int(self.formdata["type"]) else: type = 0 # Generate board list boards = FetchAll('SELECT `name`, `dir` FROM `boards` ORDER BY `dir`') for board in boards: if 'board' in self.formdata and self.formdata['board'] == board['dir']: board['checked'] = True else: board['checked'] = False # Get type filter if type != 0: type_condition = "= " + str(type) else: type_condition = "!= 0" # Table if 'board' in self.formdata and self.formdata['board'] != 'all': cboard = setBoard(self.formdata['board'])['dir'] posts = FetchAll("SELECT posts.id, posts.timestamp, timestamp_formatted, IS_DELETED, INET6_NTOA(posts.ip) AS ip, posts.message, dir, boardid FROM `posts` INNER JOIN `boards` ON boardid = boards.id WHERE `dir` = '%s' AND IS_DELETED %s ORDER BY `timestamp` DESC LIMIT %d, %d" % (cboard, type_condition, currentpage*pagesize, pagesize)) try: totals = FetchOne("SELECT COUNT(id) AS count FROM `posts` WHERE IS_DELETED %s AND `boardid` = %s LIMIT 1" % (type_condition, posts[0]['boardid']) ) except: skip = True else: cboard = 'all' posts = FetchAll("SELECT posts.id, posts.timestamp, timestamp_formatted, IS_DELETED, INET6_NTOA(posts.ip) AS ip, posts.message, dir FROM `posts` INNER JOIN `boards` ON boardid = boards.id WHERE IS_DELETED %s ORDER BY `timestamp` DESC LIMIT %d, %d" % (type_condition, currentpage*pagesize, pagesize)) totals = FetchOne("SELECT COUNT(id) AS count FROM `posts` WHERE IS_DELETED %s" % type_condition) template_filename = "recyclebin.html" template_values = {'message': message, 'type': type, 'boards': boards, 'skip': skip} if not skip: # Calculate number of pages total = totals["count"] pages = int(math.ceil(total / pagesize)) # Create delete form if 'board' in self.formdata and self.formdata['board'] != 'all': board = setBoard(self.formdata['board'])['dir'] else: board = None navigator = '' if currentpage > 0: navigator += '< ' else: navigator += '< ' for i in range(pages): if i != currentpage: navigator += ''+str(i)+' ' else: navigator += str(i)+' ' if currentpage < (pages-1): navigator += '> ' else: navigator += '> ' template_values.update({'currentpage': currentpage, 'curboard': board, 'posts': posts, 'navigator': navigator}) # End recyclebin elif path_split[2] == 'addboard': if not administrator: return action_taken = False board_dir = '' try: if self.formdata['name'] != '': board_dir = self.formdata['dir'] except: pass if board_dir != '': action_taken = True board_exists = FetchOne("SELECT * FROM `boards` WHERE `dir` = %s LIMIT 1", (board_dir,)) if not board_exists: os.mkdir(Settings.ROOT_DIR + board_dir) os.mkdir(Settings.ROOT_DIR + board_dir + '/res') if not os.path.exists(Settings.IMAGES_DIR + board_dir): os.mkdir(Settings.IMAGES_DIR + board_dir) os.mkdir(Settings.IMAGES_DIR + board_dir + '/src') os.mkdir(Settings.IMAGES_DIR + board_dir + '/thumb') os.mkdir(Settings.IMAGES_DIR + board_dir + '/mobile') os.mkdir(Settings.IMAGES_DIR + board_dir + '/cat') if os.path.exists(Settings.ROOT_DIR + board_dir) and os.path.isdir(Settings.ROOT_DIR + board_dir): UpdateDb("INSERT INTO `boards` (`dir`, `name`) VALUES (%s, %s)", (board_dir, self.formdata['name'])) board = setBoard(board_dir) f = open(Settings.ROOT_DIR + board['dir'] + '/.htaccess', 'w') try: f.write('DirectoryIndex index.html') finally: f.close() regenerateFrontPages() message = _('Board added') template_filename = "message.html" logAction(staff_account['username'], _( 'Added board %s') % ('/' + board['dir'] + '/')) else: message = _( 'There was a problem while making the directories.') template_filename = "message.html" else: message = _( 'There is already a board with that directory.') template_filename = "message.html" if not action_taken: template_filename = "addboard.html" template_values = {} elif path_split[2] == 'trim': if not administrator: return board = setBoard(path_split[3]) trimThreads() self.output = "done trimming" return elif path_split[2] == 'setexpires': if not administrator: return board = setBoard(path_split[3]) parentid = int(path_split[4]) days = int(path_split[5]) t = time.time() expires = int(t) + (days * 86400) date_format = '%d/%m' expires_formatted = datetime.datetime.fromtimestamp( expires).strftime(date_format) sql = "UPDATE posts SET expires = timestamp + (%s * 86400), expires_formatted = FROM_UNIXTIME((timestamp + (%s * 86400)), '%s') WHERE boardid = %s AND id = %s" % ( str(days), str(days), date_format, board["id"], str(parentid)) UpdateDb(sql) self.output = "done " + sql return elif path_split[2] == 'fixico': if not administrator: return board = setBoard(path_split[3]) if board['dir'] != 'noticias': return threads = FetchAll("SELECT * FROM posts WHERE boardid = %s AND parentid = 0 AND message NOT LIKE '' fname = Settings.ROOT_DIR + \ board["dir"] + "/kako/" + \ str(item["timestamp"]) + ".json" if os.path.isfile(fname): import json with open(fname) as f: thread = json.load(f) thread['posts'] = [dict(list(zip(thread['keys'], row))) for row in thread['posts']] thread.pop('omitted', None) thread.pop('omitted_img', None) #for post in thread['posts']: # #if post['IS_DELETED'] > 0: # # #vaciar campos si está borrado # # post['name'] = 'muerto' # # post['tripcode'] = '' # # post['email'] = '' # # post['message'] = '...' # # post['timestamp_formatted'] = 'nunca' # # post['posterid'] = '' #if 'posterid' not in thread['keys']: # for post in thread['posts']: # half = post['timestamp_formatted'].split(' ID:') # if len(half) > 1: # post['timestamp_formatted'] = half[0] # post['posterid'] = half[1] # else: # post['posterid'] = '' # # thread['keys'].append('posterid') # #if 'message' not in thread: # thread['message'] = thread['posts'][0]['message'] # thread['keys'].append('message') thread['posts'] = [[row[key] for key in thread['keys']] for row in thread['posts']] raise UserError(thread) try: with open(fname, "w") as f: json.dump(thread, f, indent=0) except: raise UserError("Can't edit") thread['posts'] = [dict(list(zip(thread['keys'], row))) for row in thread['posts']] post_preview = cut_msg(thread['message'], 0) page = renderTemplate("txt_archive.html", {"threads": [thread], "preview": post_preview}, False) with open(Settings.ROOT_DIR + board["dir"] + "/kako/" + str(thread['timestamp']) + ".html", "w") as f: f.write(page) self.output += 'done' + str(time.time() - t) + '
' else: self.output += 'El hilo no existe.
' elif path_split[2] == 'fixexpires': if not administrator: return board = setBoard(path_split[3]) if int(board["maxage"]): date_format = '%d/%m' date_format_y = '%m/%Y' if int(board["maxage"]) >= 365: date_format = date_format_y sql = "UPDATE posts SET expires = timestamp + (%s * 86400), expires_formatted = FROM_UNIXTIME((timestamp + (%s * 86400)), '%s') WHERE boardid = %s AND parentid = 0" % ( board["maxage"], board["maxage"], date_format, board["id"]) UpdateDb(sql) alert_time = int( round(int(board['maxage']) * Settings.MAX_AGE_ALERT)) sql = "UPDATE posts SET expires_alert = CASE WHEN UNIX_TIMESTAMP() > (expires - %d*86400) THEN 1 ELSE 0 END WHERE boardid = %s AND parentid = 0" % (alert_time, board["id"]) UpdateDb(sql) else: sql = "UPDATE posts SET expires = 0, expires_formatted = '', expires_alert = 0 WHERE boardid = %s AND parentid = 0" % ( board["id"]) UpdateDb(sql) self.output = "done" return elif path_split[2] == 'fixid': if not administrator: return board = setBoard(path_split[3]) posts = FetchAll('SELECT id, email, posterid, message, parentid, INET6_NTOA(ip) AS ip FROM posts WHERE boardid = %s', (board['id'],) ) for post in posts: tim = 0 if board["useid"] != 0: new_hash = iphash(post['ip'], post, tim, board["useid"], False, '', False, False, (board["countrycode"] in [1, 2])) self.output += "%s - %s
" % (post['id'], new_hash) query = "UPDATE `posts` SET posterid = '%s' WHERE boardid = '%s' AND id = '%s'" % (new_hash, board['id'], post['id']) UpdateDb(query) self.output = "total: %d
" % len(posts) return elif path_split[2] == 'fixname': return board = setBoard(path_split[3]) #posts = FetchAll('SELECT * FROM `posts` WHERE `boardid` = %s' % board['id']) #posts = FetchAll('SELECT * FROM `posts` WHERE `name` LIKE \'%s\'' % '%%') #posts = FetchAll('SELECT * FROM `posts` WHERE `name` = \'%s\'' % path_split[4]) new_name = board['anonymous'] self.output = new_name + "
" for post in posts: self.output += "%s
" % (post['id']) query = "UPDATE `posts` SET `name` = '%s' WHERE boardid = '%s' AND id = '%s'" % ( new_name, board['id'], post['id']) UpdateDb(query) return elif path_split[2] == 'fixlength': if not administrator: return board = setBoard(path_split[3]) threads = FetchAll( 'SELECT * FROM `posts` WHERE parentid = 0 AND `boardid` = %s' % board['id']) for t in threads: length = threadNumReplies(t['id']) UpdateDb('UPDATE posts SET length = %d WHERE boardid = %s AND id = %s' % ( length, board['id'], t['id'])) self.output = 'done' return elif path_split[2] == 'archive': if not administrator: return t = time.time() board = setBoard(path_split[3]) postid = int(path_split[4]) archiveThread(postid) self.output = "todo ok %s" % str(time.time() - t) elif path_split[2] == 'filters': action_taken = False if len(path_split) > 3 and path_split[3] == 'add': if "add" in self.formdata: edit_id = 0 if 'edit' in self.formdata: edit_id = int(self.formdata['edit']) # We decide what type of filter it is. # 0: Word / 1: Name/Trip filter_type = int(self.formdata["type"]) filter_action = int(self.formdata["action"]) filter_from = '' filter_tripcode = '' where = '' if 'board_all' not in self.formdata: where = [] boards = FetchAll('SELECT `dir` FROM `boards`') for board in boards: keyname = 'board_' + board['dir'] if keyname in self.formdata: if self.formdata[keyname] == "1": where.append(board['dir']) if len(where) > 0: where = boards2str(where) else: self.error( _("You must select what board the filter will affect")) return if filter_type == 0: # Word filter if len(self.formdata["word"]) > 0: filter_from = html.escape(self.formdata["word"]) else: self.error(_("You must enter a word.")) return elif filter_type == 1: # Name/trip filter can_add = False if len(self.formdata["name"]) > 0: filter_from = self.formdata["name"] can_add = True if len(self.formdata["trip"]) > 0: filter_tripcode = self.formdata["trip"] can_add = True if not can_add: self.error( _("You must enter a name and/or a tripcode.")) return # Action sql_query = '' filter_reason = '' if len(self.formdata["reason"]) > 0: filter_reason = self.formdata["reason"] if filter_action == 0: # Cancel post sql_query = "INSERT INTO `filters` (`id`, `boards`, `type`, `action`, `from`, `from_trip`, `reason`, `added`, `staff`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)" sql_params = (edit_id, where, filter_type, filter_action, filter_from, filter_tripcode, filter_reason, timestamp(), staff_account['username']) elif filter_action == 1: # Change to if len(self.formdata["changeto"]) > 0: filter_to = self.formdata["changeto"] sql_query = "INSERT INTO `filters` (`id`, `boards`, `type`, `action`, `from`, `from_trip`, `reason`, `to`, `added`, `staff`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" sql_params = (edit_id, where, filter_type, filter_action, filter_from, filter_tripcode, filter_reason, filter_to, timestamp(), staff_account['username']) else: self.error( _("You must enter a word to change to.")) return elif filter_action == 2: # Ban filter_seconds = 0 if len(self.formdata["seconds"]) > 0: filter_seconds = int(self.formdata["seconds"]) if "blind" in self.formdata and self.formdata["blind"] == 1: filter_blind = 1 else: filter_blind = 2 sql_query = "INSERT INTO `filters` (`id`, `boards`, `type`, `action`, `from`, `from_trip`, `reason`, `seconds`, `blind`, `added`, `staff`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" sql_params = (edit_id, where, filter_type, filter_action, filter_from, filter_tripcode, filter_reason, filter_seconds, filter_blind, timestamp(), staff_account['username']) elif filter_action == 3: # Redirect URL if len(self.formdata['redirect_url']) > 0: redirect_url = self.formdata['redirect_url'] redirect_time = 0 try: redirect_time = int(self.formdata['redirect_time']) except: pass else: self.error( _("You must enter a URL to redirect to.")) return sql_query = "INSERT INTO `filters` (`id`, `boards`, `type`, `action`, `from`, `from_trip`, `reason`, `redirect_url`, `redirect_time`, `added`, `staff`) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" sql_params = (edit_id, where, filter_type, filter_action, filter_from, filter_tripcode, filter_reason, redirect_url, redirect_time, timestamp(), staff_account['username']) # DO QUERY! if edit_id > 0: UpdateDb("DELETE FROM `filters` WHERE `id` = %s", (edit_id,)) UpdateDb(sql_query, sql_params) message = 'Filter edited.' else: filt = FetchOne("SELECT `id` FROM `filters` WHERE `boards` = %s AND `type` = %s AND `from` = %s", (where, filter_type, filter_from)) if not filt: UpdateDb(sql_query, sql_params) message = 'Filter added.' else: message = 'This filter already exists here:' + ' edit' action_taken = True template_filename = "message.html" else: # Create add form edit_id = 0 if 'edit' in self.formdata and int(self.formdata['edit']) > 0: # Load values edit_id = int(self.formdata['edit']) filt = FetchOne( "SELECT * FROM `filters` WHERE `id` = %s LIMIT 1" % str(edit_id)) if not filt['boards']: where = '' else: where = str2boards(filt['boards']) startvalues = {'type': filt['type'], 'trip': filt['from_trip'], 'where': where, 'action': filt['action'], 'changeto': html.escape(filt['to'], True), 'reason': filt['reason'], 'seconds': filt['seconds'], 'blind': filt['blind'], 'redirect_url': filt['redirect_url'], 'redirect_time': filt['redirect_time'], } if filt['type'] == 1: startvalues['name'] = filt['from'] startvalues['word'] = '' else: startvalues['name'] = '' startvalues['word'] = filt['from'] else: startvalues = {'type': '0', 'word': '', 'name': '', 'trip': '', 'where': [], 'action': '0', 'changeto': '', 'reason': _('Forbidden word'), 'seconds': '0', 'blind': '1', 'redirect_url': 'http://', 'redirect_time': '5'} if edit_id > 0: submit = "Editar Filtro" else: submit = "Agregar filtro" action_taken = True template_filename = "filters.html" template_values = {'mode': 1, 'edit_id': edit_id, 'boards': boardlist(), 'startvalues': startvalues, 'submit': submit} elif len(path_split) > 4 and path_split[3] == 'delete': delid = int(path_split[4]) UpdateDb("DELETE FROM `filters` WHERE id = '%s' LIMIT 1", str(delid)) message = _('Deleted filter %s.') % str(delid) template_filename = "message.html" action_taken = True if not action_taken: filters = FetchAll("SELECT * FROM `filters` ORDER BY `added` DESC") for filter in filters: if not filter['boards']: filter['boards'] = _('All boards') else: where = str2boards(filter['boards']) filter['boards'] = '/' + '/, /'.join(where) + '/' if filter['type'] == 0: filter['type_formatted'] = _('Word:') + ' ' + html.escape(filter['from']) + '' elif filter['type'] == 1: filter['type_formatted'] = _('Name/Tripcode:')+' ' if filter['from']: filter['type_formatted'] += '' + \ filter['from'] + '' if filter['from_trip'] != '': filter['type_formatted'] += '' + \ filter['from_trip'] + '' else: filter['type_formatted'] = '?' if filter['action'] == 0: filter['action_formatted'] = _('Abort post') elif filter['action'] == 1: filter['action_formatted'] = _( 'Change to:') + ' ' + html.escape(filter['to']) + '' elif filter['action'] == 2: if filter['blind'] == 1: blind = _('Yes') else: blind = _('No') filter['action_formatted'] = _('Autoban:') + '
' + \ (_('Length:')+' %s
'+_('Blind:') + ' %s') % (filter['seconds'], blind) elif filter['action'] == 3: filter['action_formatted'] = (_('Redirect to:')+' %s ('+_('in %s secs')+')') % ( filter['redirect_url'], filter['redirect_time']) else: filter['action_formatted'] = '?' filter['added'] = formatTimestamp(filter['added']) template_filename = "filters.html" template_values = {'mode': 0, 'filters': filters} elif path_split[2] == 'logs': if staff_account['rights'] not in [0, 2]: return logs = FetchAll( 'SELECT * FROM `logs` ORDER BY `timestamp` DESC') for log in logs: log['timestamp_formatted'] = formatTimestamp( log['timestamp']) template_filename = "logs.html" template_values = {'logs': logs} elif path_split[2] == 'logout': message = _('Logging out...') + '' deleteCookie(self, 'weabot_manage') deleteSession(staff_account['session_id']) template_filename = "message.html" elif path_split[2] == 'quotes': # Quotes for the post screen if "save" in self.formdata: try: f = open('quotes.conf', 'w') f.write(self.formdata["data"]) f.close() message = 'Datos guardados.' template_filename = "message.html" except: message = 'Error al guardar datos.' template_filename = "message.html" try: f = open('quotes.conf', 'r') data = html.escape(f.read(1048576), True) f.close() template_filename = "quotes.html" template_values = {'data': data} except: message = 'Error al leer datos.' template_filename = 'message.html' elif path_split[2] == 'recent_images': try: if int(self.formdata['images']) > 256: images = '256' else: images = self.formdata['images'] posts = FetchAll('SELECT * FROM `posts` INNER JOIN `boards` ON boardid = boards.id WHERE CHAR_LENGTH(`thumb`) > 0 ORDER BY `timestamp` DESC LIMIT %s' % (images) ) except: posts = FetchAll('SELECT * FROM `posts` INNER JOIN `boards` ON boardid = boards.id WHERE CHAR_LENGTH(`thumb`) > 0 ORDER BY `timestamp` DESC LIMIT 64') template_filename = "recent_images.html" template_values = {'posts': posts} elif path_split[2] == 'news': if not administrator: return type = 1 if 'type' in self.formdata: type = int(self.formdata['type']) if type > 2: raise UserError("Tipo no soportado") # canal del home if len(path_split) > 3: if path_split[3] == 'add': t = datetime.datetime.now() # Insertar el nuevo post title = '' message = self.formdata["message"].replace( "\n", "
") # Titulo if 'title' in self.formdata: title = self.formdata["title"] # Post anonimo if 'anonymous' in self.formdata and self.formdata['anonymous'] == '1': to_name = "Staff ★" else: to_name = "%s ★" % staff_account['username'] timestamp_formatted = formatDate(t) if type > 0: timestamp_formatted = re.sub(r"\(.+", "", timestamp_formatted) else: timestamp_formatted = re.sub(r"\(...\)", " ", timestamp_formatted) UpdateDb("INSERT INTO `news` (type, staffid, staff_name, title, message, name, timestamp, timestamp_formatted) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)", (type, staff_account['id'], staff_account['username'], title, message, to_name, timestamp(t), timestamp_formatted)) regenerateNews() regenerateHome() message = _("Added successfully.") template_filename = "message.html" if path_split[3] == 'delete': # Eliminar un post id = int(path_split[4]) UpdateDb("DELETE FROM `news` WHERE id = %d AND type = %d" % (id, type)) regenerateNews() regenerateHome() message = _("Deleted successfully.") template_filename = "message.html" else: posts = FetchAll( "SELECT * FROM `news` WHERE type = %d ORDER BY `timestamp` DESC" % type) template_filename = "news.html" template_values = {'action': type, 'posts': posts} elif path_split[2] == 'newschannel': # if not administrator: # return if len(path_split) > 3: if path_split[3] == 'add': t = datetime.datetime.now() # Delete old posts #posts = FetchAll("SELECT `id` FROM `news` WHERE `type` = '1' ORDER BY `timestamp` DESC LIMIT "+str(Settings.MODNEWS_MAX_POSTS)+",30") # for post in posts: # UpdateDb("DELETE FROM `news` WHERE id = " + post['id'] + " AND `type` = '0'") # Insert new post message = '' # Cut long lines message = self.formdata["message"] #message = clickableURLs(html.escape(message).rstrip()[0:8000]) #message = onlyAllowedHTML(message) if Settings.USE_MARKDOWN: message = markdown(message) if not Settings.USE_MARKDOWN: message = message.replace("\n", "
") # If it's preferred to remain anonymous... if 'anonymous' in self.formdata and self.formdata['anonymous'] == '1': to_name = "Staff ★" else: to_name = "%s ★" % staff_account['username'] timestamp_formatted = formatDate(t) UpdateDb("INSERT INTO `news` (type, staffid, staff_name, title, message, name, timestamp, timestamp_formatted) VALUES (0, %s, %s, %s, %s, %s, %s, %s)", (staff_account['id'], staff_account['username'], self.formdata['title'], message, to_name, timestamp(t), timestamp_formatted)) message = _("Added successfully.") template_filename = "message.html" if path_split[3] == 'delete': if not administrator: # We check that if he's not admin, he shouldn't be able to delete other people's posts post = FetchOne("SELECT `staffid` FROM `news` WHERE id = %s AND type = '0'" % (int(path_split[4])) ) if post['staffid'] != staff_account['id']: self.error(_('That post is not yours.')) return # Delete! UpdateDb("DELETE FROM `news` WHERE id = %s AND type = '0'", (path_split[4],)) message = _("Deleted successfully.") template_filename = "message.html" else: # If he's not admin, show only his own posts if administrator: posts = FetchAll("SELECT * FROM `news` WHERE type = '0' ORDER BY `timestamp` DESC") else: posts = FetchAll("SELECT * FROM `news` WHERE staffid = %s AND type = 0 ORDER BY `timestamp` DESC", (staff_account['id'],)) template_filename = "news.html" template_values = {'action': 'newschannel', 'posts': posts} elif path_split[2] == 'reports': if not moderator: return message = None import math pagesize = Settings.REPORTS_PER_PAGE totals = FetchOne("SELECT COUNT(id) FROM `reports`") total = int(totals['COUNT(id)']) pages = int(math.ceil(total // pagesize)) try: currentpage = int(path_split[3]) except: currentpage = 0 if len(path_split) > 4: if path_split[4] == 'ignore': # Delete report UpdateDb("DELETE FROM `reports` WHERE `id` = %s", (path_split[5],)) message = _('Report %s ignored.') % path_split[5] if 'ignore' in self.formdata: ignored = 0 if 'board' in self.formdata and self.formdata['board'] != 'all': reports = FetchAll("SELECT `id` FROM `reports` WHERE `board` = %s ORDER BY `timestamp` DESC LIMIT %s, %s", (self.formdata['board'], currentpage*pagesize, pagesize)) else: reports = FetchAll("SELECT `id` FROM `reports` ORDER BY `timestamp` DESC LIMIT %s, %s", (currentpage*pagesize, pagesize)) for report in reports: keyname = 'i' + str(report['id']) if keyname in self.formdata: # Ignore here UpdateDb("DELETE FROM `reports` WHERE `id` = %s", (report['id'],)) ignored += 1 message = _('Ignored %s report(s).') % str(ignored) # Generate board list boards = FetchAll( 'SELECT `name`, `dir` FROM `boards` ORDER BY `dir`') for board in boards: if 'board' in self.formdata and self.formdata['board'] == board['dir']: board['checked'] = True else: board['checked'] = False # Tabla if 'board' in self.formdata and self.formdata['board'] != 'all': reports = FetchAll("SELECT id, timestamp, timestamp_formatted, postid, parentid, link, board, INET6_NTOA(ip) AS ip, reason, INET6_NTOA(repip) AS repip FROM `reports` WHERE `board` = %s ORDER BY `timestamp` DESC LIMIT %s, %s", (self.formdata['board'], currentpage*pagesize, pagesize)) else: reports = FetchAll("SELECT id, timestamp, timestamp_formatted, postid, parentid, link, board, INET6_NTOA(ip) AS ip, reason, INET6_NTOA(repip) AS repip FROM `reports` ORDER BY `timestamp` DESC LIMIT %s, %s", (currentpage*pagesize, pagesize)) if 'board' in self.formdata: curboard = self.formdata['board'] else: curboard = None # for report in reports: # if report['parentid'] == '0': # report['link'] = Settings.BOARDS_URL + report['board'] + '/res/' + report['postid'] + '.html#' + report['postid'] # else: # report['link'] = Settings.BOARDS_URL + report['board'] + '/res/' + report['parentid'] + '.html#' + report['postid'] navigator = '' if currentpage > 0: navigator += '< ' else: navigator += '< ' for i in range(pages): if i != currentpage: navigator += ''+str(i)+' ' else: navigator += str(i)+' ' if currentpage < (pages-1): navigator += '> ' else: navigator += '> ' template_filename = "reports.html" template_values = {'message': message, 'boards': boards, 'reports': reports, 'currentpage': currentpage, 'curboard': curboard, 'navigator': navigator} elif path_split[2] == 'ipshow': if not moderator: return if 'ip' in self.formdata: # If an IP was given... if self.formdata['ip'] != '': ip = self.formdata['ip'] posts = FetchAll("SELECT posts.*, boards.dir, boards.board_type, boards.subject AS default_subject FROM `posts` JOIN `boards` ON boards.id = posts.boardid WHERE ip = INET6_ATON(%s) ORDER BY posts.timestamp DESC", (ip,)) template_filename = "ipshow.html" template_values = {"mode": 1, "ip": ip, "host": getHost( ip), "country": getCountry(ip), "tor": addressIsTor(ip), "posts": posts} logAction(staff_account['username'], "ipshow on {}".format(ip)) else: # Generate form template_filename = "ipshow.html" template_values = {"mode": 0} elif path_split[2] == 'ipdelete': if not moderator: return # Delete posts by IP if 'ip' in self.formdata: # If an IP was given... if self.formdata['ip'] != '': where = [] if 'board_all' not in self.formdata: # If multiple boards, add them to a list boards = FetchAll('SELECT `id`, `dir` FROM `boards`') for board in boards: keyname = 'board_' + board['dir'] if keyname in self.formdata: if self.formdata[keyname] == "1": where.append(board) else: # If all boards were selected, add them all to the list where = FetchAll('SELECT `id`, `dir` FROM `boards`') # If no board was chosen if len(where) <= 0: self.error(_("Select a board first.")) return try: secs = int(self.formdata['seconds']) except: secs = 0 if secs > 0: since = round(time.time() - secs) deletedPostsTotal = 0 ip = self.formdata['ip'] deletedPosts = 0 for theboard in where: board = setBoard(theboard['dir']) isDeletedOP = False # delete all starting posts first if secs == 0: op_posts = FetchAll("SELECT `id`, `message` FROM posts WHERE parentid = 0 AND boardid = %s AND ip = INET6_ATON(%s)", (board['id'], ip) ) else: op_posts = FetchAll("SELECT `id`, `message` FROM posts WHERE parentid = 0 AND boardid = %s AND ip = INET6_ATON(%s) AND timestamp > %s", (board['id'], ip, since) ) for post in op_posts: deletePost(post['id'], None) deletedPosts += 1 deletedPostsTotal += 1 if secs == 0: replies = FetchAll("SELECT `id`, `message`, `parentid` FROM posts WHERE parentid != 0 AND boardid = %s AND ip = INET6_ATON(%s)", (board['id'], ip) ) else: replies = FetchAll("SELECT `id`, `message`, `parentid` FROM posts WHERE parentid != 0 AND boardid = %s AND ip = INET6_ATON(%s) AND timestamp > %s", (board['id'], ip, since) ) for post in replies: deletePost(post['id'], None, 2) deletedPosts += 1 deletedPostsTotal += 1 regenerateHome() else: self.error(_("Please enter an IP first.")) return if deletedPosts > 0: message = 'En total se eliminaron %(posts)s post(s) de %(ip)s.' % {'posts': str(deletedPosts), 'ip': self.formdata['ip']} logAction(staff_account['username'], '%(posts)s post(s) eliminado(s) de IP: %(ip)s' % {'posts': str(deletedPosts), 'ip': self.formdata['ip']}) #logAction(staff_account['username'], '%(posts)s post(s) were deleted from %(board)s. IP: %(ip)s' % \ # {'posts': str(deletedPosts), # 'board': '/' + board['dir'] + '/', # 'ip': self.formdata['ip']}) else: message = "No se encontraron posts" template_filename = "message.html" else: # Show form template_filename = "ipdelete.html" template_values = {'boards': boardlist()} elif path_split[2] == 'goto': board = setBoard(path_split[3]) try: pid = int(path_split[4]) except ValueError: raise UserError("ID no válida.") if board['board_type'] == 1: first = get_parent_post(pid, board['id']) url = "/%s/read/%s/" % (board['dir'], first['timestamp']) else: url = "/%s/res/%s.html" % (board['dir'], pid) self.output = '' % url elif path_split[2] == 'fixquote': # when a mod deletes a post permanently messing with quote numbers if not administrator: return board = setBoard(path_split[3]) if board['board_type'] == 1: return pid = int(path_split[4]) parent = FetchOne("SELECT `parentid` FROM `posts` \ WHERE `id` = %s AND `boardid` = %s LIMIT 1" % (pid, board['id'])) parent = parent['parentid'] ranges = '`id` >= ' + str(pid) if len(path_split) > 5: ranges += ' AND `id` <= ' + path_split[5] posts = FetchAll('SELECT `id`, `message` FROM `posts` \ WHERE `boardid` = %s AND %s AND `parentid` = %s AND \ `message` LIKE \'%%">>>%%\'' % (board['id'], ranges, parent)) def fix_num(capture): qnum = str(int(capture.group(2)) - 1) return '/'+board['dir']+'/read/' + capture.group(1) + '/' + qnum + '">>>' + qnum for post in posts: rx = r'/'+board['dir']+'/read/(\d+)/(\d+)">>>(\d+)' new_msg = re.sub(rx, fix_num, post['message']) self.output += post['message'] + '
' + new_msg + '

' query = "UPDATE `posts` SET `message` = '%s' WHERE `boardid` = %s AND `id` = %s" % (new_msg, board['id'], post['id']) UpdateDb(query) # reflect changes threadUpdated(parent) elif path_split[2] == 'basural': if not administrator: return #basural = FetchAll('SELECT basural.id AS id, origid, timestamp_formatted AS fecha, basural.subject AS subject, basural.message AS message, boards.name AS board FROM basural INNER JOIN boards ON boardid = boards.id ORDER BY timestamp DESC') #basural = FetchAll('SELECT * FROM basural') #template_filename = "basural.html" #template_values = {'basural': basural} #posts = FetchAll('select * from posts where IS_DELETED = 2') # #count = 0 #for post in posts: # msg = formatting.remove_html(post['message']) # ts = formatTimestamp(post['timestamp']) # ins = InsertDb('INSERT INTO `basural` (`origid`, `boardid`, `timestamp`, `timestamp_formatted`, `subject`, `message`) VALUES (%s, %s, %s, %s, %s, %s)', (post['id'], post['boardid'], post['timestamp'], ts, post['subject'], msg) ) # self.output += msg + '
' # count += 1 #self.output += '
'+str(count) elif path_split[2] == 'search': if not administrator: return search_logs = FetchAll('SELECT `id`,`timestamp`,`keyword`,`ita`,INET_NTOA(`ip`) AS `ip`,`res` FROM `search_log` ORDER BY `timestamp` DESC LIMIT 256') for log in search_logs: #log['ip'] = str(inet_ntoa(log['ip'])) log['timestamp_formatted'] = formatTimestamp(log['timestamp']) if log['keyword'].startswith('k '): log['keyword'] = log['keyword'][2:] log['archive'] = True else: log['archive'] = False template_filename = "search.html" template_values = {'search': search_logs} else: # Main page. reports = FetchOne("SELECT COUNT(1) AS 'count' FROM `reports`")["count"] posts = FetchAll("SELECT * FROM `news` WHERE type = 0 ORDER BY `timestamp` DESC") template_filename = "manage.html" template_values = {'reports': reports, 'posts': posts} if not skiptemplate: try: if template_filename == 'message.html': template_values = {'message': message} except: template_filename = 'message.html' template_values = {'message': '???'} template_values.update({ 'title': 'Manage', 'validated': validated, 'page': page, }) if validated: template_values.update({ 'username': staff_account['username'], 'site_title': Settings.SITE_TITLE, 'rights': staff_account['rights'], 'administrator': administrator, 'added': formatTimestamp(staff_account['added']), }) self.output += renderTemplate("manage/" + template_filename, template_values) def switchBoard(new_type): board = Settings._.BOARD if new_type == board['board_type']: return kako_dir = os.path.join(Settings.ROOT_DIR, board['dir'], 'kako') res_dir = os.path.join(Settings.ROOT_DIR, board['dir'], 'res') if new_type == 0: # Switching to Imageboard # Delete kako if empty if os.path.exists(kako_dir) and not os.listdir(kako_dir): os.rmdir(kako_dir) elif new_type == 1: # Switching to Textboard # Make kako dir if not os.path.exists(kako_dir): os.mkdir(kako_dir) # Clean res dir cleanDir(res_dir, ext="html") def newSession(staff_id): import uuid session_uuid = uuid.uuid4().hex expires = timestamp() + Settings.SESSION_TIME InsertDb("INSERT INTO `session` (`session_id`, `expires`, `staff_id`) VALUES (UNHEX(%s), %s, %s)", (session_uuid, expires, staff_id)) return session_uuid def validateSession(session_id): cleanSessions() session = FetchOne( "SELECT HEX(session_id) as session_id, id, username, rights, added FROM `session` " "INNER JOIN `staff` ON `session`.`staff_id` = `staff`.`id` " "WHERE `session_id` = UNHEX(%s)", (session_id,)) if session: return session return None def renewSession(session_id): expires = timestamp() + Settings.SESSION_TIME UpdateDb("UPDATE `session` SET expires = %s WHERE session_id = UNHEX(%s)", (expires, session_id)) def deleteSession(session_id): UpdateDb("DELETE FROM `session` WHERE session_id = UNHEX(%s)", (session_id,)) def cleanSessions(): UpdateDb("DELETE FROM `session` WHERE expires <= %s", (timestamp(),)) def logAction(staff, action): InsertDb("INSERT INTO `logs` (`timestamp`, `staff`, `action`) VALUES (%s, %s, %s)", (timestamp(), staff, action)) def genPasswdHash(string): import argon2 ph = argon2.PasswordHasher() return ph.hash(string) def verifyPasswd(username, passwd): import argon2 ph = argon2.PasswordHasher() staff_account = FetchOne("SELECT * FROM staff WHERE username = %s", (username,)) if not staff_account: return None try: ph.verify(staff_account['password'], passwd) except argon2.exceptions.VerifyMismatchError: return None except argon2.exceptions.InvalidHash: raise UserError("Hash obsoleto o inválido. Por favor contacte al administrador.") if ph.check_needs_rehash(staff_account['password']): param_new_hash = ph.hash(staff_acount['password']) UpdateDb("UPDATE staff SET password = '%s' WHERE id = %s" % (param_new_hash, staff_account['id'])) return staff_account def boardlist(): boards = FetchAll('SELECT * FROM `boards` ORDER BY `board_type`, `dir`') return boards def filetypelist(): filetypes = FetchAll('SELECT * FROM `filetypes` ORDER BY `ext` ASC') return filetypes