OSDN Git Service

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