├── .gitignore ├── LICENSE ├── README.md ├── bin ├── initdb.py └── update.py ├── config.yaml.sample ├── controller ├── __init__.py ├── admin.py ├── ajax.py ├── auth.py ├── base.py ├── dashboard.py ├── download.py ├── main.py ├── message.py ├── open.py ├── post.py ├── publish.py ├── search.py ├── sort.py └── user.py ├── download └── .gitignore ├── extends ├── __init__.py └── torndsession │ ├── __init__.py │ ├── driver.py │ ├── filesession.py │ ├── memorysession.py │ ├── redissession.py │ ├── session.py │ ├── sessionhandler.py │ └── test │ └── TEST_DIRECTORY ├── main.py ├── model ├── __init__.py ├── article.py ├── base.py ├── sort.py └── user.py ├── requirements.txt ├── static ├── assets │ ├── ZeroClipboard │ │ ├── ZeroClipboard.min.js │ │ ├── ZeroClipboard.min.map │ │ └── ZeroClipboard.swf │ ├── cropbox │ │ ├── css │ │ │ └── style.css │ │ ├── images │ │ │ └── avatar.png │ │ └── js │ │ │ └── cropbox.js │ ├── css │ │ ├── 404.css │ │ ├── admin.css │ │ ├── amazeui.flat.min.css │ │ ├── amazeui.min.css │ │ ├── app.css │ │ └── none.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── captcha.ttf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── highlightjs │ │ ├── default.min.css │ │ └── highlight.min.js │ ├── i │ │ ├── app-icon72x72@2x.png │ │ ├── bg1.png │ │ ├── error-img.png │ │ ├── examples │ │ │ ├── admin-chrome.png │ │ │ ├── admin-firefox.png │ │ │ ├── admin-ie.png │ │ │ ├── admin-opera.png │ │ │ ├── admin-safari.png │ │ │ ├── adminPage.png │ │ │ ├── blogPage.png │ │ │ ├── landing.png │ │ │ ├── landingPage.png │ │ │ ├── loginPage.png │ │ │ └── sidebarPage.png │ │ ├── favicon.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── logo副本.svg │ │ └── startup-640x1096.png │ ├── js │ │ ├── admin.js │ │ ├── amazeui.js │ │ ├── amazeui.legacy.min.js │ │ ├── amazeui.min.js │ │ ├── amazeui.widgets.helper.min.js │ │ ├── app.js │ │ ├── edit.js │ │ ├── editpost.js │ │ ├── handlebars.min.js │ │ ├── jquery-1.11.1.min.js │ │ ├── jquery.min.js │ │ ├── like.js │ │ ├── message.js │ │ ├── nologin.js │ │ ├── polyfill │ │ │ ├── rem.min.js │ │ │ └── respond.min.js │ │ ├── post.js │ │ ├── publish.js │ │ └── qrcode.min.js │ └── simditor │ │ ├── images │ │ └── image.png │ │ ├── scripts │ │ ├── hotkeys.js │ │ ├── hotkeys.min.js │ │ ├── jquery.min.js │ │ ├── module.js │ │ ├── module.min.js │ │ ├── simditor.js │ │ ├── simditor.min.js │ │ ├── uploader.js │ │ └── uploader.min.js │ │ └── styles │ │ ├── editor.scss │ │ ├── fonticon.scss │ │ ├── simditor.css │ │ └── simditor.scss └── face │ └── guest.png ├── templates ├── 404.htm ├── admin │ ├── base.htm │ ├── error.htm │ ├── index.htm │ ├── invite.htm │ ├── newsort.htm │ ├── setting.htm │ ├── sidebar.htm │ ├── sort.htm │ ├── sortdetail.htm │ ├── userdetail.htm │ └── userlist.htm ├── base.htm ├── bookmark.htm ├── dashboard.htm ├── edit.htm ├── error.htm ├── face.htm ├── forgetpwd.htm ├── header.htm ├── like.htm ├── login.htm ├── main.htm ├── message.htm ├── msgdetail.htm ├── open_list.htm ├── open_post.htm ├── post.htm ├── profile.htm ├── publish.htm ├── register.htm ├── renewpwd.htm ├── search.htm ├── self.htm ├── sort.htm ├── upload.htm └── user.htm └── util ├── __init__.py ├── captcha.py ├── error.py ├── flash.py ├── function.py ├── pxfilter.py └── sendemail.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .idea/* 3 | *.py[cod] 4 | *.yaml 5 | *.so 6 | *.egg 7 | *.egg-info 8 | static/face/*/* 9 | static/upimg/* 10 | -------------------------------------------------------------------------------- /bin/initdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #coding=utf-8 3 | __author__ = 'phithon' 4 | import pymongo, yaml, sys, time, bcrypt 5 | try: 6 | input = raw_input 7 | except NameError: 8 | pass 9 | 10 | def createAdmin(db, config): 11 | username = input("input admin name: ") 12 | password = input("input admin password: ") 13 | password = bcrypt.hashpw(password, bcrypt.gensalt()) 14 | user = { 15 | "username": username, 16 | "password": password, 17 | "power": 20, 18 | "money": config["global"]["init_money"], 19 | "time": time.time(), 20 | "bookmark": [], 21 | "email": "", 22 | "qq": "", 23 | "website": "", 24 | "address": "", 25 | "signal": u"太懒,没有留下任何个人说明", 26 | "openwebsite": 1, 27 | "openqq": 1, 28 | "openemail": 1, 29 | "allowemail": 1, 30 | "logintime": None, 31 | "loginip": None 32 | } 33 | member = db.member 34 | member.insert(user) 35 | 36 | def create_index(db): 37 | db.member.create_index("username", unique = True) 38 | db.invite.create_index("code", unique = True) 39 | 40 | if __name__ == "__main__": 41 | try: 42 | with open("config.yaml", "r") as fin: 43 | config = yaml.load(fin) 44 | except: 45 | print "cannot find config file" 46 | sys.exit(0) 47 | 48 | dbset = config["database"] 49 | client = pymongo.MongoClient(dbset["config"]) 50 | db = client[dbset["db"]] 51 | isdo = input("create a admin user(Y/n): ") 52 | if isdo not in ("N", "n"): 53 | createAdmin(db, config) 54 | create_index(db) 55 | -------------------------------------------------------------------------------- /bin/update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | __author__ = 'phithon' 3 | import os, sys, hashlib, shutil 4 | 5 | try: 6 | input = raw_input 7 | except NameError: 8 | pass 9 | 10 | def checksum_md5(filename): 11 | md5 = hashlib.md5() 12 | with open(filename,'rb') as f: 13 | for chunk in iter(lambda: f.read(8192), b''): 14 | md5.update(chunk) 15 | return md5.digest() 16 | 17 | if not os.path.isdir(".git"): 18 | print("%s is not a git directory" % os.getcwd()) 19 | sys.exit() 20 | 21 | os.system("git stash && git pull https://github.com/phith0n/Minos.git") 22 | www_dir = input("Input your www directory:") 23 | 24 | generator = os.walk("./") 25 | for (now_dir, _, file_list) in generator: 26 | for file in file_list: 27 | file = os.path.join(now_dir, file) 28 | remote_filename = os.path.abspath(os.path.join(www_dir, file)) 29 | update = False 30 | if file.endswith(".py") or file.startswith("./templates/") or file.startswith("./static/assets/"): 31 | if not os.path.isfile(remote_filename): 32 | update = True 33 | else: 34 | if checksum_md5(file) != checksum_md5(remote_filename): 35 | update = True 36 | if update: 37 | (dir, _) = os.path.split(remote_filename) 38 | if not os.path.isdir(dir): 39 | os.makedirs(dir) 40 | print "[+] Update %s" % remote_filename 41 | shutil.copyfile(file, remote_filename) 42 | -------------------------------------------------------------------------------- /config.yaml.sample: -------------------------------------------------------------------------------- 1 | "database": 2 | "config": "mongodb://localhost:27017/" 3 | "db": "minos" 4 | "global": 5 | "captcha": 6 | "comment": !!bool "true" 7 | "login": !!bool "false" 8 | "register": !!bool "true" 9 | "cookie_secret": !!python/unicode "secret-key" 10 | "imagepath": "./static/upimg" 11 | "init_money": !!int "10" 12 | "invite_expire": !!int "604800" 13 | "register": !!python/unicode "open" 14 | "intranet": !!bool "false" 15 | "debug": !!bool "false" 16 | "site": 17 | "description": "\u8FD9\u662F\u4E00\u4E2A\u79C1\u5BC6\u7684\u798F\u5229\u5171\u4EAB\ 18 | \u793E\u533A\uFF0C\u5206\u4EAB\u4E00\u4E9B\u6709\u610F\u601D\u7684\u4E1C\u897F\ 19 | \uFF0C\u4F46\u4E0D\u8981\u60F3\u6B6A\u4E86~~~" 20 | "keyword": "\u5B89\u5168,\u7F51\u7EDC\u5B89\u5168,\u798F\u5229,0day,\u8BBA\u575B\ 21 | ,\u793E\u533A,\u4E4C\u4E91" 22 | "webname": "Minos\u798F\u5229\u793E\u533A" 23 | "email": 24 | "method": "none" 25 | "url": "https://api.mailgun.net/v3/domain" 26 | "key": "key-xxxxx" 27 | "sender": "root@domain" 28 | "session": 29 | "db": !!int "1" 30 | "host": "localhost" 31 | "port": !!int "6379" 32 | -------------------------------------------------------------------------------- /controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/controller/__init__.py -------------------------------------------------------------------------------- /controller/ajax.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web, time 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | from bson.objectid import ObjectId 7 | from util.function import time_span, intval 8 | 9 | class AjaxHandler(BaseHandler): 10 | def get(self, *args, **kwargs): 11 | self.json("fail", "no action!") 12 | 13 | def post(self, *args, **kwargs): 14 | action = "_%s_action" % args[0] 15 | if hasattr(self, action): 16 | getattr(self, action)() 17 | else: 18 | self.json("fail", "no action!") 19 | 20 | @tornado.web.asynchronous 21 | @gen.coroutine 22 | def _like_action(self): 23 | id = self.get_body_argument("post") 24 | post = yield self.db.article.find_one({ 25 | "_id": ObjectId(id) 26 | }) 27 | if not post: 28 | self.json("fail", "post id error") 29 | self.__check_already(post) 30 | post["like"].append(self.current_user["username"]) 31 | yield self.db.article.find_and_modify({ 32 | "_id": ObjectId(id) 33 | }, { 34 | "$set": {"like": post["like"]} 35 | }) 36 | self.json("success", len(post["like"])) 37 | 38 | @tornado.web.asynchronous 39 | @gen.coroutine 40 | def _unlike_action(self): 41 | id = self.get_body_argument("post") 42 | post = yield self.db.article.find_one({ 43 | "_id": ObjectId(id) 44 | }) 45 | if not post: 46 | self.json("fail", "post id error") 47 | self.__check_already(post) 48 | post["unlike"].append(self.current_user["username"]) 49 | yield self.db.article.find_and_modify({ 50 | "_id": ObjectId(id) 51 | }, { 52 | "$set": {"unlike": post["unlike"]} 53 | }) 54 | self.json("success", len(post["unlike"])) 55 | 56 | @tornado.web.asynchronous 57 | @gen.coroutine 58 | def _bookmark_action(self): 59 | id = self.get_body_argument("post") 60 | post = yield self.db.article.find_one({ 61 | "_id": ObjectId(id) 62 | }) 63 | if not post: 64 | self.json("fail", "article not found") 65 | user = yield self.db.member.find_one({ 66 | "username": self.current_user["username"] 67 | }) 68 | for row in user["bookmark"]: 69 | if id == row["id"]: 70 | self.json("fail", "already bookmark") 71 | bookmark = { 72 | "id": str(post["_id"]), 73 | "title": post["title"], 74 | "user": post["user"], 75 | "sort": post["sort"], 76 | "time": time.time() 77 | } 78 | ret = yield self.db.member.update({ 79 | "username": self.current_user["username"] 80 | }, { 81 | "$push": {"bookmark": bookmark} 82 | }) 83 | self.json("success", "done") 84 | 85 | @tornado.web.asynchronous 86 | @gen.coroutine 87 | def _thanks_action(self): 88 | id = self.get_body_argument("id") 89 | if not id: 90 | self.json("fail", "post not exists!") 91 | post = yield self.db.article.find_one({ 92 | "_id": ObjectId(id), 93 | "thanks": {"$nin": [self.current_user["username"]]} 94 | }) 95 | if not post: 96 | self.json("fail", "already thanks") 97 | if post["user"] == self.current_user["username"]: 98 | self.json("fail", "cannot thanks to yourself") 99 | old = yield self.db.member.find_and_modify({ 100 | "username": self.current_user["username"], 101 | "money": {"$gt": 0} 102 | }, { 103 | "$inc": {"money": -1} 104 | }) 105 | if not old: 106 | self.json("fail", "money not enough") 107 | yield self.db.member.find_and_modify({ 108 | "username": post["user"] 109 | }, { 110 | "$inc": {"money": 1} 111 | }) 112 | yield self.db.article.find_and_modify({ 113 | "_id": ObjectId(id) 114 | }, { 115 | "$push": {"thanks": self.current_user["username"]} 116 | }) 117 | yield self.message(fromuser = None, touser = post["user"], 118 | content = u"你的文章《%s》被%s感谢了" % (post["title"], self.current_user["username"]), 119 | jump="/post/%s" % id) 120 | self.clear_cookie("flush_info") 121 | self.json("success", "thanks success") 122 | 123 | @tornado.web.asynchronous 124 | @gen.coroutine 125 | def _newmsg_action(self): 126 | cursor = self.db.message.find({ 127 | "$and": [ 128 | {"read": False}, 129 | {"$or": [ 130 | {"to": self.current_user["username"]}, 131 | {"from": self.current_user["username"]} 132 | ]}, 133 | ] 134 | }) 135 | count = yield cursor.count() 136 | self.json("success", count) 137 | 138 | def __check_already(self, post): 139 | ''' 140 | 检查like、unlike是否重复 141 | 142 | :param post: 143 | :return: 144 | ''' 145 | if (self.current_user["username"] in post["unlike"]): 146 | self.json("fail", "already unliked") 147 | if (self.current_user["username"] in post["like"]): 148 | self.json("fail", "already liked") 149 | 150 | def json(self, status, info): 151 | self.write({ 152 | "status": status, 153 | "info": info 154 | }) 155 | raise tornado.web.Finish() -------------------------------------------------------------------------------- /controller/dashboard.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | import tornado.web, re 3 | from controller.base import BaseHandler 4 | from tornado import gen 5 | import time, pymongo 6 | from util.function import intval 7 | from bson.objectid import ObjectId 8 | 9 | class AdminHandler(BaseHandler): 10 | def initialize(self): 11 | super(AdminHandler, self).initialize() 12 | self.topbar = "admin" 13 | 14 | def prepare(self): 15 | super(AdminHandler, self).prepare() 16 | if self.power != "admin": 17 | self.redirect("/") 18 | 19 | def render(self, template_name, **kwargs): 20 | if self.power == "admin": 21 | render = "admin/%s" % template_name 22 | else: 23 | render = template_name 24 | super(AdminHandler, self).render(render, **kwargs) 25 | 26 | def get(self, *args, **kwargs): 27 | action = args[0] if len(args) else "index" 28 | method = "_view_%s" % action 29 | arg = args[2] if len(args) == 3 else None 30 | if hasattr(self, method): 31 | getattr(self, method)(arg) 32 | else: 33 | self._view_index(arg) 34 | 35 | @tornado.web.asynchronous 36 | @gen.coroutine 37 | def _view_index(self, arg): 38 | user = self.db.member.find() 39 | article = self.db.article.find() 40 | sort = self.db.sort.find() 41 | active = self.db.member.find({ 42 | "logintime": {"$gt": time.time() - 3 * 24 * 60 * 60} 43 | }) 44 | count = { 45 | "user": (yield user.count()), 46 | "article": (yield article.count()), 47 | "sort": (yield sort.count()), 48 | "active": (yield active.count()) 49 | } 50 | user.sort([('time', pymongo.DESCENDING)]).limit(10) 51 | newusers = yield user.to_list(10) 52 | self.render("index.htm", count = count, newusers = newusers) 53 | 54 | @tornado.web.asynchronous 55 | @gen.coroutine 56 | def _view_user(self, arg): 57 | username = self.get_query_argument("username", default=None) 58 | where = {"username": {"$regex": ".*"+re.escape(username)+".*"}} if username else {} 59 | limit = 15 60 | page = intval(arg) 61 | page = page if page > 1 else 1 62 | user = self.db.member.find(where) 63 | count = yield user.count() 64 | user.sort([('time', pymongo.ASCENDING)]).limit(limit).skip((page - 1) * limit) 65 | users = yield user.to_list(limit) 66 | if username: 67 | search = "?username=%s" % username 68 | else: 69 | search = "" 70 | self.render("userlist.htm", page = page, users = users, count = count, each = limit, search = search) 71 | 72 | @tornado.web.asynchronous 73 | @gen.coroutine 74 | def _view_userdetail(self, arg): 75 | username = arg 76 | user = yield self.db.member.find_one({ 77 | "username": username 78 | }) 79 | if not user: 80 | self.custom_error("不存在这个用户") 81 | self.render("userdetail.htm", user = user) 82 | 83 | @tornado.web.asynchronous 84 | @gen.coroutine 85 | def _view_sort(self, arg): 86 | limit = 15 87 | page = intval(arg) 88 | page = page if page > 1 else 1 89 | cursor = self.db.sort.find() 90 | count = yield cursor.count() 91 | cursor.limit(limit).skip((page - 1) * limit) 92 | sorts = yield cursor.to_list(limit) 93 | self.render("sort.htm", sorts = sorts, count = count, each = limit, page = page) 94 | 95 | @tornado.web.asynchronous 96 | @gen.coroutine 97 | def _view_sortdetail(self, arg): 98 | id = arg 99 | sort = yield self.db.sort.find_one({ 100 | "_id": ObjectId(id) 101 | }) 102 | if not sort: 103 | self.custom_error("不存在这个板块") 104 | self.render("sortdetail.htm", sort = sort) 105 | 106 | @tornado.web.asynchronous 107 | @gen.coroutine 108 | def _view_newsort(self, arg): 109 | self.render("newsort.htm") 110 | 111 | @tornado.web.asynchronous 112 | @gen.coroutine 113 | def _view_setting(self, arg): 114 | site = self.settings.get("site") 115 | init_money = self.settings.get("init_money") 116 | captcha = self.settings.get("captcha") 117 | register = self.settings.get("register") 118 | self.render("setting.htm", site = site, init_money = init_money, captcha = captcha, register = register) 119 | 120 | @tornado.web.asynchronous 121 | @gen.coroutine 122 | def _view_invite(self, arg): 123 | where = self.get_query_argument("act", default=None) 124 | act = { 125 | "nouse": {"used": False}, 126 | "used": {"used": True}, 127 | "expire": { 128 | "time": {"$lt": time.time() - self.settings["invite_expire"]}, 129 | "used": False 130 | } 131 | } 132 | where = act.get(where) if (where in act) else {} 133 | limit = 15 134 | page = intval(arg) 135 | page = page if page > 1 else 1 136 | cursor = self.db.invite.find(where) 137 | count = yield cursor.count() 138 | cursor.sort([('time', pymongo.DESCENDING)]).limit(limit).skip((page - 1) * limit) 139 | invites = yield cursor.to_list(limit) 140 | self.render("invite.htm", invites = invites, count = count, each = limit, page = page) 141 | -------------------------------------------------------------------------------- /controller/download.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web, os 4 | import tornado.gen 5 | 6 | class DownloadHandler(tornado.web.StaticFileHandler): 7 | @tornado.gen.coroutine 8 | def get(self, path, include_body=True): 9 | filename = self.get_absolute_path(self.root, path) 10 | cookie = self.get_secure_cookie("download_key") 11 | if not cookie or cookie != filename: 12 | self.set_status(404) 13 | self.absolute_path = None 14 | self.render("404.htm") 15 | return 16 | yield super(DownloadHandler, self).get(path, include_body) 17 | 18 | def set_extra_headers(self, path): 19 | filename = os.path.basename(path) 20 | if "MSIE" in self.request.headers.get("User-Agent"): 21 | filename = filename.encode("gbk") 22 | self.set_header("Content-Type", "application/octet-stream") 23 | self.set_header("Content-Disposition", "attachment;filename=\"%s\";" % (filename, )) 24 | self.set_header("Content-Encoding", "none") 25 | self.set_header("Content-Transfer-Encoding", "binary") 26 | self.clear_header("Server") -------------------------------------------------------------------------------- /controller/main.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | import pymongo 7 | from bson.objectid import ObjectId 8 | from util.function import time_span, intval 9 | 10 | class HomeHandler(BaseHandler): 11 | @tornado.web.asynchronous 12 | @gen.coroutine 13 | def get(self, *args, **kwargs): 14 | limit = 15 15 | page = intval(args[1]) 16 | if not page or page <= 0 : page = 1 17 | cursor = self.db.article.find() 18 | cursor.sort([('top', pymongo.DESCENDING), ("lastcomment", pymongo.DESCENDING), ('time', pymongo.DESCENDING)]).limit(limit).skip((page - 1) * limit) 19 | count = yield cursor.count() 20 | posts = yield cursor.to_list(length = limit) 21 | sorts = yield self.get_sort() 22 | self.render("main.htm", posts = posts, sorts = sorts, page = page, 23 | time_span = time_span, count = count, each = limit) 24 | 25 | @gen.coroutine 26 | def get_sort(self): 27 | sorts = [] 28 | cursor = self.db.sort.find({ 29 | "show": True 30 | }) 31 | while (yield cursor.fetch_next): 32 | sorts.append(cursor.next_object()) 33 | raise gen.Return(sorts) -------------------------------------------------------------------------------- /controller/message.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | import pymongo, re 7 | from bson.objectid import ObjectId 8 | from util.function import time_span, intval 9 | 10 | def cutstr(content): 11 | return content[0:30] + "..." 12 | 13 | class MessageHandler(BaseHandler): 14 | def initialize(self): 15 | BaseHandler.initialize(self) 16 | self.topbar = "message" 17 | 18 | def post(self, *args, **kwargs): 19 | method = self.get_body_argument("method", default=None) 20 | if method and hasattr(self, "_%s_action" % method): 21 | getattr(self, "_%s_action" % method)() 22 | else: 23 | self.custom_error("不存在这个方法") 24 | 25 | @tornado.web.asynchronous 26 | @gen.coroutine 27 | def _readall_action(self): 28 | yield self.db.message.update({ 29 | "to": self.current_user["username"], 30 | "read": False 31 | }, { 32 | "$set": {"read": True} 33 | }, multi = True) 34 | self.redirect("/message") 35 | 36 | @tornado.web.asynchronous 37 | @gen.coroutine 38 | def _deleteall_action(self): 39 | yield self.db.message.remove({ 40 | "to": self.current_user["username"] 41 | }, multi = True) 42 | self.redirect("/message") 43 | 44 | @tornado.web.asynchronous 45 | @gen.coroutine 46 | def get(self, *args, **kwargs): 47 | limit = 20 48 | page = intval(args[1]) 49 | if not page or page <= 0 : page = 1 50 | cursor = self.db.message.find({ 51 | "$or": [ 52 | {"to": self.current_user["username"]}, 53 | {"from": self.current_user["username"]} 54 | ] 55 | }) 56 | count = yield cursor.count() 57 | cursor.sort([('time', pymongo.DESCENDING)]).limit(limit).skip((page - 1) * limit) 58 | messages = yield cursor.to_list(length = limit) 59 | self.render("message.htm", messages = messages, count = count, cutstr = cutstr) 60 | 61 | class DetailHandler(BaseHandler): 62 | 63 | def initialize(self): 64 | BaseHandler.initialize(self) 65 | self.topbar = "message" 66 | 67 | @tornado.web.asynchronous 68 | @gen.coroutine 69 | def get(self, *args, **kwargs): 70 | id = args[0] 71 | message = yield self.db.message.find_and_modify({ 72 | "_id": ObjectId(id) 73 | }, { 74 | "$set": {"read": True} 75 | }) 76 | self.render("msgdetail.htm", message = message) -------------------------------------------------------------------------------- /controller/open.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web, time, re, pymongo 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | from bson.objectid import ObjectId 7 | from util.captcha import Captcha 8 | from util.function import intval, not_need_login, time_span 9 | 10 | class PostHandler(BaseHandler): 11 | def initialize(self): 12 | BaseHandler.initialize(self) 13 | self.topbar = "" 14 | 15 | @not_need_login 16 | def prepare(self): 17 | BaseHandler.prepare(self) 18 | 19 | def del_with_hide(self, post): 20 | charge = post.get("charge") 21 | content = post.get('content') 22 | template = u"
" \ 23 | u"
隐藏内容,需要登录并付费之后才能查看哦
" \ 24 | u"
需要" \ 25 | + str(post['charge']) + \ 26 | u"金币,点击 登录" \ 27 | + u"
" 28 | pattern = re.compile(r"\[hide\](.*?)\[/hide\]", re.S) 29 | if charge == 0: 30 | post["content"] = pattern.sub("\\1", content) 31 | return post 32 | if pattern.search(content): 33 | content = pattern.sub(template, content) 34 | else: 35 | content = template 36 | post["content"] = content 37 | return post 38 | 39 | @tornado.web.asynchronous 40 | @gen.coroutine 41 | def get(self, *args, **kwargs): 42 | self.set_header("Content-Security-Policy", "default-src 'self'; script-src bdimg.share.baidu.com 'self' 'unsafe-eval'; " 43 | "connect-src 'self'; img-src *.share.baidu.com nsclick.baidu.com 'self' data:; " 44 | "style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src 'self';") 45 | id = args[0] 46 | if self.current_user: 47 | self.redirect("/post/%s" % id) 48 | post = yield self.db.article.find_one({ 49 | "_id": ObjectId(id), 50 | "open": True 51 | }) 52 | if not post: 53 | self.custom_error("注册登录后才能查看哦", jump = "/register") 54 | 55 | post = self.del_with_hide(post) 56 | user = yield self.db.member.find_one({ 57 | "username": post["user"] 58 | }) 59 | yield self.db.article.find_and_modify({ 60 | "_id": ObjectId(id) 61 | }, { 62 | "$inc": {"view": 1} 63 | }) 64 | self.render("open_post.htm", post = post, user = user) 65 | 66 | @tornado.web.asynchronous 67 | @gen.coroutine 68 | def post(self, *args, **kwargs): 69 | captcha = self.get_body_argument("captcha") 70 | if not Captcha.check(captcha, self): 71 | self.custom_error("验证码错误") 72 | content = self.get_body_argument("content") 73 | postid = self.get_body_argument("postid") 74 | _id = ObjectId() 75 | post = yield self.db.article.find_and_modify({ 76 | "_id": ObjectId(postid) 77 | },{ 78 | "$push": { 79 | "comment": { 80 | "_id": _id, 81 | "content": content, 82 | "user": { 83 | "id": self.current_user["_id"], 84 | "username": self.current_user["username"] 85 | }, 86 | "time": time.time() 87 | } 88 | } 89 | }) 90 | if post: 91 | if self.current_user["username"] != post["user"]: 92 | self.message(fromuser=None, touser=post["user"], 93 | content=u"%s 评论了你的文章《%s》" % (self.current_user["username"], post["title"]), 94 | jump="/post/%s" % postid) 95 | self.at_user(content, post["title"], post["_id"], _id) 96 | self.redirect("/post/%s#%s" % (postid, _id)) 97 | else: 98 | self.custom_error("不存在这篇文章") 99 | 100 | @gen.coroutine 101 | def at_user(self, content, title, postid, comid): 102 | at = [] 103 | grp = re.findall(r"@([a-zA-Z0-9_\-\u4e00-\u9fa5]+)", content) 104 | for username in grp: 105 | try: 106 | user = yield self.db.member.find_one({ 107 | "username": username 108 | }) 109 | assert type(user) is dict 110 | assert self.current_user["username"] != username 111 | assert username not in at 112 | yield self.message(fromuser=None, touser=username, 113 | content=u"%s在文章《%s》中提到了你。" % (self.current_user["username"], title), 114 | jump="/post/%s#%s" % (postid, comid)) 115 | at.append(username) 116 | except: 117 | continue 118 | 119 | @gen.coroutine 120 | def get_user(self, username): 121 | user = yield self.db.member.find_one({ 122 | "username": {"$eq": username} 123 | }) 124 | raise gen.Return(user) 125 | 126 | class ListHandler(BaseHandler): 127 | @not_need_login 128 | def prepare(self): 129 | BaseHandler.prepare(self) 130 | 131 | @tornado.web.asynchronous 132 | @gen.coroutine 133 | def get(self, *args, **kwargs): 134 | limit = 15 135 | page = intval(args[1]) 136 | if not page or page <= 0 : page = 1 137 | cursor = self.db.article.find({ 138 | "open": True 139 | }) 140 | cursor.sort([("top", pymongo.DESCENDING), ("lastcomment", pymongo.DESCENDING), ('time', pymongo.DESCENDING)]).limit(limit).skip((page - 1) * limit) 141 | count = yield cursor.count() 142 | posts = yield cursor.to_list(length = limit) 143 | self.render("open_list.htm", posts = posts, page = page, 144 | time_span = time_span, count = count, each = limit) 145 | 146 | def test(self): 147 | pass -------------------------------------------------------------------------------- /controller/post.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web, time, re 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | from bson.objectid import ObjectId 7 | from util.captcha import Captcha 8 | from util.function import humantime 9 | 10 | class PostHandler(BaseHandler): 11 | def initialize(self): 12 | BaseHandler.initialize(self) 13 | self.topbar = "" 14 | 15 | def is_edit(self, post): 16 | return post["user"] == self.current_user.get("username") and time.time() - post["time"] < 30 * 60 17 | 18 | def no_need_buy(self, post): 19 | if self.power == "admin": 20 | return True 21 | if post["user"] == self.current_user["username"]: 22 | return True 23 | if "buyer" in post and self.current_user["username"] in post["buyer"]: 24 | return True 25 | now = int(humantime(time.time(), "%H")) 26 | if post["freebegin"] <= now < post["freeend"]: 27 | return True 28 | return False 29 | 30 | def del_with_hide(self, post): 31 | charge = post.get("charge") 32 | content = post.get('content') 33 | template = u"
" \ 34 | u"
内容隐藏,付费以后才可以看哦
" \ 35 | u"
需要" \ 36 | + str(post['charge']) + \ 37 | u"金币,点击 " \ 38 | + self.xsrf_form_html() + u"
" 40 | pattern = re.compile(r"\[hide\](.*?)\[/hide\]", re.S) 41 | if charge == 0 or self.no_need_buy(post): 42 | post["content"] = pattern.sub("\\1", content) 43 | return post 44 | if pattern.search(content): 45 | content = pattern.sub(template, content) 46 | else: 47 | content = template 48 | post["content"] = content 49 | return post 50 | 51 | @tornado.web.asynchronous 52 | @gen.coroutine 53 | def get(self, *args, **kwargs): 54 | self.set_header("Content-Security-Policy", "default-src 'self'; script-src bdimg.share.baidu.com 'self' 'unsafe-eval'; " 55 | "connect-src 'self'; img-src *.share.baidu.com nsclick.baidu.com 'self' data:; " 56 | "style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src 'self';") 57 | id = args[0] 58 | post = yield self.db.article.find_one({ 59 | "_id": ObjectId(id) 60 | }) 61 | if not post: 62 | self.custom_error("你找的文章并不存在", jump = "/") 63 | 64 | post = self.del_with_hide(post) 65 | user = yield self.db.member.find_one({ 66 | "username": post["user"] 67 | }) 68 | yield self.db.article.find_and_modify({ 69 | "_id": ObjectId(id) 70 | }, { 71 | "$inc": {"view": 1} 72 | }) 73 | self.render("post.htm", post = post, user = user, is_edit = self.is_edit) 74 | 75 | @tornado.web.asynchronous 76 | @gen.coroutine 77 | def post(self, *args, **kwargs): 78 | captcha = self.get_body_argument("captcha", default=None) 79 | if self.settings["captcha"]["comment"] and not Captcha.check(captcha, self): 80 | self.custom_error("验证码错误") 81 | content = self.get_body_argument("content") 82 | postid = self.get_body_argument("postid") 83 | _id = ObjectId() 84 | post = yield self.db.article.find_and_modify({ 85 | "_id": ObjectId(postid) 86 | },{ 87 | "$push": { 88 | "comment": { 89 | "_id": _id, 90 | "content": content, 91 | "user": { 92 | "id": self.current_user["_id"], 93 | "username": self.current_user["username"] 94 | }, 95 | "time": time.time() 96 | } 97 | }, 98 | "$set": { 99 | "lastcomment": time.time() 100 | } 101 | }) 102 | if post: 103 | if self.current_user["username"] != post["user"]: 104 | self.message(fromuser=None, touser=post["user"], 105 | content=u"%s 评论了你的文章《%s》" % (self.current_user["username"], post["title"]), 106 | jump="/post/%s" % postid) 107 | self.at_user(content, post["title"], post["_id"], _id) 108 | self.redirect("/post/%s#%s" % (postid, _id)) 109 | else: 110 | self.custom_error("不存在这篇文章") 111 | 112 | @gen.coroutine 113 | def at_user(self, content, title, postid, comid): 114 | at = [] 115 | grp = re.findall(r"@([a-zA-Z0-9_\-\u4e00-\u9fa5]+)", content) 116 | for username in grp: 117 | try: 118 | user = yield self.db.member.find_one({ 119 | "username": username 120 | }) 121 | assert type(user) is dict 122 | assert self.current_user["username"] != username 123 | assert username not in at 124 | yield self.message(fromuser=None, touser=username, 125 | content=u"%s在文章《%s》中提到了你。" % (self.current_user["username"], title), 126 | jump="/post/%s#%s" % (postid, comid)) 127 | at.append(username) 128 | except: 129 | continue 130 | 131 | @gen.coroutine 132 | def get_user(self, username): 133 | user = yield self.db.member.find_one({ 134 | "username": {"$eq": username} 135 | }) 136 | raise gen.Return(user) 137 | 138 | class BuyHandler(BaseHandler): 139 | @tornado.web.asynchronous 140 | @gen.coroutine 141 | def post(self, *args, **kwargs): 142 | id = self.get_body_argument("id", default=None) 143 | if not id: 144 | self.custom_error("文章不存在") 145 | post = yield self.db.article.find_one({ 146 | "_id": ObjectId(id) 147 | }) 148 | if self.current_user["username"] in post["buyer"]: 149 | self.custom_error("已经购买过啦,无需重复购买", jump="/post/%s" % post["_id"]) 150 | if post["charge"]: 151 | charge = int(post["charge"]) 152 | old = yield self.db.member.find_and_modify({ 153 | "username": {"$eq": self.current_user["username"]}, 154 | "money": {"$gte": charge} 155 | }, { 156 | "$inc": {"money": 0 - charge} 157 | }) 158 | if not old: 159 | self.custom_error("余额不够,不能购买帖子") 160 | post["buyer"].append(self.current_user["username"]) 161 | yield self.db.article.find_and_modify({ 162 | "_id": post["_id"] 163 | }, { 164 | "$set": {"buyer": post["buyer"]} 165 | }) 166 | yield self.db.member.find_and_modify({ 167 | "username": post["user"] 168 | }, { 169 | "$inc": {"money": charge} 170 | }) 171 | self.clear_cookie("flush_info") 172 | self.redirect("/post/%s" % post["_id"]) 173 | else: 174 | self.custom_error("这篇文章并不需要付费", jump="/post/%s" % post["_id"]) -------------------------------------------------------------------------------- /controller/search.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | import pymongo, re 7 | from bson.objectid import ObjectId 8 | from util.function import time_span, intval 9 | 10 | class SearchHandler(BaseHandler): 11 | def initialize(self): 12 | BaseHandler.initialize(self) 13 | self.topbar = "" 14 | 15 | @tornado.web.asynchronous 16 | @gen.coroutine 17 | def get(self, *args, **kwargs): 18 | keyword = self.get_query_argument("keyword", default=None) 19 | if not keyword: 20 | self.custom_error("关键词不能为空") 21 | esp_keyword = re.escape(keyword) 22 | limit = 20 23 | page = intval(args[1]) 24 | if not page or page <= 0 : page = 1 25 | cursor = self.db.article.find({ 26 | "title": {"$regex": ".*%s.*" % esp_keyword, "$options": "i"} 27 | }) 28 | count = yield cursor.count() 29 | cursor.sort([('time', pymongo.DESCENDING)]).limit(limit).skip((page - 1) * limit) 30 | posts = yield cursor.to_list(length = limit) 31 | self.render("search.htm", posts = posts, page = page, keyword = keyword, 32 | time_span = time_span, count = count, each = limit) -------------------------------------------------------------------------------- /controller/sort.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | import tornado.web 4 | from controller.base import BaseHandler 5 | from tornado import gen 6 | import pymongo 7 | from bson.objectid import ObjectId 8 | from util.function import time_span, intval 9 | 10 | class SortHandler(BaseHandler): 11 | @tornado.web.asynchronous 12 | @gen.coroutine 13 | def get(self, *args, **kwargs): 14 | sortid = args[0] 15 | limit = 15 16 | page = intval(args[2]) 17 | if not page or page <= 0 : page = 1 18 | sort = yield self.db.sort.find_one({ 19 | "_id": ObjectId(sortid) 20 | }) 21 | if not sort: 22 | self.custom_error("板块不存在") 23 | cursor = self.db.article.find({ 24 | "sort._id": ObjectId(sortid) 25 | }) 26 | count = yield cursor.count() 27 | cursor.sort([('time', pymongo.DESCENDING)]).limit(limit).skip((page - 1) * limit) 28 | posts = yield cursor.to_list(length = limit) 29 | self.render("sort.htm", posts = posts, page = page, sort = sort, time_span = time_span, 30 | count = count, each = limit) -------------------------------------------------------------------------------- /download/.gitignore: -------------------------------------------------------------------------------- 1 | # 为毛不能commit空文件夹??求解释!! 2 | -------------------------------------------------------------------------------- /extends/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'phithon' 2 | -------------------------------------------------------------------------------- /extends/torndsession/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'phithon' 2 | -------------------------------------------------------------------------------- /extends/torndsession/driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright @ 2014 Mitchell Chu 5 | 6 | class SessionDriver(object): 7 | ''' 8 | abstact class for all real session driver implements. 9 | ''' 10 | def __init__(self, **settings): 11 | self.settings = settings 12 | 13 | def get(self, session_id): 14 | raise NotImplementedError() 15 | 16 | def save(self, session_id, session_data, expires=None): 17 | raise NotImplementedError() 18 | 19 | def clear(self, session_id): 20 | raise NotImplementedError() 21 | 22 | def remove_expires(self): 23 | raise NotImplementedError() 24 | 25 | class SessionDriverFactory(object): 26 | ''' 27 | session driver factory 28 | use input settings to return suitable driver's instance 29 | ''' 30 | @staticmethod 31 | def create_driver(driver, **setings): 32 | module_name = 'extends.torndsession.%ssession' % driver.lower() 33 | module = __import__(module_name, globals(), locals(), ['object'], -1) 34 | # must use this form. 35 | # use __import__('torndsession.' + driver.lower()) just load torndsession.__init__.pyc 36 | cls = getattr(module, '%sSession' % driver.capitalize()) 37 | if not 'SessionDriver' in [base.__name__ for base in cls.__bases__]: 38 | raise InvalidSessionDriverException('%s not found in current driver implements ' % driver) 39 | return cls # just return driver class. 40 | 41 | class InvalidSessionDriverException(Exception): 42 | pass 43 | -------------------------------------------------------------------------------- /extends/torndsession/filesession.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright @ 2014 Mitchell Chu 5 | 6 | import os 7 | from os.path import join, exists, isdir 8 | import datetime 9 | 10 | from driver import SessionDriver 11 | from extends.torndsession.session import SessionConfigurationError 12 | 13 | utcnow = datetime.datetime.utcnow 14 | try: 15 | import cPickle as pickle # py2 16 | except: 17 | import pickle # py3 18 | 19 | class FileSession(SessionDriver): 20 | """ 21 | System File to save session object. 22 | """ 23 | DEFAULT_SESSION_POSITION = './#_sessions' # default host is '#_sessions' directory which is in current directory. 24 | """ 25 | Session file default save position. 26 | In a recommendation, you need give the host option.when host is missed, system will use this value by default. 27 | 28 | Additional @ Version: 1.1 29 | """ 30 | 31 | def __init__(self, **settings): 32 | """ 33 | Initialize File Session Driver. 34 | settings section 'host' is recommended, the option 'prefix' is an optional. 35 | if prefix is not given, 'default' is the default. 36 | host: where to save session object file, this is a directory path. 37 | prefix: session file name's prefix. session file like: prefix@session_id 38 | """ 39 | super(FileSession, self).__init__(**settings) 40 | self.host = settings.get("host", self.DEFAULT_SESSION_POSITION) 41 | self._prefix = settings.get("prefix", 'default') 42 | if not exists(self.host): 43 | os.makedirs(self.host, 0700) # only owner can visit this session directory. 44 | if not isdir(self.host): 45 | raise SessionConfigurationError('session host not found') 46 | 47 | def get(self, session_id): 48 | session_file = join(self.host, self._prefix + session_id) 49 | if not exists(session_file): return {} 50 | 51 | rf = file(session_file, 'rb') 52 | session = pickle.load(rf) 53 | rf.close() 54 | now = utcnow() 55 | expires = session.get('__expires__', now) 56 | if expires >= now: 57 | return session 58 | return {} 59 | 60 | def save(self, session_id, session_data, expires=None): 61 | session_file = join(self.host, self._prefix + session_id) 62 | session_data = session_data if session_data else {} 63 | 64 | if not expires: 65 | session_data.update("__expires__", expires) 66 | wf = file(session_file, 'wb') 67 | pickle.dump(session_data, wf) 68 | wf.close() 69 | 70 | def clear(self, session_id): 71 | session_file = join(self.host, self._prefix + session_id) 72 | if exists(session_file): 73 | os.remove(session_file) 74 | 75 | def remove_expires(self): 76 | if not exists(self.host) or not isdir(self.host):return 77 | now = utcnow() 78 | for file in os.listdir(self.host): 79 | if file.startswith(self._prefix): 80 | session_file = join(self.host, file) 81 | f = open(session_file, 'rb') 82 | session = pickle.load(f) 83 | f.close() 84 | expires = session.get('__expires__', now) 85 | if expires <= now: 86 | os.remove(session_file) 87 | -------------------------------------------------------------------------------- /extends/torndsession/memorysession.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright @ 2014 Mitchell Chu 5 | 6 | import datetime 7 | 8 | from driver import SessionDriver 9 | from extends.torndsession.session import SessionConfigurationError 10 | 11 | 12 | class MemorySession(SessionDriver): 13 | ''' 14 | save session data in process memory 15 | ''' 16 | 17 | MAX_SESSION_OBJECTS = 1024 18 | """The max session objects save in memory. 19 | when session objects count large than this value, 20 | system will auto to clear the expired session data. 21 | """ 22 | 23 | def __init__(self, **settings): 24 | # check settings 25 | super(MemorySession, self).__init__(**settings) 26 | host = settings.get("host") 27 | if not host: 28 | raise SessionConfigurationError('memory session driver can not found persistence position') 29 | if not hasattr(host, "session_container"): 30 | setattr(host, "session_container", {}) 31 | self._data_handler = host.session_container 32 | # init some thing 33 | 34 | def get(self, session_id): 35 | """ 36 | get session object from host. 37 | """ 38 | return self._data_handler.get(session_id) 39 | 40 | def save(self, session_id, session_data, expires=None): 41 | """ 42 | save session data to host. 43 | if host's session objects is more then MAX_SESSION_OBJECTS 44 | system will auto to clear expired session data. 45 | after cleared, system will add current to session pool, however the pool is full. 46 | """ 47 | session_data = session_data if session_data else {} 48 | session_data.update(__expires__=expires) 49 | if len(self._data_handler) >= self.MAX_SESSION_OBJECTS: 50 | self.remove_expires() 51 | if len(self._data_handler) >= self.MAX_SESSION_OBJECTS: 52 | print 'system session pool is full. need more memory to save session object.' 53 | self._data_handler[session_id]=session_data 54 | 55 | def clear(self, session_id): 56 | if self._data_handler.haskey(session_id): 57 | del self._data_handler[session_id] 58 | 59 | def remove_expires(self): 60 | for key, val in self._data_handler: 61 | now = datetime.datetime.utcnow() 62 | expires = val.get("__expires__", now) 63 | if now > expires: 64 | del self._data_handler[key] 65 | -------------------------------------------------------------------------------- /extends/torndsession/redissession.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright @ 2014 Mitchell Chu 5 | 6 | from extends.torndsession.driver import SessionDriver 7 | # from session import SessionConfigurationError 8 | import redis 9 | from copy import copy 10 | from datetime import datetime 11 | try: 12 | import cPickle as pickle # py2 13 | except: 14 | import pickle # py3 15 | 16 | class RedisSession(SessionDriver): 17 | """ 18 | Use Redis to save session object. 19 | """ 20 | 21 | def get(self, session_id): 22 | self.__create_redis_client() 23 | session_data = self.client.get(session_id) 24 | if not session_data: return {} 25 | return pickle.loads(session_data) 26 | 27 | def save(self, session_id, session_data, expires=None): 28 | session_data = session_data if session_data else {} 29 | if expires: 30 | session_data.update(__expires__ = expires) 31 | session_data = pickle.dumps(session_data) 32 | self.__create_redis_client() 33 | self.client.set(session_id, session_data) 34 | if expires: 35 | td = expires - datetime.utcnow() 36 | delta_seconds = int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6) 37 | self.client.expire(session_id, delta_seconds) 38 | 39 | def clear(self, session_id): 40 | self.__create_redis_client() 41 | self.client.delete(session_id) 42 | 43 | def remove_expires(self): 44 | pass 45 | 46 | def __create_redis_client(self): 47 | if not hasattr(self, 'client'): 48 | if 'max_connections' in self.settings: 49 | connection_pool = redis.ConnectionPool(**self.settings) 50 | settings = copy(self.settings) 51 | del settings['max_connections'] 52 | settings['connection_pool'] = connection_pool 53 | else: 54 | settings = self.settings 55 | self.client = redis.Redis(**settings) 56 | -------------------------------------------------------------------------------- /extends/torndsession/sessionhandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright @ 2014 Mitchell Chu 5 | 6 | import tornado.web 7 | import extends.torndsession.session 8 | 9 | class SessionBaseHandler(tornado.web.RequestHandler, extends.torndsession.session.SessionMixin): 10 | """ 11 | This is a tornado web request handler which is base on torndsession. 12 | Generally, user must persistent session object with manual operation when force_persistence is False 13 | but when the handler is inherit from SessionBaseHandler, in your handler, you just need to add/update/delete session values, SessionBaseHandler will auto save it. 14 | """ 15 | 16 | def prepare(self): 17 | """ 18 | Overwrite tornado.web.RequestHandler prepare. 19 | """ 20 | pass 21 | 22 | def on_finish(self): 23 | """ 24 | Overwrite tornado.web.RequestHandler on_finish. 25 | """ 26 | self.session.flush() # try to save session 27 | -------------------------------------------------------------------------------- /extends/torndsession/test/TEST_DIRECTORY: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import tornado.ioloop 3 | import tornado.web, tornado.options 4 | import motor 5 | import sys 6 | import os 7 | import yaml 8 | from concurrent import futures 9 | import controller.base 10 | 11 | tornado.options.define("port", default=8765, help="Run server on a specific port", type=int) 12 | tornado.options.define("host", default="localhost", help="Run server on a specific host") 13 | tornado.options.define("url", default=None, help="Url to show in HTML") 14 | tornado.options.define("config", default="./config.yaml", help="config file's full path") 15 | tornado.options.parse_command_line() 16 | 17 | if not tornado.options.options.url: 18 | tornado.options.options.url = "http://%s:%d" % (tornado.options.options.host, tornado.options.options.port) 19 | 20 | setting = { 21 | "base_url": tornado.options.options.url, 22 | "template_path": "templates", 23 | "cookie_secret": "s3cr3tk3y", 24 | "config_filename": tornado.options.options.config, 25 | "compress_response": True, 26 | "default_handler_class": controller.base.NotFoundHandler, 27 | "xsrf_cookies": True, 28 | "static_path": "static", 29 | "download": "./download", 30 | "session": { 31 | "driver": "redis", 32 | "driver_settings": { 33 | "host": "localhost", 34 | "port": 6379, 35 | "db": 1 36 | }, 37 | "force_persistence": False, 38 | "cache_driver": True, 39 | "cookie_config": { 40 | "httponly": True 41 | }, 42 | }, 43 | "thread_pool": futures.ThreadPoolExecutor(4) 44 | } 45 | 46 | # config file 47 | config = {} 48 | try: 49 | with open(setting["config_filename"], "r") as fin: 50 | config = yaml.load(fin) 51 | for k, v in config["global"].items(): 52 | setting[k] = v 53 | if "session" in config: 54 | setting["session"]["driver_settings"] = config["session"] 55 | except: 56 | print "cannot found config.yaml file" 57 | sys.exit(0) 58 | 59 | # mongodb connection 60 | # format: mongodb://user:pass@host:port/ 61 | # database name: minos 62 | 63 | try: 64 | client = motor.MotorClient(config["database"]["config"]) 65 | database = client[config["database"]["db"]] 66 | setting["database"] = database 67 | except: 68 | print "cannot connect mongodb, check the config.yaml" 69 | sys.exit(0) 70 | 71 | application = tornado.web.Application([ 72 | (r"^/public/post/([a-f0-9]{24})", "controller.open.PostHandler"), 73 | (r"^/public/list(/(\d*))?", "controller.open.ListHandler"), 74 | (r"^/(page/(\d+))?", "controller.main.HomeHandler"), 75 | (r"^/login", "controller.auth.LoginHandler"), 76 | (r"^/register", "controller.auth.RegisterHandler"), 77 | (r"^/nologin/([a-z]+)", "controller.auth.AjaxHandler"), 78 | (r"^/forgetpwd", "controller.auth.ForgetpwdHandler"), 79 | (r"^/captcha\.png", "controller.auth.CaptchaHanlder"), 80 | (r"^/user/([a-z]+)(/(.*))?", "controller.user.UserHandler"), 81 | (r"^/admin/([a-z]+)?", "controller.admin.AdminHandler"), 82 | (r"^/publish", "controller.publish.PublishHandler"), 83 | (r"^/edit/([a-f0-9]{24})", "controller.publish.EditHandler"), 84 | (r"^/uploader", "controller.publish.UploadHandler"), 85 | (r"^/post/([a-f0-9]{24})", "controller.post.PostHandler"), 86 | (r"^/ajax/([a-z]+)", "controller.ajax.AjaxHandler"), 87 | (r"^/sort/([a-f0-9]{24})(/(\d+))?", "controller.sort.SortHandler"), 88 | (r"^/search(/(\d+))?", "controller.search.SearchHandler"), 89 | (r"^/buy", "controller.post.BuyHandler"), 90 | (r"^/message(/(\d+))?", "controller.message.MessageHandler"), 91 | (r"^/message/([a-f0-9]{24})", "controller.message.DetailHandler"), 92 | (r"^/manage/([a-z]+)(/(.*))?", "controller.dashboard.AdminHandler"), 93 | (r"^/download/(.*)", "controller.download.DownloadHandler", {"path": "./download/"}) 94 | ], **setting) 95 | 96 | if __name__ == "__main__": 97 | try: 98 | application.listen(tornado.options.options.port) 99 | tornado.ioloop.IOLoop.instance().start() 100 | except: 101 | import traceback 102 | print traceback.print_exc() 103 | finally: 104 | sys.exit(0) -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'phithon' 2 | -------------------------------------------------------------------------------- /model/article.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | from model.base import BaseModel 4 | 5 | class ArticleModel(BaseModel): 6 | __table__ = "article" 7 | __invalid__ = { 8 | "title": { 9 | "_name": "标题", 10 | "type": unicode, 11 | "max_length": 200, 12 | "min_length": 1 13 | }, 14 | "charge": { 15 | "_name": "金币数值", 16 | "type": int, 17 | "min": 0, 18 | "max": 100 19 | }, 20 | "freebegin": { 21 | "_name": "开始时间", 22 | "type": int, 23 | "min": 0, 24 | "max": 24 25 | }, 26 | "freeend": { 27 | "_name": "结束时间", 28 | "type": int, 29 | "min": 0, 30 | "max": 24 31 | } 32 | } 33 | __msg__ = { 34 | "type": "%s类型错误", 35 | "max_length": "%s长度太长", 36 | "min_length": "%s长度太短", 37 | "max": "%s过大", 38 | "min": "%s过小" 39 | } 40 | error_msg = "" -------------------------------------------------------------------------------- /model/base.py: -------------------------------------------------------------------------------- 1 | __author__ = 'phithon' 2 | import re 3 | 4 | class BaseModel: 5 | def __call__(self, *args, **kwargs): 6 | data = args[0] 7 | for (k, value) in data.items(): 8 | if k not in self.__invalid__: 9 | continue 10 | if (not value) and ("_need" not in self.__invalid__[k]): 11 | continue 12 | for (field, limit) in self.__invalid__[k].items(): 13 | if field[0] == "_": continue 14 | func = "_check_%s" % field 15 | if hasattr(self, func): 16 | ret = getattr(self, func)(limit, value) 17 | if not ret: 18 | self.error_msg = self.__msg__[field] % self.__invalid__[k]["_name"] 19 | return False 20 | return True 21 | 22 | def _check_type(self, valid, value): 23 | return type(value) is valid 24 | 25 | def _check_max_length(self, valid, value): 26 | return len(value) <= valid 27 | 28 | def _check_min_length(self, valid, value): 29 | return len(value) >= valid 30 | 31 | def _check_max(self, valid, value): 32 | return value <= valid 33 | 34 | def _check_min(self, valid, value): 35 | return value >= valid 36 | 37 | def _check_email(self, valid, value): 38 | return re.match(r"^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$", value) 39 | 40 | def _check_number(self, valid, value): 41 | return re.match(r"^\d+$", value) 42 | 43 | def _check_url(self, valid, value): 44 | return value.startswith("http://") or value.startswith("https://") 45 | 46 | def _check_pattern(self, valid, value): 47 | return re.match(valid, value) -------------------------------------------------------------------------------- /model/sort.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | from model.base import BaseModel 4 | try: 5 | type(u"a") is unicode 6 | except: 7 | # PY3 8 | unicode = str 9 | 10 | class SortModel(BaseModel): 11 | __table__ = "sort" 12 | __invalid__ = { 13 | "username": { 14 | "_name": "板块名称", 15 | "_need": True, 16 | "type": unicode, 17 | "max_length": 16, 18 | "min_length": 1 19 | }, 20 | "intro": { 21 | "_name": "板块说明", 22 | "type": unicode, 23 | "max_length": 512 24 | } 25 | } 26 | __msg__ = { 27 | "type": "%s类型错误", 28 | "max_length": "%s长度太长", 29 | "min_length": "%s长度太短" 30 | } 31 | error_msg = "" -------------------------------------------------------------------------------- /model/user.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | __author__ = 'phithon' 3 | from model.base import BaseModel 4 | try: 5 | type(u"a") is unicode 6 | except: 7 | # PY3 8 | unicode = str 9 | 10 | class UserModel(BaseModel): 11 | __table__ = "member" 12 | __invalid__ = { 13 | "username": { 14 | "_name": "用户名", 15 | "_need": True, 16 | "type": unicode, 17 | "max_length": 36, 18 | "min_length": 1, 19 | "pattern": ur"^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$" 20 | }, 21 | "money": { 22 | "_name": "金币", 23 | "type": int, 24 | "min": 0 25 | }, 26 | "email": { 27 | "_name": "Email", 28 | "type": unicode, 29 | "max_length": 64, 30 | "email": True 31 | }, 32 | "website": { 33 | "_name": "个人主页", 34 | "type": unicode, 35 | "max_length": 128, 36 | "url": True 37 | }, 38 | "address": { 39 | "_name": "地址", 40 | "type": unicode, 41 | "max_length": 256 42 | }, 43 | "signal": { 44 | "_name": "签名", 45 | "type": unicode, 46 | "max_length": 512 47 | }, 48 | "qq": { 49 | "_name": "QQ", 50 | "type": unicode, 51 | "max_length": 16, 52 | "min_length": 5, 53 | "number": True 54 | } 55 | } 56 | __msg__ = { 57 | "type": "%s类型错误", 58 | "max_length": "%s长度太长", 59 | "min_length": "%s长度太短", 60 | "max": "%s过大", 61 | "min": "%s过小", 62 | "email": "%s格式错误", 63 | "number": "%s必须是数字", 64 | "url": "%s格式错误", 65 | "pattern": "%s格式错误" 66 | } 67 | error_msg = "" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --index-url http://pypi.douban.com/simple/ 2 | --extra-index-url http://mirrors.aliyun.com/pypi/simple/ 3 | --allow-external PIL 4 | --allow-unverified PIL 5 | tornado>=4.1 6 | motor>=0.4 7 | redis>=2.10.3 8 | pymongo==2.8.0 9 | bcrypt>=1.1.1 10 | pyyaml>=3.11 11 | PIL>=1.1.7 12 | wheezy.captcha>=0.1.44 13 | futures>=2.2.0 14 | xxtea>=0.2.1 15 | pycurl -------------------------------------------------------------------------------- /static/assets/ZeroClipboard/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/ZeroClipboard/ZeroClipboard.swf -------------------------------------------------------------------------------- /static/assets/cropbox/css/style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | .container { 3 | width: 400px; 4 | margin: 40px auto 0 auto; 5 | position: relative; 6 | font-family: 微软雅黑; 7 | font-size: 12px; 8 | } 9 | .am-container p { 10 | line-height: 12px; 11 | line-height: 0px; 12 | height: 0px; 13 | margin: 10px; 14 | color: #bbb 15 | } 16 | .action { 17 | width: 400px; 18 | height: 30px; 19 | margin: 10px 0; 20 | } 21 | .cropped { 22 | width: 200px; 23 | border: 1px #ddd solid; 24 | height: 460px; 25 | padding: 4px; 26 | box-shadow: 0px 0px 12px #ddd; 27 | text-align: center; 28 | background-color: #fff; 29 | } 30 | .imageBox { 31 | position: relative; 32 | height: 400px; 33 | width: 400px; 34 | border: 1px solid #aaa; 35 | background: #fff; 36 | overflow: hidden; 37 | background-repeat: no-repeat; 38 | cursor: move; 39 | box-shadow: 4px 4px 12px #B0B0B0; 40 | } 41 | .imageBox .thumbBox { 42 | position: absolute; 43 | top: 50%; 44 | left: 50%; 45 | width: 200px; 46 | height: 200px; 47 | margin-top: -100px; 48 | margin-left: -100px; 49 | box-sizing: border-box; 50 | border: 1px solid rgb(102, 102, 102); 51 | box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.5); 52 | background: none repeat scroll 0% 0% transparent; 53 | } 54 | .imageBox .spinner { 55 | display: none; 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | bottom: 0; 60 | right: 0; 61 | text-align: center; 62 | line-height: 400px; 63 | background: rgba(0,0,0,0.7); 64 | } 65 | .Btnsty_peyton{ 66 | float: right; 67 | width: 66px; 68 | display: inline-block; 69 | margin-bottom: 10px; 70 | height: 57px; 71 | line-height: 57px; 72 | font-size: 20px; 73 | color: #FFFFFF; 74 | margin:0px 2px; 75 | background-color: #f38e81; 76 | border-radius: 3px; 77 | text-decoration: none; 78 | cursor: pointer; 79 | box-shadow: 0px 0px 5px #B0B0B0; 80 | border: 0px #fff solid; 81 | } 82 | .Btnsty_peyton.upbtn { 83 | width: 100%; 84 | margin-top: 410px; 85 | } 86 | /*选择文件上传*/ 87 | .new-contentarea { 88 | width: 165px; 89 | overflow:hidden; 90 | margin: 0 auto; 91 | position:relative;float:left; 92 | } 93 | .new-contentarea label { 94 | width:100%; 95 | height:100%; 96 | display:block; 97 | } 98 | .new-contentarea input[type=file] { 99 | width:188px; 100 | height:60px; 101 | background:#333; 102 | margin: 0 auto; 103 | position:absolute; 104 | right:50%; 105 | margin-right:-94px; 106 | top:0; 107 | right/*\**/:0px\9; 108 | margin-right/*\**/:0px\9; 109 | width/*\**/:10px\9; 110 | opacity:0; 111 | filter:alpha(opacity=0); 112 | z-index:2; 113 | } 114 | a.upload-img{ 115 | width:165px; 116 | display: inline-block; 117 | margin-bottom: 10px; 118 | height:57px; 119 | line-height: 57px; 120 | font-size: 20px; 121 | color: #FFFFFF; 122 | background-color: #f38e81; 123 | border-radius: 3px; 124 | text-decoration:none; 125 | cursor:pointer; 126 | border: 0px #fff solid; 127 | box-shadow: 0px 0px 5px #B0B0B0; 128 | } 129 | a.upload-img:hover{ 130 | background-color: #ec7e70; 131 | } 132 | 133 | .tc{text-align:center;} 134 | .face-64 { 135 | width:64px; 136 | margin-top:4px; 137 | border-radius:64px; 138 | box-shadow:0px 0px 12px #7E7E7E; 139 | } 140 | .face-128 { 141 | width:128px; 142 | margin-top:4px; 143 | border-radius:128px; 144 | box-shadow:0px 0px 12px #7E7E7E; 145 | } 146 | .face-180 { 147 | width:180px; 148 | margin-top:4px; 149 | border-radius:180px; 150 | box-shadow:0px 0px 12px #7E7E7E; 151 | } -------------------------------------------------------------------------------- /static/assets/cropbox/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/cropbox/images/avatar.png -------------------------------------------------------------------------------- /static/assets/css/404.css: -------------------------------------------------------------------------------- 1 | /* reset */ 2 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,dl,dt,dd,ol,nav ul,nav li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;} 3 | article, aside, details, figcaption, figure,footer, header, hgroup, menu, nav, section {display: block;} 4 | ol,ul{list-style:none;margin:0px;padding:0px;} 5 | blockquote,q{quotes:none;} 6 | blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} 7 | table{border-collapse:collapse;border-spacing:0;} 8 | /* start editing from here */ 9 | a{text-decoration:none;} 10 | .txt-rt{text-align:right;}/* text align right */ 11 | .txt-lt{text-align:left;}/* text align left */ 12 | .txt-center{text-align:center;}/* text align center */ 13 | .float-rt{float:right;}/* float right */ 14 | .float-lt{float:left;}/* float left */ 15 | .clear{clear:both;}/* clear float */ 16 | .pos-relative{position:relative;}/* Position Relative */ 17 | .pos-absolute{position:absolute;}/* Position Absolute */ 18 | .vertical-base{ vertical-align:baseline;}/* vertical align baseline */ 19 | .vertical-top{ vertical-align:top;}/* vertical align top */ 20 | .underline{ padding-bottom:5px; border-bottom: 1px solid #eee; margin:0 0 20px 0;}/* Add 5px bottom padding and a underline */ 21 | nav.vertical ul li{ display:block;}/* vertical menu */ 22 | nav.horizontal ul li{ display: inline-block;}/* horizontal menu */ 23 | img{max-width:100%;} 24 | /*end reset* 25 | */ 26 | body{ 27 | background: url(../i/bg1.png); 28 | font-family: "Century Gothic",Arial, Helvetica, sans-serif; 29 | } 30 | .content p{ 31 | margin: 18px 0px 45px 0px; 32 | } 33 | .content p{ 34 | font-family: "Century Gothic"; 35 | font-size:2em; 36 | color:#666; 37 | text-align:center; 38 | } 39 | .content p span,.logo h1 a{ 40 | color:#e54040; 41 | } 42 | .content{ 43 | text-align:center; 44 | padding:115px 0px 0px 0px; 45 | } 46 | .content a{ 47 | color:#fff; 48 | font-family: "Century Gothic"; 49 | background: #666666; /* Old browsers */ 50 | background: -moz-linear-gradient(top, #666666 0%, #666666 100%); /* FF3.6+ */ 51 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#666666), color-stop(100%,#666666)); /* Chrome,Safari4+ */ 52 | background: -webkit-linear-gradient(top, #666666 0%,#666666 100%); /* Chrome10+,Safari5.1+ */ 53 | background: -o-linear-gradient(top, #666666 0%,#666666 100%); /* Opera 11.10+ */ 54 | background: -ms-linear-gradient(top, #666666 0%,#666666 100%); /* IE10+ */ 55 | background: linear-gradient(to bottom, #666666 0%,#666666 100%); /* W3C */ 56 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#666666', endColorstr='#666666',GradientType=0 ); /* IE6-9 */ 57 | padding: 15px 20px; 58 | border-radius: 1em; 59 | } 60 | .content a:hover{ 61 | color:#e54040; 62 | } 63 | .logo{ 64 | text-align:center; 65 | -webkit-box-shadow: 0 8px 6px -6px rgb(97, 97, 97); 66 | -moz-box-shadow: 0 8px 6px -6px rgb(97, 97, 97); 67 | box-shadow: 0 8px 6px -6px rgb(97, 97, 97); 68 | } 69 | .logo h1{ 70 | font-size:2em; 71 | font-family: "Century Gothic"; 72 | background: #666666; /* Old browsers */ 73 | background: -moz-linear-gradient(top, #666666 0%, #666666 100%); /* FF3.6+ */ 74 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#666666), color-stop(100%,#666666)); /* Chrome,Safari4+ */ 75 | background: -webkit-linear-gradient(top, #666666 0%,#666666 100%); /* Chrome10+,Safari5.1+ */ 76 | background: -o-linear-gradient(top, #666666 0%,#666666 100%); /* Opera 11.10+ */ 77 | background: -ms-linear-gradient(top, #666666 0%,#666666 100%); /* IE10+ */ 78 | background: linear-gradient(to bottom, #666666 0%,#666666 100%); /* W3C */ 79 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#666666', endColorstr='#666666',GradientType=0 ); /* IE6-9 */ 80 | padding: 10px 10px 18px 10px; 81 | } 82 | .logo h1 a{ 83 | font-size:1em; 84 | } 85 | .copy-right{ 86 | padding-top:20px; 87 | } 88 | .copy-right p{ 89 | font-size:0.9em; 90 | } 91 | .copy-right p a{ 92 | background:none; 93 | color:#e54040; 94 | padding:0px 0px 5px 0px; 95 | font-size:0.9em; 96 | } 97 | .copy-right p a:hover{ 98 | color:#666; 99 | } 100 | /*------responive-design--------*/ 101 | @media screen and (max-width: 1366px) { 102 | .content { 103 | padding: 58px 0px 0px 0px; 104 | } 105 | } 106 | @media screen and (max-width:1280px) { 107 | .content { 108 | padding: 58px 0px 0px 0px; 109 | } 110 | } 111 | @media screen and (max-width:1024px) { 112 | .content { 113 | padding: 58px 0px 0px 0px; 114 | } 115 | .content p { 116 | font-size: 1.5em; 117 | } 118 | .copy-right p{ 119 | font-size:0.9em; 120 | 121 | } 122 | } 123 | @media screen and (max-width:640px) { 124 | .content { 125 | padding: 58px 0px 0px 0px; 126 | } 127 | .content p { 128 | font-size: 1.3em; 129 | } 130 | .copy-right p{ 131 | font-size:0.9em; 132 | } 133 | } 134 | @media screen and (max-width:460px) { 135 | .content { 136 | padding:20px 0px 0px 0px; 137 | margin:0px 12px; 138 | } 139 | .content p { 140 | font-size:0.9em; 141 | } 142 | .copy-right p{ 143 | font-size:0.8em; 144 | } 145 | } 146 | @media screen and (max-width:320px) { 147 | .content { 148 | padding:30px 0px 0px 0px; 149 | margin:0px 12px; 150 | } 151 | .content a { 152 | padding:10px 15px; 153 | font-size:0.8em; 154 | } 155 | .content p { 156 | margin: 18px 0px 22px 0px; 157 | } 158 | } -------------------------------------------------------------------------------- /static/assets/css/admin.css: -------------------------------------------------------------------------------- 1 | /** 2 | * admin.css 3 | */ 4 | 5 | ul { 6 | margin-top: 0; 7 | } 8 | 9 | .admin-icon-yellow { 10 | color: #ffbe40; 11 | } 12 | 13 | .admin-header { 14 | font-size: 1.4rem; 15 | margin-bottom: 0; 16 | } 17 | 18 | .admin-header-list a:hover :after { 19 | content: none; 20 | } 21 | 22 | .admin-main { 23 | background: #f3f3f3; 24 | } 25 | 26 | .admin-menu { 27 | position: fixed; 28 | bottom: 30px; 29 | right: 20px; 30 | } 31 | 32 | .admin-sidebar { 33 | width: 260px; 34 | min-height: 100%; 35 | float: left; 36 | border-right: 1px solid #cecece; 37 | } 38 | 39 | .admin-sidebar-list { 40 | margin-bottom: 0; 41 | } 42 | 43 | .admin-sidebar-list li a { 44 | color: #5c5c5c; 45 | padding-left: 24px; 46 | } 47 | 48 | .admin-sidebar-list li:first-child { 49 | border-top: none; 50 | } 51 | 52 | .admin-sidebar-sub { 53 | margin-top: 0; 54 | margin-bottom: 0; 55 | box-shadow: 0 16px 8px -15px #e2e2e2 inset; 56 | background: #ececec; 57 | padding-left: 24px; 58 | } 59 | 60 | .admin-sidebar-sub li:first-child { 61 | border-top: 1px solid #dedede; 62 | } 63 | 64 | .admin-sidebar-panel { 65 | margin: 10px; 66 | } 67 | 68 | .admin-content { 69 | width: auto; 70 | overflow: hidden; 71 | height: 100%; 72 | background: #fff; 73 | } 74 | 75 | .admin-content-list { 76 | border: 1px solid #e9ecf1; 77 | margin-top: 0; 78 | } 79 | 80 | .admin-content-list li { 81 | border: 1px solid #e9ecf1; 82 | border-width: 0 1px; 83 | margin-left: -1px; 84 | } 85 | 86 | .admin-content-list li:first-child { 87 | border-left: none; 88 | } 89 | 90 | .admin-content-list li:last-child { 91 | border-right: none; 92 | } 93 | 94 | .admin-content-table a { 95 | color: #535353; 96 | } 97 | .admin-content-file { 98 | margin-bottom: 0; 99 | color: #666; 100 | } 101 | 102 | .admin-content-file p { 103 | margin: 0 0 5px 0; 104 | font-size: 1.4rem; 105 | } 106 | 107 | .admin-content-file li { 108 | padding: 10px 0; 109 | } 110 | 111 | .admin-content-file li:first-child { 112 | border-top: none; 113 | } 114 | 115 | .admin-content-file li:last-child { 116 | border-bottom: none; 117 | } 118 | 119 | .admin-content-file li .am-progress { 120 | margin-bottom: 4px; 121 | } 122 | 123 | .admin-content-file li .am-progress-bar { 124 | line-height: 14px; 125 | } 126 | 127 | .admin-content-task { 128 | margin-bottom: 0; 129 | } 130 | 131 | .admin-content-task li { 132 | padding: 5px 0; 133 | border-color: #eee; 134 | } 135 | 136 | .admin-content-task li:first-child { 137 | border-top: none; 138 | } 139 | 140 | .admin-content-task li:last-child { 141 | border-bottom: none; 142 | } 143 | 144 | .admin-task-meta { 145 | font-size: 1.2rem; 146 | color: #999; 147 | } 148 | 149 | .admin-task-bd { 150 | font-size: 1.4rem; 151 | margin-bottom: 5px; 152 | } 153 | 154 | .admin-content-comment { 155 | margin-bottom: 0; 156 | } 157 | 158 | .admin-content-comment .am-comment-bd { 159 | font-size: 1.4rem; 160 | } 161 | 162 | .admin-content-pagination { 163 | margin-bottom: 0; 164 | } 165 | .admin-content-pagination li a { 166 | padding: 4px 8px; 167 | } 168 | 169 | @media only screen and (min-width: 641px) { 170 | .admin-sidebar { 171 | display: block; 172 | position: static; 173 | background: none; 174 | } 175 | 176 | .admin-offcanvas-bar { 177 | position: static; 178 | width: auto; 179 | background: none; 180 | -webkit-transform: translate3d(0, 0, 0); 181 | -ms-transform: translate3d(0, 0, 0); 182 | transform: translate3d(0, 0, 0); 183 | } 184 | .admin-offcanvas-bar:after { 185 | content: none; 186 | } 187 | } 188 | 189 | @media only screen and (max-width: 640px) { 190 | .admin-sidebar { 191 | width: inherit; 192 | } 193 | 194 | .admin-offcanvas-bar { 195 | background: #f3f3f3; 196 | } 197 | 198 | .admin-offcanvas-bar:after { 199 | background: #BABABA; 200 | } 201 | 202 | .admin-sidebar-list a:hover, .admin-sidebar-list a:active{ 203 | -webkit-transition: background-color .3s ease; 204 | -moz-transition: background-color .3s ease; 205 | -ms-transition: background-color .3s ease; 206 | -o-transition: background-color .3s ease; 207 | transition: background-color .3s ease; 208 | background: #E4E4E4; 209 | } 210 | 211 | .admin-content-list li { 212 | padding: 10px; 213 | border-width: 1px 0; 214 | margin-top: -1px; 215 | } 216 | 217 | .admin-content-list li:first-child { 218 | border-top: none; 219 | } 220 | 221 | .admin-content-list li:last-child { 222 | border-bottom: none; 223 | } 224 | 225 | .admin-form-text { 226 | text-align: left !important; 227 | } 228 | 229 | } 230 | 231 | /* 232 | * user.html css 233 | */ 234 | .user-info { 235 | margin-bottom: 15px; 236 | } 237 | 238 | .user-info .am-progress { 239 | margin-bottom: 4px; 240 | } 241 | 242 | .user-info p { 243 | margin: 5px; 244 | } 245 | 246 | .user-info-order { 247 | font-size: 1.4rem; 248 | } 249 | 250 | /* 251 | * errorLog.html css 252 | */ 253 | 254 | .error-log .am-pre-scrollable { 255 | max-height: 40rem; 256 | } 257 | 258 | /* 259 | * table.html css 260 | */ 261 | 262 | .table-main { 263 | font-size: 1.4rem; 264 | padding: .5rem; 265 | } 266 | 267 | .table-main button { 268 | background: #fff; 269 | } 270 | 271 | .table-check { 272 | width: 30px; 273 | } 274 | 275 | .table-id { 276 | width: 50px; 277 | } 278 | 279 | @media only screen and (max-width: 640px) { 280 | .table-select { 281 | margin-top: 10px; 282 | margin-left: 5px; 283 | } 284 | } 285 | 286 | /* 287 | gallery.html css 288 | */ 289 | 290 | .gallery-list li { 291 | padding: 10px; 292 | } 293 | 294 | .gallery-list a { 295 | color: #666; 296 | } 297 | 298 | .gallery-list a:hover { 299 | color: #3bb4f2; 300 | } 301 | 302 | .gallery-title { 303 | margin-top: 6px; 304 | font-size: 1.4rem; 305 | } 306 | 307 | .gallery-desc { 308 | font-size: 1.2rem; 309 | margin-top: 4px; 310 | } 311 | 312 | .am-form-horizontal #minos-checkbox label.am-checkbox { 313 | padding-top: 0; 314 | } 315 | 316 | /* 317 | 404.html css 318 | */ 319 | 320 | .page-404 { 321 | background: #fff; 322 | border: none; 323 | width: 200px; 324 | margin: 0 auto; 325 | } 326 | -------------------------------------------------------------------------------- /static/assets/css/none.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/css/none.css -------------------------------------------------------------------------------- /static/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/assets/fonts/captcha.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/fonts/captcha.ttf -------------------------------------------------------------------------------- /static/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /static/assets/highlightjs/default.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#f0f0f0;-webkit-text-size-adjust:none}.hljs,.hljs-subst,.hljs-tag .hljs-title,.nginx .hljs-title{color:black}.hljs-string,.hljs-title,.hljs-constant,.hljs-parent,.hljs-tag .hljs-value,.hljs-rule .hljs-value,.hljs-preprocessor,.hljs-pragma,.hljs-name,.haml .hljs-symbol,.ruby .hljs-symbol,.ruby .hljs-symbol .hljs-string,.hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.hljs-addition,.hljs-flow,.hljs-stream,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.tex .hljs-special,.erlang_repl .hljs-function_or_atom,.asciidoc .hljs-header,.markdown .hljs-header,.coffeescript .hljs-attribute{color:#800}.smartquote,.hljs-comment,.hljs-annotation,.diff .hljs-header,.hljs-chunk,.asciidoc .hljs-blockquote,.markdown .hljs-blockquote{color:#888}.hljs-number,.hljs-date,.hljs-regexp,.hljs-literal,.hljs-hexcolor,.smalltalk .hljs-symbol,.smalltalk .hljs-char,.go .hljs-constant,.hljs-change,.lasso .hljs-variable,.makefile .hljs-variable,.asciidoc .hljs-bullet,.markdown .hljs-bullet,.asciidoc .hljs-link_url,.markdown .hljs-link_url{color:#080}.hljs-label,.hljs-javadoc,.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-important,.hljs-pseudo,.hljs-pi,.haml .hljs-bullet,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-formula,.erlang_repl .hljs-reserved,.hljs-prompt,.asciidoc .hljs-link_label,.markdown .hljs-link_label,.vhdl .hljs-attribute,.clojure .hljs-attribute,.asciidoc .hljs-attribute,.lasso .hljs-attribute,.coffeescript .hljs-property,.hljs-phony{color:#88f}.hljs-keyword,.hljs-id,.hljs-title,.hljs-built_in,.css .hljs-tag,.hljs-javadoctag,.hljs-phpdoc,.hljs-dartdoc,.hljs-yardoctag,.smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.hljs-type,.hljs-typename,.tex .hljs-command,.asciidoc .hljs-strong,.markdown .hljs-strong,.hljs-request,.hljs-status{font-weight:bold}.asciidoc .hljs-emphasis,.markdown .hljs-emphasis{font-style:italic}.nginx .hljs-built_in{font-weight:normal}.coffeescript .javascript,.javascript .xml,.lasso .markup,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:0.5} -------------------------------------------------------------------------------- /static/assets/i/app-icon72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/app-icon72x72@2x.png -------------------------------------------------------------------------------- /static/assets/i/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/bg1.png -------------------------------------------------------------------------------- /static/assets/i/error-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/error-img.png -------------------------------------------------------------------------------- /static/assets/i/examples/admin-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/admin-chrome.png -------------------------------------------------------------------------------- /static/assets/i/examples/admin-firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/admin-firefox.png -------------------------------------------------------------------------------- /static/assets/i/examples/admin-ie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/admin-ie.png -------------------------------------------------------------------------------- /static/assets/i/examples/admin-opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/admin-opera.png -------------------------------------------------------------------------------- /static/assets/i/examples/admin-safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/admin-safari.png -------------------------------------------------------------------------------- /static/assets/i/examples/adminPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/adminPage.png -------------------------------------------------------------------------------- /static/assets/i/examples/blogPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/blogPage.png -------------------------------------------------------------------------------- /static/assets/i/examples/landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/landing.png -------------------------------------------------------------------------------- /static/assets/i/examples/landingPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/landingPage.png -------------------------------------------------------------------------------- /static/assets/i/examples/loginPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/loginPage.png -------------------------------------------------------------------------------- /static/assets/i/examples/sidebarPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/examples/sidebarPage.png -------------------------------------------------------------------------------- /static/assets/i/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/favicon.png -------------------------------------------------------------------------------- /static/assets/i/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/logo.png -------------------------------------------------------------------------------- /static/assets/i/startup-640x1096.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/i/startup-640x1096.png -------------------------------------------------------------------------------- /static/assets/js/admin.js: -------------------------------------------------------------------------------- 1 | $(document).on("ready", function(){ 2 | function randomChar(l) { 3 | var x="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 4 | var tmp=""; 5 | var timestamp = new Date().getTime(); 6 | for(var i=0 ; i < l ; i++) { 7 | tmp += x.charAt( Math.ceil( Math.random() * 100000000 ) % x.length ); 8 | } 9 | return tmp; 10 | } 11 | $("#create-key").on("click", function(){ 12 | var key = randomChar(20); 13 | $("input[name='key']").val(key); 14 | }) 15 | if(typeof ZeroClipboard != "undefined"){ 16 | var client = new ZeroClipboard($(".zero-copy")); 17 | client.on( "ready", function( readyEvent ) { 18 | client.on( "copy", function (event) { 19 | var i = event.target.name; 20 | var clipboard = event.clipboardData; 21 | clipboard.setData( "text/plain", $(".invite-" + i).text() ); 22 | }); 23 | }) 24 | } 25 | 26 | $("#invite-act button.act").on("click", function(){ 27 | var act = this.name; 28 | $("#invite-act input[name='action']").val(act); 29 | $("#invite-act").submit(); 30 | }) 31 | }) -------------------------------------------------------------------------------- /static/assets/js/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | 4 | jQuery.cookie = function(name, value, options) { 5 | if (typeof value != 'undefined') { 6 | options = options || {}; 7 | if (value === null) { 8 | value = ''; 9 | options = $.extend({}, options); 10 | options.expires = -1; 11 | } 12 | var expires = ''; 13 | if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { 14 | var date; 15 | if (typeof options.expires == 'number') { 16 | date = new Date(); 17 | date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); 18 | } else { 19 | date = options.expires; 20 | } 21 | expires = '; expires=' + date.toUTCString(); 22 | } 23 | var path = options.path ? '; path=' + (options.path) : ''; 24 | var domain = options.domain ? '; domain=' + (options.domain) : ''; 25 | var secure = options.secure ? '; secure' : ''; 26 | document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); 27 | } else { 28 | var cookieValue = null; 29 | if (document.cookie && document.cookie != '') { 30 | var cookies = document.cookie.split(';'); 31 | for (var i = 0; i < cookies.length; i++) { 32 | var cookie = jQuery.trim(cookies[i]); 33 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 34 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 35 | break; 36 | } 37 | } 38 | } 39 | return cookieValue; 40 | } 41 | }; 42 | 43 | $(function() { 44 | $('#doc-my-tabs').tabs({noSwipe: 1}); 45 | $("[href='#back']").click(function(){ 46 | history.back(-1); 47 | return false; 48 | }) 49 | 50 | var $fullText = $('.admin-fullText'); 51 | $('#admin-fullscreen').on('click', function() { 52 | $.AMUI.fullscreen.toggle(); 53 | }); 54 | 55 | $(document).on($.AMUI.fullscreen.raw.fullscreenchange, function() { 56 | $.AMUI.fullscreen.isFullscreen ? $fullText.text('关闭全屏') : $fullText.text('开启全屏'); 57 | }); 58 | 59 | if($("#login-name").val()){ 60 | $.ajax({ 61 | "url": "/ajax/newmsg", 62 | "type": "post", 63 | "dataType": "json", 64 | "data": { 65 | "_xsrf": $.cookie("_xsrf") 66 | }, 67 | "success": function(data){ 68 | var count = parseInt(data["info"]); 69 | if(count > 0){ 70 | $("#newmsg").text(count); 71 | } 72 | } 73 | }) 74 | } 75 | }); 76 | })(jQuery); -------------------------------------------------------------------------------- /static/assets/js/edit.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | var editor = new Simditor({ 3 | textarea: $('#editor'), 4 | defaultImage: "/static/assets/simditor/images/image.png", 5 | toolbar: [ 6 | 'title', 7 | 'bold', 8 | 'italic', 9 | 'underline', 10 | 'strikethrough', 11 | 'color', 12 | '|', 13 | 'ol', 14 | 'ul', 15 | 'blockquote', 16 | 'code', 17 | 'table', 18 | '|', 19 | 'link', 20 | 'image', 21 | 'hr', 22 | '|', 23 | 'indent', 24 | 'outdent', 25 | '|', 26 | 'source', 27 | ], 28 | upload: { 29 | url: "/uploader", 30 | fileKey: "upload", 31 | connectionCount: 1, 32 | leaveConfirm: '正在上传文件,如果离开上传会自动取消' 33 | } 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /static/assets/js/editpost.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | 3 | }) -------------------------------------------------------------------------------- /static/assets/js/like.js: -------------------------------------------------------------------------------- 1 | $(document).on("ready", function(){ 2 | $(".cancel-like").click(function(t){ 3 | if(!confirm("是否取消赞这篇文章?")) return false; 4 | this.parentNode.submit(); 5 | return false; 6 | }) 7 | $(".cancel-bookmark").click(function(t){ 8 | if(!confirm("是否取消收藏这篇文章?")) return false; 9 | this.parentNode.submit(); 10 | return false; 11 | }) 12 | }) -------------------------------------------------------------------------------- /static/assets/js/message.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $("#readall a").on("click", function(){ 3 | $("#readall").submit(); 4 | return false; 5 | }); 6 | $("#deleteall a").on("click", function(){ 7 | if(confirm("是否删除所有消息,该操作将不能恢复")){ 8 | $("#deleteall").submit(); 9 | } 10 | return false; 11 | }); 12 | }) -------------------------------------------------------------------------------- /static/assets/js/nologin.js: -------------------------------------------------------------------------------- 1 | $("document").ready(function(){ 2 | var captcha = document.getElementById("captcha"); 3 | if(captcha) captcha.style.cursor = "pointer"; 4 | $("#captcha").on("click", function(){ 5 | this.src = "/captcha.png?" + Math.random(); 6 | }); 7 | 8 | invalid_msg = { 9 | "username": "" 10 | } 11 | $('#register-box').validator({ 12 | validate: function(validity){ 13 | if(validity.field.name == "username") 14 | { 15 | return $.ajax({ 16 | "url": "/nologin/checkusername", 17 | "type": "post", 18 | "dataType": "json", 19 | "data": { 20 | "_xsrf": $.AMUI.utils.cookie.get("_xsrf"), 21 | "username": validity.field.value 22 | } 23 | }).then(function(data){ 24 | if(data["status"] == "success"){ 25 | validity.valid = true; 26 | }else{ 27 | validity.valid = false; 28 | invalid_msg[validity.field.name] = data["info"]; 29 | } 30 | return validity; 31 | }); 32 | } 33 | }, 34 | onInValid: function(validity){ 35 | var $field = $(validity.field); 36 | var $group = $field.closest('.am-form-group .alert'); 37 | var $alert = $group.find('.am-alert'); 38 | var msg = invalid_msg[validity.field.name] || ($field.data("error-msg") || this.getValidationMessage(validity)); 39 | if (!$alert.length) { 40 | $alert = $('
').hide(). 41 | appendTo($group); 42 | } 43 | $alert.html(msg).show(); 44 | }, 45 | onValid: function(validity) { 46 | $(validity.field).closest('.am-form-group .alert').find('.am-alert').hide(); 47 | }, 48 | }); 49 | }) -------------------------------------------------------------------------------- /static/assets/js/polyfill/rem.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module: rem - v1.3.2 3 | * Description: A polyfill to parse CSS links and rewrite pixel equivalents into head for non supporting browsers 4 | * Date Built: 2014-07-02 5 | * Copyright (c) 2014 | Chuck Carpenter ,Lucas Serven ; 6 | **/ 7 | !function(a){"use strict";var b=function(){var a=document.createElement("div");return a.style.cssText="font-size: 1rem;",/rem/.test(a.style.fontSize)},c=function(){for(var a=document.getElementsByTagName("link"),b=[],c=0;c0?(r=[],q=[],n=[],d()):g()}},f=function(a,b){for(var c,d=k(a).replace(/\/\*[\s\S]*?\*\//g,""),e=/[\w\d\s\-\/\\\[\]:,.'"*()<>+~%#^$_=|@]+\{[\w\d\s\-\/\\%#:!;,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!;,.'"*()]*\}/g,f=d.match(e),g=/\d*\.?\d+rem/g,h=d.match(g),i=/(.*\/)/,j=i.exec(b)[0],l=/@import (?:url\()?['"]?([^'\)"]*)['"]?\)?[^;]*/gm;null!==(c=l.exec(a));)n.push(j+c[1]);null!==f&&0!==f.length&&(o=o.concat(f),p=p.concat(h))},g=function(){for(var a=/[\w\d\s\-\/\\%#:,.'"*()]+\d*\.?\d+rem[\w\d\s\-\/\\%#:!,.'"*()]*[;}]/g,b=0;b #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b/g, ">") 181 | .replace(/'/g, "'").replace(/"/g, """); 182 | txt = txt.replace(/@([a-zA-Z0-9_\-\u4e00-\u9fa5]+)/g, '@$1'); 183 | txt = txt.replace(/\n/g, "
"); 184 | $(this).html(txt); 185 | }); 186 | (function(){ 187 | var captcha = document.getElementById("captcha"); 188 | if(captcha) captcha.style.cursor = "pointer"; 189 | $("#captcha").on("click", function(){ 190 | this.src = "/captcha.png?" + Math.random(); 191 | }); 192 | $(".bdsharebuttonbox a.am-icon-weixin").click(function(){ 193 | var script = document.createElement("script"); 194 | script.setAttribute("type","text/javascript"); 195 | script.onload = function() { 196 | $("#qrcode").html(""); 197 | new QRCode("qrcode", { 198 | text: location.href, 199 | width: 128, 200 | height: 128, 201 | colorDark : "#000000", 202 | colorLight : "#ffffff", 203 | correctLevel : QRCode.CorrectLevel.H 204 | }); 205 | $("#show-qrcode").modal({ 206 | width: 150 207 | }); 208 | } 209 | script.setAttribute("src", '/static/assets/js/qrcode.min.js?cdnversion='+~(-new Date()/36e5)); 210 | (document.getElementsByTagName('head')[0]||body).appendChild(script); 211 | return false; 212 | }) 213 | 214 | // 百度分享 215 | window._bd_share_config={ 216 | "common":{ 217 | "bdSnsKey":{}, 218 | "bdText":"分享一篇来自Minos的文章", 219 | "bdMini":"2", 220 | "bdMiniList":false, 221 | "bdPic":"", 222 | "bdStyle":"0", 223 | "bdSize":"16", 224 | "bdSign": "off", 225 | }, 226 | "share":{ 227 | "bdCustomStyle": "/static/assets/css/none.css" 228 | } 229 | }; 230 | with(document)0[ 231 | (getElementsByTagName('head')[0]||body) 232 | .appendChild(createElement('script')) 233 | .src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5) 234 | ]; 235 | 236 | // 百度分享 237 | })(); 238 | }); -------------------------------------------------------------------------------- /static/assets/js/publish.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | var storage_name = "savetext"; 3 | var editor = new Simditor({ 4 | textarea: $('#editor'), 5 | defaultImage: "/static/assets/simditor/images/image.png", 6 | toolbar: [ 7 | 'title', 8 | 'bold', 9 | 'italic', 10 | 'underline', 11 | 'strikethrough', 12 | 'color', 13 | '|', 14 | 'ol', 15 | 'ul', 16 | 'blockquote', 17 | 'code', 18 | 'table', 19 | '|', 20 | 'link', 21 | 'image', 22 | 'hr', 23 | '|', 24 | 'indent', 25 | 'outdent', 26 | '|', 27 | 'source', 28 | ], 29 | upload: { 30 | url: "/uploader", 31 | fileKey: "upload", 32 | connectionCount: 1, 33 | leaveConfirm: '正在上传文件,如果离开上传会自动取消' 34 | } 35 | }); 36 | $("#publish").click(function(){ 37 | var title = $("[name='title']").val(); 38 | if(!title){ 39 | $("#title-form").addClass("am-form-error").addClass("am-form-feedback"); 40 | $("[name='title']").attr("placeholder", "标题不能为空"); 41 | return ; 42 | } 43 | if(window.localStorage){ 44 | window.localStorage.clear(storage_name); 45 | } 46 | $("#minos-pulish").submit(); 47 | }); 48 | if(window.localStorage){ 49 | $("#save").click(function(){ 50 | var text = editor.getValue(); 51 | window.localStorage.setItem(storage_name, text); 52 | }); 53 | var text = window.localStorage.getItem(storage_name); 54 | if(text){ 55 | editor.setValue(text); 56 | } 57 | setInterval(function(){ 58 | $("#save").click(); 59 | }, 10000); 60 | }else{ 61 | $("#save").click(function(){ 62 | alert("你的浏览器不支持临时存储"); 63 | }); 64 | } 65 | 66 | }) -------------------------------------------------------------------------------- /static/assets/simditor/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/assets/simditor/images/image.png -------------------------------------------------------------------------------- /static/assets/simditor/scripts/hotkeys.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module. 4 | define('simple-hotkeys', ["jquery", 5 | "simple-module"], function ($, SimpleModule) { 6 | return (root.returnExportsGlobal = factory($, SimpleModule)); 7 | }); 8 | } else if (typeof exports === 'object') { 9 | // Node. Does not work with strict CommonJS, but 10 | // only CommonJS-like enviroments that support module.exports, 11 | // like Node. 12 | module.exports = factory(require("jquery"), 13 | require("simple-module")); 14 | } else { 15 | root.simple = root.simple || {}; 16 | root.simple['hotkeys'] = factory(jQuery, 17 | SimpleModule); 18 | } 19 | }(this, function ($, SimpleModule) { 20 | 21 | var Hotkeys, hotkeys, 22 | __hasProp = {}.hasOwnProperty, 23 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 24 | 25 | Hotkeys = (function(_super) { 26 | __extends(Hotkeys, _super); 27 | 28 | function Hotkeys() { 29 | return Hotkeys.__super__.constructor.apply(this, arguments); 30 | } 31 | 32 | Hotkeys.count = 0; 33 | 34 | Hotkeys.keyNameMap = { 35 | 8: "Backspace", 36 | 9: "Tab", 37 | 13: "Enter", 38 | 16: "Shift", 39 | 17: "Control", 40 | 18: "Alt", 41 | 19: "Pause", 42 | 20: "CapsLock", 43 | 27: "Esc", 44 | 32: "Spacebar", 45 | 33: "PageUp", 46 | 34: "PageDown", 47 | 35: "End", 48 | 36: "Home", 49 | 37: "Left", 50 | 38: "Up", 51 | 39: "Right", 52 | 40: "Down", 53 | 45: "Insert", 54 | 46: "Del", 55 | 91: "Meta", 56 | 93: "Meta", 57 | 48: "0", 58 | 49: "1", 59 | 50: "2", 60 | 51: "3", 61 | 52: "4", 62 | 53: "5", 63 | 54: "6", 64 | 55: "7", 65 | 56: "8", 66 | 57: "9", 67 | 65: "A", 68 | 66: "B", 69 | 67: "C", 70 | 68: "D", 71 | 69: "E", 72 | 70: "F", 73 | 71: "G", 74 | 72: "H", 75 | 73: "I", 76 | 74: "J", 77 | 75: "K", 78 | 76: "L", 79 | 77: "M", 80 | 78: "N", 81 | 79: "O", 82 | 80: "P", 83 | 81: "Q", 84 | 82: "R", 85 | 83: "S", 86 | 84: "T", 87 | 85: "U", 88 | 86: "V", 89 | 87: "W", 90 | 88: "X", 91 | 89: "Y", 92 | 90: "Z", 93 | 96: "0", 94 | 97: "1", 95 | 98: "2", 96 | 99: "3", 97 | 100: "4", 98 | 101: "5", 99 | 102: "6", 100 | 103: "7", 101 | 104: "8", 102 | 105: "9", 103 | 106: "Multiply", 104 | 107: "Add", 105 | 109: "Subtract", 106 | 110: "Decimal", 107 | 111: "Divide", 108 | 112: "F1", 109 | 113: "F2", 110 | 114: "F3", 111 | 115: "F4", 112 | 116: "F5", 113 | 117: "F6", 114 | 118: "F7", 115 | 119: "F8", 116 | 120: "F9", 117 | 121: "F10", 118 | 122: "F11", 119 | 123: "F12", 120 | 124: "F13", 121 | 125: "F14", 122 | 126: "F15", 123 | 127: "F16", 124 | 128: "F17", 125 | 129: "F18", 126 | 130: "F19", 127 | 131: "F20", 128 | 132: "F21", 129 | 133: "F22", 130 | 134: "F23", 131 | 135: "F24", 132 | 59: ";", 133 | 61: "=", 134 | 186: ";", 135 | 187: "=", 136 | 188: ",", 137 | 190: ".", 138 | 191: "/", 139 | 192: "`", 140 | 219: "[", 141 | 220: "\\", 142 | 221: "]", 143 | 222: "'" 144 | }; 145 | 146 | Hotkeys.aliases = { 147 | "escape": "esc", 148 | "delete": "del", 149 | "return": "enter", 150 | "ctrl": "control", 151 | "space": "spacebar", 152 | "ins": "insert", 153 | "cmd": "meta", 154 | "command": "meta", 155 | "wins": "meta", 156 | "windows": "meta" 157 | }; 158 | 159 | Hotkeys.normalize = function(shortcut) { 160 | var i, key, keyname, keys, _i, _len; 161 | keys = shortcut.toLowerCase().replace(/\s+/gi, "").split("+"); 162 | for (i = _i = 0, _len = keys.length; _i < _len; i = ++_i) { 163 | key = keys[i]; 164 | keys[i] = this.aliases[key] || key; 165 | } 166 | keyname = keys.pop(); 167 | keys.sort().push(keyname); 168 | return keys.join("_"); 169 | }; 170 | 171 | Hotkeys.prototype.opts = { 172 | el: document 173 | }; 174 | 175 | Hotkeys.prototype._init = function() { 176 | this.id = ++this.constructor.count; 177 | this._map = {}; 178 | this._delegate = typeof this.opts.el === "string" ? document : this.opts.el; 179 | return $(this._delegate).on("keydown.simple-hotkeys-" + this.id, this.opts.el, (function(_this) { 180 | return function(e) { 181 | var _ref; 182 | return (_ref = _this._getHander(e)) != null ? _ref.call(_this, e) : void 0; 183 | }; 184 | })(this)); 185 | }; 186 | 187 | Hotkeys.prototype._getHander = function(e) { 188 | var keyname, shortcut; 189 | if (!(keyname = this.constructor.keyNameMap[e.which])) { 190 | return; 191 | } 192 | shortcut = ""; 193 | if (e.altKey) { 194 | shortcut += "alt_"; 195 | } 196 | if (e.ctrlKey) { 197 | shortcut += "control_"; 198 | } 199 | if (e.metaKey) { 200 | shortcut += "meta_"; 201 | } 202 | if (e.shiftKey) { 203 | shortcut += "shift_"; 204 | } 205 | shortcut += keyname.toLowerCase(); 206 | return this._map[shortcut]; 207 | }; 208 | 209 | Hotkeys.prototype.respondTo = function(subject) { 210 | if (typeof subject === 'string') { 211 | return this._map[this.constructor.normalize(subject)] != null; 212 | } else { 213 | return this._getHander(subject) != null; 214 | } 215 | }; 216 | 217 | Hotkeys.prototype.add = function(shortcut, handler) { 218 | this._map[this.constructor.normalize(shortcut)] = handler; 219 | return this; 220 | }; 221 | 222 | Hotkeys.prototype.remove = function(shortcut) { 223 | delete this._map[this.constructor.normalize(shortcut)]; 224 | return this; 225 | }; 226 | 227 | Hotkeys.prototype.destroy = function() { 228 | $(this._delegate).off(".simple-hotkeys-" + this.id); 229 | this._map = {}; 230 | return this; 231 | }; 232 | 233 | return Hotkeys; 234 | 235 | })(SimpleModule); 236 | 237 | hotkeys = function(opts) { 238 | return new Hotkeys(opts); 239 | }; 240 | 241 | 242 | return hotkeys; 243 | 244 | 245 | })); 246 | 247 | -------------------------------------------------------------------------------- /static/assets/simditor/scripts/hotkeys.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd?define("simple-hotkeys",["jquery","simple-module"],function(c,d){return a.returnExportsGlobal=b(c,d)}):"object"==typeof exports?module.exports=b(require("jquery"),require("simple-module")):(a.simple=a.simple||{},a.simple.hotkeys=b(jQuery,SimpleModule))}(this,function(a,b){var c,d,e={}.hasOwnProperty,f=function(a,b){function c(){this.constructor=a}for(var d in b)e.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};return c=function(b){function c(){return c.__super__.constructor.apply(this,arguments)}return f(c,b),c.count=0,c.keyNameMap={8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Spacebar",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",45:"Insert",46:"Del",91:"Meta",93:"Meta",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"Multiply",107:"Add",109:"Subtract",110:"Decimal",111:"Divide",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",124:"F13",125:"F14",126:"F15",127:"F16",128:"F17",129:"F18",130:"F19",131:"F20",132:"F21",133:"F22",134:"F23",135:"F24",59:";",61:"=",186:";",187:"=",188:",",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},c.aliases={escape:"esc","delete":"del","return":"enter",ctrl:"control",space:"spacebar",ins:"insert",cmd:"meta",command:"meta",wins:"meta",windows:"meta"},c.normalize=function(a){var b,c,d,e,f,g;for(e=a.toLowerCase().replace(/\s+/gi,"").split("+"),b=f=0,g=e.length;g>f;b=++f)c=e[b],e[b]=this.aliases[c]||c;return d=e.pop(),e.sort().push(d),e.join("_")},c.prototype.opts={el:document},c.prototype._init=function(){return this.id=++this.constructor.count,this._map={},this._delegate="string"==typeof this.opts.el?document:this.opts.el,a(this._delegate).on("keydown.simple-hotkeys-"+this.id,this.opts.el,function(a){return function(b){var c;return null!=(c=a._getHander(b))?c.call(a,b):void 0}}(this))},c.prototype._getHander=function(a){var b,c;if(b=this.constructor.keyNameMap[a.which])return c="",a.altKey&&(c+="alt_"),a.ctrlKey&&(c+="control_"),a.metaKey&&(c+="meta_"),a.shiftKey&&(c+="shift_"),c+=b.toLowerCase(),this._map[c]},c.prototype.respondTo=function(a){return"string"==typeof a?null!=this._map[this.constructor.normalize(a)]:null!=this._getHander(a)},c.prototype.add=function(a,b){return this._map[this.constructor.normalize(a)]=b,this},c.prototype.remove=function(a){return delete this._map[this.constructor.normalize(a)],this},c.prototype.destroy=function(){return a(this._delegate).off(".simple-hotkeys-"+this.id),this._map={},this},c}(b),d=function(a){return new c(a)}}); -------------------------------------------------------------------------------- /static/assets/simditor/scripts/module.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module. 4 | define('simple-module', ["jquery"], function ($) { 5 | return (root.returnExportsGlobal = factory($)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like enviroments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery")); 12 | } else { 13 | root['SimpleModule'] = factory(jQuery); 14 | } 15 | }(this, function ($) { 16 | 17 | var Module, 18 | __slice = [].slice; 19 | 20 | Module = (function() { 21 | Module.extend = function(obj) { 22 | var key, val, _ref; 23 | if (!((obj != null) && typeof obj === 'object')) { 24 | return; 25 | } 26 | for (key in obj) { 27 | val = obj[key]; 28 | if (key !== 'included' && key !== 'extended') { 29 | this[key] = val; 30 | } 31 | } 32 | return (_ref = obj.extended) != null ? _ref.call(this) : void 0; 33 | }; 34 | 35 | Module.include = function(obj) { 36 | var key, val, _ref; 37 | if (!((obj != null) && typeof obj === 'object')) { 38 | return; 39 | } 40 | for (key in obj) { 41 | val = obj[key]; 42 | if (key !== 'included' && key !== 'extended') { 43 | this.prototype[key] = val; 44 | } 45 | } 46 | return (_ref = obj.included) != null ? _ref.call(this) : void 0; 47 | }; 48 | 49 | Module.connect = function(cls) { 50 | if (typeof cls !== 'function') { 51 | return; 52 | } 53 | if (!cls.pluginName) { 54 | throw new Error('Module.connect: cannot connect plugin without pluginName'); 55 | return; 56 | } 57 | cls.prototype._connected = true; 58 | if (!this._connectedClasses) { 59 | this._connectedClasses = []; 60 | } 61 | this._connectedClasses.push(cls); 62 | if (cls.pluginName) { 63 | return this[cls.pluginName] = cls; 64 | } 65 | }; 66 | 67 | Module.prototype.opts = {}; 68 | 69 | function Module(opts) { 70 | var cls, instance, instances, name, _base, _i, _len; 71 | this.opts = $.extend({}, this.opts, opts); 72 | (_base = this.constructor)._connectedClasses || (_base._connectedClasses = []); 73 | instances = (function() { 74 | var _i, _len, _ref, _results; 75 | _ref = this.constructor._connectedClasses; 76 | _results = []; 77 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 78 | cls = _ref[_i]; 79 | name = cls.pluginName.charAt(0).toLowerCase() + cls.pluginName.slice(1); 80 | if (cls.prototype._connected) { 81 | cls.prototype._module = this; 82 | } 83 | _results.push(this[name] = new cls()); 84 | } 85 | return _results; 86 | }).call(this); 87 | if (this._connected) { 88 | this.opts = $.extend({}, this.opts, this._module.opts); 89 | } else { 90 | this._init(); 91 | for (_i = 0, _len = instances.length; _i < _len; _i++) { 92 | instance = instances[_i]; 93 | if (typeof instance._init === "function") { 94 | instance._init(); 95 | } 96 | } 97 | } 98 | this.trigger('initialized'); 99 | } 100 | 101 | Module.prototype._init = function() {}; 102 | 103 | Module.prototype.on = function() { 104 | var args, _ref; 105 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 106 | (_ref = $(this)).on.apply(_ref, args); 107 | return this; 108 | }; 109 | 110 | Module.prototype.one = function() { 111 | var args, _ref; 112 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 113 | (_ref = $(this)).one.apply(_ref, args); 114 | return this; 115 | }; 116 | 117 | Module.prototype.off = function() { 118 | var args, _ref; 119 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 120 | (_ref = $(this)).off.apply(_ref, args); 121 | return this; 122 | }; 123 | 124 | Module.prototype.trigger = function() { 125 | var args, _ref; 126 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 127 | (_ref = $(this)).trigger.apply(_ref, args); 128 | return this; 129 | }; 130 | 131 | Module.prototype.triggerHandler = function() { 132 | var args, _ref; 133 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 134 | return (_ref = $(this)).triggerHandler.apply(_ref, args); 135 | }; 136 | 137 | Module.prototype._t = function() { 138 | var args, _ref; 139 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 140 | return (_ref = this.constructor)._t.apply(_ref, args); 141 | }; 142 | 143 | Module._t = function() { 144 | var args, key, result, _ref; 145 | key = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 146 | result = ((_ref = this.i18n[this.locale]) != null ? _ref[key] : void 0) || ''; 147 | if (!(args.length > 0)) { 148 | return result; 149 | } 150 | result = result.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) { 151 | if (position) { 152 | return p + args[parseInt(position) - 1]; 153 | } else { 154 | return p + args.shift(); 155 | } 156 | }); 157 | return result.replace(/%%s/g, '%s'); 158 | }; 159 | 160 | Module.i18n = { 161 | 'zh-CN': {} 162 | }; 163 | 164 | Module.locale = 'zh-CN'; 165 | 166 | return Module; 167 | 168 | })(); 169 | 170 | 171 | return Module; 172 | 173 | 174 | })); 175 | -------------------------------------------------------------------------------- /static/assets/simditor/scripts/module.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd?define("simple-module",["jquery"],function(c){return a.returnExportsGlobal=b(c)}):"object"==typeof exports?module.exports=b(require("jquery")):a.SimpleModule=b(jQuery)}(this,function(a){var b,c=[].slice;return b=function(){function b(b){var c,d,e,f,g,h,i;if(this.opts=a.extend({},this.opts,b),(g=this.constructor)._connectedClasses||(g._connectedClasses=[]),e=function(){var a,b,d,e;for(d=this.constructor._connectedClasses,e=[],a=0,b=d.length;b>a;a++)c=d[a],f=c.pluginName.charAt(0).toLowerCase()+c.pluginName.slice(1),c.prototype._connected&&(c.prototype._module=this),e.push(this[f]=new c);return e}.call(this),this._connected)this.opts=a.extend({},this.opts,this._module.opts);else for(this._init(),h=0,i=e.length;i>h;h++)d=e[h],"function"==typeof d._init&&d._init();this.trigger("initialized")}return b.extend=function(a){var b,c,d;if(null!=a&&"object"==typeof a){for(b in a)c=a[b],"included"!==b&&"extended"!==b&&(this[b]=c);return null!=(d=a.extended)?d.call(this):void 0}},b.include=function(a){var b,c,d;if(null!=a&&"object"==typeof a){for(b in a)c=a[b],"included"!==b&&"extended"!==b&&(this.prototype[b]=c);return null!=(d=a.included)?d.call(this):void 0}},b.connect=function(a){if("function"==typeof a){if(!a.pluginName)throw new Error("Module.connect: cannot connect plugin without pluginName");return a.prototype._connected=!0,this._connectedClasses||(this._connectedClasses=[]),this._connectedClasses.push(a),a.pluginName?this[a.pluginName]=a:void 0}},b.prototype.opts={},b.prototype._init=function(){},b.prototype.on=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).on.apply(d,b),this},b.prototype.one=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).one.apply(d,b),this},b.prototype.off=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).off.apply(d,b),this},b.prototype.trigger=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).trigger.apply(d,b),this},b.prototype.triggerHandler=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).triggerHandler.apply(d,b)},b.prototype._t=function(){var a,b;return a=1<=arguments.length?c.call(arguments,0):[],(b=this.constructor)._t.apply(b,a)},b._t=function(){var a,b,d,e;return b=arguments[0],a=2<=arguments.length?c.call(arguments,1):[],d=(null!=(e=this.i18n[this.locale])?e[b]:void 0)||"",a.length>0?(d=d.replace(/([^%]|^)%(?:(\d+)\$)?s/g,function(b,c,d){return d?c+a[parseInt(d)-1]:c+a.shift()}),d.replace(/%%s/g,"%s")):d},b.i18n={"zh-CN":{}},b.locale="zh-CN",b}()}); -------------------------------------------------------------------------------- /static/assets/simditor/scripts/uploader.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd?define("simple-uploader",["jquery","simple-module"],function(c,d){return a.returnExportsGlobal=b(c,d)}):"object"==typeof exports?module.exports=b(require("jquery"),require("simple-module")):(a.simple=a.simple||{},a.simple.uploader=b(jQuery,SimpleModule))}(this,function(a,b){var c,d,e={}.hasOwnProperty,f=function(a,b){function c(){this.constructor=a}for(var d in b)e.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};return c=function(b){function c(){return c.__super__.constructor.apply(this,arguments)}return f(c,b),c.count=0,c.prototype.opts={url:"",params:null,fileKey:"upload_file",connectionCount:3},c.prototype._init=function(){return this.files=[],this.queue=[],this.id=++c.count,this.on("uploadcomplete",function(b){return function(c,d){return b.files.splice(a.inArray(d,b.files),1),b.queue.length>0&&b.files.lengthf;f++)d=b[f],this.upload(d,c);else a(b).is("input:file")?(e=a(b).attr("name"),e&&(c.fileKey=e),this.upload(a.makeArray(a(b)[0].files),c)):b.id&&b.obj||(b=this.getFile(b));if(b&&b.obj){if(a.extend(b,c),this.files.length>=this.opts.connectionCount)return void this.queue.push(b);if(this.triggerHandler("beforeupload",[b])!==!1)return this.files.push(b),this._xhrUpload(b),this.uploading=!0}}},c.prototype.getFile=function(a){var b,c,d;return a instanceof window.File||a instanceof window.Blob?(b=null!=(c=a.fileName)?c:a.name,{id:this.generateId(),url:this.opts.url,params:this.opts.params,fileKey:this.opts.fileKey,name:b,size:null!=(d=a.fileSize)?d:a.size,ext:b?b.split(".").pop().toLowerCase():"",obj:a}):null},c.prototype._xhrUpload=function(b){var c,d,e,f;if(c=new FormData,c.append(b.fileKey,b.obj),c.append("original_filename",b.name),b.params){f=b.params;for(d in f)e=f[d],c.append(d,e)}return b.xhr=a.ajax({url:b.url,data:c,processData:!1,contentType:!1,type:"POST",headers:{"X-File-Name":encodeURIComponent(b.name)},xhr:function(){var b;return b=a.ajaxSettings.xhr(),b&&(b.upload.onprogress=function(a){return function(b){return a.progress(b)}}(this)),b},progress:function(a){return function(c){return c.lengthComputable?a.trigger("uploadprogress",[b,c.loaded,c.total]):void 0}}(this),error:function(a){return function(c,d){return a.trigger("uploaderror",[b,c,d])}}(this),success:function(c){return function(d){return c.trigger("uploadprogress",[b,b.size,b.size]),c.trigger("uploadsuccess",[b,d]),a(document).trigger("uploadsuccess",[b,d,c])}}(this),complete:function(a){return function(c){return a.trigger("uploadcomplete",[b,c.responseText])}}(this)})},c.prototype.cancel=function(a){var b,c,d,e;if(!a.id)for(e=this.files,c=0,d=e.length;d>c;c++)if(b=e[c],b.id===1*a){a=b;break}return this.trigger("uploadcancel",[a]),a.xhr&&a.xhr.abort(),a.xhr=null},c.prototype.readImageFile=function(b,c){var d,e;if(a.isFunction(c))return e=new Image,e.onload=function(){return c(e)},e.onerror=function(){return c()},window.FileReader&&FileReader.prototype.readAsDataURL&&/^image/.test(b.type)?(d=new FileReader,d.onload=function(a){return e.src=a.target.result},d.readAsDataURL(b)):c()},c.prototype.destroy=function(){var b,c,d,e;for(this.queue.length=0,e=this.files,c=0,d=e.length;d>c;c++)b=e[c],this.cancel(b);return a(window).off(".uploader-"+this.id),a(document).off(".uploader-"+this.id)},c.i18n={"zh-CN":{leaveConfirm:"正在上传文件,如果离开上传会自动取消"}},c.locale="zh-CN",c}(b),d=function(a){return new c(a)}}); -------------------------------------------------------------------------------- /static/assets/simditor/styles/simditor.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import 'fonticon'; 4 | @import 'editor'; 5 | -------------------------------------------------------------------------------- /static/face/guest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/static/face/guest.png -------------------------------------------------------------------------------- /templates/404.htm: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | Oooops, Page not found 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 23 | 24 | 25 |
26 | 27 |

hh.....You Requested the page that is no longer There.

28 | Back To Home 29 |
30 | 31 |
32 |
33 | 34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /templates/admin/base.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block header_title %}Site Title{% end %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% block header_static %}{% end %} 17 | 18 | 19 | 23 | 24 |
25 |
26 | Minos 后台管理面板 27 |
28 | 29 | 30 | 31 |
32 | 33 | 36 |
37 |
38 | 39 | {% block body %}Hello world{% end %} 40 | 41 |
42 |
43 |

© 2015 Minos, Inc. Licensed under MPL license.

44 |
45 | 46 | 47 | 48 | 49 | 50 | {% block footer_static %}{% end %} 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /templates/admin/error.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}提示 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
提示页面 / {{ error_title }}
13 |
14 | 15 |
16 |
17 |
18 | 19 |

{{ error_info }}

20 |
21 |

点击返回...

22 |
23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 | 31 | 32 | {% end %} -------------------------------------------------------------------------------- /templates/admin/index.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}后台管理首页{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 站点统计
13 |
14 | 15 | 21 | 22 |
23 |
新注册的用户
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% set i = 1 %} 36 | {% for user in newusers %} 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | {% set i += 1 %} 47 | {% end %} 48 | 49 |
ID用户名最近登录时间金币管理
{{ i }}{{ user["username"] }}{{ humantime(user.get('logintime', user.get('time'))) }}{{ user['money'] }} 43 | 查看详情 44 |
50 |
51 |
52 | 53 |
54 | 55 | 56 |
57 | 58 | 59 | 60 | {% end %} -------------------------------------------------------------------------------- /templates/admin/invite.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}邀请码管理 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 邀请码列表
13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 | 23 | 32 |
33 |
34 | 35 | {% raw xsrf_form_html() %} 36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {% set i = 1 %} 47 | {% for invite in invites %} 48 | 49 | 50 | 51 | 52 | 59 | 70 | 71 | {% set i += 1 %} 72 | {% end %} 73 | 74 |
ID邀请码生成时间使用者管理
{{ i }}{{ invite["code"] }}{{ humantime(invite.get('time')) }} 53 | {% if invite.get('used') %} 54 | {{ invite['user'] }} 55 | {% else %} 56 | 尚未使用 57 | {% end %} 58 | 60 |
61 |
62 | 63 | 64 | 65 | 66 |
67 | {% raw xsrf_form_html() %} 68 |
69 |
75 | {% raw pagenav(count = count, url = '/manage/invite/%d', each = each, now = page, 76 | end = '
') %} 77 |
78 |
79 |
80 | 81 |
82 | 83 | 84 |
85 | 86 | 87 | 88 | {% end %} 89 | {% block footer_static %} 90 | 91 | {% end %} -------------------------------------------------------------------------------- /templates/admin/newsort.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}新增板块 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 13 | 板块 / 14 | 新增板块
15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | {% raw xsrf_form_html() %} 55 |
56 |
57 |
58 | 59 |
60 | 61 | 62 |
63 | 64 | 65 | 66 | {% end %} -------------------------------------------------------------------------------- /templates/admin/setting.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}网站设置 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 13 | 网站设置
14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |

网站设置

24 | 25 |
26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 | 41 |
42 | 43 |
44 | 45 | 重新设置后网站所有用户将需要重新登录 46 |
47 |
48 | 49 |
50 | 51 |
52 | 55 | 59 | 63 |
64 |
65 | 66 |
67 | 68 |
69 | 70 | 新用户注册后送金币 71 |
72 |
73 | 74 |
75 | 76 |
77 | 82 |
83 |
84 | 85 |
86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | 94 |
95 | 96 |
97 |
98 | 99 |
100 |
101 | 102 |
103 |
104 | {% raw xsrf_form_html() %} 105 |
106 |
107 |
108 | 109 |
110 | 111 | 112 |
113 | 114 | 115 | 116 | {% end %} -------------------------------------------------------------------------------- /templates/admin/sidebar.htm: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 17 | 18 |
19 |
20 |

公告

21 |

这是Minos第一版的论坛,各种功能尚待完善

22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /templates/admin/sort.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}板块(节点)管理 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 板块(节点)管理
13 |
14 | 15 |
16 |
17 |
18 |  添加  19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% set i = 1 %} 28 | {% for sort in sorts %} 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | {% set i += 1 %} 39 | {% end %} 40 | 41 |
ID版块名称首页显示文章数量管理
{{ i }}{{ sort['name'] }}{% if sort['show'] %}是{% else %}否{% end %}{{ sort.get('article') }} 35 | 查看详情 36 |
42 | {% raw pagenav(count = count, url = '/manage/sort/%d', each = each, now = page, 43 | end = '
') %} 44 |
45 |
46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 | 54 | {% end %} -------------------------------------------------------------------------------- /templates/admin/sortdetail.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}板块 {{ sort["name"] }} 管理 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 13 | 板块 / 14 | 板块 {{ sort["name"] }} 管理
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |

板块详情

25 |
26 |
27 |

帖子数量:{{ sort['article'] }}

28 |
29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 | 51 |
52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 |
60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | {% raw xsrf_form_html() %} 68 |
69 |
70 |
71 | 72 |
73 | 74 | 75 |
76 | 77 | 78 | 79 | {% end %} -------------------------------------------------------------------------------- /templates/admin/userdetail.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}{{ user["username"] }}的资料 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / {{ user['username'] }} 的资料
13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |

用户头像

26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 |
48 | 49 |
50 | 51 |
52 | 53 | 20为管理员,0为普通用户,-1为禁用 54 |
55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 | 66 |
67 | 68 |
69 |
70 | 71 |
72 | 73 |
74 | 75 |
76 |
77 | 78 |
79 | 80 |
81 | 82 |
83 |
84 | 85 |
86 | 87 |
88 | 89 |
90 |
91 | 92 |
93 | 94 |
95 | 96 |
97 |
98 | 99 |
100 | 101 |
102 | 103 |
104 |
105 | 106 |
107 | 108 |
109 | 110 |
111 |
112 | 113 |
114 | 115 |
116 | 117 |
118 |
119 | 120 |
121 |
122 | 123 |
124 |
125 | 126 | {% raw xsrf_form_html() %} 127 |
128 |
129 |
130 | 131 |
132 | 133 | 134 |
135 | 136 | 137 | 138 | {% end %} -------------------------------------------------------------------------------- /templates/admin/userlist.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | {% block header_title %}用户管理 - 后台管理页面{% end %} 3 | 4 | {% block body %} 5 |
6 | {% include "sidebar.htm" %} 7 | 8 | 9 |
10 | 11 |
12 |
首页 / 用户列表
13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% set i = 1 %} 35 | {% for user in users %} 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | {% set i += 1 %} 46 | {% end %} 47 | 48 |
ID用户名最近登录时间金币管理
{{ i }}{{ user["username"] }}{{ humantime(user.get('logintime', user.get('time'))) }}{{ user['money'] }} 42 | 查看详情 43 |
49 | {% raw pagenav(count = count, url = '/manage/user/%d' + search, each = each, now = page, 50 | end = '
') %} 51 |
52 |
53 | 54 |
55 | 56 | 57 |
58 | 59 | 60 | 61 | {% end %} -------------------------------------------------------------------------------- /templates/base.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block header_title %}Site Title{% end %} - {{ handler.settings['site']['webname'] }} 9 | 10 | 11 | 12 | 13 | 14 | {% block header_static %}{% end %} 15 | 16 | 17 |
18 | {% block body %}Hello world! {% end %} 19 |
20 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | {% block footer_static %}{% end %} 36 | 37 | -------------------------------------------------------------------------------- /templates/bookmark.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}{{ current_user.get('username') }}的书签{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | 7 |
8 |
9 |
10 |
11 |
12 |
    13 |
  1. 首页
  2. 14 |
  3. {{ current_user.get('username') }} 的书签
  4. 15 |
16 |
17 |
18 | 总共收藏了{{ count }}篇文章 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for post in bookmark %} 28 | 29 | 30 | 31 | 32 | 39 | 40 | {% end %} 41 |
作者标题板块操作
{{ post['title'] }}{{ post.get("sort").get("name") }} 33 |
34 | 取消 35 | 36 | {% raw xsrf_form_html() %} 37 |
38 |
42 | 46 |
47 |
48 |
49 | {% include "self.htm" %} 50 |
51 |
52 |
53 | 54 | 55 | {% end %} 56 | {% block footer_static %} 57 | 58 | 59 | {% end %} -------------------------------------------------------------------------------- /templates/dashboard.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}控制面板{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 | 8 | 9 |
10 | 11 |
12 |
任务 / Task
13 |
14 | 15 |
16 | 20 | 21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% set i = 1 %} 37 | {% for task in tasks %} 38 | 39 | 40 | 41 | 42 | 46 | 55 | 56 | {% set i += 1 %} 57 | {% end %} 58 | 59 |
#文件名大小进度管理
{{ i }}{{ task['filename'] }}{{ getsize(task['savepath']) }}已完成{% else %}warning">未完成{% 44 | end %} 45 | 47 |
48 | 下载 50 | 删除 52 | 53 |
54 |
60 |
61 |
62 |
63 | 67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
75 | 下载链接 76 |
77 |
78 | 79 |
80 |
81 | 82 |
83 |
84 | 储存文件名 85 |
86 |
87 | 88 |
89 |
为空则随机生成文件名
90 |
91 | 92 |
93 |
94 |
95 | 96 |
97 |
98 | {% raw xsrf_form_html() %} 99 |
100 |
101 | 102 |
103 |
104 |
105 | 106 | 107 |
108 |
109 |
MessageBox
110 |
111 | 确定删除这个任务? 112 |
113 | 117 |
118 |
119 | 120 |
121 | {% end %} 122 | {% block footer_static %} 123 | 124 | {% end %} -------------------------------------------------------------------------------- /templates/edit.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}文章编辑{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | 7 |
8 |
9 |
10 |
11 | 文章标题 12 | 13 |
14 |
15 |
板块
16 |
17 | 22 |
23 | 24 |
25 | {% set text = flash['article'] %} 26 | 27 |
28 |
29 |
30 | 收费 31 | 32 | 金币 33 |
34 |
35 | 免费时段 36 | 37 | 38 | 39 |
40 |
41 |
42 | 43 | 44 | {% raw xsrf_form_html() %} 45 |
46 |
47 |
48 | 49 | 50 | {% end %} 51 | {% block header_static %} 52 | 53 | {% end %} 54 | 55 | {% block footer_static %} 56 | 57 | 58 | 59 | 60 | 61 | 62 | {% end %} -------------------------------------------------------------------------------- /templates/error.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}{{ error_title }}{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 | 9 |

{{ error_title }}

10 |

{{ error_info }}

11 | 点击返回 12 |
13 |
14 | 15 | {% end %} 16 | {% block footer_static %} 17 | 18 | {% end %} -------------------------------------------------------------------------------- /templates/face.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}头像上传{% end %} 4 | {% block header_static %} 5 | 6 | {% end %} 7 | {% block body %} 8 | {% include "header.htm" %} 9 |
10 |
11 |
12 |
13 |
14 |
Loading...
15 |
16 |
17 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 |
上传出错
39 |
40 | 上传出错,请重试 41 |
42 | 45 |
46 |
47 | 48 | {% end %} 49 | {% block footer_static %} 50 | 51 | 52 | {% end %} -------------------------------------------------------------------------------- /templates/forgetpwd.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}找回密码{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |
找回密码 填写了邮箱的用户可以找回密码
9 |
10 | 11 |
12 | {% if success %} 13 |
14 | 15 |

邮件已经发送到你的邮箱啦,请进入邮箱修改密码。

16 |
17 | {% end %} 18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | 返回 42 | 43 |
44 |
45 |
46 | {% raw xsrf_form_html() %} 47 |
48 | 49 |
50 |
51 |
52 | {% end %} 53 | {% block footer_static %} 54 | 55 | {% end %} -------------------------------------------------------------------------------- /templates/header.htm: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Minos 5 |

6 | 7 | 10 | 11 |
12 | 44 | 45 | 51 | 52 |
53 |
54 |
-------------------------------------------------------------------------------- /templates/like.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}{{ current_user.get('username') }}喜欢的文章{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | 7 |
8 |
9 |
10 |
11 |
12 |
    13 |
  1. 首页
  2. 14 |
  3. {{ current_user.get('username') }} 喜欢的文章
  4. 15 |
16 |
17 |
18 | 你总共顶过{{ count }}篇文章 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for post in posts %} 28 | 29 | 30 | 31 | 32 | 39 | 40 | {% end %} 41 |
作者标题板块操作
{{ post['title'] }}{{ post.get('sort').get('name') }} 33 |
34 | 35 | 36 | {% raw xsrf_form_html() %} 37 |
38 |
42 | 46 |
47 |
48 |
49 | {% include "self.htm" %} 50 |
51 |
52 |
53 | 54 | {% end %} 55 | {% block footer_static %} 56 | 57 | 58 | {% end %} -------------------------------------------------------------------------------- /templates/login.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}用户登录{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |

登录

9 |
10 | 11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 | {% if handler.settings['captcha']['login'] %} 27 |
28 | 29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 | {% end %} 37 | 38 |
39 |
40 |
41 | 忘记密码 42 |
43 |
44 |
45 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 | {% raw xsrf_form_html() %} 56 |
57 | 58 |
59 |
60 |
61 | {% end %} 62 | {% block footer_static %} 63 | 64 | {% end %} -------------------------------------------------------------------------------- /templates/main.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}主页{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |
9 |
10 |

福利の社

11 |
12 |
13 | {% for sort in sorts %} 14 | {{ sort["name"] }} 15 | {% end %} 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for post in posts %} 26 | 27 | 28 | 34 | 43 | 44 | 45 | {% end %} 46 |
时间标题状态作者
29 | {% if post.get('star') %}[精华]{% end %} 30 | 31 | {{ post['title'] }} 32 | 33 | 35 | {% if post['charge'] and current_user["username"] not in post["buyer"] %} 36 | {{ post['charge'] }}金币 37 | {% elif current_user["username"] in post["buyer"] %} 38 | 已购买 39 | {% else %} 40 | 免费 41 | {% end %} 42 | {{ post['user'] }}
47 | 51 |
52 |
53 |
54 | 55 | {% end %} 56 | {% block footer_static %} 57 | 58 | {% end %} -------------------------------------------------------------------------------- /templates/message.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}{{ current_user["username"] }}的信箱{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | 7 |
8 |
9 |
10 |
11 |
12 |
    13 |
  1. 首页
  2. 14 |
  3. {{ current_user['username'] }} 的信箱
  4. 15 |
16 |
17 | {% if messages %} 18 |
    19 | {% for message in messages %} 20 | {% if not message['from'] %} 21 | {% set message['from'] = '系统' %} 22 | {% end %} 23 | {% if not message['read'] %} 24 |
  • {{ message['from'] }} 给 {{ message['to'] }} 的消息: 25 | {{ message['content'] }}
  • 26 | {% else %} 27 |
  • {{ message['from'] }} 给 {{ message['to'] }} 的消息: 28 | {{ message['content'] }}
  • 29 | {% end %} 30 | {% end %} 31 |
32 | {% else %} 33 |
34 | 没有短消息哦 35 |
36 | {% end %} 37 |
38 |
39 |
40 | {% include "self.htm" %} 41 |
42 |
短消息管理
43 | 50 | 57 |
58 |
59 |
60 |
61 | 62 | 63 | {% end %} 64 | {% block footer_static %} 65 | 66 | 67 | {% end %} -------------------------------------------------------------------------------- /templates/msgdetail.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}短消息详情{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | {% if not message['from'] %} 7 | {% set message['from'] = '系统' %} 8 | {% end %} 9 |
10 |
11 |
12 |
13 |
14 |
    15 |
  1. 首页
  2. 16 |
  3. 短消息
  4. 17 |
  5. {{ message['from'] }} 给 {{ message['to'] }} 的短消息
  6. 18 |
19 |
20 |
21 | {{ message['content'] }} 22 |
23 | {% if message.get('jump') %} 24 |
25 | 点击查看... 26 |
27 | {% end %} 28 |
29 |
30 |
31 | {% include "self.htm" %} 32 |
33 |
34 |
35 | 36 | 37 | {% end %} 38 | {% block footer_static %} 39 | 40 | {% end %} -------------------------------------------------------------------------------- /templates/open_list.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}主页{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |
9 |
10 |

福利の社

11 |
12 |
13 | Minos社区是一个平等、自由的社区,并通过各种有意思的安全知识、漏洞分享,来提高每个成员。 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for post in posts %} 23 | 24 | 25 | 31 | 38 | 39 | 40 | {% end %} 41 |
时间标题状态作者
26 | {% if post.get('star') %}[精华]{% end %} 27 | 28 | {{ post['title'] }} 29 | 30 | 32 | {% if post['charge'] %} 33 | {{ post['charge'] }}金币 34 | {% else %} 35 | 免费 36 | {% end %} 37 | {{ post['user'] }}
42 | 46 |
47 |
48 |
49 | 50 | {% end %} 51 | {% block footer_static %} 52 | 53 | {% end %} -------------------------------------------------------------------------------- /templates/publish.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}文章发表{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | 7 |
8 |
9 |
10 |
11 | 文章标题 12 | 13 |
14 |
15 |
板块
16 |
17 | 22 |
23 | 24 |
25 | {% set text = flash["article"] %} 26 | 27 |
28 |
29 |
30 | 收费 31 | 32 | 金币 33 |
34 |
35 |
36 | 免费时段 37 | 38 |
39 |
40 | 41 | 42 |
43 | 44 |
45 |
46 |
47 | 立即发布 48 | 临时保存 49 | {% raw xsrf_form_html() %} 50 |
51 |
52 |
53 | 54 | 55 | {% end %} 56 | {% block header_static %} 57 | 58 | {% end %} 59 | 60 | {% block footer_static %} 61 | 62 | 63 | 64 | 65 | 66 | 67 | {% end %} -------------------------------------------------------------------------------- /templates/register.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}用户注册{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |

注册

9 |
10 | 11 |
12 | {% if error %} 13 |
14 | 15 |

{{ error }}

16 |
17 | {% end %} 18 | 19 | {% if method == "invite" %} 20 |
21 | 22 |
23 | 24 |
25 |
26 | {% end %} 27 | {% set user_reg = flash['user_reg'] %} 28 | {% if type(user_reg) is not dict %} 29 | {% set user_reg = {'username':'','password':'','repassword':''} %} 30 | {% end %} 31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 |
42 | 44 |
45 |
46 | 47 |
48 | 49 |
50 | 52 |
53 |
54 | 55 | {% if handler.settings['captcha']['register'] %} 56 |
57 | 58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | {% end %} 66 | 67 |
68 |
69 | 登录 70 | 71 |
72 |
73 | {% raw xsrf_form_html() %} 74 |
75 | 76 |
77 |
78 |
79 | {% end %} 80 | {% block footer_static %} 81 | 82 | {% end %} -------------------------------------------------------------------------------- /templates/renewpwd.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}更换新密码{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |

填写新密码

9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 | {% raw xsrf_form_html() %} 37 |
38 | 39 |
40 |
41 |
42 | {% end %} 43 | {% block footer_static %} 44 | 45 | {% end %} -------------------------------------------------------------------------------- /templates/search.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}{{ keyword }}的搜索结果{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |
9 |
10 |
11 |

首页 > {{ keyword }}的搜索结果

12 |
13 |
14 | 总共找到{{ count }}篇相关文章 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for post in posts %} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% end %} 31 |
时间标题状态作者
{{ post['title'] }}公开{{ post['user'] }}
32 | 36 |
37 |
38 |
39 | {% include "self.htm" %} 40 |
41 |
42 |
43 | 44 | {% end %} 45 | {% block footer_static %} 46 | 47 | {% end %} -------------------------------------------------------------------------------- /templates/self.htm: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | {{ current_user["username"] }} 6 | 7 |

8 | 10 |

11 | 我的金币:{{ current_user["money"] }}
12 | 等级:{% if power == 'admin' %}管理员{% else %}普通白帽子{% end %}
13 | 我的收藏 | 我的喜欢 14 |

15 |
16 |
-------------------------------------------------------------------------------- /templates/sort.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}文章列表{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 |
7 |
8 |
9 |
10 |
11 |
    12 |
  1. 首页
  2. 13 |
  3. {{ sort["name"] }}
  4. 14 |
15 |
16 |
17 | {{ sort.get('intro') }} 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for post in posts %} 27 | 28 | 29 | 30 | 39 | 40 | 41 | {% end %} 42 |
时间标题状态作者
{{ post['title'] }} 31 | {% if post['charge'] and current_user["username"] not in post["buyer"] %} 32 | {{ post['charge'] }}金币 33 | {% elif current_user["username"] in post["buyer"] %} 34 | 已购买 35 | {% else %} 36 | 免费 37 | {% end %} 38 | {{ post['user'] }}
43 | 47 |
48 |
49 |
50 | {% include "self.htm" %} 51 |
52 |
53 |
54 | 55 | {% end %} 56 | {% block footer_static %} 57 | 58 | {% end %} -------------------------------------------------------------------------------- /templates/upload.htm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/user.htm: -------------------------------------------------------------------------------- 1 | {% extends "base.htm" %} 2 | 3 | {% block header_title %}{{ user["username"] }}的资料{% end %} 4 | {% block body %} 5 | {% include "header.htm" %} 6 | 7 |
8 |
9 |
10 |
11 |
12 |
    13 |
  1. 首页
  2. 14 |
  3. {{ user.get('username') }} 的主页
  4. 15 |
16 |
17 |
18 |
{{ user.get('signal') }}
19 | {% if user.get('website') and user.get('openwebsite') %} 20 | 主页:{{ user.get('website') }} 21 |
22 | {% end %} 23 | {% if user.get('email') and user.get('openemail') %} 24 | Email:{{ user.get('email') }} 25 |
26 | {% end %} 27 | {% if user.get('qq') and user.get('openqq') %} 28 | QQ:{{ user.get('qq') }} 29 | {% end %} 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | {% for post in posts %} 38 | 39 | 40 | 44 | 45 | 46 | {% end %} 47 |
时间标题板块
41 | {% if post.get('star') %}[精华]{% end %} 42 | {{ post.get('title') }} 43 | {{ post['sort']['name'] }}
48 | 52 |
53 |
54 |
55 | {% if user["username"] != current_user["username"] %} 56 |
57 |
58 |

59 | 60 | {{ user["username"] }} 61 | 62 |

63 | 65 |

66 | 等级:{% if user["power"] == 20 %}管理员{% else %}普通白帽子{% end %}
67 |

68 |
69 |
70 | {% else %} 71 | {% include "self.htm" %} 72 | {% end %} 73 |
74 |
75 |
76 | 77 | 78 | {% end %} 79 | {% block footer_static %} 80 | 81 | {% end %} -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phith0n/Minos/1c98fb367121df1e8b8e7a20ea7de391df583e8f/util/__init__.py -------------------------------------------------------------------------------- /util/captcha.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding: utf-8 3 | 4 | __author__ = 'outofmemory.cn' 5 | 6 | from wheezy.captcha.image import captcha 7 | 8 | from wheezy.captcha.image import background 9 | from wheezy.captcha.image import curve 10 | from wheezy.captcha.image import noise 11 | from wheezy.captcha.image import smooth 12 | from wheezy.captcha.image import text 13 | 14 | from wheezy.captcha.image import offset 15 | from wheezy.captcha.image import rotate 16 | from wheezy.captcha.image import warp 17 | 18 | import random 19 | from os import path 20 | 21 | _chars = 'ABCDEFGHJKMNPQRSTWXYZ23456789' 22 | 23 | class Captcha: 24 | '''验证码''' 25 | _fontsDir = "./static/assets/fonts" 26 | _session_name_ = "captcha" 27 | 28 | @staticmethod 29 | def check(input, request): 30 | if input.upper() == request.session.get(Captcha._session_name_): 31 | request.session.delete(Captcha._session_name_) 32 | return True 33 | else: 34 | return False 35 | 36 | @staticmethod 37 | def get(request): 38 | captcha_image = captcha(drawings=[ 39 | background(), 40 | text(fonts=[ 41 | path.join(Captcha._fontsDir,'captcha.ttf')], 42 | drawings=[ 43 | warp(), 44 | rotate(), 45 | offset() 46 | ]), 47 | curve(), 48 | noise(), 49 | smooth() 50 | ]) 51 | chars = random.sample(_chars, 4) 52 | image = captcha_image(chars) 53 | request.session[Captcha._session_name_] = ''.join(chars) 54 | #print request.session[Captcha._session_name_] 55 | return image, ''.join(chars) 56 | -------------------------------------------------------------------------------- /util/error.py: -------------------------------------------------------------------------------- 1 | __author__ = 'phithon' 2 | 3 | class SpecialError(Exception): 4 | def __init__(self, message): 5 | self.message = message 6 | 7 | def __str__(self): 8 | return repr(self.message) -------------------------------------------------------------------------------- /util/flash.py: -------------------------------------------------------------------------------- 1 | __author__ = 'phithon' 2 | 3 | class flash(dict): 4 | def __init__(self, request): 5 | dict.__init__(self) 6 | self.request = request 7 | 8 | def __getitem__(self, item): 9 | item = "flash_%s" % item 10 | if item in self.request.session: 11 | value = self.request.session.get(item) 12 | self.request.session.delete(item) 13 | else: 14 | value = "" 15 | return value 16 | 17 | def get(self, k, d = None): 18 | return self.__getitem__(k) 19 | 20 | def __delattr__(self, item): 21 | item = "flash_%s" % item 22 | self.request.session.delete(item) 23 | 24 | def __setitem__(self, key, value): 25 | key = "flash_%s" % key 26 | self.request.session[key] = value -------------------------------------------------------------------------------- /util/function.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | import bcrypt, os, string, random, hashlib, time, re, tornado.escape, datetime 3 | 4 | class hash: 5 | ''' 6 | crypt哈希加密类 7 | ''' 8 | @staticmethod 9 | def get(str): 10 | str = str.encode("utf-8") 11 | return bcrypt.hashpw(str, bcrypt.gensalt()) 12 | 13 | @staticmethod 14 | def verify(str, hashed): 15 | str = str.encode("utf-8") 16 | hashed = hashed.encode("utf-8") 17 | return bcrypt.hashpw(str, hashed) == hashed 18 | 19 | def md5(str): 20 | ''' 21 | 计算简单的md5 hex格式字符串 22 | 23 | :param str: 原字符串 24 | :return: 返回的32尾hex字符串 25 | ''' 26 | m = hashlib.md5() 27 | m.update(str) 28 | return m.hexdigest() 29 | 30 | def not_need_login(func): 31 | ''' 32 | 修饰器,修饰prepare方法,使其不需要登录即可使用。 33 | 34 | :param func: prepare方法 35 | :return: 处理过的prepare方法 36 | ''' 37 | def do_prepare(self, *args, **kwargs): 38 | before = self.current_user 39 | self.current_user = {'username': 'guest', 'power': -1} 40 | func(self, *args, **kwargs) 41 | self.current_user = before 42 | return do_prepare 43 | 44 | def humansize(file): 45 | ''' 46 | 计算文件大小并输出为可读的格式(如 1.3MB) 47 | 48 | :param file: 文件路径 49 | :return: 可读的文件大小 50 | ''' 51 | if os.path.exists(file): 52 | nbytes = os.path.getsize(file) 53 | suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] 54 | if nbytes == 0: return '0 B' 55 | i = 0 56 | while nbytes >= 1024 and i < len(suffixes)-1: 57 | nbytes /= 1024. 58 | i += 1 59 | f = ('%.2f' % nbytes).rstrip('0').rstrip('.') 60 | return '%s %s' % (f, suffixes[i]) 61 | else: 62 | return u"未知" 63 | 64 | def humantime(t = None, format = "%Y-%m-%d %H:%M:%S", span = False): 65 | ''' 66 | 67 | %y 两位数的年份表示(00-99) 68 | %Y 四位数的年份表示(000-9999) 69 | %m 月份(01-12) 70 | %d 月内中的一天(0-31) 71 | %H 24小时制小时数(0-23) 72 | %I 12小时制小时数(01-12) 73 | %M 分钟数(00=59) 74 | %S 秒(00-59) 75 | 76 | %a 本地简化星期名称 77 | %A 本地完整星期名称 78 | %b 本地简化的月份名称 79 | %B 本地完整的月份名称 80 | %c 本地相应的日期表示和时间表示 81 | %j 年内的一天(001-366) 82 | %p 本地A.M.或P.M.的等价符 83 | %U 一年中的星期数(00-53)星期天为星期的开始 84 | %w 星期(0-6),星期天为星期的开始 85 | %W 一年中的星期数(00-53)星期一为星期的开始 86 | %x 本地相应的日期表示 87 | %X 本地相应的时间表示 88 | %Z 当前时区的名称 89 | %% %号本身 90 | 91 | :param t: 时间戳,默认为当前时间 92 | :param format: 格式化字符串 93 | :return: 当前时间字符串 94 | ''' 95 | if not t: 96 | t = time.time() 97 | if span: 98 | return time_span(t) 99 | return time.strftime(format, time.localtime(t)) 100 | 101 | def time_span(ts): 102 | ''' 103 | 计算传入的时间戳与现在相隔的时间 104 | 105 | :param ts: 传入时间戳 106 | :return: 人性化时间差 107 | ''' 108 | delta = datetime.datetime.now() - datetime.datetime.fromtimestamp(ts) 109 | if delta.days >= 365: 110 | return '%d年前' % int(delta.days / 365) 111 | elif delta.days >= 30: 112 | return '%d个月前' % int(delta.days / 30) 113 | elif delta.days > 0: 114 | return '%d天前' % delta.days 115 | elif delta.seconds < 60: 116 | return "%d秒前" % delta.seconds 117 | elif delta.seconds < 60 * 60: 118 | return "%d分钟前" % int(delta.seconds / 60) 119 | else: 120 | return "%d小时前" % int(delta.seconds / 60 / 60) 121 | 122 | def random_str(randomlength = 12): 123 | ''' 124 | 获得随机字符串,包含所有大小写字母+数字 125 | 126 | :param randomlength: 字符串长度,默认12 127 | :return: 随机字符串 128 | ''' 129 | a = list(string.ascii_letters + string.digits) 130 | random.shuffle(a) 131 | return ''.join(a[:randomlength]) 132 | 133 | def intval(str): 134 | ''' 135 | 如php中的intval,将字符串强制转换成数字 136 | 137 | :param str: 输入的字符串 138 | :return: 数字 139 | ''' 140 | if type(str) is int: return str 141 | try: 142 | ret = re.match(r"^(\-?\d+)[^\d]?.*$", str).group(1) 143 | ret = int(ret) 144 | except: 145 | ret = 0 146 | return ret 147 | 148 | def nl2br(str): 149 | str = tornado.escape.xhtml_escape(str) 150 | return str 151 | 152 | def dump(obj): 153 | '''return a printable representation of an object for debugging''' 154 | newobj=obj 155 | if '__dict__' in dir(obj): 156 | newobj=obj.__dict__ 157 | if ' object at ' in str(obj) and not newobj.has_key('__type__'): 158 | newobj['__type__']=str(obj) 159 | for attr in newobj: 160 | newobj[attr]=dump(newobj[attr]) 161 | return newobj -------------------------------------------------------------------------------- /util/pxfilter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Python 富文本XSS过滤类 4 | @package XssHtml 5 | @version 0.1 6 | @link http://phith0n.github.io/python-xss-filter 7 | @since 20150407 8 | @copyright (c) Phithon All Rights Reserved 9 | 10 | Based on native Python module HTMLParser purifier of HTML, To Clear all javascript in html 11 | You can use it in all python web framework 12 | Written by Phithon in 2015 and placed in the public domain. 13 | phithon 编写于20150407 14 | From: XDSEC & 离别歌 15 | GitHub Pages: https://github.com/phith0n/python-xss-filter 16 | Usage: 17 | parser = XssHtml() 18 | parser.feed('') 19 | parser.close() 20 | html = parser.getHtml() 21 | print html 22 | 23 | Requirements 24 | Python 2.6+ or 3.2+ 25 | Cannot defense xss in browser which is belowed IE7 26 | 浏览器版本:IE7+ 或其他浏览器,无法防御IE6及以下版本浏览器中的XSS 27 | """ 28 | import re 29 | try: 30 | from html.parser import HTMLParser 31 | except: 32 | from HTMLParser import HTMLParser 33 | 34 | class XssHtml(HTMLParser): 35 | allow_tags = ['a', 'img', 'br', 'strong', 'b', 'code', 'pre', 36 | 'p', 'div', 'em', 'span', 'h1', 'h2', 'h3', 'h4', 37 | 'h5', 'h6', 'blockquote', 'ul', 'ol', 'tr', 'th', 'td', 38 | 'hr', 'li', 'u', 's', 'table', 'thead', 'tbody', 39 | 'caption', 'small', 'q', 'sup', 'sub', 'font'] 40 | common_attrs = ["data-indent", "class"] 41 | nonend_tags = ["img", "hr", "br", "embed"] 42 | tags_own_attrs = { 43 | "img": ["src", "width", "height", "alt"], 44 | "a": ["href", "target"], 45 | # "embed": ["src", "width", "height", "type", "allowfullscreen", "loop", "play", "wmode", "menu"], 46 | "pre": ["data-lang"], 47 | "font": ["color"] 48 | } 49 | 50 | def __init__(self, allows = []): 51 | HTMLParser.__init__(self) 52 | self.allow_tags = allows if allows else self.allow_tags 53 | self.result = [] 54 | self.start = [] 55 | self.data = [] 56 | 57 | def getHtml(self): 58 | """ 59 | Get the safe html code 60 | """ 61 | for i in range(0, len(self.result)): 62 | tmp = self.result[i].rstrip('\n') 63 | tmp = tmp.lstrip('\n') 64 | if tmp: 65 | self.data.append(tmp) 66 | return ''.join(self.data) 67 | 68 | def handle_startendtag(self, tag, attrs): 69 | self.handle_starttag(tag, attrs) 70 | 71 | def handle_starttag(self, tag, attrs): 72 | if tag not in self.allow_tags: 73 | return 74 | end_diagonal = ' /' if tag in self.nonend_tags else '' 75 | if not end_diagonal: 76 | self.start.append(tag) 77 | attdict = {} 78 | for attr in attrs: 79 | attdict[attr[0]] = attr[1] 80 | 81 | attdict = self.__wash_attr(attdict, tag) 82 | if hasattr(self, "node_%s" % tag): 83 | attdict = getattr(self, "node_%s" % tag)(attdict) 84 | else: 85 | attdict = self.node_default(attdict) 86 | 87 | attrs = [] 88 | for (key, value) in attdict.items(): 89 | attrs.append('%s="%s"' % (key, self.__htmlspecialchars(value, True))) 90 | attrs = (' ' + ' '.join(attrs)) if attrs else '' 91 | self.result.append('<' + tag + attrs + end_diagonal + '>') 92 | 93 | def handle_endtag(self, tag): 94 | if self.start and tag == self.start[len(self.start) - 1]: 95 | self.result.append('') 96 | self.start.pop() 97 | 98 | def handle_data(self, data): 99 | self.result.append(self.__htmlspecialchars(data)) 100 | 101 | def handle_entityref(self, name): 102 | if name.isalpha(): 103 | self.result.append("&%s;" % name) 104 | 105 | def handle_charref(self, name): 106 | if name.isdigit(): 107 | self.result.append("&#%s;" % name) 108 | 109 | def node_default(self, attrs): 110 | attrs = self.__common_attr(attrs) 111 | return attrs 112 | 113 | def node_a(self, attrs): 114 | attrs = self.__common_attr(attrs) 115 | attrs = self.__get_link(attrs, "href") 116 | attrs = self.__set_attr_default(attrs, "target", "_blank") 117 | attrs = self.__limit_attr(attrs, { 118 | "target": ["_blank", "_self"] 119 | }) 120 | return attrs 121 | 122 | def node_embed(self, attrs): 123 | attrs = self.__common_attr(attrs) 124 | attrs = self.__get_link(attrs, "src") 125 | attrs = self.__limit_attr(attrs, { 126 | "type": ["application/x-shockwave-flash"], 127 | "wmode": ["transparent", "window", "opaque"], 128 | "play": ["true", "false"], 129 | "loop": ["true", "false"], 130 | "menu": ["true", "false"], 131 | "allowfullscreen": ["true", "false"] 132 | }) 133 | attrs["allowscriptaccess"] = "never" 134 | attrs["allownetworking"] = "none" 135 | return attrs 136 | 137 | def __true_url(self, url): 138 | prog = re.compile(r"^(http|https|ftp)://.+", re.I | re.S) 139 | if prog.match(url): 140 | return url 141 | else: 142 | return "http://%s" % url 143 | 144 | def __true_style(self, style): 145 | if style: 146 | style = re.sub(r"(\\|&#|/\*|\*/)", "_", style) 147 | style = re.sub(r"e.*x.*p.*r.*e.*s.*s.*i.*o.*n", "_", style) 148 | return style 149 | 150 | def __get_style(self, attrs): 151 | if "style" in attrs: 152 | attrs["style"] = self.__true_style(attrs.get("style")) 153 | return attrs 154 | 155 | def __get_link(self, attrs, name): 156 | if name in attrs: 157 | attrs[name] = self.__true_url(attrs[name]) 158 | return attrs 159 | 160 | def __wash_attr(self, attrs, tag): 161 | if tag in self.tags_own_attrs: 162 | other = self.tags_own_attrs.get(tag) 163 | else: 164 | other = [] 165 | if attrs: 166 | for (key, value) in attrs.items(): 167 | if key not in self.common_attrs + other: 168 | del attrs[key] 169 | return attrs 170 | 171 | def __common_attr(self, attrs): 172 | attrs = self.__get_style(attrs) 173 | return attrs 174 | 175 | def __set_attr_default(self, attrs, name, default = ''): 176 | if name not in attrs: 177 | attrs[name] = default 178 | return attrs 179 | 180 | def __limit_attr(self, attrs, limit = {}): 181 | for (key, value) in limit.items(): 182 | if key in attrs and attrs[key] not in value: 183 | del attrs[key] 184 | return attrs 185 | 186 | def __htmlspecialchars(self, html, strict=False): 187 | html = html.replace("&", "&").replace("<", "<")\ 188 | .replace(">", ">")\ 189 | .replace('"', """)\ 190 | .replace("'", "'") 191 | if strict: 192 | html = html.replace("[", "[").replace("]", "]") 193 | return html 194 | 195 | 196 | if "__main__" == __name__: 197 | parser = XssHtml() 198 | parser.feed("""
""") 199 | parser.close() 200 | print(parser.getHtml()) -------------------------------------------------------------------------------- /util/sendemail.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'phithon' 3 | from tornado.httpclient import AsyncHTTPClient, HTTPRequest 4 | from tornado.escape import url_escape 5 | from tornado import ioloop 6 | 7 | def callback(self): 8 | pass 9 | 10 | class Sendemail: 11 | def __init__(self, setting): 12 | AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient") 13 | self.url = setting.get("url") 14 | self.key = setting.get("key") 15 | self.sender = setting.get("sender") 16 | self.http_client = AsyncHTTPClient() 17 | 18 | def _parseurl(self, dic): 19 | ret = [] 20 | for k, v in dic.items(): 21 | ret.append("%s=%s" % (k, url_escape(v, False))) 22 | return "&".join(ret) 23 | 24 | def send(self, title, content, to, orgin = None, callback = callback): 25 | orgin = self.sender if not orgin else orgin 26 | url = "%s/messages" % self.url 27 | data = self._parseurl({ 28 | "from": orgin, 29 | "to": to, 30 | "subject": title, 31 | "html": content 32 | }) 33 | self.http_client.fetch(url, method = "POST", body = data, callback = callback, auth_username = "api", auth_password = self.key, 34 | follow_redirects = True, validate_cert = False) 35 | 36 | if __name__ == "__main__": 37 | def main(): 38 | setting = { 39 | "url": "https://api.mailgun.net/v3/domain", 40 | "key": "key-xxxxx" 41 | } 42 | Sendemail(setting).send( 43 | title = u"你好,test", 44 | content=u"这是一封测试邮件", 45 | to=u"test@qq.com", 46 | orgin=u"postmaster@domain" 47 | ) 48 | 49 | io_loop = ioloop.IOLoop.instance() 50 | io_loop.run_sync(main) 51 | io_loop.start() 52 | --------------------------------------------------------------------------------