3 from tornado import escape,web,ioloop,httpserver,httpclient
5 from datetime import datetime,timedelta
7 from bson.objectid import ObjectId #don't remove
8 from linebot.api import LineBotApi
9 from linebot.exceptions import (InvalidSignatureError)
10 from linebot.models import (TextSendMessage)
13 class BaseHandler(web.RequestHandler):
14 def get_current_user(self):
15 user = self.get_secure_cookie('admin_user')
16 return escape.utf8(user)
18 def set_current_user(self,username):
19 self.set_secure_cookie('admin_user',username)
21 def clear_current_user(self):
22 self.clear_cookie('admin_user')
24 class IndexHandler(BaseHandler):
25 def main(self,dbname,page):
26 params = self.application.db['params'].find_one({'app':'bbs'})
27 if params['mentenance'] is True:
28 self.render('mentenance.htm',title=params['title'],db=dbname)
30 if dbname not in self.application.mylist():
31 if self.current_user == b'admin':
32 coll = self.application.db[dbname]
36 raise web.HTTPError(404)
37 key = self.get_argument('key','')
39 table = self.application.db[dbname]
40 rec = table.find_one({'number':int(key)})
42 self.render('article.htm',record=rec)
45 raise web.HTTPError(404)
46 self.rule = escape.url_unescape(self.get_cookie('aikotoba',''))
47 self.na = escape.url_unescape(self.get_cookie('username',u'誰かさん'))
48 self.pos = self.application.gpos(dbname,page)
49 table = self.application.db[dbname]
51 start = (self.pos-1)*i
53 start = table.count()-i
57 self.bool = (dbname == params['info name'])
62 self.rec = rec.skip(start).limit(i)
64 def get(self,dbname,page='0'):
65 self.main(dbname,page)
66 db = self.application.db
67 table = db[dbname].find()
68 params = db['params'].find_one({'app':'bbs'})
69 if table.count() >= 10*params['count']:
70 self.render('modules/full.htm',position=self.pos,records=self.rec,data=params,db=dbname)
71 if self.bool is True and self.current_user != b'admin':
72 self.render('modules/info.htm',position=self.pos,records=self.rec,data=params,db=dbname,error='')
74 self.render_admin(dbname)
76 def render_admin(self,dbname,title='',com='',er='',img='',ch='checked'):
77 t = self.get_argument('img','')
78 params = self.application.db['params'].find_one({'app':'bbs'})
79 if self.current_user == b'admin':
80 s = '<label><p>URL </p><input name="img" placeholder="src=http://~" value=' + t + '></label>'
82 s = '<input type=hidden>'
83 self.render('modules/index.htm',position=self.pos,records=self.rec,data=params,username=self.na,title=title,
84 comment=com,db=dbname,aikotoba=self.rule,error=er+img,check=ch,admin=s)
86 class LoginHandler(BaseHandler):
88 info = self.application.db['params'].find_one({'app':'bbs'})
89 query = self.get_query_argument('next','/'+info['info name'])
90 i = query[1:].find('/')
95 self.render('login.htm',db=escape.url_unescape(qs))
98 dbname = self.get_argument('record','')
100 self.redirect('/login')
102 pw = self.application.db['params'].find_one({'app':'bbs'})
103 if self.get_argument('password') == pw['password']:
104 self.set_current_user('admin')
105 if dbname == 'master':
106 self.redirect('/master')
108 self.redirect('/'+dbname+'/admin/0/')
110 class LogoutHandler(BaseHandler):
112 self.clear_current_user()
113 self.redirect('/login')
115 class JumpHandler(BaseHandler):
117 self.clear_current_user()
120 class NaviHandler(web.RequestHandler):
122 if 'params' not in self.application.mylist():
123 item = {"mentenance":False,"out_words":[u"阿保",u"馬鹿",u"死ね"],"password":"admin",
124 "title2":"<h1 style=color:maroon;font-style:italic;text-align:center>とるね~ど号</h1>",
125 "bad_words":["<style","<link","<script","<img","<a"],"count":30,
126 "title":u"とるね~ど号","info name":"info",'app':'bbs'}
127 self.application.db['params'].insert(item)
128 self.application.db['info'].find()
129 table = self.application.db['params'].find_one({'app':'bbs'})
130 if table['mentenance'] is True:
131 self.render('mentenance.htm',title=table['title'],db=table['info name'])
133 coll = self.application.coll()
134 na = table['info name']
135 self.render('top.htm',coll=coll,name=na,full=self.full,new=self.new)
137 def full(self,dbname):
138 if dbname in self.application.coll():
139 i = 10*self.application.db['params'].find_one({'app':'bbs'})['count']
140 table = self.application.db[dbname]
141 if table.count() >= i:
145 def new(self,dbname):
146 if dbname in self.application.coll():
147 table = self.application.db[dbname]
151 rec = sorted(table.find(),key=lambda x:x['date'])
152 time = rec[i-1]['date']
153 delta = datetime.now()-datetime.strptime(time,'%Y/%m/%d %H:%M')
154 return delta.total_seconds() < 24*3600
156 class TitleHandler(NaviHandler):
158 rec = sorted(self.title(),key=lambda x: x['date2'])
159 self.render('title.htm',coll=rec,full=self.full)
162 for x in self.application.coll():
165 table = self.application.db[x]
168 tmp = table.find_one({'number':1})
178 rec = table.find().sort('number')
181 i = datetime.strptime(s,'%Y/%m/%d %H:%M')
182 year = datetime.now().year-i.year
189 item['date2'] = j+31*(i.month-1)+i.day
192 class RegistHandler(IndexHandler):
193 def post(self,dbname):
194 self.main(dbname,'0')
195 if dbname not in self.application.coll(info=True):
196 raise web.HTTPError(404)
197 params = self.application.db['params'].find_one({'app':'bbs'})
198 words = params['bad_words']
199 out = params['out_words']
200 rule = self.get_argument('aikotoba')
201 na = self.get_argument('name')
202 sub = self.get_argument('title')
203 com = self.get_argument('comment',None,False)
209 for line in com.splitlines():
213 error += u'禁止ワード.<br>'
217 if word in line.lower():
218 tag = escape.xhtml_escape(word)
219 error += u'タグ違反.('+tag+')<br>'
221 obj = re.finditer('http[s]?://(?:[a-zA-Z]|[0-9]|[#$%._?&~+*=/]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
223 if x.group() not in url:
224 url.append(x.group())
232 line = line.replace(' ',' ',j)
234 text += '<p><br>\n</p>'
236 text += '<p>'+self.link(line,dbname)+'\n</p>'
238 error += u'合言葉未入力.<br>'
241 s = s+'<tr><td><a href={0} class=livepreview target=_blank>{0}</a></td></tr>'.format(x)
243 text = text+'<table><tr><td>検出url:</td></tr>'+s+'</table>'
244 pw = self.get_argument('password')
246 error += u'文字数が1,000をこえました.<br>'
248 if self.current_user == b'admin':
254 article = self.application.db[dbname]
255 if article.count() == 0:
258 items = article.find()
259 item = items.sort('number')[article.count()-1]
260 no = item['number']+1
263 if self.get_argument('show', 'false') == 'true':
267 t = self.get_cookie('time')
268 if t and s - datetime.strptime(escape.url_unescape(t),k) < timedelta(seconds=10):
270 img = self.get_argument('img','')
272 img = '<div style=text-align:center><img src="' + escape.url_unescape(img) + '"/></div>'
275 error = '<p style=font-size:2.5em;color:blue>↓↓プレビュー↓↓</p>\n' + text
280 reg = {'number': no, 'name': na, 'title': sub, 'comment': text, 'raw': com, 'password': pw,
281 'date': s.strftime('%Y/%m/%d %H:%M')}
283 self.set_cookie('aikotoba', escape.url_escape(rule))
284 self.set_cookie('username', escape.url_escape(na))
285 self.set_cookie('time',escape.url_escape(s.strftime(k)))
286 self.redirect('/' + dbname + '#article')
289 error = '<p style=color:red>' + error + '</p>'
293 self.render_admin(dbname,title=sub,com=com,er=error,ch=ch,img=img)
295 def link(self,command,database):
298 obj = re.finditer('>>[0-9]+',command)
300 s = '<a class=minpreview data-preview-url=/{0}?key={1} href=/{0}/userdel?job={1}>>>{1}</a>'.format(database,x.group()[2:])
301 text = text+command[i:x.start()]+s
304 text = text+command[i:]
307 class AdminHandler(BaseHandler):
309 def get(self,dbname,page='0'):
311 dbname = self.get_argument('record','')
312 table = self.application.db[dbname]
313 rec = table.find().sort('number')
314 mente = self.application.db['params'].find_one({'app':'bbs'})
315 if mente['mentenance'] is True:
316 check = 'checked=checked'
319 pos = self.application.gpos(dbname,page)
323 start = table.count()-i
326 rec.skip(start).limit(i)
327 self.render('modules/admin.htm',position=pos,records=rec,mente=check,password=mente['password'],db=dbname)
329 class AdminConfHandler(BaseHandler):
331 def post(self,dbname,func):
333 param = self.application.db['params'].find_one({'app':'bbs'})
334 if self.get_argument('mente','') == 'on':
338 word = self.get_argument('pass','')
340 self.render('regist.htm',content='パスワードを設定してください')
343 param['mentenance']=mente
344 param['password']=word
345 self.application.db['params'].save(param)
347 table = self.application.db[dbname]
348 for x in self.get_arguments('item'):
349 table.remove({'number':int(x)})
350 self.redirect('/'+dbname+'/admin/0/')
352 class UserHandler(web.RequestHandler):
353 def get(self,dbname):
354 q = self.get_query_argument('job','0',strip=True)
355 self.redirect(self.application.page(dbname,q))
357 def post(self,dbname):
358 number = self.get_argument('number')
359 if number.isdigit() is True:
361 url = self.application.page(dbname,number)
362 if 'password' in self.request.arguments.keys():
363 pas = self.get_argument('password')
367 table = self.application.db[dbname]
368 obj = table.find_one({'number':num})
369 if obj and(obj['password'] == pas):
370 table.update({'number':num},{'$set':{'title':u'削除されました','name':'','comment':u'<i><b>投稿者により削除されました</b></i>','raw':''}})
373 self.redirect('/'+dbname)
375 class SearchHandler(web.RequestHandler):
376 def post(self,dbname=''):
377 arg = self.get_argument('word1')
379 self.andor = self.get_argument('type')
380 self.radiobox = self.get_argument('filter')
383 for x in self.application.coll():
384 moji = self.search(x)
385 for y in sorted(moji,key=lambda k: k['number']):
389 rec = sorted(self.search(dbname),key=lambda x: x['number'])
390 self.render('modules/search.htm',records=rec,word1=arg,db=dbname)
392 def get(self,dbname=''):
393 if dbname not in self.application.coll(info=True) and dbname != '':
394 raise web.HTTPError(404)
395 self.render('modules/search.htm',records=[],word1='',db=dbname)
397 def search(self,dbname):
398 table = self.application.db[dbname]
399 andor = self.andor == 'OR'
400 element = self.word.split()
404 elm.append(re.escape(x))
405 if self.radiobox == 'comment':
408 query.append({'raw':re.compile(qu,re.IGNORECASE)})
412 result = table.find({'$or':query})
415 result = table.find({'$and':query})
420 for text in x['raw'].splitlines():
426 text = text.replace(' ',' ',i)
428 if y.lower() in text.lower():
429 com += '<p style=background-color:'+color+'>'+text+'<br></p>'
435 com += '<p>'+text+'</p>'
442 query.append({'name':x})
445 for x in table.find({'$or':query}):
448 class HelpHandler(web.RequestHandler):
450 self.render('help.htm',req='')
453 com = self.get_argument('help','')
454 line = com.splitlines(True)
458 time = datetime.now()
459 db = self.application.db['master']
460 db.insert({'comment':com,'time':time.strftime('%Y/%m/%d')})
461 self.render('help.htm',req='送信しました')
463 class MasterHandler(BaseHandler):
466 if self.current_user == b'admin':
467 com = self.application.db['master'].find()
468 sum = self.application.db['temp'].find().count()
469 self.render('master.htm',com=com,sum=sum)
471 raise web.HTTPError(404)
473 class AlertHandler(web.RequestHandler):
475 db = self.get_query_argument('db')
476 num = self.get_query_argument('num')
477 table = self.application.db[db]
478 tb = table.find_one({'number':int(num)})
480 time = datetime.now().strftime('%Y/%m/%d')
481 link = self.application.page(db,num)
482 jump = '<p><a href={0}>{0}</a>'.format(link)
483 d = datetime.now().weekday()
484 table = self.application.db['temp']
485 table.remove({'date':{'$ne':d}})
486 result = table.insert(
487 {'comment':com+jump,'time':time,'link':link,'date':d,'db':db,'num':num})
488 self.render('alert.htm',com=com+jump,num=str(result))
491 id = ObjectId(self.get_argument('num'))
492 table = self.application.db['temp']
493 tb = table.find_one({'_id':id})
495 com = self.get_argument('com')
496 table.remove({'_id':id})
497 if self.get_argument('cancel','') == 'cancel':
501 tb['comment'] = com+tb['comment']
503 table = self.application.db['master']
507 class CleanHandler(web.RequestHandler):
509 bool = self.get_argument('all', 'false').lower()
510 table = self.application.db['master']
513 self.application.db['temp'].remove()
514 elif bool == 'false':
515 for x in list(table.find()):
516 if not 'num' in x.keys():
517 table.remove({'_id':x['_id']})
519 item = self.application.db[x['db']].find_one({'number':int(x['num'])})
520 if (not item)or(item['raw'] == ''):
521 table.remove({'_id':x['_id']})
522 com = self.application.db['master'].find()
523 sum = self.application.db['temp'].find().count()
524 self.render('master.htm', com=com, sum=sum)
526 class FooterModule(web.UIModule):
527 def render(self,number,url,link):
528 return self.render_string('modules/footer.htm',index=number,url=url,link=link)
530 class HeadlineApi(web.RequestHandler):
533 for coll in self.application.coll():
534 table = self.application.db[coll]
535 if table.count() == 0:
538 text = table.find().sort('number')[table.count()-1]
539 mydict = {'number':text['number'],'name':text['name'],'title':text['title'],'comment':text['raw'][0:20]}
540 response[coll] = mydict
541 self.write(json.dumps(response,ensure_ascii=False))
543 class ArticleApi(web.RequestHandler):
544 def get(self,dbname,number):
546 if dbname in self.application.coll():
547 table = self.application.db[dbname]
548 response = table.find_one({'number':int(number)})
553 del response['comment']
554 del response['password']
555 self.write(json.dumps(response,ensure_ascii=False))
557 class ListApi(web.RequestHandler):
558 def get(self,dbname):
560 if dbname in self.application.coll():
561 table = self.application.db[dbname]
563 for data in table.find().sort('number'):
564 response[data['number']] = data['raw'][0:20]
567 self.write(json.dumps(response,ensure_ascii=False))
569 class WebHookHandler(web.RequestHandler):
571 table, na = self.users()
572 item = table.find({'no':re.compile(no,re.IGNORECASE)})
573 if item.count() == 1:
575 ans = x['name']+'\n'+x['no']
576 elif item.count() > 1:
579 list1 = sorted(obj, key=lambda k:k['name'])
581 if x['name'] == list1[0]['name']:
582 ans += x['name']+'\n'+x['no']+'\n'
587 ans = self.itr(sorted(list1, key=lambda k:k['no']))
589 ans = self.itr(table.find().sort('no'))
590 ans = '-*-'+na+' list-*-\n'+ans
596 ans += '【'+x['no']+'】 '
600 s = '-*-database names-*-\n'
601 out = ['objectlabs-system','objectlabs-system.admin.collections','users_bot']
602 for x in self.application.mylist():
603 if x not in out and x[-4:] == '_bot' and x != '_bot':
607 def setting(self, dbname):
608 dbname = dbname.lower()+'_bot'
609 ca = self.application.mylist()
610 if 'users_bos' in ca:
611 ca.remove('users_bot')
613 db = self.application.db['users_bot']
614 item = db.find_one({'name':self.uid})
615 if item['dbname'] == dbname:
618 db.update({'name':self.uid}, {'name':self.uid, 'dbname':dbname})
623 db = self.application.db['users_bot']
624 item = db.find_one({'name':self.uid})
626 return self.application.db[x], x[:-4]
630 signature = self.request.headers['X-Line-Signature']
631 body = self.request.body
632 parser = WebhookParser(self.application.ch)
634 parser.parse(body, signature)
635 except InvalidSignatureError:
639 dic = escape.json_decode(self.request.body)
640 for event in dic['events']:
641 if 'replyToken' in event.keys():
642 self.uid = event['source']['userId']
644 if event['type'] == 'unfollow':
645 self.application.db[bot].remove({'name':self.uid})
647 elif event['type'] != 'message' or event['message']['type'] != 'text':
649 item = self.application.db['params'].find_one({'app':'bot'})
654 if item and 'access_token' in item.keys():
655 token = item['access_token']
657 token =self.application.tk
658 if bot not in self.application.mylist() or not self.application.db[bot].find_one({'name':self.uid}):
659 db = self.application.db[bot]
660 db.insert({'name':self.uid, 'dbname':de})
661 x = event['message']['text']
668 linebot = LineBotApi(token)
669 linebot.reply_message(event['replyToken'], TextSendMessage(text=te))
671 class InitHandler(web.RequestHandler):
673 de = self.get_argument('default', '')
675 names = self.application.mylist()
678 if x[-4:] == '_bot' and x != 'users_bot':
680 self.render('init.htm',db=db)
682 tb = self.application.db['params']
683 if tb.find_one({'app':'bot'}):
684 tb.update({'app':'bot'}, {'app':'bot', 'default':de+'_bot'})
686 tb.insert({'app':'bot', 'default':de+'_bot'})
687 for x in glob.glob('./*.txt'):
691 self.main(x[2:-4].lower(), data)
693 def main(self, name, data):
694 if name == 'requirements':
698 for x in data.split('\n'):
699 if len(x) > 0 and x[0] == '@':
705 table = self.application.db[name+'_bot']
710 class TokenHandler(web.RequestHandler):
711 def on_response(self, response):
712 dic = escape.json_decode(response.body)
713 token = dic['access_token']
714 table = self.application.db['params']
715 data = {'app':'bot', 'access_token':token}
716 if table.find_one({'app':'bot'}):
724 url = 'https://api.line.me/v2/oauth/accessToken'
725 headers = 'application/x-www-form-urlencoded'
726 data = {'grant_type':'client_credentials', 'client_id':self.application.id, 'client_secret':self.application.ch}
727 body = urllib.parse.urlencode(data)
728 req = httpclient.HTTPRequest(url=url,method='POST',headers=headers,body=body)
729 http = httpclient.AsyncHTTPClient()
730 http.fetch(req, callback=self.on_response)
732 class Application(web.Application):
733 ch = os.environ['Channel_Secret']
734 uri = os.environ['MONGODB_URI']
735 ac = os.environ['ACCOUNT']
736 tk = os.environ['Access_Token']
737 db = pymongo.MongoClient(uri)[ac]
739 handlers = [(r'/',NaviHandler),(r'/login',LoginHandler),(r'/logout',LogoutHandler),(r'/title',TitleHandler),
740 (r'/headline/api',HeadlineApi),(r'/read/api/([a-zA-Z0-9_%]+)/([0-9]+)',ArticleApi),
741 (r'/write/api/([a-zA-Z0-9_%]+)/()/()/()',ArticleApi),(r'/list/api/([a-zA-Z0-9_%]+)',ListApi),
742 (r'/help',HelpHandler),(r'/master',MasterHandler),(r'/alert',AlertHandler),(r'/jump',JumpHandler),
743 (r'/callback',WebHookHandler),(r'/init',InitHandler),(r'/search',SearchHandler),(r'/clean',CleanHandler),(r'/token',TokenHandler),
744 (r'/([a-zA-Z0-9_%]+)',IndexHandler),(r'/([a-zA-Z0-9_%]+)/([0-9]+)/',IndexHandler),
745 (r'/([a-zA-Z0-9_%]+)/admin/([0-9]+)/*',AdminHandler),(r'/([a-zA-Z0-9_%]+)/admin/([a-z]+)/*',AdminConfHandler),(r'/([a-zA-Z0-9_%]+)/userdel',UserHandler),
746 (r'/([a-zA-Z0-9_%]+)/search',SearchHandler),(r'/([a-zA-Z0-9_%]+)/regist',RegistHandler)]
747 settings = {'template_path':os.path.join(os.path.dirname(__file__),'templates'),
748 'static_path':os.path.join(os.path.dirname(__file__),'static'),
749 'ui_modules':{'Footer':FooterModule},
750 'cookie_secret':os.environ['cookie'],
751 'xsrf_cookies':False,
755 super().__init__(handlers,**settings)
757 def gpos(self,dbname,page):
758 params = self.db['params'].find_one({'app':'bbs'})
762 elif (pos-1)*params['count'] >= self.db[dbname].count():
766 def page(self,dbname,number):
767 table = self.db[dbname]
768 rec = table.find({'number':{'$lte':int(number)}}).count()
769 s = self.db['params'].find_one({'app':'bbs'})
770 conf = int(s['count'])
771 if table.find().count() - rec >= conf:
772 return '/'+dbname+'/'+str(1+rec//conf)+'/#'+number
774 return '/'+dbname+'#'+number
777 return self.db.list_collection_names()[:]
779 def coll(self,info=False):
781 item = self.db['params'].find_one({'app':'bbs'})
782 target = ['objectlabs-system', 'objectlabs-system.admin.collections', 'system.indexes',
783 'params', 'master', 'temp']
785 target.append(item['info name'])
793 if __name__ == '__main__':
795 http_server = httpserver.HTTPServer(app)
796 port = int(os.environ.get('PORT',5000))
797 http_server.listen(port)
798 ioloop.IOLoop.instance().start()