├── .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"
"
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 |
O 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 |
38 |
39 | {% block body %}Hello world{% end %}
40 |
41 |
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 |
14 |
15 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ID 用户名 最近登录时间 金币 管理
32 |
33 |
34 |
35 | {% set i = 1 %}
36 | {% for user in newusers %}
37 |
38 | {{ i }}
39 | {{ user["username"] }}
40 | {{ humantime(user.get('logintime', user.get('time'))) }}
41 | {{ user['money'] }}
42 |
43 | 查看详情
44 |
45 |
46 | {% set i += 1 %}
47 | {% end %}
48 |
49 |
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 |
14 |
15 |
16 |
38 |
39 |
40 |
41 |
42 | ID 邀请码 生成时间 使用者 管理
43 |
44 |
45 |
46 | {% set i = 1 %}
47 | {% for invite in invites %}
48 |
49 | {{ i }}
50 | {{ invite["code"] }}
51 | {{ humantime(invite.get('time')) }}
52 |
53 | {% if invite.get('used') %}
54 | {{ invite['user'] }}
55 | {% else %}
56 | 尚未使用
57 | {% end %}
58 |
59 |
60 |
69 |
70 |
71 | {% set i += 1 %}
72 | {% end %}
73 |
74 |
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 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
网站设置
24 |
生成加密密钥
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
40 |
41 |
48 |
49 |
65 |
66 |
73 |
74 |
84 |
85 |
91 |
92 |
98 |
99 |
104 | {% raw xsrf_form_html() %}
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | {% end %}
--------------------------------------------------------------------------------
/templates/admin/sidebar.htm:
--------------------------------------------------------------------------------
1 |
2 |
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 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 | ID 版块名称 首页显示 文章数量 管理
24 |
25 |
26 |
27 | {% set i = 1 %}
28 | {% for sort in sorts %}
29 |
30 | {{ i }}
31 | {{ sort['name'] }}
32 | {% if sort['show'] %}是{% else %}否{% end %}
33 | {{ sort.get('article') }}
34 |
35 | 查看详情
36 |
37 |
38 | {% set i += 1 %}
39 | {% end %}
40 |
41 |
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 |
26 |
27 |
帖子数量:{{ sort['article'] }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
43 |
44 |
53 |
54 |
60 |
61 |
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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
41 |
42 |
48 |
49 |
56 |
57 |
63 |
64 |
70 |
71 |
77 |
78 |
84 |
85 |
91 |
92 |
98 |
99 |
105 |
106 |
112 |
113 |
119 |
120 |
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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 搜索
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ID 用户名 最近登录时间 金币 管理
31 |
32 |
33 |
34 | {% set i = 1 %}
35 | {% for user in users %}
36 |
37 | {{ i }}
38 | {{ user["username"] }}
39 | {{ humantime(user.get('logintime', user.get('time'))) }}
40 | {{ user['money'] }}
41 |
42 | 查看详情
43 |
44 |
45 | {% set i += 1 %}
46 | {% end %}
47 |
48 |
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 | 首页
14 | {{ current_user.get('username') }} 的书签
15 |
16 |
17 |
18 | 总共收藏了{{ count }}篇文章
19 |
20 |
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 |
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 | {{ i }}
40 | {{ task['filename'] }}
41 | {{ getsize(task['savepath']) }}
42 | 已完成{% else %}warning">未完成{%
44 | end %}
45 |
46 |
47 |
54 |
55 |
56 | {% set i += 1 %}
57 | {% end %}
58 |
59 |
60 |
61 |
62 |
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 |
25 | {% set text = flash['article'] %}
26 | {{ post['content'] if not text else text }}
27 |
28 |
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 |
26 |
27 |
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 |
25 |
26 |
35 |
36 |
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 |
6 |
7 |
导航切换
10 |
11 |
12 |
13 | {% if current_user %}
14 | 首页
15 | {% else %}
16 | 首页
17 | 登录
18 | 注册
19 | {% end %}
20 |
21 | {% if current_user and power == "admin" %}
22 | 管理
23 | {% end %}
24 | {% if current_user %}
25 | 我的消息
26 |
27 |
28 |
29 | 个人中心
30 |
31 |
32 |
33 | 我的主页
34 | 个人设置
35 | 修改头像
36 | 退出
37 |
38 |
39 |
42 | {% end %}
43 |
44 |
45 |
46 |
47 |
48 |
49 | 搜索
50 |
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 | 首页
14 | {{ current_user.get('username') }} 喜欢的文章
15 |
16 |
17 |
18 | 你总共顶过{{ count }}篇文章
19 |
20 |
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 |
18 |
19 |
25 |
26 | {% if handler.settings['captcha']['login'] %}
27 |
36 | {% end %}
37 |
38 |
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 |
18 |
19 |
20 | 时间
21 | 标题
22 | 状态
23 | 作者
24 |
25 | {% for post in posts %}
26 |
27 | {{ time_span(post['time']) }}
28 |
29 | {% if post.get('star') %}[精华] {% end %}
30 |
31 | {{ post['title'] }}
32 |
33 |
34 |
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 |
43 | {{ post['user'] }}
44 |
45 | {% end %}
46 |
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 | 首页
14 | {{ current_user['username'] }} 的信箱
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 |
44 |
45 |
46 | 全部已读
47 | {% raw xsrf_form_html() %}
48 |
49 |
50 |
51 |
52 |
53 | 全部删除
54 | {% raw xsrf_form_html() %}
55 |
56 |
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 | 首页
16 | 短消息
17 | {{ message['from'] }} 给 {{ message['to'] }} 的短消息
18 |
19 |
20 |
21 | {{ message['content'] }}
22 |
23 | {% if message.get('jump') %}
24 |
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 | {{ time_span(post['time']) }}
25 |
26 | {% if post.get('star') %}[精华] {% end %}
27 |
28 | {{ post['title'] }}
29 |
30 |
31 |
32 | {% if post['charge'] %}
33 | {{ post['charge'] }}金币
34 | {% else %}
35 | 免费
36 | {% end %}
37 |
38 | {{ post['user'] }}
39 |
40 | {% end %}
41 |
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 |
25 | {% set text = flash["article"] %}
26 | {{ text }}
27 |
28 |
29 |
30 | 收费
31 |
32 | 金币
33 |
34 |
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 |
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 |
38 |
39 |
46 |
47 |
54 |
55 | {% if handler.settings['captcha']['register'] %}
56 |
65 | {% end %}
66 |
67 |
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 |
19 |
20 |
26 |
27 |
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 | {{ time_span(post['time']) }}
26 | {{ post['title'] }}
27 | 公开
28 | {{ post['user'] }}
29 |
30 | {% end %}
31 |
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 |
--------------------------------------------------------------------------------
/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 | 首页
13 | {{ sort["name"] }}
14 |
15 |
16 |
17 | {{ sort.get('intro') }}
18 |
19 |
20 |
21 | 时间
22 | 标题
23 | 状态
24 | 作者
25 |
26 | {% for post in posts %}
27 |
28 | {{ time_span(post['time']) }}
29 | {{ post['title'] }}
30 |
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 |
39 | {{ post['user'] }}
40 |
41 | {% end %}
42 |
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 | 首页
14 | {{ user.get('username') }} 的主页
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 | {{ time_span(post.get('time')) }}
40 |
41 | {% if post.get('star') %}[精华] {% end %}
42 | {{ post.get('title') }}
43 |
44 | {{ post['sort']['name'] }}
45 |
46 | {% end %}
47 |
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('' + tag + '>')
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 |
--------------------------------------------------------------------------------