aboutsummaryrefslogblamecommitdiff
path: root/cgi/api.py
blob: 9c1c344e5ba793b98b71ffff1b0bb680e224bd3a (plain) (tree)
1
2
3
4
5
6
7
8
9

              






                          


                                                       
                             
                                                    
                              
                                                     
                              




                                                                                      
 
                                                                              
         




                                                               
                





                             
                                                    











                                                                                                                                          



                                                                       











                                          
                                                       




                                       
                                                       

                      
                                                 
 
                                                                                                                                                                                                                                                                                                                                                                                               



                                       








                                                       
 



                                           



                                       
                                                       
 





                                                                         
 



                                                 
 









                                              
                                                




                                       
                                                       




                                         
                                                        




                                              
                                                         

                                       
                                                 



                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             





                                                                   
                                                                                                                                                                                                                                                                                                                                                              
                                               











                                                                 



                                        


                                                           







                                                                       





                                                                   



                                                      
 











                                                                       
                                                




                                       
                                                       




                                         
                                                        




















                                           
                                          




                                                                                                                                       
                                          
 

                                                  
                                              
                                            
 

                                                                                                            


                                               
                                                                                                                                                                                                                                                                                                                

                                                                       
















                                                               





                                                           




                                                                                        
 






                                                                                            
                                                









                                                           
                                                     
 
                                                                                                                                                                                                                                                                      
                                             

                    
                                                     


                            



                                               

                              
                                           





                                                           





                                                       
                                        
 






                                                                  
                                                






                                     
                                                     



                                                  
                                                                   
 



                                        
                                                













                                                     
                                       







                                                                                                                                             
 






                                          
                                                       

                      
                                                 


                                      
 
                             
                                                                                                                                                                               
                                    
 



                                                                                                                                                                                                                                                                                                                








                                                                       
         
                                        





                                                                    
                                                    









                                               
                          
        
# coding=utf-8
import json
import time

from framework import *
from database import *
from post import *

def api(self, path_split):
    if len(path_split) > 2:
        try:
            self.output = api_process(self, path_split)
        except APIError as e:
            self.output = api_error("error", str(e))
        except UserError as e:
            self.output = api_error("failed", str(e))
        except Exception as e:
            import sys
            import traceback
            exc_type, exc_value, exc_traceback = sys.exc_info()
            detail = ["%s : %s : %s : %s" % (os.path.basename(
                o[0]), o[1], o[2], o[3]) for o in traceback.extract_tb(exc_traceback)]

            self.output = api_error("exception", str(e), str(type(e)), detail)
    else:
        self.output = api_error("error", "No method specified")


def api_process(self, path_split):
    formdata = self.formdata
    ip = self.ip
    t = time.time()
    method = path_split[2]

    #bans = ['181.72.116.62']
    bans = []
    if ip in bans:
        raise APIError("You have been blacklisted.")

    # with open('../api_log.txt', 'a') as f:
    #  logstr = "[%s] %s: %s\n" % (formatTimestamp(t), ip, repr(path_split))
    #  f.write(logstr)

    values = {'state': 'success'}

    if method == 'boards':
        boards = FetchAll(
            'SELECT dir, name, board_type, allow_images, allow_image_replies, maxsize FROM `boards` WHERE `secret`=0 ORDER BY `sort` ASC')
        values['boards'] = boards
        for board in values['boards']:
            board['board_type'] = board['board_type']
            board['allow_images'] = board['allow_images']
            board['allow_image_replies'] = board['allow_image_replies']
            board['maxsize'] = board['maxsize']

    elif method == 'last':
        data_limit = formdata.get('limit')
        data_since = formdata.get('since')

        limit = 10
        since = 0

        if data_limit:
            try:
                limit = int(data_limit)
            except ValueError:
                raise APIError("Limit must be numeric")

        if data_since:
            try:
                since = int(data_since)
            except ValueError:
                raise APIError("Since must be numeric")

        if limit > 50:
            raise APIError("Maximum limit is 50")

        sql = "SELECT posts.id, boards.dir, timestamp, timestamp_formatted, posts.name, tripcode, email, posterid, posts.subject, posts.message, file, file_size, image_height, image_width, thumb, thumb_width, thumb_height, parentid FROM posts INNER JOIN boards ON boardid = boards.id WHERE timestamp > %d AND IS_DELETED = 0 AND boards.secret = 0 ORDER BY timestamp DESC LIMIT %d" % (
            since, limit)
        values['posts'] = FetchAll(sql)

        for post in values['posts']:
            post['id'] = post['id']
            post['timestamp'] = post['timestamp']
            post['parentid'] = post['parentid']
            post['file_size'] = post['file_size']
            post['image_width'] = post['image_width']
            post['image_height'] = post['image_height']
            post['thumb_width'] = post['thumb_width']
            post['thumb_height'] = post['thumb_height']
            post['message'] = post['message']

    elif method == 'lastage':
        data_limit = formdata.get('limit')
        data_time = formdata.get('time', 0)

        if data_limit:
            try:
                limit = int(data_limit)
            except ValueError:
                raise APIError("Limit must be numeric")

        if limit > 30:
            raise APIError("Maximum limit is 30")

        threads = getLastAge(0, limit)
        threads += getLastAge(1, limit)
        threads = sorted(threads, key=lambda b:b['bumped'], reverse=True)

        if threads[0]['bumped'] > int(data_time):
            values['threads'] = threads
        else:
            values['threads'] = []

    elif method == 'list':
        data_board = formdata.get('dir')
        data_offset = formdata.get('offset')
        data_limit = formdata.get('limit')
        data_replies = formdata.get('replies')
        offset = 0
        limit = 10
        numreplies = 2

        if not data_board:
            raise APIError("Missing parameters")

        if data_limit:
            try:
                limit = int(data_limit)
            except ValueError:
                raise APIError("Limit must be numeric")

        if data_offset:
            try:
                offset = int(data_offset)
            except ValueError:
                raise APIError("Offset must be numeric")

        if data_replies:
            try:
                numreplies = int(data_replies)
            except ValueError:
                raise APIError("Replies must be numeric")

        if data_replies and limit > 30:
            raise APIError("Maximum limit is 30")

        board = setBoard(data_board)

        #sql = "SELECT id, timestamp, bumped, timestamp_formatted, name, tripcode, email, subject, message, file, thumb FROM posts WHERE boardid = %s AND parentid = 0 AND IS_DELETED = 0 ORDER BY bumped DESC LIMIT %d" % (board['id'], limit)
        sql = "SELECT p.id, p.timestamp, p.bumped, p.expires, p.expires_formatted, p.timestamp_formatted, p.name, p.tripcode, p.email, p.posterid, p.subject, p.message, p.file, p.file_size, p.image_width, p.image_height, p.thumb, p.thumb_height, p.thumb_width, p.locked, coalesce(x.count,0) AS total_replies, coalesce(x.files,0) AS total_files FROM `posts` AS p LEFT JOIN (SELECT parentid, count(1) as count, count(nullif(file, '')) as files FROM `posts` WHERE boardid = %(board)s GROUP BY parentid) AS x ON p.id=x.parentid WHERE p.parentid = 0 AND p.boardid = %(board)s AND p.IS_DELETED = 0 ORDER BY `bumped` DESC LIMIT %(limit)d OFFSET %(offset)d" % {
            'board': board["id"], 'limit': limit, 'offset': offset}

        threads = FetchAll(sql)

        if numreplies:
            for thread in threads:
                lastreplies = FetchAll("SELECT id, timestamp, timestamp_formatted, name, tripcode, email, posterid, subject, message, file, file_size, image_height, image_width, thumb, thumb_width, thumb_height, IS_DELETED FROM `posts` WHERE parentid = %s AND boardid = %s ORDER BY `timestamp` DESC LIMIT %s", (thread['id'], board['id'], numreplies))
                lastreplies = lastreplies[::-1]
                thread['id'] = thread['id']
                thread['timestamp'] = thread['timestamp']
                thread['bumped'] = thread['bumped']
                thread['expires'] = thread['expires']
                thread['total_replies'] = thread['total_replies']
                thread['total_files'] = thread['total_files']
                thread['file_size'] = thread['file_size']
                thread['image_width'] = thread['image_width']
                thread['image_height'] = thread['image_height']
                thread['thumb_width'] = thread['thumb_width']
                thread['thumb_height'] = thread['thumb_height']
                thread['locked'] = thread['locked']

                thread['replies'] = []

                for post in lastreplies:
                    post['IS_DELETED'] = post['IS_DELETED']
                    post['id'] = post['id']
                    post['timestamp'] = post['timestamp']

                    if post['IS_DELETED']:
                        empty_post = {'id': post['id'],
                                      'IS_DELETED': post['IS_DELETED'],
                                      'timestamp': post['timestamp'],
                                      }
                        thread['replies'].append(empty_post)
                    else:
                        post['file_size'] = post['file_size']
                        post['image_width'] = post['image_width']
                        post['image_height'] = post['image_height']
                        post['thumb_width'] = post['thumb_width']
                        post['thumb_height'] = post['thumb_height']
                        post['message'] = post['message']

                        thread['replies'].append(post)

        values['threads'] = threads

    elif method == 'thread':
        data_board = formdata.get('dir')
        data_threadid = formdata.get('id')
        data_threadts = formdata.get('ts')
        data_offset = formdata.get('offset')
        data_limit = formdata.get('limit')
        data_striphtml = formdata.get('nohtml')
        striphtml = False
        offset = 0
        limit = 1000

        if not data_board or (not data_threadid and not data_threadts):
            raise APIError("Missing parameters")

        if data_limit:
            try:
                limit = int(data_limit)
            except ValueError:
                raise APIError("Limit must be numeric")

        if data_offset:
            try:
                offset = int(data_offset)
            except ValueError:
                raise APIError("Offset must be numeric")

        if data_striphtml:
            if int(data_striphtml) == 1:
                striphtml = True

        board = setBoard(data_board)
        search_field = 'id'
        search_val = 0

        try:
            search_val = int(data_threadid)
        except (ValueError, TypeError):
            pass

        try:
            search_val = int(data_threadts)
            search_field = 'timestamp'
        except (ValueError, TypeError):
            pass

        if not search_val:
            raise APIError("No thread ID")

        op_post = FetchOne("SELECT id, timestamp, subject, locked FROM posts WHERE `%s` = '%d' AND boardid = '%s' AND parentid = 0" % (
            search_field, search_val, board["id"]))

        if not op_post:
            raise APIError("Not a thread")

        values['id'] = op_post['id']
        values['timestamp'] = op_post['timestamp']
        values['subject'] = op_post['subject']
        values['locked'] = op_post['locked']

        total_replies = FetchOne("SELECT COUNT(1) AS count FROM posts WHERE boardid = %s AND parentid = %s",
                        (board["id"], values['id']))["count"]

        values['total_replies'] = total_replies

        sql = "SELECT id, parentid, timestamp, timestamp_formatted, name, tripcode, email, posterid, subject, message, file, file_size, image_width, image_height, thumb, thumb_width, thumb_height, IS_DELETED FROM posts WHERE boardid = %s AND (parentid = %s OR id = %s) ORDER BY id ASC LIMIT %s OFFSET %s"
        sqlv = (board['id'], values['id'], values['id'], limit, offset)
        posts = FetchAll(sql, sqlv)

        values['posts'] = []

        for post in posts:
            post['IS_DELETED'] = int(post['IS_DELETED'])
            post['id'] = int(post['id'])
            post['parentid'] = int(post['parentid'])
            post['timestamp'] = int(post['timestamp'])

            if post['IS_DELETED']:
                empty_post = {'id': post['id'],
                              'IS_DELETED': post['IS_DELETED'],
                              'parentid': post['parentid'],
                              'timestamp': post['timestamp'],
                              }
                values['posts'].append(empty_post)
            else:
                post['file_size'] = post['file_size']
                post['image_width'] = post['image_width']
                post['image_height'] = post['image_height']
                post['thumb_width'] = post['thumb_width']
                post['thumb_height'] = post['thumb_height']
                post['message'] = post['message']
                if striphtml:
                    post['message'] = post['message'].replace("<br />", " ")
                    post['message'] = re.compile(
                        r"<[^>]*?>", re.DOTALL | re.IGNORECASE).sub("", post['message'])
                values['posts'].append(post)

    elif method == 'get':
        data_board = formdata.get('dir')
        data_parentid = formdata.get('thread')
        data_postid = formdata.get('id')
        data_postnum = formdata.get('num')

        if not data_board and (not data_postid or (not data_postnum and not data_parentid)):
            raise APIError("Missing parameters")

        board = setBoard(data_board)
        postid = 0

        if data_postnum:
            data_postid = getID(data_parentid, data_postid)

        try:
            postid = int(data_postid)
        except ValueError:
            raise APIError("Post ID must be numeric")

        post = FetchOne("SELECT id, parentid, timestamp, timestamp_formatted, name, tripcode, email, posterid, subject, message, file, file_size, image_width, image_height, thumb, thumb_width, thumb_height, IS_DELETED FROM posts WHERE `id` = %s AND boardid = %s"
                       (postid, board["id"]))

        if not post:
            raise APIError("Post ID cannot be found")

        values['posts'] = []

        post['IS_DELETED'] = post['IS_DELETED']
        post['id'] = post['id']
        post['parentid'] = post['parentid']
        post['timestamp'] = post['timestamp']

        if post['IS_DELETED']:
            empty_post = {'id': post['id'],
                          'IS_DELETED': post['IS_DELETED'],
                          'parentid': post['parentid'],
                          'timestamp': post['timestamp'],
                          }
            values['posts'].append(empty_post)
        else:
            post['file_size'] = post['file_size']
            post['image_width'] = post['image_width']
            post['image_height'] = post['image_height']
            post['thumb_width'] = post['thumb_width']
            post['thumb_height'] = post['thumb_height']
            post['message'] = post['message']
            values['posts'].append(post)

    elif method == 'delete':
        data_board = formdata.get('dir')
        data_postid = formdata.get('id')
        data_imageonly = formdata.get('imageonly')
        data_password = formdata.get('password')

        if not data_board or not data_postid or not data_password:
            raise APIError("Missing parameters")

        imageonly = False
        board = setBoard(data_board)

        try:
            postid = int(data_postid)
        except ValueError:
            raise APIError("Post ID must be numeric")

        if data_imageonly and data_imageonly == 1:
            imageonly = True

        deletePosts(board['dir'], postid, imageonly, data_password)

    elif method == 'post':
        boarddir = formdata.get('board')

        if not boarddir:
            raise APIError("Missing parameters")

        parent = formdata.get('parent')
        trap1 = formdata.get('name', '')
        trap2 = formdata.get('email', '')
        name = formdata.get('fielda', '')
        email = formdata.get('fieldb', '')
        subject = formdata.get('subject', '')
        message = formdata.get('message', '')
        file = formdata.get('file')
        file_original = formdata.get('file_original')
        spoil = formdata.get('spoil')
        oek_file = formdata.get('oek_file')
        password = formdata.get('password', '')
        noimage = formdata.get('noimage')
        mobile = ("mobile" in formdata)

        # call post function
        (post_url, ttaken, postid) = self.make_post(ip, boarddir, parent, trap1, trap2, name,
                                                    email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile)

        values['post_url'] = post_url
        values['time_taken'] = ttaken
        values['post_id'] = postid

    elif method == 'newThreads':
        data_limit = formdata.get('limit')
        limit = 30
        if data_limit:
            try:
                limit = int(data_limit)
            except ValueError:
                raise APIError("Limit must be numeric")

        if limit > 30:
            raise APIError("Maximum limit is 30")

        threads = getNewThreads(limit)
        values['threads'] = threads

    elif method == "blotter":
        latest_news = FetchAll("SELECT `timestamp`, `message`, `timestamp_formatted` FROM `news` WHERE `type` = '2' ORDER BY `timestamp` DESC LIMIT %s", (Settings.HOME_NEWS,))
        values["news"] = latest_news

    elif method == 'boardsExtra':
        boards = FetchAll('SELECT dir, name, longname, subname, postarea_desc, postarea_extra, anonymous, subject, message, disable_name, disable_subject, allow_spoilers, allow_oekaki, numthreads, board_type, allow_images, allow_image_replies, maxsize FROM `boards` WHERE `secret`=0 ORDER BY `sort` ASC')
        values['boards'] = boards
        for board in values['boards']:
            board['board_type'] = board['board_type']
            board['allow_images'] = board['allow_images']
            board['allow_image_replies'] = board['allow_image_replies']
            board['disable_name'] = board['disable_name']
            board['disable_subject'] = board['disable_subject']
            board['allow_spoilers'] = board['allow_spoilers']
            board['allow_oekaki'] = board['allow_oekaki']
            board['numthreads'] = board['numthreads']
            board['maxsize'] = board['maxsize']
    else:
        raise APIError("Invalid method")

    values['time'] = int(t)
    #values['time_taken'] = time.time() - t
    return json.dumps(values, sort_keys=True, separators=(',', ':'))


def api_error(errtype, msg, type=None, detail=None):
    values = {'state': errtype, 'message': msg}

    if type:
        values['type'] = type
    if detail:
        values['detail'] = detail

    return json.dumps(values)


class APIError(Exception):
    pass