OSDN Git Service

rewrite
[pybbs/pybbs.git] / index.py
1
2 import os.path
3 import shutil,re
4 import tornado.escape
5 import tornado.web
6 import tornado.httpserver
7 import tornado.ioloop
8 import tornado.options
9 from tornado.options import define,options
10 from tinydb import TinyDB,Query,where
11 from tinydb.operations import delete
12 from datetime import datetime,date
13 import json
14
15 define('port',default=8000,help='run on the given port',type=int)
16
17 class BaseHandler(tornado.web.RequestHandler):
18     def get_current_user(self):
19         user = self.get_secure_cookie('admin_user')
20         return tornado.escape.utf8(user)
21     
22     def set_current_user(self,username):
23         self.set_secure_cookie('admin_user',username)
24         
25     def clear_current_user(self):
26         self.clear_cookie('admin_user')
27
28 class IndexHandler(BaseHandler):
29     def get(self,dbname,page='0'):
30         params = self.application.db.get(where('kinds') == 'conf')
31         if params['mentenance'] == True:
32             self.render('mentenance.htm',title=params['title'],db=dbname)
33             return
34         if self.application.collection(dbname) == False:
35             if self.current_user == b'admin':
36                 self.application.db.table(dbname)
37             else:
38                 raise tornado.web.HTTPError(404)
39                 return
40         key = self.get_argument('key','')
41         if key:
42             table = self.application.db.table(dbname)
43             rec = table.get(where('number') == int(key))
44             if rec:
45                 self.render('article.htm',record=rec)
46                 return
47             else:
48                 raise tornado.web.HTTPError(404)
49                 return
50         i = params['count']      
51         rule = tornado.escape.url_unescape(self.get_cookie('aikotoba',''))
52         na = tornado.escape.url_unescape(self.get_cookie("username",u"誰かさん"))
53         pos = self.application.gpos(dbname,page)
54         table = self.application.db.table(dbname)
55         start = (pos-1)*i
56         if start < 0:
57             start = len(table)-i
58             if start < 0:
59                 start = 0
60         bool = (dbname == params['info name'])
61         rec = sorted(table.all(),key=lambda x: x['number'])[start:start+i]
62         if bool == True:
63             rec = rec[::-1]
64         if len(table) >= 10*i:
65             self.render('modules/full.htm',position=pos,records=rec,data=params,db=dbname)
66             return
67         if (bool == True)and(self.current_user != b'admin'):
68             self.render('modules/info.htm',position=pos,records=rec,data=params,db=dbname)
69         else:
70             self.render('modules/index.htm',position=pos,records=rec,data=params,username=na,db=dbname,aikotoba=rule)
71         
72 class LoginHandler(BaseHandler):
73     def get(self):
74         query = self.get_query_argument('next','')
75         i = query[1:].find('/')
76         if i == -1:
77             qs = query[1:]
78         else:
79             qs = query[1:i+1]  
80         self.render('login.htm',db=qs)
81         
82     def post(self):
83         pw = self.application.db.get(where('kinds') == 'conf')
84         if self.get_argument('password') == pw['password']:
85             self.set_current_user('admin')
86         dbname = self.get_argument('record')
87         if dbname == 'master':
88             self.redirect('/master')
89         else:
90             self.redirect('/'+dbname+'/admin/0/')
91         
92 class LogoutHandler(BaseHandler):
93     def get(self):
94         self.clear_current_user()
95         self.redirect('/login')
96         
97 class NaviHandler(tornado.web.RequestHandler):              
98     def get(self):
99         col,na = self.name()
100         self.render('top.htm',coll=col,name=na,full=self.full)
101         
102     def name(self):
103         names = self.application.db.tables()
104         na = self.application.db.get(where('kinds') == 'conf')['info name']
105         for s in ['_default','master','temp']:
106             if s in names:
107                 names.remove(s)
108         if na in names:
109             names.remove(na)
110         else:
111             na = ''
112         return sorted(names),na
113                 
114     def full(self,dbname):
115         if dbname in self.application.db.tables():
116             i = 10*self.application.db.get(where('kinds') == 'conf')['count']
117             table = self.application.db.table(dbname)
118             if len(table) >= i:
119                 return True
120         return False
121
122 class TitleHandler(NaviHandler):
123     def get(self):
124         rec = sorted(self.title(),key=lambda x: x['date2'])
125         self.render('title.htm',coll=rec,full=self.full)  
126         
127     def title(self):
128         names = self.application.db.tables()
129         for s in ['_default','master','temp']:
130             if s in names:
131                 names.remove(s)
132         for x in names:
133             item = {}
134             item['name'] = x
135             table = self.application.db.table(x)
136             i = len(table)
137             item['count'] = i            
138             if table.contains(where('number') == 1) == True:
139                 s = table.get(where('number') == 1)['title']
140             else:
141                 s = ''
142             item['title'] = s   
143             if i == 0:
144                 item['date'] = ''
145                 item['date2'] = 0
146             else:
147                 rec = sorted(table.all(),key=lambda k: k['number'])
148                 s = rec[i-1]['date']
149                 item['date'] = s
150                 i = datetime.strptime(s,'%Y/%m/%d %H:%M')
151                 year = datetime.now().year-i.year
152                 if year == 0:
153                     j = 800
154                 elif year == 1:
155                     j = 400
156                 else:
157                     j = 0
158                 item['date2'] = j+31*(i.month-1)+i.day
159             yield item
160         
161 class RegistHandler(tornado.web.RequestHandler):
162     def post(self,dbname):
163         if self.application.collection(dbname) == False:
164             raise tornado.web.HTTPError(404)
165             return
166         self.database = dbname
167         rec = self.application.db.get(where('kinds') == 'conf')
168         words = rec['bad_words']
169         out = rec['out_words']
170         na = self.get_argument('name')
171         sub = self.get_argument('title')
172         com = self.get_argument('comment',None,False)
173         text = ''
174         i = 0
175         url = []
176         error = ''
177         for word in out:
178             if word in com:
179                 error = error + u'禁止ワード.'
180                 break
181         for line in com.splitlines(True):
182             if error != '':
183                 break
184             for word in words:
185                 if word in line.lower():
186                     error = error + u'タグ違反.('+word+')'       
187             i += len(line)   
188             obj = re.finditer('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
189             for x in obj:
190                 if x.group() not in url:
191                     url.append(x.group())
192             if re.match(' ',line):
193                 line = line.replace(' ','&nbsp;',1)
194             text = text+'<p>'+self.link(line)+'<br></p>'
195         s = ''
196         for x in url:
197             s = s+'<tr><td><a class=livepreview target=_blank href={0}>{0}</a></td></tr>'.format(x)
198         if s:
199             text = text+'<table><tr><td>検出URL:</td></tr>'+s+'</table>'
200         pw = self.get_argument('password')
201         if i == 0:
202             error = error + u'本文がありません.'
203         elif i > 1000:
204             error = error +u'文字数が1,000をこえました.'
205         article = self.application.db.table(dbname)
206         if len(article) == 0:
207             no = 1
208         else:
209             item = sorted(article.all(),key=lambda x: x['number'])[len(article)-1]
210             no = item['number']+1
211         if error == '':
212             if not na:
213                 na = u'誰かさん'
214             if sub == '':
215                 sub = u'タイトルなし.'
216             s = datetime.now()
217             reg = {'number':no,'name':na,'title':sub,'comment':text,'raw':com,'password':pw,'date':s.strftime('%Y/%m/%d %H:%M')}
218             article.insert(reg)
219             self.set_cookie('username',tornado.escape.url_escape(na))
220             self.redirect('/'+dbname+'#article')
221         else:
222             self.render('regist.htm',content=error)
223     
224     def link(self,command):
225         i = 0
226         text = ''
227         obj = re.finditer('>>[0-9]+',command)
228         for x in obj:
229             s = '<a class=minpreview data-preview-url=/{0}?key={1} href=/{0}/userdel?job={1}>>>{1}</a>'.format(self.database,x.group()[2:])
230             text = text+command[i:x.start()]+s
231             i = x.end()
232         else:
233             text = text+command[i:]
234         return text
235     
236 class AdminHandler(BaseHandler):
237     @tornado.web.authenticated               
238     def get(self,dbname,page):
239         if dbname == '':
240             dbname = self.get_argument('record','')
241         if self.application.collection(dbname) == False:
242             raise tornado.web.HTTPError(404)
243             return
244         table = self.application.db.table(dbname) 
245         rec = sorted(table.all(),key=lambda x: x['number'])                   
246         mente = self.application.db.get(where('kinds') == 'conf')
247         if mente['mentenance'] == True:
248             check = 'checked=checked'
249         else:
250             check = ''
251         pos = self.application.gpos(dbname,page)
252         i = mente['count']
253         start = (pos-1)*i
254         if start < 0:
255             start = len(table)-i
256             if start < 0:
257                 start = 0
258         self.render('modules/admin.htm',position=pos,records=rec[start:start+i],mente=check,password=mente['password'],db=dbname)
259
260 class AdminConfHandler(BaseHandler):
261     @tornado.web.authenticated
262     def post(self,dbname,func):
263         if func == 'set':
264             param = self.application.db.get(where('kinds') == 'conf')['mentenance']
265             if self.get_argument('mente','') == 'on':
266                 mente = True
267                 if param != mente:
268                     self.store()
269             else:
270                 mente = False  
271                 if param != mente:
272                     self.restore()
273             word = self.get_argument('pass','')
274             if word == '':
275                 self.render('regist.htm',content='パスワードを設定してください')
276                 return
277             else:
278                 self.application.db.update({'mentenance':mente,'password':word},where('kinds') == 'conf')  
279         elif func == 'del':
280             table = self.application.db.table(dbname)
281             for x in self.get_arguments('item'):
282                 table.remove(where('number') == int(x))
283         self.redirect('/'+dbname+'/admin/0/')
284         
285     def store(self):
286         self.application.db.close()
287         shutil.copy(st.json,st.bak)
288         self.application.db = TinyDB(st.json)
289         
290     def restore(self):
291         database = self.application.db
292         bak = TinyDB(st.bak)
293         for x in database.tables():
294             if self.application.collection(x) == True:
295                 database.purge_table(x)
296                 if x in bak.tables():
297                     table = database.table(x)
298                     table.insert_multiple(bak.table(x).all())
299           
300 class UserHandler(tornado.web.RequestHandler):
301     table = None
302     def get(self,dbname):
303         self.table = self.application.db.table(dbname)
304         q = self.get_query_argument('job','0',strip=True)
305         num = self.page(int(q))        
306         if num == '':
307             self.redirect('/{0}#{1}'.format(dbname,q))           
308         else:
309             self.redirect('/{0}{1}#{2}'.format(dbname,num,q))
310         
311     def post(self,dbname):
312         number = self.get_argument('number')
313         if number.isdigit() == True:
314             num = int(number)
315             pas = self.get_argument('password')
316             self.table = self.application.db.table(dbname)
317             qwr = Query()
318             obj = self.table.get(qwr.number == num)
319             if obj and(obj['password'] == pas):
320                 self.table.update({'title':u'削除されました','name':'','comment':u'<i><b>投稿者により削除されました</b></i>'},qwr.number == num)
321                 self.redirect('/{0}{1}#{2}'.format(dbname,self.page(num),number))
322             else:
323                 self.redirect('/'+dbname)
324                 
325     def page(self,number):
326         if self.table != None:
327             rec = self.table.count(where('number') <= number)
328             conf = self.application.db.get(where('kinds') == 'conf')
329             if len(self.table)-rec >= conf['count']:
330                 return '/'+str(1+rec//conf['count'])+'/'
331             else:
332                 return ''
333       
334 class SearchHandler(tornado.web.RequestHandler):       
335     def post(self,dbname):
336         arg = self.get_argument('word1')
337         self.word = arg 
338         self.radiobox = self.get_argument('filter')      
339         rec = sorted(self.search(dbname),key=lambda x: x['number'])
340         self.render('modules/search.htm',records=rec,word1=arg,db=dbname)
341     
342     def get(self,dbname):
343         if self.application.collection(dbname) == False:
344             raise tornado.web.HTTPError(404)
345             return
346         self.render('modules/search.htm',records=[],word1='',db=dbname)
347         
348     def search(self,dbname):
349         table = self.application.db.table(dbname)    
350         element = self.word.split()
351         if len(element) == 0:
352             element = ['']
353         while len(element) < 3:
354             element.append(element[0])
355         if self.radiobox == 'comment':
356             query = (Query().raw.search(element[0])) | (Query().raw.search(element[1])) | (Query().raw.search(element[2]))
357         else:
358             query = (Query().name == element[0]) | (Query().name == element[1]) | (Query().name == element[2])
359         if self.radiobox == 'comment':    
360             for x in table.search(query):
361                 com = ''
362                 for text in x['raw'].splitlines(True):                  
363                     for word in element:                        
364                         if text.find(word) > -1:
365                             com = com +'<p style=background-color:yellow>'+text+'<br></p>'  
366                             break                          
367                     else:
368                         com = com+'<p>'+text+'<br></p>'
369                 x['comment'] = com
370                 yield x       
371         else:
372             for x in table.search(query):
373                 yield x
374                                         
375 class FooterModule(tornado.web.UIModule):
376     def render(self,number,url,link):
377         return self.render_string('modules/footer.htm',index=number,url=url,link=link)
378     
379 class HeadlineApi(tornado.web.RequestHandler):
380     def get(self):
381         response = {}
382         for x in self.application.db.tables():
383             if x != '_default':
384                 response[x] = self.get_data(x)           
385         self.write(json.dumps(response,ensure_ascii=False))
386     
387     def get_data(self,dbname):
388         table = self.application.db.table(dbname)
389         i = len(table)
390         if i == 0:
391             return {}
392         else:
393             rec = sorted(table.all(),key=lambda x: x['number'])[i-1]
394             return {'number':rec['number'],'title':rec['title'],'name':rec['name'],'comment':rec['raw'][0:19]}
395         
396 class ArticleApi(tornado.web.RequestHandler):
397     def get(self,dbname,number):
398         if self.application.collection(dbname) == True:
399             table = self.application.db.table(dbname)
400             response = table.get(where('number') == int(number))
401             if response == None:
402                 response = {}
403             else:
404                 del response['comment']
405             self.write(json.dumps(response,ensure_ascii=False))
406         else:
407             tornado.web.HTTPError(404)
408     
409     def post(self,dbname):
410         name = self.get_argument('name',u'誰かさん')
411         title = self.get_argument('title',u'タイトルなし')
412         comment = self.get_argument('comment')
413         table = self.application.db.table(dbname)
414         table.insert({'name':name,'title':title,'comment':comment})
415         
416 class HelpHandler(tornado.web.RequestHandler):
417     def get(self):
418         self.render('help.htm',req='') 
419         
420     def post(self):
421         com = self.get_argument('help','')
422         text = ''
423         for line in com.splitlines():
424             text +='<p>'+line
425         table = self.application.db.table('master')
426         time = datetime.now()
427         table.insert({'comment':text,'time':time.strftime('%Y/%m/%d %H:%M')})
428         if com == '':
429             req = ''
430         else:
431             req = '送信しました'
432         self.render('help.htm',req=req)
433         
434 class MasterHandler(BaseHandler):
435     @tornado.web.authenticated
436     def get(self):
437         if self.current_user == b'admin':
438             com = self.application.db.table('master').all()
439             self.render('master.htm',com=com)
440         else:
441             raise tornado.web.HTTPError(404)
442         
443 class AlertHandler(UserHandler):
444     def get(self):
445         db = self.get_query_argument('db')
446         num = self.get_query_argument('num')
447         self.table = self.application.db.table(db)
448         tb = self.table.get(where('number') == int(num))
449         s = self.page(int(num))
450         jump = '/'+db+s+'#'+num
451         link = '<p><a href={0}>{0}</a>'.format(jump)
452         time = datetime.now()
453         data = {'comment':tb['comment']+link,'time':time.strftime('%Y/%m/%d'),
454                 'link':jump,'date':date.weekday(time)}
455         id = self.application.db.table('temp').insert(data)
456         self.render('alert.htm',com=data['comment'],num=id)
457     
458     def post(self):
459         id = int(self.get_argument('num'))
460         table = self.application.db.table('temp')
461         tb = table.get(eid=id)
462         link = tb['link']
463         table.remove(eids=[id])
464         table.remove(where('date') != date.weekday(datetime.now()))
465         if self.get_argument('admit','') == 'ok':
466             com = self.get_argument('com')
467             tb['comment'] = com+tb['comment']
468             del tb['date']
469             table = self.application.db.table('master')
470             table.insert(tb)
471         self.redirect(link)
472         
473 class Application(tornado.web.Application):    
474     def __init__(self):
475         self.db = TinyDB(st.json)             
476         handlers = [(r'/',NaviHandler),(r'/login',LoginHandler),(r'/logout',LogoutHandler),(r'/title',TitleHandler),
477                     (r'/headline/api',HeadlineApi),(r'/read/api/([a-zA-Z0-9_]+)/([0-9]+)',ArticleApi),(r'/write/api/([a-zA-Z0-9_]+)',ArticleApi),
478                     (r'/help',HelpHandler),(r'/master/*',MasterHandler),(r'/alert',AlertHandler),
479                     (r'/([a-zA-Z0-9_]+)',IndexHandler),(r'/([a-zA-Z0-9_]+)/([0-9]+)/*',IndexHandler),
480                     (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),
481                     (r'/([a-zA-Z0-9_]+)/search',SearchHandler),(r'/([a-zA-Z0-9_]+)/regist',RegistHandler)]
482         settings = {'template_path':os.path.join(os.path.dirname(__file__),'templates'),
483                         'static_path':os.path.join(os.path.dirname(__file__),'static'),
484                         'ui_modules':{'Footer':FooterModule},
485                         'cookie_secret':'bZJc2sWbQLKo6GkHn/VB9oXwQt8SOROkRvJ5/xJ89Eo=',
486                         'xsrf_cookies':True,
487                         'debug':True,
488                         'login_url':'/login'
489                         }
490         tornado.web.Application.__init__(self,handlers,**settings)
491  
492     def gpos(self,dbname,page):
493         params = self.db.get(where('kinds') == 'conf')
494         pos = int(page)
495         if pos <= 0:
496             pos = 0
497         elif (pos-1)*params['count'] >= len(self.db.table(dbname)):
498             pos = 0
499         return pos
500     
501     def collection(self,name):
502         if name in self.db.tables():
503             return True
504         else:
505             return False
506
507 class static():
508     json = 'static/db/db.json'
509     bak = 'static/db/bak.json'
510
511 st = static()
512 if __name__ == '__main__':
513     tornado.options.parse_command_line()
514     http_server = tornado.httpserver.HTTPServer(Application())
515     http_server.listen(options.port)
516     tornado.ioloop.IOLoop.instance().start()