3 from tornado import escape,web,ioloop,httpserver,httpclient
5 from datetime import datetime
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 get(self,dbname,page='0'):
26 dbname = escape.url_unescape(dbname)
27 params = self.application.db['params'].find_one({'app':'bbs'})
28 if params['mentenance'] is True:
29 self.render('mentenance.htm',title=params['title'],db=dbname)
31 if self.application.collection(dbname) is False:
32 if self.current_user == b'admin':
33 coll = self.application.db[dbname]
37 raise web.HTTPError(404)
38 key = self.get_argument('key','')
40 table = self.application.db[dbname]
41 rec = table.find_one({'number':int(key)})
43 self.render('article.htm',record=rec)
46 raise web.HTTPError(404)
48 rule = escape.url_unescape(self.get_cookie('aikotoba',''))
49 na = escape.url_unescape(self.get_cookie('username',u'誰かさん'))
50 pos = self.application.gpos(dbname,page)
51 table = self.application.db[dbname]
54 start = table.count()-i
58 bool = (dbname == params['info name'])
63 rec.skip(start).limit(i)
64 if table.count() >= 10*i:
65 self.render('modules/full.htm',position=pos,records=rec,data=params,db=dbname)
67 if bool is True and self.current_user != b'admin':
68 self.render('modules/info.htm',position=pos,records=rec,data=params,db=dbname)
70 self.render('modules/index.htm',position=pos,records=rec,data=params,username=na,db=dbname,aikotoba=rule)
72 class LoginHandler(BaseHandler):
74 info = self.application.db['params'].find_one({'app':'bbs'})
75 query = self.get_query_argument('next','/'+info['info name'])
76 i = query[1:].find('/')
81 self.render('login.htm',db=escape.url_unescape(qs))
84 dbname = self.get_argument('record','')
86 self.redirect('/login')
88 pw = self.application.db['params'].find_one({'app':'bbs'})
89 if self.get_argument('password') == pw['password']:
90 self.set_current_user('admin')
91 if dbname == 'master':
92 self.redirect('/master')
94 self.redirect('/'+dbname+'/admin/0/')
96 class LogoutHandler(BaseHandler):
98 self.clear_current_user()
99 self.redirect('/login')
101 class JumpHandler(BaseHandler):
103 self.clear_current_user()
106 class NaviHandler(web.RequestHandler):
108 if self.application.collection('params') is False:
109 item = {"mentenance":False,"out_words":[u"阿保",u"馬鹿",u"死ね"],"password":"admin",
110 "title2":"<h1 style=color:gray;text-align:center>pybbs</h1>",
111 "bad_words":["<style","<link","<script","<img"],"count":30,
112 "title":"pybbs","info name":"info",'app':'bbs'}
113 self.application.db['params'].insert(item)
114 self.application.db['info']
115 table = self.application.db['params'].find_one({'app':'bbs'})
116 if table['mentenance'] is True:
117 self.render('mentenance.htm',title=table['title'],db=table['info name'])
119 coll,na = self.name()
120 self.render('top.htm',coll=coll,name=na,full=self.full)
123 coll = sorted(self.application.coll(),key=str.lower)
124 na = self.application.db['params'].find_one({'app':'bbs'})['info name']
131 def full(self,dbname):
132 if dbname in self.application.db.collection_names():
133 i = 10*self.application.db['params'].find_one({'app':'bbs'})['count']
134 table = self.application.db[dbname]
135 if table.count() >= i:
139 class TitleHandler(NaviHandler):
141 rec = sorted(self.title(),key=lambda x: x['date2'])
142 self.render('title.htm',coll=rec,full=self.full)
145 name = self.application.coll()
149 table = self.application.db[x]
152 tmp = table.find_one({'number':1})
162 rec = table.find().sort('number')
165 i = datetime.strptime(s,'%Y/%m/%d %H:%M')
166 year = datetime.now().year-i.year
173 item['date2'] = j+31*(i.month-1)+i.day
176 class RegistHandler(web.RequestHandler):
177 def post(self,dbname):
178 if self.application.collection(dbname) is False:
179 raise web.HTTPError(404)
180 self.database = dbname
181 rec = self.application.db['params'].find_one({'app':'bbs'})
182 words = rec['bad_words']
183 out = rec['out_words']
184 rule = self.get_argument('aikotoba')
185 na = self.get_argument('name')
186 sub = self.get_argument('title')
187 com = self.get_argument('comment',None,False)
198 for line in com.splitlines():
202 if word in line.lower():
203 error += u'タグ違反.('+word+')'
205 obj = re.finditer('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
207 if x.group() not in url:
208 url.append(x.group())
216 line = line.replace(' ',' ',j)
220 text += '<p>'+self.link(line)+'</p>'
223 s = s+'<tr><td><a href={0} class=livepreview target=_blank>{0}</a></td></tr>'.format(x)
225 text = text+'<table><tr><td>検出url:</td></tr>'+s+'</table>'
226 pw = self.get_argument('password')
228 error += u'文字数が1,000をこえました.'
233 article = self.application.db[dbname]
234 if article.count() == 0:
237 items = article.find()
238 item = items.sort('number')[article.count()-1]
239 no = item['number']+1
241 self.set_cookie('aikotoba',escape.url_escape(rule))
243 reg = {'number':no,'name':na,'title':sub,'comment':text,'raw':com,'password':pw,'date':s.strftime('%Y/%m/%d %H:%M')}
245 self.set_cookie('username',escape.url_escape(na))
246 self.redirect('/'+dbname+'#article')
248 self.render('regist.htm',content=error)
250 def link(self,command):
253 obj = re.finditer('>>[0-9]+',command)
255 s = '<a class=minpreview data-preview-url=/{0}?key={1} href=/{0}/userdel?job={1}>>>{1}</a>'.format(self.database,x.group()[2:])
256 text = text+command[i:x.start()]+s
259 text = text+command[i:]
262 class AdminHandler(BaseHandler):
264 def get(self,dbname,page='0'):
266 dbname = self.get_argument('record','')
267 if self.application.collection(dbname) is False:
268 raise web.HTTPError(404)
269 table = self.application.db[dbname]
270 rec = table.find().sort('number')
271 mente = self.application.db['params'].find_one({'app':'bbs'})
272 if mente['mentenance'] is True:
273 check = 'checked=checked'
276 pos = self.application.gpos(dbname,page)
280 start = table.count()-i
283 rec.skip(start).limit(i)
284 self.render('modules/admin.htm',position=pos,records=rec,mente=check,password=mente['password'],db=dbname)
286 class AdminConfHandler(BaseHandler):
288 def post(self,dbname,func):
290 param = self.application.db['params'].find_one({'app':'bbs'})
291 if self.get_argument('mente','') == 'on':
295 word = self.get_argument('pass','')
297 self.render('regist.htm',content='パスワードを設定してください')
300 param['mentenance']=mente
301 param['password']=word
302 self.application.db['params'].save(param)
304 table = self.application.db[dbname]
305 for x in self.get_arguments('item'):
306 table.remove({'number':int(x)})
307 self.redirect('/'+dbname+'/admin/0/')
309 class UserHandler(web.RequestHandler):
310 def get(self,dbname):
311 q = self.get_query_argument('job','0',strip=True)
312 tb = self.application.db[dbname]
313 num = self.application.page(tb,int(q))
314 self.redirect('/{0}{1}#{2}'.format(dbname,num,q))
316 def post(self,dbname):
317 number = self.get_argument('number')
318 if number.isdigit() is True:
320 if 'password' in self.request.arguments.keys():
321 pas = self.get_argument('password')
323 num = self.application.page(dbname,num)
324 self.redirect('/{0}{1}#{2}'.format(dbname, num, number))
326 table = self.application.db[dbname]
327 obj = table.find_one({'number':num})
328 if obj and(obj['password'] == pas):
329 table.update({'number':num},{'$set':{'title':u'削除されました','name':'','comment':u'<i><b>投稿者により削除されました</b></i>','raw':''}})
330 self.redirect('/'+dbname+self.application.page(table,num)+'#'+number)
332 self.redirect('/'+dbname)
334 class SearchHandler(web.RequestHandler):
335 def post(self,dbname=''):
336 arg = self.get_argument('word1')
338 self.andor = self.get_argument('type')
339 self.radiobox = self.get_argument('filter')
342 for x in self.application.coll():
343 moji = self.search(x)
344 for y in sorted(moji,key=lambda k: k['number']):
348 rec = sorted(self.search(dbname),key=lambda x: x['number'])
349 self.render('modules/search.htm',records=rec,word1=arg,db=dbname)
351 def get(self,dbname=''):
352 if self.application.collection(dbname) is False and dbname != '':
353 raise web.HTTPError(404)
354 self.render('modules/search.htm',records=[],word1='',db=dbname)
356 def search(self,dbname):
357 table = self.application.db[dbname]
358 andor = self.andor == 'OR'
359 element = self.word.split()
363 elm.append(re.escape(x))
364 if self.radiobox == 'comment':
367 query.append({'raw':re.compile(qu,re.IGNORECASE)})
371 result = table.find({'$or':query})
374 result = table.find({'$and':query})
379 for text in x['raw'].splitlines():
385 text = text.replace(' ',' ',i)
387 if y.lower() in text.lower():
388 com += '<p style=background-color:'+color+'>'+text+'<br></p>'
394 com += '<p>'+text+'</p>'
401 query.append({'name':x})
404 for x in table.find({'$or':query}):
407 class HelpHandler(web.RequestHandler):
409 self.render('help.htm',req='')
412 com = self.get_argument('help','')
413 line = com.splitlines(True)
417 time = datetime.now()
418 db = self.application.db['master']
419 db.insert({'comment':com,'time':time.strftime('%Y/%m/%d')})
420 self.render('help.htm',req='送信しました')
422 class MasterHandler(BaseHandler):
425 if self.current_user == b'admin':
426 com = self.application.db['master'].find()
427 sum = self.application.db['temp'].find().count()
428 self.render('master.htm',com=com,sum=sum)
430 raise web.HTTPError(404)
432 class AlertHandler(web.RequestHandler):
434 db = self.get_query_argument('db')
435 num = self.get_query_argument('num')
436 table = self.application.db[db]
437 tb = table.find_one({'number':int(num)})
439 time = datetime.now().strftime('%Y/%m/%d')
440 s = self.application.page(table,int(num))
441 link = '/'+db+s+'#'+num
442 jump = '<p><a href={0}>{0}</a>'.format(link)
443 d = datetime.now().weekday()
444 table = self.application.db['temp']
445 table.remove({'date':{'$ne':d}})
446 result = table.insert(
447 {'comment':com+jump,'time':time,'link':link,'date':d,'db':db,'num':num})
448 self.render('alert.htm',com=com+jump,num=str(result))
451 id = ObjectId(self.get_argument('num'))
452 table = self.application.db['temp']
453 tb = table.find_one({'_id':id})
455 com = self.get_argument('com')
456 table.remove({'_id':id})
457 if self.get_argument('cancel','') == 'cancel':
461 tb['comment'] = com+tb['comment']
463 table = self.application.db['master']
467 class CleanHandler(web.RequestHandler):
469 bool = self.get_argument('all', 'false').lower()
470 table = self.application.db['master']
473 self.application.db['temp'].remove()
474 elif bool == 'false':
475 for x in list(table.find()):
476 if not 'num' in x.keys():
477 table.remove({'_id':x['_id']})
479 item = self.application.db[x['db']].find_one({'number':int(x['num'])})
480 if (not item)or(item['raw'] == ''):
481 table.remove({'_id':x['_id']})
482 com = self.application.db['master'].find()
483 sum = self.application.db['temp'].find().count()
484 self.render('master.htm', com=com, sum=sum)
486 class FooterModule(web.UIModule):
487 def render(self,number,url,link):
488 return self.render_string('modules/footer.htm',index=number,url=url,link=link)
490 class HeadlineApi(web.RequestHandler):
493 for coll in self.application.coll():
494 table = self.application.db[coll]
495 if table.count() == 0:
498 text = table.find().sort('number')[table.count()-1]
499 mydict = {'number':text['number'],'name':text['name'],'title':text['title'],'comment':text['raw'][0:20]}
500 response[coll] = mydict
501 self.write(json.dumps(response,ensure_ascii=False))
503 class ArticleApi(web.RequestHandler):
504 def get(self,dbname,number):
506 if self.application.collection(dbname) == True:
507 table = self.application.db[dbname]
508 response = table.find_one({'number':int(number)})
513 del response['comment']
514 del response['password']
515 self.write(json.dumps(response,ensure_ascii=False))
517 def post(self,dbname,name,title,article):
518 coll = self.application.db[dbname]
519 coll.insert({'name':name,'title':title,'comment':article})
521 class ListApi(web.RequestHandler):
522 def get(self,dbname):
524 if self.application.collection(dbname) is True:
525 table = self.application.db[dbname]
527 for data in table.find().sort('number'):
528 response[data['number']] = data['raw'][0:20]
531 self.write(json.dumps(response,ensure_ascii=False))
533 class WebHookHandler(web.RequestHandler):
535 #pz = pytz.timezone('Asia/Tokyo')
536 now = datetime.now()#pz)
539 if (w < 5)and(t >= 9)and(t < 16):
541 table, na = self.users()
542 item = table.find({'no':re.compile(no,re.IGNORECASE)})
543 if item.count() == 1:
545 ans = x['name']+'\n'+x['no']
546 elif item.count() > 1:
549 list1 = sorted(obj, key=lambda k:k['name'])
551 if x['name'] == list1[0]['name']:
552 ans += x['name']+'\n'+x['no']+'\n'
557 ans = self.itr(sorted(list1, key=lambda k:k['no']))
559 ans = self.itr(table.find().sort('no'))
560 ans = '-*-'+na+' list-*-\n'+ans
566 ans += '【'+x['no']+'】 '
570 s = '-*-database names-*-\n'
571 out = ['objectlabs-system','objectlabs-system.admin.collections','users_bot']
572 for x in self.application.db.collection_names(include_system_collections=False):
573 if not x in out and x[-4:] == '_bot' and x != '_bot':
577 def setting(self, dbname):
578 dbname = dbname.lower()+'_bot'
579 ca = self.application.db.collection_names(include_system_collections=False)
580 if 'users_bos' in ca:
581 ca.remove('users_bot')
583 db = self.application.db['users_bot']
584 item = db.find_one({'name':self.uid})
585 if item['dbname'] == dbname:
588 db.update({'name':self.uid}, {'name':self.uid, 'dbname':dbname})
593 db = self.application.db['users_bot']
594 item = db.find_one({'name':self.uid})
596 return self.application.db[x], x[:-4]
600 signature = self.request.headers['X-Line-Signature']
601 body = self.request.body
602 parser = WebhookParser(self.application.ch)
604 parser.parse(body, signature)
605 except InvalidSignatureError:
609 dic = escape.json_decode(self.request.body)
610 for event in dic['events']:
611 if 'replyToken' in event.keys():
612 self.uid = event['source']['userId']
614 if event['type'] == 'unfollow':
615 self.application.db[bot].remove({'name':self.uid})
617 elif event['type'] != 'message' or event['message']['type'] != 'text':
619 item = self.application.db['params'].find_one({'app':'bot'})
624 if item and 'access_token' in item.keys():
625 token = item['access_token']
627 token =self.application.tk
628 if not bot in self.application.db.collection_names() or not self.application.db[bot].find_one({'name':self.uid}):
629 db = self.application.db[bot]
630 db.insert({'name':self.uid, 'dbname':de})
631 x = event['message']['text']
638 linebot = LineBotApi(token)
639 linebot.reply_message(event['replyToken'], TextSendMessage(text=te))
641 class InitHandler(web.RequestHandler):
643 de = self.get_argument('default', '')
645 names = self.application.db.collection_names()
648 if x[-4:] == '_bot' and x != 'users_bot':
650 self.render('init.htm',db=db)
652 tb = self.application.db['params']
653 if tb.find_one({'app':'bot'}):
654 tb.update({'app':'bot'}, {'app':'bot', 'default':de+'_bot'})
656 tb.insert({'app':'bot', 'default':de+'_bot'})
657 for x in glob.glob('./*.txt'):
661 self.main(x[2:-4].lower(), data)
663 def main(self, name, data):
664 if name == 'requirements':
668 for x in data.split('\n'):
669 if len(x) > 0 and x[0] == '@':
675 table = self.application.db[name+'_bot']
680 class TokenHandler(web.RequestHandler):
681 def on_response(self, response):
682 dic = escape.json_decode(response.body)
683 token = dic['access_token']
684 table = self.application.db['params']
685 data = {'app':'bot', 'access_token':token}
686 if table.find_one({'app':'bot'}):
694 url = 'https://api.line.me/v2/oauth/accessToken'
695 headers = 'application/x-www-form-urlencoded'
696 data = {'grant_type':'client_credentials', 'client_id':self.application.id, 'client_secret':self.application.ch}
697 body = urllib.parse.urlencode(data)
698 req = httpclient.HTTPRequest(url=url,method='POST',headers=headers,body=body)
699 http = httpclient.AsyncHTTPClient()
700 http.fetch(req, callback=self.on_response)
702 class Application(web.Application):
703 id = os.environ['Bot_Id']
704 ch = os.environ['Channel_Secret']
705 uri = os.environ['MONGODB_URI']
706 ac = os.environ['ACCOUNT']
707 tk = os.environ['long_token']
708 db = pymongo.MongoClient(uri)[ac]
710 handlers = [(r'/',NaviHandler),(r'/login',LoginHandler),(r'/logout',LogoutHandler),(r'/title',TitleHandler),
711 (r'/headline/api',HeadlineApi),(r'/read/api/([a-zA-Z0-9_]+)/([0-9]+)',ArticleApi),
712 (r'/write/api/([a-zA-Z0-9_%]+)/()/()/()',ArticleApi),(r'/list/api/([a-zA-Z0-9_%]+)',ListApi),
713 (r'/help',HelpHandler),(r'/master',MasterHandler),(r'/alert',AlertHandler),(r'/jump',JumpHandler),
714 (r'/callback',WebHookHandler),(r'/init',InitHandler),(r'/search',SearchHandler),(r'/clean',CleanHandler),(r'/token',TokenHandler),
715 (r'/([a-zA-Z0-9_%]+)',IndexHandler),(r'/([a-zA-Z0-9_%]+)/([0-9]+)/',IndexHandler),
716 (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),
717 (r'/([a-zA-Z0-9_%]+)/search',SearchHandler),(r'/([a-zA-Z0-9_%]+)/regist',RegistHandler)]
718 settings = {'template_path':os.path.join(os.path.dirname(__file__),'templates'),
719 'static_path':os.path.join(os.path.dirname(__file__),'static'),
720 'ui_modules':{'Footer':FooterModule},
721 'cookie_secret':os.environ['cookie'],
722 'xsrf_cookies':False,
726 super().__init__(handlers,**settings)
728 def gpos(self,dbname,page):
729 params = self.db['params'].find_one({'app':'bbs'})
733 elif (pos-1)*params['count'] >= self.db[dbname].count():
737 def page(self,table,number):
738 rec = table.find({'number':{'$lte':number}}).count()
739 s = self.db['params'].find_one({'app':'bbs'})
740 conf = int(s['count'])
741 if table.find().count() - rec >= conf:
742 return '/'+str(1+rec//conf)+'/'
746 def collection(self,name):
747 if name in self.db.collection_names():
753 name = self.db.collection_names()
754 for x in ['objectlabs-system.admin.collections','objectlabs-system','system.indexes',
755 'params','master','temp']:
763 if __name__ == '__main__':
765 http_server = httpserver.HTTPServer(app)
766 port = int(os.environ.get('PORT',5000))
767 http_server.listen(port)
768 ioloop.IOLoop.instance().start()