Procházet zdrojové kódy

Merge pull request #611 from 2du/master

New Stable
잉여강화기 (SPUP) před 7 roky
rodič
revize
457b5d814b
90 změnil soubory, kde provedl 5675 přidání a 3469 odebrání
  1. 1 1
      .github/PULL_REQUEST_TEMPLATE.md
  2. binární
      .github/logo.png
  3. 3 3
      .gitignore
  4. 10 11
      Docker-Install.md
  5. 12 8
      Dockerfile
  6. 19 0
      Dockerfile.arm32v7
  7. 19 0
      Dockerfile.arm64v8
  8. 154 2887
      app.py
  9. 6 0
      data/app_variables.json
  10. 20 0
      data/oauthsettings.json
  11. 63 29
      emergency_tool.py
  12. 245 131
      language/en-US.json
  13. 244 128
      language/ko-KR.json
  14. 0 18
      language/test.py
  15. 36 37
      readme-ko.md
  16. 49 21
      readme.md
  17. 117 0
      route/acl.py
  18. 47 0
      route/acl_list.py
  19. 23 0
      route/admin_list.py
  20. 26 0
      route/admin_log.py
  21. 85 0
      route/admin_plus.py
  22. 65 0
      route/adsense_setting.py
  23. 25 0
      route/alarm.py
  24. 91 0
      route/block_log.py
  25. 120 0
      route/change_password.py
  26. 83 0
      route/check_key.py
  27. 69 0
      route/close_topic_list.py
  28. 34 0
      route/count_edit.py
  29. 51 0
      route/custom_head_view.py
  30. 61 0
      route/deep_search.py
  31. 9 0
      route/del_alarm.py
  32. 20 0
      route/del_inter.py
  33. 57 0
      route/delete.py
  34. 30 0
      route/diff_data.py
  35. 18 0
      route/down.py
  36. 118 0
      route/edit.py
  37. 22 0
      route/give_log.py
  38. 17 0
      route/history_hidden.py
  39. 11 0
      route/image_view.py
  40. 60 0
      route/indexing.py
  41. 75 0
      route/inter_wiki.py
  42. 91 0
      route/login.py
  43. 187 0
      route/login_oauth.py
  44. 83 0
      route/manager.py
  45. 103 0
      route/move.py
  46. 67 0
      route/need_email.py
  47. 19 0
      route/not_close_topic.py
  48. 57 0
      route/now_update.py
  49. 100 0
      route/oauth_setting.py
  50. 45 0
      route/other.py
  51. 29 0
      route/please.py
  52. 92 0
      route/plus_inter.py
  53. 41 0
      route/preview.py
  54. 47 0
      route/raw_view.py
  55. 172 0
      route/read_view.py
  56. 178 0
      route/recent_changes.py
  57. 44 0
      route/recent_discuss.py
  58. 109 0
      route/register.py
  59. 24 0
      route/restart.py
  60. 77 0
      route/revert.py
  61. 403 0
      route/setting.py
  62. 72 0
      route/title_index.py
  63. 177 99
      route/tool/func.py
  64. 68 0
      route/tool/init.py
  65. 5 5
      route/tool/mark.py
  66. 0 0
      route/tool/set_mark/namu.py
  67. 0 0
      route/tool/set_mark/tool.py
  68. 201 0
      route/topic.py
  69. 77 0
      route/topic_admin.py
  70. 55 0
      route/topic_stop.py
  71. 23 0
      route/topic_top.py
  72. 92 0
      route/upload.py
  73. 66 0
      route/user_admin.py
  74. 82 0
      route/user_ban.py
  75. 92 0
      route/user_check.py
  76. 101 0
      route/user_info.py
  77. 57 0
      route/user_log.py
  78. 32 0
      route/user_tool.py
  79. 55 0
      route/user_topic_list.py
  80. 25 0
      route/watch_list.py
  81. 24 0
      route/watch_list_name.py
  82. 35 0
      route/xref.py
  83. 0 6
      views/easter_egg.html
  84. 2 1
      views/main_css/main.css
  85. 56 0
      views/main_css/oauth.css
  86. 1 1
      views/main_css/topic_reload.js
  87. 14 7
      views/neo_yousoro/css/main.css
  88. 11 6
      views/neo_yousoro/index.html
  89. 0 70
      views/neo_yousoro/js/main.js
  90. 69 0
      views/neo_yousoro/js/skin_set.js

+ 1 - 1
.github/PULL_REQUEST_TEMPLATE.md

@@ -1,4 +1,4 @@
 <!--
-stable branch로 request를 보내지 마십시오. 개발은 master branch에서 이루어집니다.
+stable branch로 요청을 보내지 마십시오. 개발은 master branch에서 이루어집니다.
 Don't request merge your commit to stable branch, please request to master branch.
 -->

binární
.github/logo.png


+ 3 - 3
.gitignore

@@ -1,12 +1,12 @@
 set.json
 
-set_mark/__pycache__
-/__pycache__
+__pycache__
 /app_session
 .vscode
 
 *.db
-image
+*.db-journal
+images
 robots.txt
 
 views/liberty

+ 10 - 11
Docker-Install.md

@@ -1,11 +1,10 @@
-## Installation
-```
-docker pull hotococoa/opennamu
-```
-
-## Setting
-```
-export NAMU_PORT=port
-export NAMU_LANG=lang
-```
-Default: Port 3000, en-US
+## Installation
+```
+docker pull opennamu/stable
+```
+
+## Start
+```
+docker run -p 3000:3000 -v data:/app/data --name opennamu opennamu/stable
+docker run -p <host-port>:3000 -v <host-data_directory>:/app/data --name <docker-containername> opennamu/stable
+```

+ 12 - 8
Dockerfile

@@ -1,15 +1,19 @@
-FROM ubuntu:16.04
+FROM python:3.6.8-stretch
 
-MAINTAINER Hoto Cocoa <cocoa@hoto.us>
+MAINTAINER 2du <min08101@naver.com>
+MAINTAINER hoparkgo9ma <me@ho9.me>
 
-ENV NAMU_PORT=3000
-ENV NAMU_LANG=en-US
+ENV NAMU_DB = data
+ENV NAMU_HOST = 0.0.0.0
+ENV NAMU_PORT = 3000
+ENV NAMU_LANG = en-US
+ENV NAMU_MARKUP = namumark
+ENV NAMU_ENCRYPT = sha3
 
 ADD . /app
-
 WORKDIR /app
 
-RUN apt update && apt install -y --no-install-recommends python3 python3-dev python3-pip python3-setuptools
-RUN python3 -m pip install pip --upgrade && python3 -m pip install -r requirements.txt
+RUN pip install -r requirements.txt
+EXPOSE 3000
 
-CMD python3 app.py
+CMD [ "python", "./app.py" ]

+ 19 - 0
Dockerfile.arm32v7

@@ -0,0 +1,19 @@
+FROM arm32v7/python:3.6.8-stretch
+
+MAINTAINER 2du <min08101@naver.com>
+MAINTAINER hoparkgo9ma <me@ho9.me>
+
+ENV NAMU_DB = data
+ENV NAMU_HOST = 0.0.0.0
+ENV NAMU_PORT = 3000
+ENV NAMU_LANG = en-US
+ENV NAMU_MARKUP = namumark
+ENV NAMU_ENCRYPT = sha3
+
+ADD . /app
+WORKDIR /app
+
+RUN pip install -r requirements.txt
+EXPOSE 3000
+
+CMD [ "python", "./app.py" ]

+ 19 - 0
Dockerfile.arm64v8

@@ -0,0 +1,19 @@
+FROM arm64v8/python:3.6.8-stretch
+
+MAINTAINER 2du <min08101@naver.com>
+MAINTAINER hoparkgo9ma <me@ho9.me>
+
+ENV NAMU_DB = data
+ENV NAMU_HOST = 0.0.0.0
+ENV NAMU_PORT = 3000
+ENV NAMU_LANG = en-US
+ENV NAMU_MARKUP = namumark
+ENV NAMU_ENCRYPT = sha3
+
+ADD . /app
+WORKDIR /app
+
+RUN pip install -r requirements.txt
+EXPOSE 3000
+
+CMD [ "python", "./app.py" ]

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 154 - 2887
app.py


+ 6 - 0
data/app_variables.json

@@ -0,0 +1,6 @@
+{
+    "_README" : "DO NOT MODIFY THIS FILE.",
+    "path_oauth_setting" : "data/oauthsettings.json",
+    "path_set_json" : "data/set.json",
+    "path_data_image" : "data/images"
+}

+ 20 - 0
data/oauthsettings.json

@@ -0,0 +1,20 @@
+{
+    "_README" : {
+        "en" : "To use the oAuth login feature, you must set the 'publish_url' value to a domain address that includes the HTTPS protocol, and actually support HTTPS connections.",
+        "ko" : "oAuth 로그인 기능을 사용하려면 'publish_url' 값을 HTTPS 프로토콜을 포함한 도메인 주소로 설정하고, 실제로 HTTPS 연결을 지원해야 합니다.",
+        "support" : ["discord", "facebook", "naver"]
+    },
+    "publish_url" : "https://",
+    "discord" : {
+        "client_id" : "",
+        "client_secret" : ""
+    },
+    "facebook" : {
+        "client_id" : "",
+        "client_secret" : ""
+    },
+    "naver" : {
+        "client_id" : "",
+        "client_secret" : ""
+    }
+}

+ 63 - 29
emergency_tool.py

@@ -1,29 +1,50 @@
+import os
 import json
 import sqlite3
-import bcrypt
 import hashlib
 import threading
 
-from func import *
-from mark import load_conn2, namumark
+from route.tool.func import *
+from route.tool.mark import load_conn2, namumark
 
-json_data = open('set.json').read()
-set_data = json.loads(json_data)
+all_src = []
+for i_data in os.listdir("."):
+    f_src = re.search("(.+)\.db$", i_data)
+    if f_src:
+        all_src += [f_src.groups()[0]]
 
-conn = sqlite3.connect(set_data['db'] + '.db', check_same_thread = False)
+if len(all_src) == 0:
+    exit()
+elif len(all_src) > 1:
+    db_num = 1
+
+    for i_data in all_src:
+        print(str(db_num) + ' : ' + i_data)
+
+    print('Number : ', end = '')    
+    db_name = all_src[int(number_check(input())) - 1]
+else:
+    db_name = all_src[0]
+
+if len(all_src) == 1:
+    print('DB\'s name : ' + db_name)
+
+conn = sqlite3.connect(db_name + '.db', check_same_thread = False)
 curs = conn.cursor()
 
 load_conn(conn)
 
-print('1. backlink reset')
-print('2. recaptcha delete')
-print('3. ban delete')
-print('4. change port')
-print('5. change skin')
-print('6. change password')
-print('7. reset version')
-
-print('select : ', end = '')
+print('1. Backlink reset')
+print('2. reCAPTCHA delete')
+print('3. Ban delete')
+print('4. Change host')
+print('5. Change port')
+print('6. Change skin')
+print('7. Change password')
+print('8. Reset version')
+print('9. New DB create')
+
+print('Select : ', end = '')
 what_i_do = input()
 
 if what_i_do == '1':
@@ -50,7 +71,7 @@ elif what_i_do == '2':
     curs.execute("delete from other where name = 'recaptcha'")
     curs.execute("delete from other where name = 'sec_re'")
 elif what_i_do == '3':
-    print('ip or name : ', end = '')
+    print('IP or Name : ', end = '')
     user_data = input()
 
     if re.search("^([0-9]{1,3}\.[0-9]{1,3})$", user_data):
@@ -61,36 +82,49 @@ elif what_i_do == '3':
         curs.execute("insert into rb (block, end, today, blocker, why, band) values (?, ?, ?, ?, ?, ?)", [user_data, load_lang('release', 1), get_time(), load_lang('tool', 1) + ':emergency', '', band])
     curs.execute("delete from ban where block = ?", [user_data])
 elif what_i_do == '4':
-    print('port : ', end = '')
-    port = input()
+    print('Host : ', end = '')
+    host = input()
 
-    curs.execute("update other set data = ? where name = 'port'", [port])
+    curs.execute("update other set data = ? where name = 'host'", [host])
 elif what_i_do == '5':
-    print('skin name : ', end = '')
+    print('Port : ', end = '')
+    port = int(input())
+
+    curs.execute("update other set data = ? where name = 'port'", [port])
+elif what_i_do == '6':
+    print('Skin\'s name : ', end = '')
     skin = input()
 
     curs.execute("update other set data = ? where name = 'skin'", [skin])
-elif what_i_do == '6':
+elif what_i_do == '7':
     print('1. sha256')
-    print('2. bcrypt')
-    print('select : ', end = '')
-    what_i_do = input()
+    print('2. sha3')
+    print('Select : ', end = '')
+    what_i_do = int(input())
 
-    print('user name : ', end = '')
+    print('User\'s name : ', end = '')
     user_name = input()
 
-    print('user password : ', end = '')
+    print('User\'s password : ', end = '')
     user_pw = input()
 
     if what_i_do == '1':
         hashed = hashlib.sha256(bytes(user_pw, 'utf-8')).hexdigest()
     elif what_i_do == '2':
-        hashed = bcrypt.hashpw(bytes(user_pw, 'utf-8'), bcrypt.gensalt()).decode()
+        hashed = sha3_256(bytes(user_pw, 'utf-8')).hexdigest()
        
     curs.execute("update user set pw = ? where id = ?", [hashed, user_name])
-elif what_i_do == '7':
+elif what_i_do == '8':
     curs.execute("update other set data = '00000' where name = 'ver'")
+elif what_i_do == '9':
+    print('DB\'s name (data) : ', end = '')
+    
+    db_name = input()
+    if db_name == '':
+        db_name = 'data'
+
+    sqlite3.connect(db_name + '.db', check_same_thread = False)
 
 conn.commit()
 
-print('ok')
+print('OK')

+ 245 - 131
language/en-US.json

@@ -1,132 +1,246 @@
 {
-    "edit" : "edit",
-    "raw" : "raw",
-    "history" : "history",
-    "delete" : "delete",
-    "logo" : "logo",
-    "regex" : "regex",
-    "frontpage" : "frontpage",
-    "max_file_size" : "max file size",
-    "backup_interval" : "backup interval",
-    "default" : "default",
-    "language" : "language",
-    "port" : "port",
-    "secret_key" : "secret key",
-    "update_branch" : "update branch",
-    "main" : "main",
-    "indexing" : "indexing",
-    "easy" : "easy",
-    "server" : "server",
-    "load" : "load",
-    "skin" : "skin",
-    "view" : "view",
-    "bottom" : "bottom",
-    "text" : "notice",
-    "template" : "template",
-    "move" : "move",
-    "hide" : "hide",
-    "list" : "list",
-    "id" : "id",
-    "out" : "out",
-    "revert" : "undo",
-    "version" : " ver",
-    "normal_version" : "version",
-    "new" : "new",
-    "document" : "documents",
-    "all" : "everything",
-    "ban" : "ban",
-    "release" : "release",
-    "save" : "save",
-    "other" : "other",
-    "tool" : "tools",
-    "plus" : "add",
-    "open" : "open",
-    "search" : "search",
-    "need" : "needful",
-    "upload" : "file upload",
-    "record" : "record",
-    "name" : "name",
-    "license" : "license",
-    "interwiki" : "interwiki",
-    "update" : "update",
-    "setting" : "set",
-    "create" : "create",
-    "editor" : "editor",
-    "hour" : "hour",
-    "time" : "time",
-    "close" : "close",
-    "stop" : "stop",
-    "restart" : "restart",
-    "agreement" : "agreement",
-    "backlink" : "backlink",
-    "why" : "reason",
-    "random" : "random",
-    "authority" : "authority",
-    "file" : "file",
-    "change" : "changes",
-    "compare" : "compare",
-    "count" : "count",
-    "check" : "check",
-    "user" : "user",
-    "alarm" : "alarm",
-    "preview" : "preview",
-    "watchlist" : "watching list",
-    "state" : "state",
-    "recent" : "recent",
-    "discussion" : "debate",
-    "login" : "login",
-    "logout" : "logout",
-    "register" : "register",
-    "able" : "able",
-    "second": "second",
-    "normal" : "normal",
-    "subscriber" : "registor",
-    "admin" : "admin",
-    "next" : "next",
-    "previous" : "previous",
-    "owner" : "owner",
-    "admin_group" : "mod group",
-    "limitless" : "limitless",
-    "period" : "period",
-    "now" : "now",
-    "blocked" : "blocked",
-    "band" : "band",
-    "notice" : "notice",
-    "writer" : "writer",
-    "upper" : "upper",
-    "under" : "under",
-    "pass" : "pass",
-    "category" : "category",
-    "filter" : "filter",
-    "send" : "send",
-    "reload" : "reload",
-    "password" : "password",
-    "confirm" : "confirm",
-
-    "user_head_warring" : "user's head will deleted if you close the browser or when you are editting as guest",
-    "http_warring" : "warning : if you are not on https connection, your information can be leaked. we won't response to that.",
-    "no_login_warring" : "non-login status. ip is logged when working with non-login.",
-    
-    "authority_error" : "insufficient privileges.",
-    "no_login_error" : "non-login status.",
-    "no_exist_user_error" : "account does not exist.",
-    "no_admin_block_error" : "administrators can not block, check.",
-    "skin_error" : "this skin is not support setting.",
-    "same_id_exist_error" : "there are users with the same id.",
-    "long_id_error" : "id must be shorter than 20 characters.",
-    "id_char_error" : "only hangul, alphabet and space are allowed for id.",
-    "file_exist_error" : "file does not exist.",
-    "password_error" : "the password is different.",
-    "recaptcha_error" : "go through the recaptcha.",
-    "file_extension_error" : "only jpg, gif, jpeg, png, webp is possible.",
-    "edit_record_error" : "edit record can not be more than 500 characters.",
-    "same_file_error" : "a file with the same name exists.",
-    "file_capacity_error" : "maximum file capacity (mb) :",
-    "decument_exist_error" : "the document already exists where you want to move it.",
-    "password_diffrent_error" : "reconfirm password and input password are different.",
-    "edit_filter_error" : "censored by edit filter.",
-    "file_name_error" : "only alphabet, hangul, space, underscore, and minus signs are allowed for file names.",
-
-    "register_text" : "register text",
-    "non_login_alert" : "non-login alert"
-}
+    "_comment_1_" : "Common",
+        "server" : "Server",
+        "filter" : "Filter",
+        "delete" : "Delete",
+        "notice" : "Notice",
+        "add" : "Add",
+        "etc" : "Etc",
+        "name" : "Name",
+        "regex" : "Regex",
+        "id" : "ID",
+        "list" : "List",
+        "main" : "Main",
+        "return" : "Return",
+        "skin" : "Skin",
+        "save" : "Save",
+        "secret_key" : "Secret key",
+        "host" : "Host",
+        "port" : "Port",
+        "restart" : "Restart",
+        "document_name" : "Document[s] name",
+        "discussion_name" : "Discussion[s] name",
+        "user_name" : "User[s] name",
+        "go" : "Go",
+        "document" : "Document",
+        "discussion" : "Discussion",
+        "backlink" : "Backlink",
+        "closed" : "Closed",
+        "reload" : "Reload",
+        "send" : "Send",
+        "ongoing" : "Ongoing",
+        "normal" : "Normal",
+        "range" : "Range",
+        "search" : "Search",
+        "raw" : "Raw",
+        "history" : "History",
+        "user_discussion" : "User discussion",
+        "record" : "Record",
+        "state" : "State",
+        "revert" : "Revert",
+        "why" : "Reason",
+        "edit" : "Edit",
+        "preview" : "Preview",
+        "move" : "Move",
+        "upload" : "Upload",
+        "version" : "Version",
+        "stop" : "Stop",
+        "close" : "Closed",
+        "open" : "Open",
+        "agreement" : "Agreement",
+        "template" : "Template",
+        "category" : "Category",
+        "file" : "File",
+        "writer" : "Writer",
+        "editor" : "Editor",
+        "hide" : "Hide",
+        "check" : "Check",
+        "destruction" : "Destruction",
+        "tool" : "Tool",
+        "recent" : "Recently",
+        "password" : "Password",
+        "login" : "Sign-in",
+        "logout" : "Sing-out",
+        "register" : "Sign-up",
+        "language" : "Language",
+        "compare" : "Compare",
+        "email" : "Email",
+        "key" : "Key",
+        "all" : "All",
+        "sub" : "Sub",
+        "create" : "Create",
+        "acl" : "ACL",
+        "upper" : "Parent",
+        "other" : "Other",
+        "random" : "Random",
+        "error" : "Error",
+        "next" : "Next",
+        "previous" : "Previous",
+        "authority" : "Authority",
+        "connect" : "Connect",
+        "_comment_1.1_" : "Time",
+            "second" : "Second(s)",
+            "hour" : "Hour(s)",
+            "limitless" : "No time limit",
+            "time" : "Time",
+            "period" : "Period",
+            "end" : "End",
+        "_comment_1.2_" : "User",
+            "user" : "User",
+            "admin" : "Admin",
+            "owner" : "Owner",
+            "ip" : "IP",
+            "member" : "Member",
+        "_comment_1.3_" : "Ban",
+            "ban" : "Block",
+            "blocked" : "Blocked",
+            "release" : "Release",
+    "_comment_2_" : "Func",
+        "wiki_restart" : "Wiki engine restart",
+        "update" : "Update",
+        "need_document" : "Required Document(s)",
+        "close_discussion" : "Closed discussion(s)",
+        "open_discussion" : "Open discussion(s)",
+        "recent_discussion" : "Recently discussion(s)",
+        "recent_change" : "Recently edit(s)",
+        "edit_filter" : "Contents filter",
+        "recent_ban" : "Recently Block(s)",
+        "load" : "Import another document",
+        "edit_filter_rule" : "Contents filter[s] rule",
+        "move_history" : "History of move",
+        "other_tool" : "Other tool",
+        "admin_tool" : "Admin[s] tool",
+        "check_user" : "Check user",
+        "compare_target" : "Comparison target name",
+        "authorize" : "Authorize",
+        "indexing" : "DB Indexing",
+        "hide_release" : "Unhide",
+        "notice_release" : "Release notice",
+        "ban_release" : "Unblock",
+        "discussion_tool" : "Discussion tools",
+        "discussion_raw" : "Discussion[s] raw",
+        "oauth_signin_facebook" : "Sign-in with Facebook",
+        "oauth_signin_naver" : "Sign-in with NAVER",
+        "connection" : "Connection",
+        "new_connection" : "Connect...",
+        "user_setting" : "User settings",
+        "now_password" : "Now password",
+        "new_password" : "New password",
+        "password_confirm" : "Password confirm",
+        "oauth_connection" : "Oauth Connection",
+        "password_search" : "Password finder",
+        "login_able" : "Loginable",
+        "band_ban" : "Range block",
+        "band_blocked" : "Range blocked",
+        "document_acl" : "Document[s] ACL",
+        "discussion_acl" : "Discussion[s] ACL",
+        "view_acl" : "Document viewed ACL",
+        "under_category" : "Sub-category",
+        "count" : "Number of Contributions",
+        "alarm" : "Notice(s)",
+        "user_document" : "User[s] document",
+        "user_head" : "User[s] <head>",
+        "user_document_acl" : "User[s] document ACL",
+        "_comment_2.1_" : "Filter",
+            "_comment_2.1.1_" : "List",
+                "interwiki_list" : "Interwiki(s) list",
+                "email_filter_list" : "Email filter(s) list",
+                "id_filter_list" : "ID filter(s) list",
+                "edit_filter_list" : "Contents filter(s) list",
+            "_comment_2.1.2_" : "Add",
+                "interwiki_add" : "Interwiki add",
+                "edit_filter_add" : "Contents filter add",
+                "id_filter_add" : "ID filter add",
+                "email_filter_add" : "Email filter add",
+        "_comment_2.2_" : "Setting",
+            "setting" : "Setting",
+            "restart_required" : "Restart required",
+            "oauth_setting" : "OAuth settings",
+            "adsense_setting" : "Adsense settings",
+            "adsense_enable" : "Adsense enable",
+            "skin_setting" : "Skin settings",
+            "_comment_2.2.1_" : "List",
+                "main_setting" : "Main settings",
+                "text_setting" : "Text settings",
+                "main_head" : "Global <head>",
+                "main_body" : "Global <body>",
+            "_comment_2.2.2_" : "Main",
+                "wiki_name" : "Wiki[s] name",
+                "wiki_logo" : "Wiki[s] logo",
+                "main_page" : "Main page",
+                "bottom_text" : "Bottom text",
+                "max_file_size" : "Max file size",
+                "backup_interval" : "Backup Cycles",
+                "wiki_skin" : "Wiki[s] Skin",
+                "default_acl" : "Default edit ACL",
+                "default_discussion_acl" : "Default discussion ACL",
+                "no_register" : "No sign-up",
+                "hide_ip" : "Hide IP",
+                "wiki_host" : "Wiki[s] host",
+                "wiki_port" : "Wiki[s] port",
+                "wiki_secret_key" : "Wiki[s] secret key",
+                "email_required" : "Email required",
+                "google_imap_required" : "Google IMAP setting required",
+                "update_branch" : "Branch to import updates",
+            "_comment_2.2.3_" : "Text",
+                "register_text" : "Terms of sign-up",
+                "non_login_alert" : "Non-login alert",
+            "_comment_2.2.4_" : "Google",
+                "recaptcha" : "reCAPTCHA",
+                "google_imap" : "Google IMAP",
+                "google_email" : "Google email",
+                "google_app_password" : "Google APP password",
+        "_comment_2.3_" : "List",
+            "open_discussion_list" : "Open discussion(s) list",
+            "discussion_list" : "Discussion(s) list",
+            "admin_list" : "Admin(s) list",
+            "member_list" : "Member(s) list",
+            "authority_use_list" : "Authority use list",
+            "admin_group_list" : "Admin group(s) list",
+            "all_document_list" : "All document(s) list",
+            "watchlist" : "Watchlist",
+            "_comment_2.3.1_" : "ACL document list",
+                "acl_document_list" : "ACL document(s) list",
+                "acl_required" : "Required ACL",
+            "_comment_2.3.2_" : "ACL List",
+                "admin_group_add" : "Admin group add",
+                "ban_authority" : "Block authority",
+                "discussion_authority" : "Discussion manage authority",
+                "user_check_authority" : "User check authority",
+                "document_acl_authority" : "Document ACL manage authority",
+                "history_hide_authority" : "History hide authority",
+                "authorization_authority" : "Authorization authority",
+                "owner_authority" : "Owner authority",
+            "_comment_2.3.3_" : "Record",
+                "edit_record" : "Edit record",
+                "discussion_record" : "Discussion record",
+    "_comment_3_" : "Long",
+        "ie_no_data_required" : "Operation cannot continue because all required data has not been collected.",
+        "oauth_settings_not_found" : "Administrator has not provided any data about using this feature.",
+        "oauth_disabled" : "Administrator has disabled this feature.",
+        "http_warring" : "Warning : If you are not on HTTPS connection, Your information can be leaked. We won't response to that.",
+        "user_head_warring" : "User[s] <head> will deleted if you close the browser or sign-in",
+        "no_login_warring" : "Non-login status. IP is logged when working with non-login.",
+        "_comment_3.1_" : "Error",
+            "update_error" : "Auto update is not support.",
+            "inter_error" : "Internal error.",
+            "authority_error" : "Insufficient privileges.",
+            "no_login_error" : "Non-login status.",
+            "no_exist_user_error" : "Account does not exist.",
+            "no_admin_block_error" : "Administrators can not block, check.",
+            "skin_error" : "This skin is not support setting.",
+            "same_id_exist_error" : "There are users with the same ID.",
+            "long_id_error" : "ID must be shorter than 20 characters.",
+            "id_char_error" : "Only hangul, alphabet and space are allowed for ID.",
+            "file_exist_error" : "File does not exist.",
+            "password_error" : "The password is different.",
+            "recaptcha_error" : "Go through the recaptcha.",
+            "file_extension_error" : "Only jpg, gif, jpeg, png, webp is possible.",
+            "edit_record_error" : "Edit reason can not be more than 500 characters.",
+            "same_file_error" : "A file with the same name exists.",
+            "file_capacity_error" : "Maximum file capacity (MB) :",
+            "decument_exist_error" : "The document already exists where you want to move it.",
+            "password_diffrent_error" : "Reconfirm password and input password are different.",
+            "edit_filter_error" : "Censored by edit filter.",
+            "file_name_error" : "Only alphabet, hangul, space, underscore, and minus signs are allowed for file names."
+}

+ 244 - 128
language/ko-KR.json

@@ -1,130 +1,246 @@
 {
-    "edit": "편집",
-    "raw" : "원본",
-    "history": "역사",
-    "easy": "간단",
-    "skin": "스킨",
-    "delete": "삭제",
-    "regex" : "정규표현식",
-    "language" : "언어",
-    "server": "서버",
-    "filter": "필터",
-    "move": "이동",
-    "hide": "숨김",
-    "list": "목록",
-    "id" : "아이디",
-    "revert": "되돌리기",
-    "version": "판",
-    "normal_version": "버전",
-    "document": "문서",
-    "all": "모든",
-    "ban": "차단",
-    "release": "해제",
-    "save": "저장",
-    "other": "기타",
-    "tool": "도구",
-    "plus": "추가",
-    "open": "열기",
-    "search": "검색",
-    "user": "사용자",
-    "alarm": "알림",
-    "watchlist": "주시 문서",
-    "recent": "최근",
-    "recent_changes": "최근 변경",
-    "discussion": "토론",
-    "login": "로그인",
-    "logout": "로그아웃",
-    "register": "회원가입",
-    "able": "가능",
-    "second": "초",
-    "normal": "일반",
-    "subscriber": "가입자",
-    "admin": "관리자",
-    "owner": "소유자",
-    "admin_group": "관리 그룹",
-    "user_head_warring": "비 로그인의 경우에는 사용자 head가 로그인하거나 브라우저 닫으면 날아갑니다.",
-    "http_warring": "주의 : 만약 https 연결이 아닌 경우 데이터가 유출될 가능성이 있습니다. 이에 대해 책임지지 않습니다.",
-    "new": "새",
-    "need": "필요한",
-    "upload": "파일 올리기",
-    "record": "기록",
-    "name": "이름",
-    "license": "라이선스",
-    "bottom" : "하단",
-    "text" : "문구",
-    "interwiki": "인터위키",
-    "update": "업데이트",
-    "setting": "설정",
-    "create": "생성",
-    "editor": "수정자",
-    "hour": "시간",
-    "time": "시각",
-    "close": "닫기",
-    "stop": "정지",
-    "restart": "재시작",
-    "agreement": "합의",
-    "load" : "불러오기",
-    "backlink": "역링크",
-    "why": "사유",
-    "random": "무작위",
-    "authority": "권한",
-    "file": "파일",
-    "change": "변경",
-    "compare": "비교",
-    "count": "횟수",
-    "check": "검사",
-    "view" : "보기",
-    "preview": "미리보기",
-    "next": "다음",
-    "previous": "이전",
-    "no_login_warring": "비 로그인 상태로 진행 시 ip가 기록될 수 있습니다.",
-    "state": "상태",
-    "limitless": "무기한",
-    "period": "기간",
-    "now": "현재",
-    "blocked": "차단자",
-    "band": "대역",
-    "notice": "공지",
-    "writer": "작성자",
-    "upper": "상위",
-    "under": "하위",
-    "pass": "통과",
-    "category": "분류",
-    "send" : "전송",
-    "reload" : "새로고침",
-    "password" : "암호",
-    "confirm" : "확인",
-    "authority_error": "권한이  부족합니다.",
-    "no_login_error": "비 로그인 상태 입니다.",
-    "no_exist_user_error": "계정이 없습니다.",
-    "no_admin_block_error": "관리자는 차단, 검사 할 수 없습니다.",
-    "skin_error" : "이 스킨은 스킨 설정을 지원하지 않습니다.",
-    "same_id_exist_error": "동일한 아이디의 사용자가 있습니다.",
-    "long_id_error": "아이디는 20글자보다 짧아야 합니다.",
-    "id_char_error": "아이디에는 한글과 알파벳과 공백만 허용 됩니다.",
-    "file_exist_error": "파일이 없습니다.",
-    "password_error": "비밀번호가 다릅니다.",
-    "recaptcha_error": "리캡차를 통과하세요.",
-    "file_extension_error": "jpg, gif, jpeg, png, webp만 가능 합니다.",
-    "edit_record_error": "편집 기록은 500자를 넘을 수 없습니다.",
-    "same_file_error": "동일한 이름의 파일이 있습니다.",
-    "file_capacity_error": "파일 최대 용량 (mb) :",
-    "decument_exist_error": "내용이 원래 문서와 동일 합니다.",
-    "password_diffrent_error": "재 확인 비밀번호와 입력 비밀번호가 다릅니다.",
-    "edit_filter_error": "편집 필터에 의해 검열 되었습니다.",
-    "file_name_error": "파일 이름은 알파벳, 한글, 띄어쓰기, 언더바,  빼기표만 허용 됩니다.",
-    "template": "틀",
-    "out": "외부",
-    "logo": "로고",
-    "frontpage": "대문",
-    "max_file_size": "최대 파일 크기",
-    "backup_interval": "백업 간격",
-    "default": "기본",
-    "port": "포트",
-    "secret_key": "비밀키",
-    "update_branch": "업데이트 브랜치",
-    "main": "메인",
-    "indexing" : "인덱싱",
-    "register_text" : "회원가입 문구",
-    "non_login_alert" : "비 로그인 경고문"
+    "_comment_1_" : "공통",
+        "server" : "서버",
+        "filter" : "필터",
+        "delete" : "삭제",
+        "notice" : "알림",
+        "add" : "추가",
+        "etc" : "기타",
+        "name" : "이름",
+        "regex" : "정규표현식",
+        "id" : "아이디",
+        "list" : "목록",
+        "main" : "메인",
+        "return" : "돌아가기",
+        "skin" : "스킨",
+        "save" : "저장",
+        "secret_key" : "비밀키",
+        "host" : "호스트",
+        "port" : "포트",
+        "restart" : "재시작",
+        "document_name" : "문서명",
+        "discussion_name" : "토론명",
+        "user_name" : "사용자 이름",
+        "go" : "이동",
+        "document" : "문서",
+        "discussion" : "토론",
+        "backlink" : "역링크",
+        "closed" : "닫힘",
+        "reload" : "다시 로드",
+        "send" : "보냄",
+        "ongoing" : "진행중",
+        "normal" : "보통",
+        "range" : "범위",
+        "search" : "검색",
+        "raw" : "원본",
+        "history" : "역사",
+        "user_discussion" : "사용자 토론",
+        "record" : "기록",
+        "state" : "상태",
+        "revert" : "복원",
+        "why" : "사유",
+        "edit" : "편집",
+        "preview" : "미리보기",
+        "move" : "이동",
+        "upload" : "업로드",
+        "version" : "버전",
+        "stop" : "중지",
+        "close" : "닫음",
+        "open" : "염",
+        "agreement" : "동의",
+        "template" : "틀",
+        "category" : "분류",
+        "file" : "파일",
+        "writer" : "작성자",
+        "editor" : "에디터",
+        "hide" : "숨김",
+        "check" : "검사",
+        "destruction" : "삭제",
+        "tool" : "도구",
+        "recent" : "최근",
+        "password" : "비밀번호",
+        "login" : "로그인",
+        "logout" : "로그아웃",
+        "register" : "회원가입",
+        "language" : "언어",
+        "compare" : "비교",
+        "email" : "이메일",
+        "key" : "키",
+        "all" : "전체",
+        "sub" : "하위",
+        "create" : "생성",
+        "acl" : "ACL",
+        "upper" : "상위",
+        "other" : "기타",
+        "random" : "랜덤",
+        "error" : "오류",
+        "next" : "다음",
+        "previous" : "이전",
+        "authority" : "권한",
+        "connect" : "연결",
+        "_comment_1.1_" : "시간",
+            "second" : "초",
+            "hour" : "시간",
+            "limitless" : "무제한",
+            "time" : "시간",
+            "period" : "기간",
+            "end" : "끝",
+        "_comment_1.2_" : "사용자",
+            "user" : "사용자",
+            "admin" : "관리자",
+            "owner" : "소유자",
+            "ip" : "IP",
+            "member" : "가입자",
+        "_comment_1.3_" : "차단",
+            "ban" : "차단",
+            "blocked" : "차단됨",
+            "release" : "차단 해제",
+    "_comment_2_" : "기능",
+        "wiki_restart" : "위키 엔진 재시작",
+        "update" : "업데이트",
+        "need_document" : "필요한 문서들",
+        "close_discussion" : "닫힌 토론",
+        "open_discussion" : "열린 토론",
+        "recent_discussion" : "최근 토론",
+        "recent_change" : "최근 수정",
+        "edit_filter" : "편집 필터",
+        "recent_ban" : "최근 차단",
+        "load" : "다른 문서 불러오기",
+        "edit_filter_rule" : "편집 필터 규칙",
+        "move_history" : "이동 기록",
+        "other_tool" : "기타 도구",
+        "admin_tool" : "관리 도구",
+        "check_user" : "사용자 검사",
+        "compare_target" : "비교 대상 이름",
+        "authorize" : "인증",
+        "indexing" : "DB 인덱싱",
+        "hide_release" : "표시",
+        "notice_release" : "릴리즈 노트",
+        "ban_release" : "차단 해제",
+        "discussion_tool" : "토론 도구",
+        "discussion_raw" : "토론 원본",
+        "oauth_signin_facebook" : "Facebook 아이디로 로그인",
+        "oauth_signin_naver" : "네이버 아이디로 로그인",
+        "connection" : "연결",
+        "new_connection" : "연결...",
+        "user_setting" : "사용자 설정",
+        "now_password" : "현재 비밀번호",
+        "new_password" : "새 비밀번호",
+        "password_confirm" : "비밀번호 확인",
+        "oauth_connection" : "Oauth 연결",
+        "password_search" : "비밀번호 찾기",
+        "login_able" : "로그인 가능",
+        "band_ban" : "대역 차단",
+        "band_blocked" : "대역 차단됨",
+        "document_acl" : "문서 ACL",
+        "discussion_acl" : "토론 ACL",
+        "view_acl" : "읽기 ACL",
+        "under_category" : "하위 분류",
+        "count" : "기여 횟수",
+        "alarm" : "알림",
+        "user_document" : "사용자 문서",
+        "user_head" : "사용자 <head>",
+        "user_document_acl" : "사용자 문서 ACL",
+        "_comment_2.1_" : "필터",
+            "_comment_2.1.1_" : "목록",
+                "interwiki_list" : "인터위키 목록",
+                "email_filter_list" : "이메일 필터 목록",
+                "id_filter_list" : "ID 필터 목록",
+                "edit_filter_list" : "편집 필터 목록",
+            "_comment_2.1.2_" : "추가",
+                "interwiki_add" : "인터위키 추가",
+                "edit_filter_add" : "편집 필터 추가",
+                "id_filter_add" : "ID 필터 추가",
+                "email_filter_add" : "이메일 필터 추가",
+        "_comment_2.2_" : "설정",
+            "setting" : "설정",
+            "restart_required" : "재시작 필요",
+            "oauth_setting" : "OAuth 설정",
+            "adsense_setting" : "애드센스 설정",
+            "adsense_enable" : "애드센스 사용",
+            "skin_setting" : "스킨 설정",
+            "_comment_2.2.1_" : "목록",
+                "main_setting" : "메인 설정",
+                "text_setting" : "문자 설정",
+                "main_head" : "전역 <head>",
+                "main_body" : "전역 <body>",
+            "_comment_2.2.2_" : "메인",
+                "wiki_name" : "위키 이름",
+                "wiki_logo" : "위키 목록",
+                "main_page" : "대문",
+                "bottom_text" : "하단 텍스트",
+                "max_file_size" : "파일 최대 파일 크기",
+                "backup_interval" : "백업 주기",
+                "wiki_skin" : "위키 스킨",
+                "default_acl" : "기본 수정 ACL",
+                "default_discussion_acl" : "기본 토론 ACL",
+                "no_register" : "가입불가",
+                "hide_ip" : "IP 숨기기",
+                "wiki_host" : "위키 호스트",
+                "wiki_port" : "위키 포트",
+                "wiki_secret_key" : "위키 비밀키",
+                "email_required" : "이메일 필요",
+                "google_imap_required" : "Google IMAP 설정 필요",
+                "update_branch" : "업데이트를 가져올 브랜치",
+            "_comment_2.2.3_" : "문자열",
+                "register_text" : "회원가입 정책",
+                "non_login_alert" : "비로그인 알림",
+            "_comment_2.2.4_" : "Google",
+                "recaptcha" : "reCAPTCHA",
+                "google_imap" : "Google IMAP",
+                "google_email" : "Google 이매일",
+                "google_app_password" : "Google 앱 비밀번호",
+        "_comment_2.3_" : "목록",
+            "open_discussion_list" : "열린 토론 목록",
+            "discussion_list" : "토론 목록",
+            "admin_list" : "관리자 목록",
+            "member_list" : "사용자 목록",
+            "authority_use_list" : "권한 목록",
+            "admin_group_list" : "관리자 그룹 목록",
+            "all_document_list" : "모든 문서 목록",
+            "watchlist" : "주시 목록",
+            "_comment_2.3.1_" : "ACL 문서 목록",
+                "acl_document_list" : "ACL 문서 목록",
+                "acl_required" : "ACL 필요",
+            "_comment_2.3.2_" : "ACL 목록",
+                "admin_group_add" : "관리자 그룹 추가",
+                "ban_authority" : "차단 권한",
+                "discussion_authority" : "토론 관리 권한",
+                "user_check_authority" : "사용자 검사 권한",
+                "document_acl_authority" : "문서 ACL 관리 권한",
+                "history_hide_authority" : "역사 숨김 권한",
+                "authorization_authority" : "인증 권한",
+                "owner_authority" : "소유자 권한",
+            "_comment_2.3.3_" : "기록",
+                "edit_record" : "편집 기록",
+                "discussion_record" : "토론 기록",
+    "_comment_3_" : "장문",
+        "ie_no_data_required" : "이 기능을 수행하는데 필요한 데이터가 제공되지 않았습니다.",
+        "oauth_settings_not_found" : "관리자가 이 기능을 수행하는데 필요한 데이터를 제공하지 않았습니다.",
+        "oauth_disabled" : "관리자가 이 기능을 비활성화시켰습니다.",
+        "http_warring" : "경고: HTTPS 연결을 사용하지 않는다면 개인정보가 유출될 수 있습니다. 이 사항에 의해 입는 피해는 사용자에게 책임이 있음을 알려드립니다.",
+        "user_head_warring" : "비로그인시 브라우저를 닫거나 로그인시 사용자의 <head>는 삭제됩니다.",
+        "no_login_warring" : "비로그인 상태입니다. 편집시 지금 접속한 IP 명의로 기록됩니다.",
+        "_comment_3.1_" : "오류",
+            "update_error" : "자동 업데이트가 지원되지 않습니다.",
+            "inter_error" : "내부 오류.",
+            "authority_error" : "권한이 없습니다.",
+            "no_login_error" : "비로그인 상태입니다.",
+            "no_exist_user_error" : "계정이 존재하지 않습니다.",
+            "no_admin_block_error" : "관리자는 검사, 차단을 수행할 수 없습니다.",
+            "skin_error" : "이 스킨은 설정을 지원하지 않습니다.",
+            "same_id_exist_error" : "이미 같은 이름의 계정이 있습니다.",
+            "long_id_error" : "ID는 20자보다 짧아야 합니다.",
+            "id_char_error" : "오직 한글과 알파벳, 공백만 사용 가능합니다.",
+            "file_exist_error" : "파일이 존재하지 않습니다.",
+            "password_error" : "비밀번호가 다릅니다.",
+            "recaptcha_error" : "'나는 로봇이 아닙니다'를 통해 reCaptcha를 수행하세요.",
+            "file_extension_error" : "오직 jpg, gif, jpeg, png, webp 만 업로드할 수 있습니다.",
+            "edit_record_error" : "수정 요약은 500자를 넘길 수 없습니다.",
+            "same_file_error" : "똑같은 이름의 파일이 존재합니다.",
+            "file_capacity_error" : "최대 파일 크기 (MB) :",
+            "decument_exist_error" : "이동하려는 이름에 이미 문서가 존재합니다.",
+            "password_diffrent_error" : "입력한 비밀번호와 비밀번호 확인이 서로 다릅니다.",
+            "edit_filter_error" : "편집 필터에 의해 금지된 단어가 사용되었습니다.",
+            "file_name_error" : "파일 이름에는 알파벳, 한글, 공백, 밑줄 과 빼기 기호만 사용할 수 있습니다."
 }

+ 0 - 18
language/test.py

@@ -1,18 +0,0 @@
-import json
-
-print('name : ', end = '')
-b = input()
-
-json_data = open('en-US.json', 'rt', encoding='utf-8').read()
-a_json = json.loads(json_data)
-
-json_data = open(str(b) + '.json', 'rt', encoding='utf-8').read()
-b_json = json.loads(json_data)
-
-for a_in in a_json:
-    if not a_in in b_json:
-        print(a_in + ' : ', end = '')
-        c = input()
-        b_json[a_in] = c
-        
-print(str(b_json).replace('", ', '",\n    ').replace('{', '{\n    ').replace('}', '\n}').replace('\'', '"'))

+ 36 - 37
readme-ko.md

@@ -1,58 +1,57 @@
-opennamu
+openNAMU
 ====
-![Python 3.5 이상 필요](https://img.shields.io/badge/python-%3E%3D%203.5-blue.svg)
+[![Python 3.5 이상의 버전 필요](https://img.shields.io/badge/python-3.5%20or%20higher-blue.svg)](https://python.org)
+[![라이선스](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
+
+![](https://raw.githubusercontent.com/2du/openNAMU/master/.github/logo.png)
 
 오픈나무는 파이썬 기반의 위키 엔진입니다. 파이썬과 그 의존성 모듈만 설치하면 사용할 수 있으며, 코드를 직접 수정하여 좀 더 주제에 특화된 위키를 만들 수 있습니다.
 
 ### 목차
- * [시작하기](#시작하기)
- * [클론](#클론)
- * [기여](#기여)
- * [라이선스](#라이선스)
- * [기여자 목록](#기여자-목록)
- * [기타](#기타)
+[클론](#클론) | [기여](#기여) | [라이선스](#라이선스) | [기여자 목록](#기여자-목록) | [기타](#기타)
+
+## 시작하기
+오픈나무는 파이썬 환경에서 동작하는 파이썬 애플리케이션으로, 파이썬 환경을 필요로 합니다.
+
+쉬운 오픈나무 설치를 위해 오픈나무 가이드를 따로 생성해두었으며, [이곳](https://github.com/Make-openNAMU/guide)에서 확인하실 수 있습니다.
 
-# 시작하기
- * 오픈나무 위키에 명시되어 있습니다. [참조](http://namu.ml/w/오픈나무%2F설치법)
+### 가이드 목록
+ * [파이썬 설치](https://github.com/Make-openNAMU/guide/blob/master/articles/ko-kr/install-python.md)
+ * [오픈나무 시작](https://github.com/Make-openNAMU/guide/blob/master/articles/ko-kr/start-opennamu.md)
+   * [오픈나무 도커 시작(실험적 기능)](https://github.com/Make-openNAMU/guide/blob/master/articles/ko-kr/docker-install.md)
 
-# 클론
+## 클론
 아래 명령을 터미널(명령 프롬프트)에 입력하여 본 리포지토리를 클론할 수 있습니다.
-## 일반
- * `git clone -b stable https://github.com/2du/opennamu.git`
+### 일반
+ * `git clone -b stable https://github.com/2du/openNAMU.git`
 
-## 베타
- * `git clone -b master https://github.com/2du/opennamu.git`
+### 개발중
+ * `git clone -b master https://github.com/2du/openNAMU.git`
 
-# 기여
+## 기여
 오픈나무에는 검증되지 않은 몇가지 버그가 존재할 수 있습니다. 당신의 오픈나무 사용과 버그 발견은 오픈나무의 발전을 돕습니다.
-[이슈 생성하기](https://github.com/2du/opennamu/issues/new)
+[이슈 생성하기](https://github.com/2du/openNAMU/issues/new)
 
-오픈나무는 완전한 오픈소스 프로젝트입니다. 새로운 기능을 추가하고 Pull Request를 해보세요. [다음 절차]에 따라 기여할 수 있습니다.
-[Pull Requests 생성하기](https://github.com/2du/opennamu/compare)
+오픈나무는 완전한 오픈소스 프로젝트입니다. 새로운 기능을 추가하고 Pull Request를 생성해보세요.
+[Pull Request 생성하기](https://github.com/2du/openNAMU/compare)
 
-# 라이선스
-오픈나무는 [BSD 3-Clause License](./LICENSE)에 의해 보호받고 있습니다. 자세한 내용은 문서를 참고하세요.
+## 라이선스
+openNAMU 프로젝트는 [BSD 3-Clause License](./LICENSE)(이하 이용허락)의 보호를 받고 있으며, openNAMU 프로젝트를 사용하고자 한다면 를 준수해야 합니다. 본 이용허락를 위반할 경우 개발자는 DMCA Takedown 등 관련 제재를 관계자에게 요청할 권리가 있으며, 그 책임은 모두 이용허락 위반 사용자에게 있습니다. 자세한 내용은 문서를 참고하세요.
 
-## 외부 프로젝트 라이선스
- * Quotes icon [Dave Gandy](http://www.flaticon.com/free-icon/quote-left_25672) CC 3.0 BY
- * Syntax highlighting [highlightjs](https://highlightjs.org/)
- * Numerical expression [MathJax](https://www.mathjax.org/)
+### 포함된 외부 프로젝트
+ * Quotes icon - [Dave Gandy](http://www.flaticon.com/free-icon/quote-left_25672) - CC 3.0 BY
+ * Syntax highlighting - [highlightjs](https://highlightjs.org/) - [BSD License](https://github.com/highlightjs/highlight.js/blob/master/LICENSE)
+ * Numerical expression - [MathJax](https://www.mathjax.org/) - [Apache License 2.0](https://github.com/mathjax/MathJax/blob/master/LICENSE)
 
-# 기여자 목록
- * [참고](https://github.com/2DU/opennamu/graphs/contributors)
+## 기여자 목록
+ * [참고](https://github.com/2DU/openNAMU/graphs/contributors)
 
-## 도움을 주신 분들
+### 도움을 주신 분들
  * [Team Croatia](https://github.com/TeamCroatia)
  * Basix
  * Efrit
  * Other chat rooms
 
-# 기타
-`set.json`은 몇가지 로컬 설정을 저장하는 설정 파일입니다.
- * db = 데이터베이스 이름
-
-`set.json`은 삭제해도 다시 새로 만들 수 있습니다.
-
-[테스트 서버](http://namu.ml/)
-
-첫 번째 가입자에게 소유자 권한이 부여됩니다.
+## 기타
+ * [테스트 서버](http://namu.ml/)
+ * 첫 가입자에게 소유자 권한이 부여됩니다.

+ 49 - 21
readme.md

@@ -1,10 +1,13 @@
-opennamu
+openNAMU
 ====
-![Python 3.5 or later Required](https://img.shields.io/badge/python-%3E%3D%203.5-blue.svg)
+[![Python 3.5 or later Required](https://img.shields.io/badge/python-3.5%20or%20higher-blue.svg)](https://python.org)
+[![LICENSE](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
 
-opennamu is a Python-based wiki engine. If you install Python and its underlying modules, you will be able to create wikis.
+![](https://raw.githubusercontent.com/2du/openNAMU/master/.github/logo.png)
 
- * [(README for korean)](./readme-ko.md)
+openNAMU is a Python-based wiki engine. You can use openNAMU by installing Python and its dependency modules, and you can modify the code yourself to create more specialized wikis.
+
+ * [(README for Korean)](./readme-ko.md)
 
 ### Index
  * [Getting Started](#getting-started)
@@ -12,27 +15,58 @@ opennamu is a Python-based wiki engine. If you install Python and its underlying
  * [Contribute](#contribute)
  * [License](#license)
  * [Authors](#authors)
- * [Etc.](#etc.)
+ * [Etc.](#etc)
 
 # Getting Started
+openNAMU is based upon Python, and it requires a Python environment.
+
+## Set-Ups
+### Install Python
+See [Python Installation Guide(KR)](https://github.com/404-sdok/how-to-python/blob/master/0.md).
+
+### Download Releases
+Download the [release version of openNAMU](https://github.com/2du/openNAMU/releases), and unzip the file. It is also possible to download releases by [cloning this repository](#Clone).
+
+### Install Modules
+Windows
+```
+pip install -r requirements.txt
+```
+
+Linux
+```
+pip3 install -r requirements.txt
+```
+## Launching Application
+Windows
+```
+python app.py
+```
+
+Linux
+```
+python3 app.py
+```
+
+## Publishing Application
 
 # Clone
 You can clone this repository by entering the following command at the terminal (command prompt):
 ## Stable
- * `git clone -b stable https://github.com/2du/opennamu.git`
+ * `git clone -b stable https://github.com/2du/openNAMU.git`
 
 ## Beta
- * `git clone -b master https://github.com/2du/opennamu.git`
+ * `git clone -b master https://github.com/2du/openNAMU.git`
 
 # Contribute
-opennamu may have some untested bugs. Your use of opennamu and bug discovery will help develop opennamu.
-[Create Issues](https://github.com/2du/opennamu/issues/new)
+openNAMU may have some untested bugs. Your use of openNAMU and bug discovery will help develop openNAMU.
+[Create Issues](https://github.com/2du/openNAMU/issues/new)
 
-opennamu is open source project. Add new features and request pull requests. 
-[Create Pull Requests](https://github.com/2du/opennamu/compare)
+openNAMU is open source project. Add new features and request pull requests. 
+[Create Pull Requests](https://github.com/2du/openNAMU/compare)
 
 # Lisence
-opennamu is protected by [BSD 3-Clause License](./LICNESE). Please refer to the documentation for details.
+openNAMU is protected by [BSD 3-Clause License](./LICNESE). Please refer to the documentation for details.
 
 ## External Projects
  * Quotes icon [Dave Gandy](http://www.flaticon.com/free-icon/quote-left_25672) CC 3.0 BY
@@ -40,7 +74,7 @@ opennamu is protected by [BSD 3-Clause License](./LICNESE). Please refer to the
  * Numerical expression [MathJax](https://www.mathjax.org/)
 
 # Authors
- * [Reference](https://github.com/2DU/opennamu/graphs/contributors)
+ * [Reference](https://github.com/2DU/openNAMU/graphs/contributors)
 
 ## Special Thanks
  * [Team Croatia](https://github.com/TeamCroatia)
@@ -49,11 +83,5 @@ opennamu is protected by [BSD 3-Clause License](./LICNESE). Please refer to the
  * Other chat rooms
 
 # Etc.
-`set.json` is a configuration file that stores some local settings.
- * db = Database name
-
-If you delete `set.json`, you can create a new one again.
-
-[Test Server](http://namu.ml/)
-
-Owner rights are granted to the first registor.
+ * [Test Server](http://namu.ml/)
+ * Owner rights are granted to the first registor.

+ 117 - 0
route/acl.py

@@ -0,0 +1,117 @@
+from .tool.func import *
+
+def acl_2(conn, name):
+    curs = conn.cursor()
+
+    check_ok = ''
+    
+    if flask.request.method == 'POST':
+        check_data = 'acl (' + name + ')'
+    else:
+        check_data = None
+    
+    user_data = re.search('^user:(.+)$', name)
+    if user_data:
+        if check_data and custom()[2] == 0:
+            return redirect('/login')
+        
+        if user_data.groups()[0] != ip_check():
+            if admin_check(5, check_data) != 1:
+                if check_data:
+                    return re_error('/error/3')
+                else:
+                    check_ok = 'disabled'
+    else:
+        if admin_check(5, check_data) != 1:
+            if check_data:
+                return re_error('/error/3')
+            else:
+                check_ok = 'disabled'
+
+    if flask.request.method == 'POST':
+        if flask.request.form.get('dec', '') != flask.request.form.get('view', ''):
+            dec = flask.request.form.get('view', '')
+            view = flask.request.form.get('view', '')
+        else:
+            dec = flask.request.form.get('dec', '')
+            view = flask.request.form.get('view', '')
+
+        curs.execute("select title from acl where title = ?", [name])
+        if curs.fetchall():
+            curs.execute("update acl set dec = ? where title = ?", [dec, name])
+            curs.execute("update acl set dis = ? where title = ?", [flask.request.form.get('dis', ''), name])
+            curs.execute("update acl set why = ? where title = ?", [flask.request.form.get('why', ''), name])
+            curs.execute("update acl set view = ? where title = ?", [view, name])
+        else:
+            curs.execute("insert into acl (title, dec, dis, why, view) values (?, ?, ?, ?, ?)", [name, dec, flask.request.form.get('dis', ''), flask.request.form.get('why', ''), view])
+        
+        curs.execute("select title from acl where title = ? and dec = '' and dis = ''", [name])
+        if curs.fetchall():
+            curs.execute("delete from acl where title = ?", [name])
+
+        conn.commit()
+            
+        return redirect('/acl/' + url_pas(name))            
+    else:
+        data = '' + load_lang('document_acl') + '<br><br><select name="dec" ' + check_ok + '>'
+    
+        if re.search('^user:', name):
+            acl_list = [['', load_lang('normal')], ['user', load_lang('member')], ['all', load_lang('all')]]
+        else:
+            acl_list = [['', load_lang('normal')], ['user', load_lang('member')], ['admin', load_lang('admin')]]
+        
+        curs.execute("select dec from acl where title = ?", [name])
+        acl_data = curs.fetchall()
+        for data_list in acl_list:
+            if acl_data and acl_data[0][0] == data_list[0]:
+                check = 'selected="selected"'
+            else:
+                check = ''
+            
+            data += '<option value="' + data_list[0] + '" ' + check + '>' + data_list[1] + '</option>'
+            
+        data += '</select>'
+        
+        if not re.search('^user:', name):
+            data += '<hr class=\"main_hr\">' + load_lang('discussion_acl') + '<br><br><select name="dis" ' + check_ok + '>'
+        
+            curs.execute("select dis, why, view from acl where title = ?", [name])
+            acl_data = curs.fetchall()
+            for data_list in acl_list:
+                if acl_data and acl_data[0][0] == data_list[0]:
+                    check = 'selected="selected"'
+                else:
+                    check = ''
+                    
+                data += '<option value="' + data_list[0] + '" ' + check + '>' + data_list[1] + '</option>'
+                
+            data += '</select>'
+
+            data += '<hr class=\"main_hr\">' + load_lang('view_acl') + '<br><br><select name="view" ' + check_ok + '>'
+            for data_list in acl_list:
+                if acl_data and acl_data[0][2] == data_list[0]:
+                    check = 'selected="selected"'
+                else:
+                    check = ''
+                    
+                data += '<option value="' + data_list[0] + '" ' + check + '>' + data_list[1] + '</option>'
+                
+            data += '</select>'
+                
+            if check_ok == '':
+                if acl_data:
+                    data += '<hr class=\"main_hr\"><input value="' + html.escape(acl_data[0][1]) + '" placeholder="' + load_lang('why') + '" name="why" type="text" ' + check_ok + '>'
+                else:
+                    data += '<hr class=\"main_hr\"><input placeholder="' + load_lang('why') + '" name="why" type="text" ' + check_ok + '>'
+            
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('acl') + ')', 0])],
+            data =  '''
+                <form method="post">
+                    ''' + data + '''
+                    <hr class=\"main_hr\">
+                    <button type="submit" ''' + check_ok + '''>''' + load_lang('save') + '''</button>
+                </form>
+            ''',
+            menu = [['w/' + url_pas(name), load_lang('document')], ['manager', load_lang('admin')]]
+        ))

+ 47 - 0
route/acl_list.py

@@ -0,0 +1,47 @@
+from .tool.func import *
+
+def acl_list_2(conn):
+    curs = conn.cursor()
+    
+    div =   '''
+        <table id="main_table_set">
+            <tbody>
+                <tr>
+                    <td id="main_table_width_quarter">''' + load_lang('document_name') + '''</td>
+                    <td id="main_table_width_quarter">''' + load_lang('document') + ''' acl</td>
+                    <td id="main_table_width_quarter">''' + load_lang('discussion') + ''' acl</td>
+                    <td id="main_table_width_quarter">''' + load_lang('acl_required') + '''</td>
+    '''
+    
+    curs.execute("select title, dec, dis, view, why from acl where dec = 'admin' or dec = 'user' or dis = 'admin' or dis = 'user' or view = 'admin' or view = 'user' order by title desc")
+    list_data = curs.fetchall()
+    for data in list_data:
+        if not re.search('^user:', data[0]) and not re.search('^file:', data[0]):
+            acl = []
+            for i in range(1, 4):
+                if data[i] == 'admin':
+                    acl += [load_lang('admin')]
+                else:
+                    acl += [load_lang('member')]
+
+            div +=  '''
+                <tr>
+                    <td>
+                        <a href="/w/''' + url_pas(data[0]) + '">' + data[0] + '''</a>
+                    </td>
+                    <td>''' + acl[0] + '''</td>
+                    <td>''' + acl[1] + '''</td>
+                    <td>''' + acl[2] + '''</td>
+                </tr>
+            '''
+        
+    div +=  '''
+            </tbody>
+        </table>
+    '''
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('acl_document_list'), wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['other', load_lang('return')]]
+    ))

+ 23 - 0
route/admin_list.py

@@ -0,0 +1,23 @@
+from .tool.func import *
+
+def admin_list_2(conn):
+    curs = conn.cursor()
+
+    div = '<ul>'
+    
+    curs.execute("select id, acl, date from user where not acl = 'user' order by date desc")
+    for data in curs.fetchall():
+        name = ip_pas(data[0]) + ' <a href="/admin_plus/' + url_pas(data[1]) + '">(' + data[1] + ')</a>'
+        
+        if data[2] != '':
+            name += '(' + data[2] + ')'
+
+        div += '<li>' + name + '</li>'
+        
+    div += '</ul>'
+                
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('admin_list'), wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['other', load_lang('return')]]
+    ))

+ 26 - 0
route/admin_log.py

@@ -0,0 +1,26 @@
+from .tool.func import *
+
+def admin_log_2(conn):
+    curs = conn.cursor()
+    
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+
+    list_data = '<ul>'
+
+    curs.execute("select who, what, time from re_admin order by time desc limit ?, '50'", [str(sql_num)])
+    get_list = curs.fetchall()
+    for data in get_list:            
+        list_data += '<li>' + ip_pas(data[0]) + ' / ' + data[1] + ' / ' + data[2] + '</li>'
+
+    list_data += '</ul>'
+    list_data += next_fix('/admin_log?num=', num, get_list)
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('authority_use_list'), wiki_set(), custom(), other2([0, 0])],
+        data = list_data,
+        menu = [['other', load_lang('return')]]
+    ))

+ 85 - 0
route/admin_plus.py

@@ -0,0 +1,85 @@
+from .tool.func import *
+
+def admin_plus_2(conn):
+    curs = conn.cursor()
+    
+    if flask.request.method == 'POST':
+        if admin_check(None, 'admin_plus (' + name + ')') != 1:
+            return re_error('/error/3')
+
+        curs.execute("delete from alist where name = ?", [name])
+        
+        if flask.request.form.get('ban', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'ban')", [name])
+
+        if flask.request.form.get('toron', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'toron')", [name])
+            
+        if flask.request.form.get('check', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'check')", [name])
+
+        if flask.request.form.get('acl', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'acl')", [name])
+
+        if flask.request.form.get('hidel', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'hidel')", [name])
+
+        if flask.request.form.get('give', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'give')", [name])
+
+        if flask.request.form.get('owner', 0) != 0:
+            curs.execute("insert into alist (name, acl) values (?, 'owner')", [name])
+            
+        conn.commit()
+        
+        return redirect('/admin_plus/' + url_pas(name))
+    else:        
+        data = '<ul>'
+        
+        exist_list = ['', '', '', '', '', '', '', '']
+
+        curs.execute('select acl from alist where name = ?', [name])
+        acl_list = curs.fetchall()    
+        for go in acl_list:
+            if go[0] == 'ban':
+                exist_list[0] = 'checked="checked"'
+            elif go[0] == 'toron':
+                exist_list[2] = 'checked="checked"'
+            elif go[0] == 'check':
+                exist_list[3] = 'checked="checked"'
+            elif go[0] == 'acl':
+                exist_list[4] = 'checked="checked"'
+            elif go[0] == 'hidel':
+                exist_list[5] = 'checked="checked"'
+            elif go[0] == 'give':
+                exist_list[6] = 'checked="checked"'
+            elif go[0] == 'owner':
+                exist_list[7] = 'checked="checked"'
+
+        if admin_check() != 1:
+            state = 'disabled'
+        else:
+            state = ''
+
+        data += '''
+                    <li><input type="checkbox" ''' + state +  ' name="ban" ' + exist_list[0] + '> ' + load_lang('ban_authority') + '''</li>
+                    <li><input type="checkbox" ''' + state +  ' name="toron" ' + exist_list[2] + '> ' + load_lang('discussion_authority') + '''</li>
+                    <li><input type="checkbox" ''' + state +  ' name="check" ' + exist_list[3] + '> ' + load_lang('user_check_authority') + '''</li>
+                    <li><input type="checkbox" ''' + state +  ' name="acl" ' + exist_list[4] + '> ' + load_lang('document_acl_authority') + '''</li>
+                    <li><input type="checkbox" ''' + state +  ' name="hidel" ' + exist_list[5] + '> ' + load_lang('history_hide_authority') + '''</li>
+                    <li><input type="checkbox" ''' + state +  ' name="give" ' + exist_list[6] + '> ' + load_lang('authorization_authority') + '''</li>
+                    <li><input type="checkbox" ''' + state +  ' name="owner" ' + exist_list[7] + '> ' + load_lang('owner_authority') + '''</li>
+                </ul>
+                '''
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('admin_group_add'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        ''' + data + '''
+                        <hr class=\"main_hr\">
+                        <button id="save" ''' + state +  ''' type="submit">''' + load_lang('save') + '''</button>
+                    </form>
+                    ''',
+            menu = [['manager', load_lang('return')]]
+        ))     

+ 65 - 0
route/adsense_setting.py

@@ -0,0 +1,65 @@
+from .tool.func import *
+
+def adsense_setting_2(conn):
+    curs = conn.cursor()
+
+    if admin_check(None, 'adsense setting') != 1:
+        return re_error('/error/3')
+    
+    if flask.request.method == 'POST':
+        try:
+            adsense_enabled = flask.request.form.get('adsense_enabled')
+            adsense_code = flask.request.form['adsense_code']
+        except:
+            return easy_minify(flask.render_template(skin_check(),
+                imp = [load_lang('inter_error'), wiki_set(), custom(), other2([0, 0])],
+                data = '<h2>ie_no_data_required</h2>' + load_lang('ie_no_data_required'),
+                menu = [['other', load_lang('return')]]
+            ))
+        
+        if adsense_enabled == 'on':
+            curs.execute('update other set data = "True" where name = "adsense"')
+        else:
+            curs.execute('update other set data = "False" where name = "adsense"')
+        
+        curs.execute('update other set data = ? where name = "adsense_code"', [adsense_code])
+        conn.commit()
+        
+        return redirect('/adsense_setting')
+
+    body_content = ''
+
+    curs.execute('select data from other where name = "adsense"')
+    adsense_enabled = curs.fetchall()[0][0]
+
+    curs.execute('select data from other where name = "adsense_code"')
+    adsense_code = curs.fetchall()[0][0]
+
+    template = '''
+        <form action="" accept-charset="utf-8" method="post">
+            <div class="form-check">
+                <label class="form-check-label">
+                    <input class="form-check-input" name="adsense_enabled" type="checkbox" {}>
+                    {}
+                </label>
+            </div>
+            <hr>
+            <div class="form-group">
+                <textarea class="form-control" id="adsense_code" name="adsense_code" rows="12">{}</textarea>
+            </div>
+            <button type="submit" value="publish">{}</button>
+        </form>
+    '''
+    
+    body_content += template.format(
+        'checked' if adsense_enabled == 'True' else template.format(''),
+        load_lang('adsense_enable'),
+        load_lang('save'),
+        adsense_code
+    )
+
+    return easy_minify(flask.render_template(skin_check(),
+        imp = [load_lang('adsense_setting'), wiki_set(), custom(), other2([0, 0])],
+        data = body_content,
+        menu = [['other', load_lang('return')]]
+    ))

+ 25 - 0
route/alarm.py

@@ -0,0 +1,25 @@
+from .tool.func import *
+
+def alarm_2(conn):
+    curs = conn.cursor()
+    
+    if custom()[2] == 0:
+        return redirect('/login')    
+
+    data = '<ul>'    
+    
+    curs.execute("select data, date from alarm where name = ? order by date desc", [ip_check()])
+    data_list = curs.fetchall()
+    if data_list:
+        data = '<a href="/del_alarm">(' + load_lang('delete') + ')</a><hr class=\"main_hr\">' + data
+
+        for data_one in data_list:
+            data += '<li>' + data_one[0] + ' (' + data_one[1] + ')</li>'
+    
+    data += '</ul>'
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('notice'), wiki_set(), custom(), other2([0, 0])],
+        data = data,
+        menu = [['user', load_lang('return')]]
+    ))

+ 91 - 0
route/block_log.py

@@ -0,0 +1,91 @@
+from .tool.func import *
+
+def block_log_2(conn, name, tool):
+    curs = conn.cursor()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+    
+    div =   '''
+            <table id="main_table_set">
+                <tbody>
+                    <tr>
+                        <td id="main_table_width">''' + load_lang('blocked') + '''</td>
+                        <td id="main_table_width">''' + load_lang('admin') + '''</td>
+                        <td id="main_table_width">''' + load_lang('period') + '''</td>
+                    </tr>
+            '''
+    
+    data_list = ''
+    
+    if not name:
+        div =   '''
+                <a href="/manager/11">(''' + load_lang('blocked') + ''')</a> <a href="/manager/12">(''' + load_lang('admin') + ''')</a>
+                <hr class=\"main_hr\">
+                ''' + div
+        
+        sub = 0
+        menu = 0
+        
+        curs.execute("select why, block, blocker, end, today from rb order by today desc limit ?, '50'", [str(sql_num)])
+    else:
+        menu = [['block_log', load_lang('normal')]]
+        
+        if tool == 'block_user':
+            sub = ' (' + load_lang('blocked') + ')'
+            
+            curs.execute("select why, block, blocker, end, today from rb where block = ? order by today desc limit ?, '50'", [name, str(sql_num)])
+        else:
+            sub = ' (' + load_lang('admin') + ')'
+            
+            curs.execute("select why, block, blocker, end, today from rb where blocker = ? order by today desc limit ?, '50'", [name, str(sql_num)])
+
+    if data_list == '':
+        data_list = curs.fetchall()
+
+    for data in data_list:
+        why = html.escape(data[0])
+        if why == '':
+            why = '<br>'
+        
+        band = re.search("^([0-9]{1,3}\.[0-9]{1,3})$", data[1])
+        if band:
+            ip = data[1] + ' (' + load_lang('range') + ')'
+        else:
+            ip = ip_pas(data[1])
+
+        if data[3] != '':
+            end = data[3]
+        else:
+            end = load_lang('limitless') + ''
+            
+        div +=  '''
+            <tr>
+                <td>''' + ip + '''</td>
+                <td>''' + ip_pas(data[2]) + '''</td>
+                <td>
+                    start : ''' + data[4] + '''
+                    <br>
+                    end : ''' + end + '''
+                </td>
+            </tr>
+            <tr>
+                <td colspan="3">''' + why + '''</td>
+            </tr>
+        '''
+
+    div += '</tbody></table>'
+    
+    if not name:
+        div += next_fix('/block_log?num=', num, data_list)
+    else:
+        div += next_fix('/' + url_pas(tool) + '/' + url_pas(name) + '?num=', num, data_list)
+                
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('recent_ban'), wiki_set(), custom(), other2([sub, 0])],
+        data = div,
+        menu = menu
+    ))

+ 120 - 0
route/change_password.py

@@ -0,0 +1,120 @@
+from .tool.func import *
+
+def change_password_2(conn):
+    curs = conn.cursor()
+
+    support_language = server_init.server_set_var['language']['list']
+
+    if ban_check() == 1:
+        return re_error('/ban')
+
+    if custom()[2] == 0:
+        return redirect('/login')
+
+    ip = ip_check()
+    user_state = flask.request.args.get('user', 'ip')
+    
+    if user_state == 'ip':
+        if flask.request.method == 'POST':    
+            if flask.request.form.get('pw4', None) and flask.request.form.get('pw2', None):
+                if flask.request.form.get('pw2', None) != flask.request.form.get('pw3', None):
+                    return re_error('/error/20')
+
+                curs.execute("select pw, encode from user where id = ?", [flask.session['id']])
+                user = curs.fetchall()
+                if not user:
+                    return re_error('/error/2')
+                
+                pw_check_d = pw_check(
+                    flask.request.form.get('pw4', ''), 
+                    user[0][0],
+                    user[0][1],
+                    flask.request.form.get('id', None)
+                )
+                if pw_check_d != 1:
+                    return re_error('/error/10')
+
+                hashed = pw_encode(flask.request.form.get('pw2', None))
+                
+                curs.execute("update user set pw = ? where id = ?", [hashed, flask.session['id']])
+
+            auto_list = ['email', 'skin', 'lang']
+
+            for auto_data in auto_list:
+                curs.execute('select data from user_set where name = ? and id = ?', [auto_data, ip])
+                if curs.fetchall():
+                    curs.execute("update user_set set data = ? where name = ? and id = ?", [flask.request.form.get(auto_data, ''), auto_data, ip])
+                else:
+                    curs.execute("insert into user_set (name, id, data) values (?, ?, ?)", [auto_data, ip, flask.request.form.get(auto_data, '')])
+
+            conn.commit()
+            
+            return redirect('/change')
+        else:        
+            curs.execute('select data from user_set where name = "email" and id = ?', [ip])
+            data = curs.fetchall()
+            if data:
+                email = data[0][0]
+            else:
+                email = ''
+
+            div2 = load_skin()
+            
+            div3 = ''
+            var_div3 = ''
+
+            curs.execute('select data from user_set where name = "lang" and id = ?', [flask.session['id']])
+            data = curs.fetchall()
+
+            for lang_data in support_language:
+                if data and data[0][0] == lang_data:
+                    div3 = '<option value="' + lang_data + '">' + lang_data + '</option>'
+                else:
+                    var_div3 += '<option value="' + lang_data + '">' + lang_data + '</option>'
+
+            div3 += var_div3
+
+            oauth_provider = load_oauth('_README')['support']
+            oauth_content = '<ul>'
+            for i in range(len(oauth_provider)):
+                curs.execute('select name, picture from oauth_conn where wiki_id = ? and provider = ?', [flask.session['id'], oauth_provider[i]])
+                oauth_data = curs.fetchall()
+                if len(oauth_data) == 1:
+                    oauth_content += '<li>{} - {}</li>'.format(oauth_provider[i], load_lang('connection') + ' : <img src="{}" width="17px" height="17px">{}'.format(oauth_data[0][1], oauth_data[0][0]))
+                else:
+                    oauth_content += '<li>{} - {}</li>'.format(oauth_provider[i], load_lang('connection') + ' : <a href="/oauth/{}/init">{}</a>'.format(oauth_provider[i], load_lang('connect')))
+            
+            oauth_content += '</ul>'
+
+            return easy_minify(flask.render_template(skin_check(),    
+                imp = [load_lang('user_setting'), wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            <span>id : ''' + ip + '''</span>
+                            <hr class=\"main_hr\">
+                            <input placeholder="''' + load_lang('now_password') + '''" name="pw4" type="password">
+                            <hr class=\"main_hr\">
+                            <input placeholder="''' + load_lang('new_password') + '''" name="pw2" type="password">
+                            <hr class=\"main_hr\">
+                            <input placeholder="''' + load_lang('password_confirm') + '''" name="pw3" type="password">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('skin') + '''</span>
+                            <hr class=\"main_hr\">
+                            <select name="skin">''' + div2 + '''</select>
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('language') + '''</span>
+                            <hr class=\"main_hr\">
+                            <select name="lang">''' + div3 + '''</select>
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('oauth_connection') + '''</span>
+                            ''' + oauth_content + '''
+                            <hr class=\"main_hr\">
+                            <button type="submit">''' + load_lang('save') + '''</button>
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('http_warring') + '''</span>
+                        </form>
+                        ''',
+                menu = [['user', load_lang('return')]]
+            ))
+    else:
+        pass

+ 83 - 0
route/check_key.py

@@ -0,0 +1,83 @@
+from .tool.func import *
+
+def check_key_2(conn, tool):
+    curs = conn.cursor()
+
+    if flask.request.method == 'POST':
+        if tool == 'check_key':
+            if 'c_id' in flask.session and flask.session['c_key'] == flask.request.form.get('key', None):
+                curs.execute('select data from other where name = "encode"')
+                db_data = curs.fetchall()
+
+                curs.execute("select id from user limit 1")
+                if not curs.fetchall():
+                    curs.execute("insert into user (id, pw, acl, date, encode) values (?, ?, 'owner', ?, ?)", [flask.session['c_id'], flask.session['c_pw'], get_time(), db_data[0][0]])
+
+                    first = 1
+                else:
+                    curs.execute("insert into user (id, pw, acl, date, encode) values (?, ?, 'user', ?, ?)", [flask.session['c_id'], flask.session['c_pw'], get_time(), db_data[0][0]])
+
+                    first = 0
+
+                ip = ip_check()
+                agent = flask.request.headers.get('User-Agent')
+
+                curs.execute("insert into user_set (name, id, data) values ('email', ?, ?)", [flask.session['c_id'], flask.session['c_email']])
+                curs.execute("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')", [flask.session['c_id'], ip, agent, get_time()])
+
+                flask.session['state'] = 1
+                flask.session['id'] = flask.session['c_id']
+                flask.session['head'] = ''
+                        
+                conn.commit()
+                
+                flask.session.pop('c_id', None)
+                flask.session.pop('c_pw', None)
+                flask.session.pop('c_key', None)
+                flask.session.pop('c_email', None)
+
+                if first == 0:
+                    return redirect('/change')
+                else:
+                    return redirect('/setting')
+            else:
+                flask.session.pop('c_id', None)
+                flask.session.pop('c_pw', None)
+                flask.session.pop('c_key', None)
+                flask.session.pop('c_email', None)
+
+                return redirect('/register')
+        else:
+            if 'c_id' in flask.session and flask.session['c_key'] == flask.request.form.get('key', None):
+                hashed = pw_encode(flask.session['c_key'])
+                curs.execute("update user set pw = ? where id = ?", [hashed, flask.session['c_id']])
+
+                d_id = flask.session['c_id']
+                pw = flask.session['c_key']
+
+                flask.session.pop('c_id', None)
+                flask.session.pop('c_key', None)
+
+                return easy_minify(flask.render_template(skin_check(),    
+                    imp = ['check', wiki_set(), custom(), other2([0, 0])],
+                    data =  '''
+                            ''' + load_lang('id') + ' : ' + d_id + '''
+                            <br>
+                            ''' + load_lang('password') + ' : ' + pw + '''
+                            ''',
+                    menu = [['user', load_lang('return')]]
+                ))
+            else:
+                return redirect('/pass_find')
+    else:
+        return easy_minify(flask.render_template(skin_check(),    
+            imp = ['check', wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <input placeholder="''' + load_lang('key') + '''" name="key" type="text">
+                        <hr class=\"main_hr\">
+                        <button type="submit">''' + load_lang('save') + '''</button>
+                    </form>
+                    ''',
+            menu = [['user', load_lang('return')]]
+        ))

+ 69 - 0
route/close_topic_list.py

@@ -0,0 +1,69 @@
+from .tool.func import *
+
+def close_topic_list_2(conn, name, tool):
+    curs = conn.cursor()
+    
+    div = ''
+    
+    if flask.request.method == 'POST':
+        t_num = ''
+        
+        while 1:
+            curs.execute("select title from topic where title = ? and sub = ? limit 1", [name, flask.request.form.get('topic', None) + t_num])
+            if curs.fetchall():
+                if t_num == '':
+                    t_num = ' 2'
+                else:
+                    t_num = ' ' + str(int(t_num.replace(' ', '')) + 1)
+            else:
+                break
+
+        return redirect('/topic/' + url_pas(name) + '/sub/' + url_pas(flask.request.form.get('topic', None) + t_num))
+    else:
+        plus = ''
+        menu = [['topic/' + url_pas(name), load_lang('return')]]
+        
+        if tool == 'close':
+            curs.execute("select sub from rd where title = ? and stop = 'O' order by sub asc", [name])
+            
+            sub = load_lang('close') + ''
+        elif tool == 'agree':
+            curs.execute("select sub from rd where title = ? and agree = 'O' order by sub asc", [name])
+            
+            sub = load_lang('agreement') + ''
+        else:
+            curs.execute("select sub from rd where title = ? order by date desc", [name])
+            
+            sub = load_lang('discussion_list')
+            
+            menu = [['w/' + url_pas(name), load_lang('document')]]
+            
+            plus =  '''
+                    <a href="/topic/''' + url_pas(name) + '''/close">(''' + load_lang('close') + ''')</a> <a href="/topic/''' + url_pas(name) + '''/agree">(''' + load_lang('agreement') + ''')</a>
+                    <hr class=\"main_hr\">
+                    <input placeholder="''' + load_lang('discussion_name') + '''" name="topic" type="text">
+                    <hr class=\"main_hr\">
+                    <button type="submit">''' + load_lang('go') + '''</button>
+                    '''
+
+        for data in curs.fetchall():
+            curs.execute("select data, date, ip, block from topic where title = ? and sub = ? and id = '1'", [name, data[0]])
+            if curs.fetchall():                
+                it_p = 0
+                
+                if sub == load_lang('discussion_list'):
+                    curs.execute("select title from rd where title = ? and sub = ? and stop = 'O' order by sub asc", [name, data[0]])
+                    if curs.fetchall():
+                        it_p = 1
+                
+                if it_p != 1:
+                    div += '<h2><a href="/topic/' + url_pas(name) + '/sub/' + url_pas(data[0]) + '">' + data[0] + '</a></h2>'
+
+        if div == '':
+            plus = re.sub('^<br>', '', plus)
+        
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + sub + ')', 0])],
+            data =  '<form method="post">' + div + plus + '</form>',
+            menu = menu
+        ))

+ 34 - 0
route/count_edit.py

@@ -0,0 +1,34 @@
+from .tool.func import *
+
+def count_edit_2(conn, name):
+    curs = conn.cursor()
+
+    if name == None:
+        that = ip_check()
+    else:
+        that = name
+
+    curs.execute("select count(title) from history where ip = ?", [that])
+    count = curs.fetchall()
+    if count:
+        data = count[0][0]
+    else:
+        data = 0
+
+    curs.execute("select count(title) from topic where ip = ?", [that])
+    count = curs.fetchall()
+    if count:
+        t_data = count[0][0]
+    else:
+        t_data = 0
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('count'), wiki_set(), custom(), other2([0, 0])],
+        data =  '''
+                <ul>
+                    <li><a href="/record/''' + url_pas(that) + '''">''' + load_lang('edit_record') + '''</a> : ''' + str(data) + '''</li>
+                    <li><a href="/topic_record/''' + url_pas(that) + '''">''' + load_lang('discussion_record') + '''</a> : ''' + str(t_data) + '''</a></li>
+                </ul>
+                ''',
+        menu = [['user', load_lang('return')]]
+    ))

+ 51 - 0
route/custom_head_view.py

@@ -0,0 +1,51 @@
+from .tool.func import *
+
+def custom_head_view_2(conn):
+    curs = conn.cursor()
+
+    ip = ip_check()
+
+    if flask.request.method == 'POST':
+        if custom()[2] != 0:
+            curs.execute("select user from custom where user = ?", [ip + ' (head)'])
+            if curs.fetchall():
+                curs.execute("update custom set css = ? where user = ?", [flask.request.form.get('content', None), ip + ' (head)'])
+            else:
+                curs.execute("insert into custom (user, css) values (?, ?)", [ip + ' (head)', flask.request.form.get('content', None)])
+            
+            conn.commit()
+
+        flask.session['head'] = flask.request.form.get('content', None)
+
+        return redirect('/user')
+    else:
+        if custom()[2] != 0:
+            start = ''
+
+            curs.execute("select css from custom where user = ?", [ip + ' (head)'])
+            head_data = curs.fetchall()
+            if head_data:
+                data = head_data[0][0]
+            else:
+                data = ''
+        else:
+            start = '<span>' + load_lang('user_head_warring') + '</span><hr class=\"main_hr\">'
+            
+            if 'head' in flask.session:
+                data = flask.session['head']
+            else:
+                data = ''
+
+        start += '<span>&lt;style&gt;CSS&lt;/style&gt;<br>&lt;script&gt;JS&lt;/script&gt;</span><hr class=\"main_hr\">'
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang(data = 'user_head', safe = 1), wiki_set(), custom(), other2([0, 0])],
+            data =  start + '''
+                    <form method="post">
+                        <textarea rows="25" cols="100" name="content">''' + data + '''</textarea>
+                        <hr class=\"main_hr\">
+                        <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                    </form>
+                    ''',
+            menu = [['user', load_lang('return')]]
+        ))

+ 61 - 0
route/deep_search.py

@@ -0,0 +1,61 @@
+from .tool.func import *
+
+def deep_search_2(conn, name):
+    curs = conn.cursor()
+
+    if name == '':
+        return redirect()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+
+    div = '<ul>'
+    
+    div_plus = ''
+    test = ''
+    
+    curs.execute("select title from data where title = ?", [name])
+    if curs.fetchall():
+        link_id = ''
+    else:
+        link_id = 'id="not_thing"'
+    
+    div =   '''
+            <ul>
+                <li>
+                    <a ''' + link_id + ' href="/w/' + url_pas(name) + '">' + name + '''</a>
+                </li>
+            </ul>
+            <hr class=\"main_hr\">
+            <ul>
+            '''
+
+    curs.execute(
+        "select distinct title, case when title like ? then '제목' else '내용' \
+        end from data where title like ? or data like ? order by case \
+        when title like ? then 1 else 2 end limit ?, '50'",
+        ['%' + name + '%', '%' + name + '%', '%' + name + '%', '%' + name + '%', str(sql_num)]
+    )
+    all_list = curs.fetchall()
+    if all_list:
+        test = all_list[0][1]
+        
+        for data in all_list:
+            if data[1] != test:
+                div_plus += '</ul><hr class=\"main_hr\"><ul>'
+                
+                test = data[1]
+
+            div_plus += '<li><a href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a> (' + data[1] + ')</li>'
+
+    div += div_plus + '</ul>'
+    div += next_fix('/search/' + url_pas(name) + '?num=', num, all_list)
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [name, wiki_set(), custom(), other2([' (' + load_lang('search') + ')', 0])],
+        data = div,
+        menu = 0
+    ))

+ 9 - 0
route/del_alarm.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+def del_alarm_2(conn):
+    curs = conn.cursor()
+    
+    curs.execute("delete from alarm where name = ?", [ip_check()])
+    conn.commit()
+
+    return redirect('/alarm')

+ 20 - 0
route/del_inter.py

@@ -0,0 +1,20 @@
+from .tool.func import *
+
+def del_inter_2(conn, tools, name):
+    curs = conn.cursor()
+    
+    if admin_check(None, tools) == 1:
+        if tools == 'del_inter_wiki':
+            curs.execute("delete from inter where title = ?", [name])
+        elif tools == 'del_edit_filter':
+            curs.execute("delete from filter where name = ?", [name])
+        elif tools == 'del_name_filter':
+            curs.execute("delete from html_filter where html = ? and kind = 'name'", [name])
+        else:
+            curs.execute("delete from html_filter where html = ? and kind = 'email'", [name])
+        
+        conn.commit()
+
+        return redirect('/' + re.sub('^del_', '', tools))
+    else:
+        return re_error('/error/3')

+ 57 - 0
route/delete.py

@@ -0,0 +1,57 @@
+from .tool.func import *
+
+def delete_2(conn, name):
+    curs = conn.cursor()
+
+    ip = ip_check()
+    if acl_check(name) == 1:
+        return re_error('/ban')
+    
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        curs.execute("select data from data where title = ?", [name])
+        data = curs.fetchall()
+        if data:
+            today = get_time()
+            leng = '-' + str(len(data[0][0]))
+            
+            history_plus(
+                name, 
+                '', 
+                today, 
+                ip, 
+                flask.request.form.get('send', None) + ' (delete)', 
+                leng
+            )
+            
+            curs.execute("select title, link from back where title = ? and not type = 'cat' and not type = 'no'", [name])
+            for data in curs.fetchall():
+                curs.execute("insert into back (title, link, type) values (?, ?, 'no')", [data[0], data[1]])
+            
+            curs.execute("delete from back where link = ?", [name])
+            curs.execute("delete from data where title = ?", [name])
+            conn.commit()
+            
+        return redirect('/w/' + url_pas(name))
+    else:
+        curs.execute("select title from data where title = ?", [name])
+        if not curs.fetchall():
+            return redirect('/w/' + url_pas(name))
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('delete') + ')', 0])],
+            data =  '''
+                    <form method="post">
+                        ''' + ip_warring() + '''
+                        <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('delete') + '''</button>
+                    </form>
+                    ''',
+            menu = [['w/' + url_pas(name), load_lang('return')]]
+        ))     

+ 30 - 0
route/diff_data.py

@@ -0,0 +1,30 @@
+from .tool.func import *
+
+def diff_data_2(conn, name):
+    curs = conn.cursor()
+
+    first = flask.request.args.get('first', '1')
+    second = flask.request.args.get('second', '1')
+
+    curs.execute("select data from history where id = ? and title = ?", [first, name])
+    first_raw_data = curs.fetchall()
+    if first_raw_data:
+        curs.execute("select data from history where id = ? and title = ?", [second, name])
+        second_raw_data = curs.fetchall()
+        if second_raw_data:
+            first_data = html.escape(first_raw_data[0][0])            
+            second_data = html.escape(second_raw_data[0][0])
+
+            if first == second:
+                result = '-'
+            else:            
+                diff_data = difflib.SequenceMatcher(None, first_data, second_data)
+                result = re.sub('\r', '', diff(diff_data))
+            
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = [name, wiki_set(), custom(), other2([' (' + load_lang('compare') + ')', 0])],
+                data = '<pre>' + result + '</pre>',
+                menu = [['history/' + url_pas(name), load_lang('return')]]
+            ))
+
+    return redirect('/history/' + url_pas(name))

+ 18 - 0
route/down.py

@@ -0,0 +1,18 @@
+from .tool.func import *
+
+def down_2(conn, name):
+    curs = conn.cursor()
+
+    div = '<ul>'
+
+    curs.execute("select title from data where title like ?", ['%' + name + '/%'])
+    for data in curs.fetchall():
+        div += '<li><a href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a></li>'
+        
+    div += '</ul>'
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [name, wiki_set(), custom(), other2([' (' + load_lang('sub') + ')', 0])],
+        data = div,
+        menu = [['w/' + url_pas(name), load_lang('return')]]
+    ))

+ 118 - 0
route/edit.py

@@ -0,0 +1,118 @@
+from .tool.func import *
+
+def edit_2(conn, name):
+    curs = conn.cursor()
+
+    ip = ip_check()
+    if acl_check(name) == 1:
+        return re_error('/ban')
+    
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+            
+        if len(flask.request.form.get('send', None)) > 500:
+            return re_error('/error/15')
+
+        if flask.request.form.get('otent', None) == flask.request.form.get('content', None):
+            return redirect('/w/' + url_pas(name))
+            
+        if edit_filter_do(flask.request.form.get('content', '')) == 1:
+            return re_error('/error/21')
+
+        today = get_time()
+        content = savemark(flask.request.form.get('content', None))
+        
+        curs.execute("select data from data where title = ?", [name])
+        old = curs.fetchall()
+        if old:
+            leng = leng_check(len(flask.request.form.get('otent', None)), len(content))
+            
+            if flask.request.args.get('section', None):
+                content = old[0][0].replace(flask.request.form.get('otent', None), content)
+                
+            curs.execute("update data set data = ? where title = ?", [content, name])
+        else:
+            leng = '+' + str(len(content))
+            
+            curs.execute("insert into data (title, data) values (?, ?)", [name, content])
+
+        curs.execute("select user from scan where title = ?", [name])
+        for _ in curs.fetchall():
+            curs.execute("insert into alarm (name, data, date) values (?, ?, ?)", [ip, ip + ' - <a href="/w/' + url_pas(name) + '">' + name + '</a> (Edit)', today])
+
+        history_plus(
+            name,
+            content,
+            today,
+            ip,
+            flask.request.form.get('send', None),
+            leng
+        )
+        
+        curs.execute("delete from back where link = ?", [name])
+        curs.execute("delete from back where title = ? and type = 'no'", [name])
+        
+        render_set(
+            title = name,
+            data = content,
+            num = 1
+        )
+        
+        conn.commit()
+        
+        return redirect('/w/' + url_pas(name))
+    else:            
+        curs.execute("select data from data where title = ?", [name])
+        new = curs.fetchall()
+        if new:
+            if flask.request.args.get('section', None):
+                test_data = '\n' + re.sub('\r\n', '\n', new[0][0]) + '\n'   
+                
+                section_data = re.findall('((?:={1,6}) ?(?:(?:(?!={1,6}\n).)+) ?={1,6}\n(?:(?:(?!(?:={1,6}) ?(?:(?:(?!={1,6}\n).)+) ?={1,6}\n).)*\n*)*)', test_data)
+                data = section_data[int(flask.request.args.get('section', None)) - 1]
+            else:
+                data = new[0][0]
+        else:
+            data = ''
+            
+        data_old = data
+        
+        if not flask.request.args.get('section', None):
+            get_name =  '''
+                        <a href="/manager/15?plus=''' + url_pas(name) + '">(' + load_lang('load') + ')</a> <a href="/edit_filter">(' + load_lang('edit_filter_rule') + ''')</a>
+                        <hr class=\"main_hr\">
+                        '''
+            action = ''
+        else:
+            get_name = ''
+            action = '?section=' + flask.request.args.get('section', None)
+            
+        if flask.request.args.get('plus', None):
+            curs.execute("select data from data where title = ?", [flask.request.args.get('plus', None)])
+            get_data = curs.fetchall()
+            if get_data:
+                data = get_data[0][0]
+                get_name = ''
+
+        js_data = edit_help_button()
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('edit') + ')', 0])],
+            data =  get_name + js_data[0] + '''
+                    <form method="post" action="/edit/''' + url_pas(name) + action + '''">
+                        ''' + js_data[1] + '''
+                        <textarea id="content" rows="25" name="content">''' + html.escape(re.sub('\n$', '', data)) + '''</textarea>
+                        <textarea style="display: none;" name="otent">''' + html.escape(re.sub('\n$', '', data_old)) + '''</textarea>
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + ip_warring() + '''
+                        <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                        <button id="preview" type="submit" formaction="/preview/''' + url_pas(name) + action + '">' + load_lang('preview') + '''</button>
+                    </form>
+                    ''',
+            menu = [['w/' + url_pas(name), load_lang('return')], ['delete/' + url_pas(name), load_lang('delete')], ['move/' + url_pas(name), load_lang('move')]]
+        ))

+ 22 - 0
route/give_log.py

@@ -0,0 +1,22 @@
+from .tool.func import *
+
+def give_log_2(conn):
+    curs = conn.cursor()
+
+    list_data = '<ul>'
+    back = ''
+
+    curs.execute("select distinct name from alist order by name asc")
+    for data in curs.fetchall():                      
+        if back != data[0]:
+            back = data[0]
+
+        list_data += '<li><a href="/admin_plus/' + url_pas(data[0]) + '">' + data[0] + '</a></li>'
+    
+    list_data += '</ul><hr class=\"main_hr\"><a href="/manager/8">(' + load_lang('add') + ')</a>'
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('admin_group_list'), wiki_set(), custom(), other2([0, 0])],
+        data = list_data,
+        menu = [['other', load_lang('return')]]
+    ))    

+ 17 - 0
route/history_hidden.py

@@ -0,0 +1,17 @@
+from .tool.func import *
+
+def history_hidden_2(conn, name):
+    curs = conn.cursor()
+
+    num = number_check(flask.request.args.get('num', '1'))
+
+    if admin_check(6, 'history_hidden (' + name + '#' + num + ')') == 1:
+        curs.execute("select title from history where title = ? and id = ? and hide = 'O'", [name, num])
+        if curs.fetchall():
+            curs.execute("update history set hide = '' where title = ? and id = ?", [name, num])
+        else:
+            curs.execute("update history set hide = 'O' where title = ? and id = ?", [name, num])
+            
+        conn.commit()
+    
+    return redirect('/history/' + url_pas(name))

+ 11 - 0
route/image_view.py

@@ -0,0 +1,11 @@
+from .tool.func import *
+
+APPVAR = json.loads(open('data/app_variables.json', encoding='utf-8').read())
+
+def image_view_2(conn, name):
+    curs = conn.cursor()
+    
+    if os.path.exists(os.path.join(APPVAR['PATH_DATA_IMAGES'], name)):
+        return flask.send_from_directory('.'+APPVAR['PATH_DATA_IMAGES'], name)
+    else:
+        return redirect()

+ 60 - 0
route/indexing.py

@@ -0,0 +1,60 @@
+from .tool.func import *
+
+def indexing_2(conn):
+    curs = conn.cursor()
+
+    if admin_check() != 1:
+        return re_error('/error/3')
+
+    if flask.request.method == 'POST':
+        admin_check(None, 'indexing')
+
+        curs.execute("select name from sqlite_master where type = 'index'")
+        data = curs.fetchall()
+        if data:
+            for delete_index in data:
+                print('Delete : ' + delete_index[0])
+
+                sql = 'drop index if exists ' + delete_index[0]
+                
+                try:
+                    curs.execute(sql)
+                except:
+                    pass
+        else:
+            curs.execute("select name from sqlite_master where type in ('table', 'view') and name not like 'sqlite_%' union all select name from sqlite_temp_master where type in ('table', 'view') order by 1;")
+            for table in curs.fetchall():            
+                curs.execute('select sql from sqlite_master where name = ?', [table[0]])
+                cul = curs.fetchall()
+                
+                r_cul = re.findall('(?:([^ (]*) text)', str(cul[0]))
+                
+                for n_cul in r_cul:
+                    print('Create : index_' + table[0] + '_' + n_cul)
+
+                    sql = 'create index index_' + table[0] + '_' + n_cul + ' on ' + table[0] + '(' + n_cul + ')'
+                    try:
+                        curs.execute(sql)
+                    except:
+                        pass
+
+        conn.commit()
+        
+        return redirect()  
+    else:
+        curs.execute("select name from sqlite_master where type = 'index'")
+        data = curs.fetchall()
+        if data:
+            b_data = load_lang('delete')
+        else:
+            b_data = load_lang('create')
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('indexing'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <button type="submit">''' + b_data + '''</button>
+                    </form>
+                    ''',
+            menu = [['manager', load_lang('return')]]
+        ))   

+ 75 - 0
route/inter_wiki.py

@@ -0,0 +1,75 @@
+from .tool.func import *
+
+def inter_wiki_2(conn, tools):
+    curs = conn.cursor()
+    
+    div = ''
+    admin = admin_check()
+
+    if tools == 'inter_wiki':
+        del_link = 'del_inter_wiki'
+        plus_link = 'plus_inter_wiki'
+        title = load_lang('interwiki_list')
+        div = ''
+
+        curs.execute('select title, link from inter')
+    elif tools == 'email_filter':
+        del_link = 'del_email_filter'
+        plus_link = 'plus_email_filter'
+        title = load_lang('email_filter_list')
+        div =   '''
+                <ul>
+                    <li>gmail.com</li>
+                    <li>naver.com</li>
+                    <li>daum.net</li>
+                    <li>hanmail.net</li>
+                    <li>hanmail2.net</li>
+                </ul>
+                '''
+
+        curs.execute("select html from html_filter where kind = 'email'")
+    elif tools == 'name_filter':
+        del_link = 'del_name_filter'
+        plus_link = 'plus_name_filter'
+        title = load_lang('id_filter_list')
+        div = ''
+
+        curs.execute("select html from html_filter where kind = 'name'")
+    else:
+        del_link = 'del_edit_filter'
+        plus_link = 'manager/9'
+        title = load_lang('edit_filter_list')
+        div = ''
+
+        curs.execute("select name from filter")
+
+    db_data = curs.fetchall()
+    if db_data:
+        div += '<ul>'
+
+        for data in db_data:
+            if tools == 'inter_wiki':
+                div += '<li>' + data[0] + ' : <a id="out_link" href="' + data[1] + '">' + data[1] + '</a>'
+            elif tools == 'edit_filter':
+                div += '<li><a href="/plus_edit_filter/' + url_pas(data[0]) + '">' + data[0] + '</a>'
+            else:
+                div += '<li>' + data[0]
+
+            if admin == 1:
+                div += ' <a href="/' + del_link + '/' + url_pas(data[0]) + '">(' + load_lang('delete') + ')</a>'
+
+            div += '</li>'
+
+        div += '</ul>'
+
+        if admin == 1:
+            div += '<hr class=\"main_hr\"><a href="/' + plus_link + '">(' + load_lang('add') + ')</a>'
+    else:
+        if admin == 1:
+            div += '<a href="/' + plus_link + '">(' + load_lang('add') + ')</a>'
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [title, wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['other', load_lang('return')]]
+    ))

+ 91 - 0
route/login.py

@@ -0,0 +1,91 @@
+from .tool.func import *
+
+def login_2(conn):
+    curs = conn.cursor()
+
+    if custom()[2] != 0:
+        return redirect('/user')
+    
+    if ban_check(tool = 'login') == 1:
+        return re_error('/ban')
+        
+    if flask.request.method == 'POST':        
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        ip = ip_check()
+        agent = flask.request.headers.get('User-Agent')
+
+        curs.execute("select pw, encode from user where id = ?", [flask.request.form.get('id', None)])
+        user = curs.fetchall()
+        if not user:
+            return re_error('/error/2')
+
+        pw_check_d = pw_check(
+            flask.request.form.get('pw', ''), 
+            user[0][0],
+            user[0][1],
+            flask.request.form.get('id', None)
+        )
+        if pw_check_d != 1:
+            return re_error('/error/10')
+
+        flask.session['state'] = 1
+        flask.session['id'] = flask.request.form.get('id', None)
+        
+        curs.execute("select css from custom where user = ?", [flask.request.form.get('id', None)])
+        css_data = curs.fetchall()
+        if css_data:
+            flask.session['head'] = css_data[0][0]
+        else:
+            flask.session['head'] = ''
+
+        curs.execute("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')", [flask.request.form.get('id', None), ip_check(1), agent, get_time()])
+
+        conn.commit()
+        
+        return redirect('/user')  
+    else:
+        oauth_content = '<link rel="stylesheet" href="/views/main_css/oauth.css"><div class="oauth-wrapper"><ul class="oauth-list">'
+        oauth_supported = load_oauth('_README')['support']
+        for i in range(len(oauth_supported)):
+            oauth_data = load_oauth(oauth_supported[i])
+            if oauth_data['client_id'] != '' and oauth_data['client_secret'] != '':
+                oauth_content +=    '''
+                                    <li>
+                                        <a href="/oauth/{}/init">
+                                            <div class="oauth-btn oauth-btn-{}">
+                                                <div class="oauth-btn-logo oauth-btn-{}"></div>
+                                                {}
+                                            </div>
+                                        </a>
+                                    </li>
+                                    '''.format(
+                                        oauth_supported[i], 
+                                        oauth_supported[i], 
+                                        oauth_supported[i], 
+                                        load_lang('oauth_signin_' + oauth_supported[i])
+                                    )
+        
+        oauth_content += '</ul></div>'
+        
+        return easy_minify(flask.render_template(skin_check(),    
+            imp = [load_lang('login'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <input placeholder="''' + load_lang('id') + '''" name="id" type="text">
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('password') + '''" name="pw" type="password">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('login') + '''</button>
+                        <hr class=\"main_hr\">
+                        ''' + oauth_content + '''
+                        <hr class=\"main_hr\">
+                        <span>''' + load_lang('http_warring') + '''</span>
+                    </form>
+                    ''',
+            menu = [['user', load_lang('return')]]
+        ))

+ 187 - 0
route/login_oauth.py

@@ -0,0 +1,187 @@
+from .tool.func import *
+
+def login_oauth_2(conn, platform, func):
+    curs = conn.cursor()
+
+    publish_url = load_oauth('publish_url')
+    oauth_data = load_oauth(platform)
+    api_url = {}
+    data = {
+        'client_id' : oauth_data['client_id'],
+        'client_secret' : oauth_data['client_secret'],
+        'redirect_uri' : publish_url + '/oauth/' + platform + '/callback',
+        'state' : 'RAMDOMVALUE'
+    }
+
+    if platform == 'discord':
+        api_url['redirect'] = 'https://discordapp.com/api/oauth2/authorize'
+        api_url['token'] = 'https://discordapp.com/api/oauth2/token'
+        api_url['profile'] = 'https://discordapp.com/api/users/@me'
+    elif platform == 'naver':
+        api_url['redirect'] = 'https://nid.naver.com/oauth2.0/authorize'
+        api_url['token'] = 'https://nid.naver.com/oauth2.0/token'
+        api_url['profile'] = 'https://openapi.naver.com/v1/nid/me'
+    elif platform == 'facebook':
+        api_url['redirect'] = 'https://www.facebook.com/v3.1/dialog/oauth'
+        api_url['token'] = 'https://graph.facebook.com/v3.1/oauth/access_token'
+        api_url['profile'] = 'https://graph.facebook.com/me'
+
+    if func == 'init':
+        if oauth_data['client_id'] == '' or oauth_data['client_secret'] == '':
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = [load_lang('error'), wiki_set(), custom(), other2([0, 0])], 
+                data = load_lang('oauth_disabled'), 
+                menu = [['user', load_lang('return')]]
+            ))
+        elif publish_url == 'https://':
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = [load_lang('error'), wiki_set(), custom(), other2([0, 0])], 
+                data = load_lang('oauth_setting_not_found'), 
+                menu = [['user', load_lang('return')]]
+            ))
+
+        referrer_re = re.compile(r'(?P<host>^(https?):\/\/([^\/]+))\/(?P<refer>[^\/?]+)')
+        if flask.request.referrer != None:
+            referrer = referrer_re.search(flask.request.referrer)
+            if referrer.group('host') != load_oauth('publish_url'):
+                return redirect()
+            else:
+                flask.session['referrer'] = referrer.group('refer')
+        else:
+            return redirect()
+
+        flask.session['refer'] = flask.request.referrer
+
+        if platform == 'discord':
+            return redirect(api_url['redirect'] + '?client_id={}&redirect_uri={}&response_type=code&scope=identify'.format(
+                data['client_id'], 
+                data['redirect_uri']
+            ))
+        elif platform == 'naver':
+            return redirect(api_url['redirect'] + '?response_type=code&client_id={}&redirect_uri={}&state={}'.format(
+                data['client_id'], 
+                data['redirect_uri'], 
+                data['state']
+            ))
+        elif platform == 'facebook':
+            return redirect(api_url['redirect'] + '?client_id={}&redirect_uri={}&state={}'.format(
+                data['client_id'], 
+                data['redirect_uri'], 
+                data['state']
+            ))
+
+    elif func == 'callback':
+        code = flask.request.args.get('code')
+        state = flask.request.args.get('state')
+
+        if code == None:
+            return easy_minify(flask.render_template(skin_check(),
+                imp = [load_lang('inter_error'), wiki_set(), custom(), other2([0, 0])],
+                data = '<h2>ie_wrong_callback</h2>' + load_lang('ie_wrong_callback'),
+                menu = [['user', load_lang('return')]]
+            ))
+
+        if platform == 'discord':
+            data = {
+                'client_id'     : data['client_id'],
+                'client_secret' : data['client_secret'],
+                'grant_type'    : 'authorization_code',
+                'redirect_uri'  : data['redirect_uri'],
+                'scope'         : 'identify',
+                'code'          : code
+            }
+            headers = {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'User-Agent': 'Mozilla/5.0'
+            }
+            token_exchange = urllib.request.Request(
+                'https://discordapp.com/api/oauth2/token',
+                data = bytes(urllib.parse.urlencode(data).encode()),
+                headers = headers
+            )
+            token_result = urllib.request.urlopen(token_exchange).read()
+            token_json = json.loads(token_result)
+
+            headers = {
+                'User-Agent'    : 'Mozilla/5.0',
+                'Authorization' : 'Bearer ' + token_json['access_token']
+            }
+            profile_exchange = urllib.request.Request(
+                'https://discordapp.com/api/users/@me',
+                headers = headers
+            )
+            profile_result =  urllib.request.urlopen(profile_exchange).read().decode('utf-8')
+            profile_result_json = json.loads(profile_result)
+            stand_json = {
+                'id'        : profile_result_json['id'], 
+                'name'      : profile_result_json['username'] + '#' + profile_result_json['discriminator'],
+                'picture'   : profile_result_json['avatar']
+            }
+        elif platform == 'naver':
+            token_access = api_url['token'] + '?grant_type=authorization_code&client_id={}&client_secret={}&code={}&state={}'.format(
+                data['client_id'], 
+                data['client_secret'], 
+                code, 
+                state
+            )
+            token_result = urllib.request.urlopen(token_access).read().decode('utf-8')
+            token_result_json = json.loads(token_result)
+
+            headers = {
+                'Authorization': 'Bearer {}'.format(token_result_json['access_token'])
+            }
+
+            profile_access = urllib.request.Request(api_url['profile'], headers = headers)
+            profile_result = urllib.request.urlopen(profile_access).read().decode('utf-8')
+            profile_result_json = json.loads(profile_result)
+
+            stand_json = {
+                'id'        : profile_result_json['response']['id'],
+                'name'      : profile_result_json['response']['name'],
+                'picture'   : profile_result_json['response']['profile_image']
+            }
+        elif platform == 'facebook':
+            token_access = api_url['token'] + '?client_id={}&redirect_uri={}&client_secret={}&code={}'.format(
+                data['client_id'], 
+                data['redirect_uri'], 
+                data['client_secret'], 
+                code
+            )
+            token_result = urllib.request.urlopen(token_access).read().decode('utf-8')
+            token_result_json = json.loads(token_result)
+
+            profile_access = api_url['profile'] + '?fields=id,name,picture&access_token={}'.format(token_result_json['access_token'])
+            profile_result = urllib.request.urlopen(profile_access).read().decode('utf-8')
+            profile_result_json = json.loads(profile_result)
+
+            stand_json = {
+                'id': profile_result_json['id'], 
+                'name': profile_result_json['name'], 
+                'picture': profile_result_json['picture']['data']['url']
+            }
+        
+        if flask.session['referrer'][0:6] == 'change':
+            curs.execute('select * from oauth_conn where wiki_id = ? and provider = ?', [flask.session['id'], platform])
+            oauth_result = curs.fetchall()
+            if len(oauth_result) == 0:
+                curs.execute('insert into oauth_conn (provider, wiki_id, sns_id, name, picture) values(?, ?, ?, ?, ?)', [
+                    platform, 
+                    flask.session['id'], 
+                    stand_json['id'], 
+                    stand_json['name'], 
+                    stand_json['picture']
+                ])
+            else:
+                curs.execute('update oauth_conn set name = ? picture = ? where wiki_id = ?', [stand_json['name'], stand_json['pricture'], flask.session['id']])
+
+            conn.commit()
+        elif flask.session['referrer'][0:5] == 'login':
+            curs.execute('select * from oauth_conn where provider = ? and sns_id = ?', [platform, stand_json['id']])
+            curs_result = curs.fetchall()
+            if len(curs_result) == 0:
+                return re_error('/error/2')
+            else:
+                flask.session['state'] = 1
+                flask.session['id'] = curs_result[0][2]
+        
+        return redirect(flask.session['refer'])

+ 83 - 0
route/manager.py

@@ -0,0 +1,83 @@
+from .tool.func import *
+
+def manager_2(conn, num):
+    curs = conn.cursor()
+    
+    title_list = {
+        0 : [load_lang('document_name'), 'acl'], 
+        1 : [0, 'check'], 
+        2 : [0, 'ban'], 
+        3 : [0, 'admin'], 
+        4 : [0, 'record'], 
+        5 : [0, 'topic_record'], 
+        6 : [load_lang('name'), 'admin_plus'], 
+        7 : [load_lang('name'), 'plus_edit_filter'], 
+        8 : [load_lang('document_name'), 'search'], 
+        9 : [0, 'block_user'], 
+        10 : [0, 'block_admin'], 
+        11 : [load_lang('document_name'), 'watch_list'], 
+        12 : [load_lang('compare_target'), 'check'], 
+        13 : [load_lang('document_name'), 'edit']
+    }
+    
+    if num == 1:
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('admin_tool'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <h2>''' + load_lang('admin') + '''</h2>
+                    <ul>
+                        <li><a href="/manager/2">''' + load_lang('acl_document_list') + '''</a></li>
+                        <li><a href="/manager/3">''' + load_lang('check_user') + '''</a></li>
+                        <li><a href="/manager/4">''' + load_lang('ban') + '''</a></li>
+                        <li><a href="/manager/5">''' + load_lang('authorize') + '''</a></li>
+                        <li><a href="/edit_filter">''' + load_lang('edit_filter_list') + '''</a></li>
+                    </ul>
+                    <br>
+                    <h2>''' + load_lang('owner') + '''</h2>
+                    <ul>
+                        <li><a href="/manager/8">''' + load_lang('admin_group_add') + '''</a></li>
+                        <li><a href="/setting">''' + load_lang('setting') + '''</a></li>
+                    </ul>
+                    <h3>''' + load_lang('filter') + '''</h3>
+                    <ul>
+                        <li><a href="/inter_wiki">''' + load_lang('interwiki_list') + '''</a></li>
+                        <li><a href="/email_filter">''' + load_lang('email_filter_list') + '''</a></li>
+                        <li><a href="/name_filter">''' + load_lang('id_filter_list') + '''</a></li>
+                    </ul>
+                    <br>
+                    <h2>''' + load_lang('server') + '''</h2>
+                    <ul>
+                        <li><a href="/indexing">''' + load_lang('indexing') + '''</a></li>
+                        <li><a href="/restart">''' + load_lang('wiki_restart') + '''</a></li>
+                        <li><a href="/update">''' + load_lang('update') + '''</a></li>
+                        <li><a href="/oauth_setting">''' + load_lang('oauth_setting') + '''</a></li>
+                        <li><a href="/adsense_setting">''' + load_lang('adsense_setting') + '''</a></li>
+                    </ul>
+                    ''',
+            menu = [['other', load_lang('return')]]
+        ))
+    elif not num - 1 > len(title_list):
+        if flask.request.method == 'POST':
+            if flask.request.args.get('plus', None):
+                return redirect('/' + title_list[(num - 2)][1] + '/' + url_pas(flask.request.args.get('plus', None)) + '?plus=' + flask.request.form.get('name', None))
+            else:
+                return redirect('/' + title_list[(num - 2)][1] + '/' + url_pas(flask.request.form.get('name', None)))
+        else:
+            if title_list[(num - 2)][0] == 0:
+                placeholder = load_lang('user_name')
+            else:
+                placeholder = title_list[(num - 2)][0]
+
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = ['Redirect', wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            <input placeholder="''' + placeholder + '''" name="name" type="text">
+                            <hr class=\"main_hr\">
+                            <button type="submit">''' + load_lang('go') + '''</button>
+                        </form>
+                        ''',
+                menu = [['manager', load_lang('return')]]
+            ))
+    else:
+        return redirect()

+ 103 - 0
route/move.py

@@ -0,0 +1,103 @@
+from .tool.func import *
+
+def move_2(conn, name):
+    curs = conn.cursor()
+
+    if acl_check(name) == 1:
+        return re_error('/ban')
+
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        curs.execute("select title from history where title = ?", [flask.request.form.get('title', None)])
+        if curs.fetchall():
+            if admin_check(None, 'merge documents') == 1:
+                curs.execute("select data from data where title = ?", [flask.request.form.get('title', None)])
+                data = curs.fetchall()
+                if data:            
+                    curs.execute("delete from data where title = ?", [flask.request.form.get('title', None)])
+                    curs.execute("delete from back where link = ?", [flask.request.form.get('title', None)])
+                
+                curs.execute("select data from data where title = ?", [name])
+                data = curs.fetchall()
+                if data:            
+                    curs.execute("update data set title = ? where title = ?", [flask.request.form.get('title', None), name])
+                    curs.execute("update back set link = ? where link = ?", [flask.request.form.get('title', None), name])
+                    
+                    data_in = data[0][0]
+                else:
+                    data_in = ''
+
+                history_plus(
+                    name, 
+                    data_in, 
+                    get_time(), 
+                    ip_check(), 
+                    flask.request.form.get('send', None) + ' (marge <a>' + name + '</a> - <a>' + flask.request.form.get('title', None) + '</a> move)', 
+                    '0'
+                )
+
+                curs.execute("update back set type = 'no' where title = ? and not type = 'cat' and not type = 'no'", [name])
+                curs.execute("delete from back where title = ? and not type = 'cat' and type = 'no'", [flask.request.form.get('title', None)])
+
+                curs.execute("select id from history where title = ? order by id + 0 desc limit 1", [flask.request.form.get('title', None)])
+                data = curs.fetchall()
+                
+                num = data[0][0]
+
+                curs.execute("select id from history where title = ? order by id + 0 asc", [name])
+                data = curs.fetchall()
+                for move in data:
+                    curs.execute("update history set title = ?, id = ? where title = ? and id = ?", [flask.request.form.get('title', None), str(int(num) + int(move[0])), name, move[0]])
+
+                conn.commit()
+
+                return redirect('/w/' + url_pas(flask.request.form.get('title', None)))
+            else:
+                return re_error('/error/19')
+        else:
+            curs.execute("select data from data where title = ?", [name])
+            data = curs.fetchall()
+            if data:            
+                curs.execute("update data set title = ? where title = ?", [flask.request.form.get('title', None), name])
+                curs.execute("update back set link = ? where link = ?", [flask.request.form.get('title', None), name])
+                
+                data_in = data[0][0]
+            else:
+                data_in = ''
+                
+            history_plus(
+                name, 
+                data_in, 
+                get_time(), 
+                ip_check(), 
+                flask.request.form.get('send', None) + ' (<a>' + name + '</a> - <a>' + flask.request.form.get('title', None) + '</a> move)', 
+                '0'
+            )
+            
+            curs.execute("update back set type = 'no' where title = ? and not type = 'cat' and not type = 'no'", [name])
+            curs.execute("delete from back where title = ? and not type = 'cat' and type = 'no'", [flask.request.form.get('title', None)])
+
+            curs.execute("update history set title = ? where title = ?", [flask.request.form.get('title', None), name])
+            conn.commit()
+
+            return redirect('/w/' + url_pas(flask.request.form.get('title', None)))
+    else:            
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('move') + ')', 0])],
+            data =  '''
+                    <form method="post">
+                        ''' + ip_warring() + '''
+                        <input placeholder="''' + load_lang('document_name') + '" value="' + name + '''" name="title" type="text">
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('move') + '''</button>
+                    </form>
+                    ''',
+            menu = [['w/' + url_pas(name), load_lang('return')]]
+        ))

+ 67 - 0
route/need_email.py

@@ -0,0 +1,67 @@
+from .tool.func import *
+
+def need_email_2(conn, tool):
+    curs = conn.cursor()
+
+    if flask.request.method == 'POST':
+        if tool == 'need_email':
+            if 'c_id' in flask.session:
+                main_email = ['naver.com', 'gmail.com', 'daum.net', 'hanmail.net', 'hanmail2.net']
+                data = re.search('@([^@]+)$', flask.request.form.get('email', ''))
+                if data:
+                    data = data.groups()[0]
+
+                    curs.execute("select html from html_filter where html = ? and kind = 'email'", [data])
+                    if curs.fetchall() or (data in main_email):
+                        curs.execute('select id from user_set where name = "email" and data = ?', [flask.request.form.get('email', '')])
+                        if curs.fetchall():
+                            flask.session.pop('c_id', None)
+                            flask.session.pop('c_pw', None)
+                            flask.session.pop('c_key', None)
+
+                            return redirect('/register')
+                        else:
+                            send_email(flask.request.form.get('email', ''), wiki_set()[0] + ' key', 'key : ' + flask.session['c_key'])
+                            flask.session['c_email'] = flask.request.form.get('email', '')
+
+                            return redirect('/check_key')
+
+            return redirect('/register')
+        else:
+            curs.execute("select id from user where id = ? and email = ?", [flask.request.form.get('id', ''), flask.request.form.get('email', '')])
+            if curs.fetchall():
+                flask.session['c_key'] = ''.join(random.choice("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") for i in range(16))
+                flask.session['c_id'] = flask.request.form.get('id', '')
+
+                send_email(flask.request.form.get('email', ''), wiki_set()[0] + ' ' + load_lang('password_search') + ' key', 'key : ' + flask.session['c_key'])
+
+                return redirect('/check_pass_key')
+    else:
+        if tool == 'need_email':
+            return easy_minify(flask.render_template(skin_check(),    
+                imp = [load_lang('email'), wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <a href="/email_filter">(''' + load_lang('email_filter_list') + ''')</a>
+                        <hr class=\"main_hr\">
+                        <form method="post">
+                            <input placeholder="''' + load_lang('email') + '''" name="email" type="text">
+                            <hr class=\"main_hr\">
+                            <button type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['user', load_lang('return')]]
+            ))
+        else:
+            return easy_minify(flask.render_template(skin_check(),    
+                imp = [load_lang('password_search'), wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            <input placeholder="''' + load_lang('id') + '''" name="id" type="text">
+                            <hr class=\"main_hr\">
+                            <input placeholder="email" name="email" type="text">
+                            <hr class=\"main_hr\">
+                            <button type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['user', load_lang('return')]]
+            ))

+ 19 - 0
route/not_close_topic.py

@@ -0,0 +1,19 @@
+from .tool.func import *
+
+def not_close_topic_2(conn):
+    curs = conn.cursor()
+
+    div = '<ul>'
+    
+    curs.execute('select title, sub from rd where stop != "O" order by date desc')
+    n_list = curs.fetchall()
+    for data in n_list:
+        div += '<li><a href="/topic/' + url_pas(data[0]) + '/sub/' + url_pas(data[1]) + '">' + html.escape(data[0]) + ' (' + data[1] + ')</a></li>'
+            
+    div += '</ul>'
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('open_discussion_list'), wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['manager', load_lang('return')]]
+    ))

+ 57 - 0
route/now_update.py

@@ -0,0 +1,57 @@
+from .tool.func import *
+
+def now_update_2(conn):
+    curs = conn.cursor()
+
+    if admin_check() != 1:
+        return re_error('/error/3')
+
+    if flask.request.method == 'POST':
+        admin_check(None, 'update')
+
+        curs.execute('select data from other where name = "update"')
+        up_data = curs.fetchall()
+        if up_data:
+            up_data = up_data[0][0]
+        else:
+            up_data = 'stable'
+
+        if platform.system() == 'Linux':
+            print('Update')
+
+            os.system('git remote rm origin')
+            os.system('git remote add origin https://github.com/2DU/opennamu.git')
+            ok = os.system('git fetch origin ' + up_data)
+            ok = os.system('git reset --hard origin/' + up_data)
+            if ok == 0:
+                return redirect('/restart')
+        else:
+            if platform.system() == 'Windows':
+                print('Update')
+
+                urllib.request.urlretrieve('https://github.com/2DU/opennamu/archive/' + up_data + '.zip', 'update.zip')
+                zipfile.ZipFile('update.zip').extractall('')
+                ok = os.system('xcopy /y /r opennamu-' + up_data + ' .')
+                if ok == 0:
+                    print('Remove')
+                    os.system('rd /s /q opennamu-' + up_data)
+                    os.system('del update.zip')
+
+                    return redirect('/restart')
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('update'), wiki_set(), custom(), other2([0, 0])],
+            data = load_lang("update_error") + ' <a href="https://github.com/2DU/opennamu">(Github)</a>',
+            menu = [['manager/1', load_lang('return')]]
+        ))
+    else:
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('update'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <button type="submit">''' + load_lang('update') + '''</button>
+                    </form>
+                    ''',
+            menu = [['manager', load_lang('return')]]
+        ))
+

+ 100 - 0
route/oauth_setting.py

@@ -0,0 +1,100 @@
+from .tool.func import *
+
+def oauth_setting_2(conn):
+    curs = conn.cursor()
+
+    if admin_check(None, 'oauth setting') != 1:
+        return re_error('/error/3')
+
+    if flask.request.method == 'POST':
+        try:
+            facebook_client_id = flask.request.form['facebook_client_id']
+            facebook_client_secret = flask.request.form['facebook_client_secret']
+            
+            naver_client_id = flask.request.form['naver_client_id']
+            naver_client_secret = flask.request.form['naver_client_secret']
+        except:
+            return easy_minify(flask.render_template(skin_check(),
+                imp = [load_lang('inter_error'), wiki_set(), custom(), other2([0, 0])],
+                data = '<h2>ie_no_data_required</h2>' + load_lang('ie_no_data_required'),
+                menu = [['other', load_lang('return')]]
+            ))
+
+        with open(app_var['path_oauth_setting'], 'r', encoding='utf-8') as f:
+            legacy = json.loads(f.read())
+
+        with open(app_var['path_oauth_setting'], 'w', encoding='utf-8') as f:
+            f.write('''
+                {
+                    "_README" : {
+                        "en" : "''' + legacy['_README']['en'] + '''",
+                        "ko" : "''' + legacy['_README']['ko'] + '''",
+                        "support" : ''' + str(legacy['_README']['support']).replace("'", '"') + '''
+                    },
+                    "publish_url" : "''' + legacy['publish_url'] + '''",
+                    "facebook" : {
+                        "client_id" : "''' + facebook_client_id + '''",
+                        "client_secret" : "''' + facebook_client_secret + '''"
+                    },
+                    "naver" : {
+                        "client_id" : "''' + naver_client_id + '''",
+                        "client_secret" : "''' + naver_client_secret + '''"
+                    }
+                }
+            ''')
+        
+        return flask.redirect('/oauth_setting')
+
+    oauth_supported = load_oauth('_README')['support']
+
+    body_content = ''
+    body_content += '''
+        <script>
+            function check_value (target) {
+                target_box = document.getElementById(target.id + "_box");
+                if (target.value !== "") {
+                    target_box.checked = true;
+                } else {
+                    target_box.checked = false;
+                } 
+            }
+        </script>
+    '''
+
+    init_js = ''
+    body_content += '<form method="post">'
+
+    for i in range(len(oauth_supported)):
+        oauth_data = load_oauth(oauth_supported[i])
+        for j in range(2):
+            if j == 0:
+                load_target = 'id'
+            elif j == 1:
+                load_target = 'secret'
+
+            init_js += 'check_value(document.getElementById("{}_client_{}"));'.format(oauth_supported[i], load_target)
+
+            body_content += '''
+                <input id="{}_client_{}_box" type="checkbox" disabled>
+                <input placeholder="{}_client_{}" id="{}_client_{}" name="{}_client_{}" value="{}" type="text" onChange="check_value(this)" style="width: 80%;">
+                <hr>
+            '''.format(
+                oauth_supported[i],
+                load_target,
+                oauth_supported[i], 
+                load_target, 
+                oauth_supported[i], 
+                load_target, 
+                oauth_supported[i], 
+                load_target, 
+                oauth_data['client_{}'.format(load_target)]
+            )
+    
+    body_content += '<button id="save" type="submit">' + load_lang('save') + '</button></form>'
+    body_content += '<script>' + init_js + '</script>'
+    
+    return easy_minify(flask.render_template(skin_check(),
+        imp = [load_lang('oauth_setting'), wiki_set(), custom(), other2([0, 0])],
+        data = body_content,
+        menu = [['other', load_lang('return')]]
+    ))

+ 45 - 0
route/other.py

@@ -0,0 +1,45 @@
+from .tool.func import *
+
+def other_2(conn, r_ver):
+    curs = conn.cursor()
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('other_tool'), wiki_set(), custom(), other2([0, 0])],
+        data =  '''
+                <h2>''' + load_lang('record') + '''</h2>
+                <ul>
+                    <li><a href="/manager/6">''' + load_lang('edit_record') + '''</a></li>
+                    <li><a href="/manager/7">''' + load_lang('discussion_record') + '''</a></li>
+                </ul>
+                <br>
+                <h2>''' + load_lang('list') + '''</h2>
+                <ul>
+                    <li><a href="/admin_list">''' + load_lang('admin_list') + '''</a></li>
+                    <li><a href="/give_log">''' + load_lang('admin_group_list') + '''</a></li>
+                    <li><a href="/not_close_topic">''' + load_lang('open_discussion_list') + '''</a></li>
+                    <li><a href="/title_index">''' + load_lang('all_document_list') + '''</a></li>
+                    <li><a href="/acl_list">''' + load_lang('acl_document_list') + '''</a></li>
+                    <li><a href="/please">''' + load_lang('need_document') + '''</a></li>
+                    <li><a href="/block_log">''' + load_lang('recent_ban') + '''</a></li>
+                    <li><a href="/user_log">''' + load_lang('member_list') + '''</a></li>
+                    <li><a href="/admin_log">''' + load_lang('authority_use_list') + '''</a></li>
+                </ul>
+                <br>
+                <h2>''' + load_lang('other') + '''</h2>
+                <ul>
+                    <li><a href="/upload">''' + load_lang('upload') + '''</a></li>
+                    <li><a href="/manager/10">''' + load_lang('search') + '''</a></li>
+                </ul>
+                <br>
+                <h2>''' + load_lang('admin') + '''</h2>
+                <ul>
+                    <li><a href="/manager/1">''' + load_lang('admin_tool') + '''</a></li>
+                </ul>
+                <br>
+                <h2>''' + load_lang('version') + '''</h2>
+                <ul>
+                    <li>''' + load_lang('version') + ' : <a id="out_link" href="https://github.com/2DU/opennamu/blob/master/version.md">' + r_ver + '''</a></li>
+                </ul>
+                ''',
+        menu = 0
+    ))

+ 29 - 0
route/please.py

@@ -0,0 +1,29 @@
+from .tool.func import *
+
+def please_2(conn):
+    curs = conn.cursor()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+        
+    div = '<ul>'
+    var = ''
+    
+    curs.execute("select distinct title from back where type = 'no' order by title asc limit ?, '50'", [str(sql_num)])
+    data_list = curs.fetchall()
+    for data in data_list:
+        if var != data[0]:
+            div += '<li><a id="not_thing" href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a></li>'   
+
+            var = data[0]
+        
+    div += '</ul>' + next_fix('/please?num=', num, data_list)
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('need_document'), wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['other', load_lang('return')]]
+    ))

+ 92 - 0
route/plus_inter.py

@@ -0,0 +1,92 @@
+from .tool.func import *
+
+def plus_inter_2(conn, tools, name):
+    curs = conn.cursor()
+    
+    if flask.request.method == 'POST':
+        if tools == 'plus_inter_wiki':
+            curs.execute('insert into inter (title, link) values (?, ?)', [flask.request.form.get('title', None), flask.request.form.get('link', None)])
+            admin_check(None, 'inter_wiki_plus')
+        elif tools == 'plus_edit_filter':
+            if admin_check(1, 'edit_filter edit') != 1:
+                return re_error('/error/3')
+
+            if flask.request.form.get('limitless', '') != '':
+                end = 'X'
+            else:
+                end = flask.request.form.get('second', 'X')
+
+            curs.execute("select name from filter where name = ?", [name])
+            if curs.fetchall():
+                curs.execute("update filter set regex = ?, sub = ? where name = ?", [flask.request.form.get('content', 'test'), end, name])
+            else:
+                curs.execute("insert into filter (name, regex, sub) values (?, ?, ?)", [name, flask.request.form.get('content', 'test'), end])
+        else:
+            if tools == 'plus_name_filter':
+                admin_check(None, 'name_filter edit')
+                type_d = 'name'
+            else:
+                admin_check(None, 'email_filter edit')
+                type_d = 'email'
+            
+            curs.execute('insert into html_filter (html, kind) values (?, ?)', [flask.request.form.get('title', 'test'), type_d])
+        
+        conn.commit()
+    
+        return redirect('/' + re.sub('^plus_', '', tools))
+    else:
+        if admin_check(1) != 1:
+            stat = 'disabled'
+        else:
+            stat = ''
+
+        if tools == 'plus_inter_wiki':
+            title = load_lang('interwiki_add')
+            form_data = '''
+                        <input placeholder="''' + load_lang('name') + '''" type="text" name="title">
+                        <hr class=\"main_hr\">
+                        <input placeholder="link" type="text" name="link">
+                        '''
+        elif tools == 'plus_edit_filter':
+            curs.execute("select regex, sub from filter where name = ?", [name])
+            exist = curs.fetchall()
+            if exist:
+                textarea = exist[0][0]
+                
+                if exist[0][1] == 'X':
+                    time_check = 'checked="checked"'
+                    time_data = ''
+                else:
+                    time_check = ''
+                    time_data = exist[0][1]
+            else:
+                textarea = ''
+                time_check = ''
+                time_data = ''
+
+            title = load_lang('edit_filter_add')
+            form_data = '''
+                        <input placeholder="''' + load_lang('second') + '''" name="second" type="text" value="''' + html.escape(time_data) + '''">
+                        <hr class=\"main_hr\">
+                        <input ''' + stat + ''' type="checkbox" ''' + time_check + ''' name="limitless"> ''' + load_lang('limitless') + '''
+                        <hr class=\"main_hr\">
+                        <input ''' + stat + ''' placeholder="''' + load_lang('regex') + '''" name="content" value="''' + html.escape(textarea) + '''" type="text">
+                        '''
+        elif tools == 'plus_name_filter':
+            title = load_lang('id_filter_add')
+            form_data = '<input placeholder="' + load_lang('id') + ' ' + load_lang('regex') + '" type="text" name="title">'
+        else:
+            title = load_lang('email_filter_add')
+            form_data = '<input placeholder="email" type="text" name="title">'
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [title, wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        ''' + form_data + '''
+                        <hr class=\"main_hr\">
+                        <button ''' + stat + ''' type="submit">''' + load_lang('add') + '''</button>
+                    </form>
+                    ''',
+            menu = [[re.sub('^plus_', '', tools), load_lang('return')]]
+        ))

+ 41 - 0
route/preview.py

@@ -0,0 +1,41 @@
+from .tool.func import *
+
+def preview_2(conn, name):
+    curs = conn.cursor()
+
+    if acl_check(name) == 1:
+        return re_error('/ban')
+         
+    new_data = re.sub('^\r\n', '', flask.request.form.get('content', None))
+    new_data = re.sub('\r\n$', '', new_data)
+    
+    end_data = render_set(
+        title = name,
+        data = new_data
+    )
+    
+    if flask.request.args.get('section', None):
+        action = '?section=' + flask.request.args.get('section', None)
+    else:
+        action = ''
+
+    js_data = edit_help_button()
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [name, wiki_set(), custom(), other2([' (' + load_lang('preview') + ')', 0])],
+        data =  '<a href="/edit_filter">(' + load_lang('edit_filter_rule') + ')</a>' + js_data[0] + '''
+                <form method="post" action="/edit/''' + url_pas(name) + action + '''">
+                    ''' + js_data[1] + '''
+                    <textarea id="content" rows="25" name="content">''' + html.escape(flask.request.form.get('content', None)) + '''</textarea>
+                    <textarea style="display: none;" name="otent">''' + html.escape(flask.request.form.get('otent', None)) + '''</textarea>
+                    <hr class=\"main_hr\">
+                    <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
+                    <hr class=\"main_hr\">
+                    ''' + captcha_get() + '''
+                    <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                    <button id="preview" type="submit" formaction="/preview/''' + url_pas(name) + action + '">' + load_lang('preview') + '''</button>
+                </form>
+                <hr class=\"main_hr\">
+                ''' + end_data,
+        menu = [['w/' + url_pas(name), load_lang('return')]]
+    ))

+ 47 - 0
route/raw_view.py

@@ -0,0 +1,47 @@
+from .tool.func import *
+
+def raw_view_2(conn, name, sub_title, num):
+    curs = conn.cursor()
+
+    v_name = name
+    sub = ' (' + load_lang('raw') + ')'
+    
+    if not num:
+        num = flask.request.args.get('num', None)
+        if num:
+            num = int(number_check(num))
+    
+    if not sub_title and num:
+        curs.execute("select title from history where title = ? and id = ? and hide = 'O'", [name, str(num)])
+        if curs.fetchall() and admin_check(6) != 1:
+            return re_error('/error/3')
+        
+        curs.execute("select data from history where title = ? and id = ?", [name, str(num)])
+        
+        sub += ' (r' + str(num) + ')'
+
+        menu = [['history/' + url_pas(name), load_lang('history')]]
+    elif sub_title:
+        curs.execute("select data from topic where id = ? and title = ? and sub = ? and block = ''", [str(num), name, sub_title])
+        
+        v_name = load_lang('discussion_raw')
+        sub = ' (' + str(num) + ')'
+
+        menu = [['topic/' + url_pas(name) + '/sub/' + url_pas(sub_title) + '#' + str(num), load_lang('discussion')], ['topic/' + url_pas(name) + '/sub/' + url_pas(sub_title) + '/admin/' + str(num), load_lang('return')]]
+    else:
+        curs.execute("select data from data where title = ?", [name])
+        
+        menu = [['w/' + url_pas(name), load_lang('return')]]
+
+    data = curs.fetchall()
+    if data:
+        p_data = html.escape(data[0][0])
+        p_data = '<textarea readonly rows="25">' + p_data + '</textarea>'
+        
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [v_name, wiki_set(), custom(), other2([sub, 0])],
+            data = p_data,
+            menu = menu
+        ))
+    else:
+        return redirect('/w/' + url_pas(name))

+ 172 - 0
route/read_view.py

@@ -0,0 +1,172 @@
+from .tool.func import *
+
+def read_view_2(conn, name):
+    curs = conn.cursor()
+
+    data_none = 0
+    sub = ''
+    acl = ''
+    div = ''
+
+    num = flask.request.args.get('num', None)
+    if num:
+        num = int(number_check(num))
+    else:
+        if not flask.request.args.get('from', None):
+            curs.execute("select title from back where link = ? and type = 'redirect'", [name])
+            redirect_data = curs.fetchall()
+            if redirect_data:
+                return redirect('/w/' + redirect_data[0][0] + '?from=' + name)
+
+    curs.execute("select sub from rd where title = ? and not stop = 'O' order by date desc", [name])
+    if curs.fetchall():
+        sub += ' (' + load_lang('discussion') + ')'
+
+    curs.execute("select link from back where title = ? and type = 'cat' order by link asc", [name])
+                
+    curs.execute("select title from data where title like ?", ['%' + name + '/%'])
+    if curs.fetchall():
+        down = 1
+    else:
+        down = 0
+        
+    m = re.search("^(.*)\/(.*)$", name)
+    if m:
+        uppage = m.groups()[0]
+    else:
+        uppage = 0
+        
+    if re.search('^category:', name):        
+        curs.execute("select link from back where title = ? and type = 'cat' order by link asc", [name])
+        back = curs.fetchall()
+        if back:
+            div = '<br><h2 id="cate_normal">' + load_lang('category') + '</h2><ul>'
+            u_div = ''
+
+            for data in back:    
+                if re.search('^category:', data[0]):
+                    u_div += '<li><a href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a></li>'
+                else:
+                    curs.execute("select title from back where title = ? and type = 'include'", [data[0]])
+                    db_data = curs.fetchall()
+                    if db_data:
+                        div += '<li><a href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a> <a id="inside" href="/xref/' + url_pas(data[0]) + '">(' + load_lang('backlink') + ')</a></li>'
+                    else: 
+                        div += '<li><a href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a></li>'
+
+            div += '</ul>'
+            
+            if div == '<br><h2 id="cate_normal">' + load_lang('category') + '</h2><ul></ul>':
+                div = ''
+            
+            if u_div != '':
+                div += '<br><h2 id="cate_under">' + load_lang('under_category') + '</h2><ul>' + u_div + '</ul>'
+
+
+    if num:
+        curs.execute("select title from history where title = ? and id = ? and hide = 'O'", [name, str(num)])
+        if curs.fetchall() and admin_check(6) != 1:
+            return redirect('/history/' + url_pas(name))
+
+        curs.execute("select title, data from history where title = ? and id = ?", [name, str(num)])
+    else:
+        curs.execute("select title, data from data where title = ?", [name])
+    
+    data = curs.fetchall()
+    if data:
+        else_data = data[0][1]
+        response_data = 200
+    else:
+        data_none = 1
+        response_data = 404
+        else_data = None
+
+    m = re.search("^user:([^/]*)", name)
+    if m:
+        g = m.groups()
+        
+        curs.execute("select acl from user where id = ?", [g[0]])
+        test = curs.fetchall()
+        if test and test[0][0] != 'user':
+            acl = ' (' + load_lang('admin') + ')'
+        else:
+            if ban_check(g[0]) == 1:
+                sub += ' (' + load_lang('blocked') + ')'
+            else:
+                acl = ''
+
+    curs.execute("select dec from acl where title = ?", [name])
+    data = curs.fetchall()
+    if data:
+        acl += ' (' + load_lang('acl') + ')'
+            
+    if flask.request.args.get('from', None) and else_data:
+        else_data = re.sub('^\r\n', '', else_data)
+        else_data = re.sub('\r\n$', '', else_data)
+            
+    end_data = render_set(
+        title = name,
+        data = else_data
+    )
+
+    if end_data == 'HTTP Request 401.3':
+        response_data = 401
+    
+    if num:
+        menu = [['history/' + url_pas(name), load_lang('history')]]
+        sub = ' (r' + str(num) + ')'
+        acl = ''
+        r_date = 0
+    else:
+        if data_none == 1:
+            menu = [['edit/' + url_pas(name), load_lang('create')]]
+        else:
+            menu = [['edit/' + url_pas(name), load_lang('edit')]]
+
+        menu += [['topic/' + url_pas(name), load_lang('discussion')], ['history/' + url_pas(name), load_lang('history')], ['xref/' + url_pas(name), load_lang('backlink')], ['acl/' + url_pas(name), load_lang('acl')]]
+
+        if flask.request.args.get('from', None):
+            menu += [['w/' + url_pas(name), load_lang('pass')]]
+            end_data =  '''
+                        <div id="redirect">
+                            <a href="/w/''' + url_pas(flask.request.args.get('from', None)) + '?from=' + url_pas(name) + '">' + flask.request.args.get('from', None) + '</a> - ' + name + '''
+                        </div>
+                        <br>''' + end_data
+
+        if uppage != 0:
+            menu += [['w/' + url_pas(uppage), load_lang('upper')]]
+
+        if down:
+            menu += [['down/' + url_pas(name), load_lang('sub')]]
+    
+        curs.execute("select date from history where title = ? order by date desc limit 1", [name])
+        date = curs.fetchall()
+        if date:
+            r_date = date[0][0]
+        else:
+            r_date = 0
+
+    div = end_data + div
+            
+    adsense_code = '<div align="center" style="display: block; margin-bottom: 10px;">{}</div>'
+
+    curs.execute("select data from other where name = 'adsense'")
+    adsense_enabled = curs.fetchall()[0][0]
+    if adsense_enabled == 'True':
+        curs.execute("select data from other where name = 'adsense_code'")
+        adsense_code = adsense_code.format(curs.fetchall()[0][0])
+    else:
+        adsense_code = adsense_code.format('')
+
+    curs.execute("select data from other where name = 'body'")
+    body = curs.fetchall()
+    if body:
+        div = body[0][0] + '<hr class=\"main_hr\">' + div
+    
+    div = adsense_code + '<div>' + div + '</div>'
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [flask.request.args.get('show', name), wiki_set(), custom(), other2([sub + acl, r_date])],
+        data = div,
+        menu = menu
+    )), response_data

+ 178 - 0
route/recent_changes.py

@@ -0,0 +1,178 @@
+from .tool.func import *
+
+def recent_changes_2(conn, name, tool):
+    curs = conn.cursor()
+
+    if flask.request.method == 'POST':
+        return redirect('/diff/' + url_pas(name) + '?first=' + flask.request.form.get('b', None) + '&second=' + flask.request.form.get('a', None))
+    else:
+        one_admin = admin_check(1)
+        six_admin = admin_check(6)
+        
+        ban = ''
+        select = ''
+
+        div =   '''
+                <table id="main_table_set">
+                    <tbody>
+                        <tr>
+                '''
+        
+        if name:
+            num = int(number_check(flask.request.args.get('num', '1')))
+            if num * 50 > 0:
+                sql_num = num * 50 - 50
+            else:
+                sql_num = 0      
+
+            if tool == 'history':
+                div +=  '''
+                        <td id="main_table_width">''' + load_lang('version') + '''</td>
+                        <td id="main_table_width">''' + load_lang('editor') + '''</td>
+                        <td id="main_table_width">''' + load_lang('time') + '''</td></tr>
+                        '''
+                
+                curs.execute("select id, title, date, ip, send, leng from history where title = ? order by id + 0 desc limit ?, '50'", [name, str(sql_num)])
+            else:
+                div +=  '''
+                            <td id="main_table_width">''' + load_lang('document_name') + '''</td>
+                            <td id="main_table_width">''' + load_lang('editor') + '''</td>
+                            <td id="main_table_width">''' + load_lang('time') + '''</td>
+                        </tr>
+                        '''
+
+                div = '<a href="/topic_record/' + url_pas(name) + '">(' + load_lang('discussion') + ')</a><hr class=\"main_hr\">' + div
+                
+                curs.execute("select id, title, date, ip, send, leng from history where ip = ? order by date desc limit ?, '50'", [name, str(sql_num)])
+        else:
+            num = int(number_check(flask.request.args.get('num', '1')))
+            if num * 50 > 0:
+                sql_num = num * 50 - 50
+            else:
+                sql_num = 0            
+            
+            div +=  '''
+                        <td id="main_table_width">''' + load_lang('document_name') + '''</td>
+                        <td id="main_table_width">''' + load_lang('editor') + '''</td>
+                        <td id="main_table_width">''' + load_lang('time') + '''</td>
+                    </tr>
+                    '''
+            
+            curs.execute("select id, title, date, ip, send, leng from history where not title like 'user:%' order by date desc limit ?, 50", [str(sql_num)])
+
+        data_list = curs.fetchall()
+        for data in data_list:    
+            select += '<option value="' + data[0] + '">' + data[0] + '</option>'     
+            send = '<br>'
+            
+            if data[4]:
+                if not re.search("^(?: *)$", data[4]):
+                    send = data[4]
+            
+            if re.search("\+", data[5]):
+                leng = '<span style="color:green;">(' + data[5] + ')</span>'
+            elif re.search("\-", data[5]):
+                leng = '<span style="color:red;">(' + data[5] + ')</span>'
+            else:
+                leng = '<span style="color:gray;">(' + data[5] + ')</span>'
+                
+            ip = ip_pas(data[3])
+            if int(data[0]) - 1 == 0:
+                revert = ''
+            else:
+                revert = '<a href="/diff/' + url_pas(data[1]) + '?first=' + str(int(data[0]) - 1) + '&second=' + data[0] + '">(' + load_lang('compare') + ')</a> <a href="/revert/' + url_pas(data[1]) + '?num=' + str(int(data[0]) - 1) + '">(' + load_lang('revert') + ')</a>'
+            
+            style = ['', '']
+            date = data[2]
+
+            curs.execute("select title from history where title = ? and id = ? and hide = 'O'", [data[1], data[0]])
+            hide = curs.fetchall()
+            
+            if six_admin == 1:
+                if hide:                            
+                    hidden = ' <a href="/hidden/' + url_pas(data[1]) + '?num=' + data[0] + '">(' + load_lang('hide_release') + ')'
+                    
+                    style[0] = 'id="toron_color_grey"'
+                    style[1] = 'id="toron_color_grey"'
+                    
+                    if send == '<br>':
+                        send = '(' + load_lang('hide') + ')'
+                    else:
+                        send += ' (' + load_lang('hide') + ')'
+                else:
+                    hidden = ' <a href="/hidden/' + url_pas(data[1]) + '?num=' + data[0] + '">(' + load_lang('hide') + ')'
+            elif not hide:
+                hidden = ''
+            else:
+                ip = ''
+                hidden = ''
+                ban = ''
+                date = ''
+
+                send = '(' + load_lang('hide') + ')'
+
+                style[0] = 'style="display: none;"'
+                style[1] = 'id="toron_color_grey"'
+
+            if tool == 'history':
+                title = '<a href="/w/' + url_pas(name) + '?num=' + data[0] + '">r' + data[0] + '</a> <a href="/raw/' + url_pas(name) + '?num=' + data[0] + '">(' + load_lang('raw') + ')</a> '
+            else:
+                title = '<a href="/w/' + url_pas(data[1]) + '">' + html.escape(data[1]) + '</a> <a href="/history/' + url_pas(data[1]) + '">(r' + data[0] + ')</a> '
+                    
+            div +=  '''
+                    <tr ''' + style[0] + '''>
+                        <td>''' + title + revert + ' ' + leng + '''</td>
+                        <td>''' + ip + ban + hidden + '''</td>
+                        <td>''' + date + '''</td>
+                    </tr>
+                    <tr ''' + style[1] + '''>
+                        <td colspan="3">''' + send_parser(send) + '''</td>
+                    </tr>
+                    '''
+
+        div +=  '''
+                    </tbody>
+                </table>
+                '''
+        sub = ''
+
+        if name:
+            if tool == 'history':
+                div =   '''
+                        <form method="post">
+                            <select name="a">''' + select + '''</select> <select name="b">''' + select + '''</select>
+                            <button type="submit">''' + load_lang('compare') + '''</button>
+                        </form>
+                        <hr class=\"main_hr\">
+                        ''' + div
+                title = name
+                
+                sub += ' (' + load_lang('history') + ')'
+                
+                menu = [['w/' + url_pas(name), load_lang('document')], ['raw/' + url_pas(name), 'raw']]
+                
+                div += next_fix('/history/' + url_pas(name) + '?num=', num, data_list)
+            else:
+                curs.execute("select end from ban where block = ?", [name])
+                if curs.fetchall():
+                    sub += ' (' + load_lang('blocked') + ')'
+
+                title = load_lang('edit_record')
+                
+                menu = [['other', load_lang('other')], ['user', load_lang('user')], ['count/' + url_pas(name), load_lang('count')]]
+                
+                div += next_fix('/record/' + url_pas(name) + '?num=', num, data_list)
+        else:
+            menu = 0
+            title = load_lang('recent_change')
+                
+            div += next_fix('/recent_changes?num=', num, data_list)
+        
+        if sub == '':
+            sub = 0
+                
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [title, wiki_set(), custom(), other2([sub, 0])],
+            data = div,
+            menu = menu
+        ))

+ 44 - 0
route/recent_discuss.py

@@ -0,0 +1,44 @@
+from .tool.func import *
+
+def recent_discuss_2(conn):
+    curs = conn.cursor()
+
+    div = ''
+    
+    if flask.request.args.get('what', 'normal') == 'normal':
+        div += '<a href="/recent_discuss?what=close">(' + load_lang('close_discussion') + ')</a>'
+       
+        m_sub = 0
+    else:
+        div += '<a href="/recent_discuss">(' + load_lang('open_discussion') + ')</a>'
+        
+        m_sub = ' (' + load_lang('closed') + ')'
+
+    div +=  '''
+            <hr class=\"main_hr\">
+            <table id="main_table_set">
+                <tbody>
+                    <tr>
+                        <td id="main_table_width_half">''' + load_lang('discussion_name') + '''</td>
+                        <td id="main_table_width_half">''' + load_lang('time') + '''</td>
+                    </tr>
+            '''
+    
+    if m_sub == 0:
+        curs.execute("select title, sub, date from rd where not stop = 'O' order by date desc limit 50")
+    else:
+        curs.execute("select title, sub, date from rd where stop = 'O' order by date desc limit 50")
+        
+    for data in curs.fetchall():
+        title = html.escape(data[0])
+        sub = html.escape(data[1])
+
+        div += '<tr><td><a href="/topic/' + url_pas(data[0]) + '/sub/' + url_pas(data[1]) + '">' + title + '</a> (' + sub + ')</td><td>' + data[2] + '</td></tr>'
+    
+    div += '</tbody></table>'
+            
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('recent_discussion'), wiki_set(), custom(), other2([m_sub, 0])],
+        data = div,
+        menu = 0
+    ))

+ 109 - 0
route/register.py

@@ -0,0 +1,109 @@
+from .tool.func import *
+
+def register_2(conn):
+    curs = conn.cursor()
+
+    if ban_check() == 1:
+        return re_error('/ban')
+
+    if custom()[2] != 0:
+        return redirect('/user')
+
+    if not admin_check() == 1:
+        curs.execute('select data from other where name = "reg"')
+        set_d = curs.fetchall()
+        if set_d and set_d[0][0] == 'on':
+            return re_error('/ban')
+    
+    if flask.request.method == 'POST': 
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        if flask.request.form.get('pw', None) != flask.request.form.get('pw2', None):
+            return re_error('/error/20')
+
+        if re.search('(?:[^A-Za-zㄱ-힣0-9 ])', flask.request.form.get('id', None)):
+            return re_error('/error/8')
+            
+        curs.execute('select html from html_filter where kind = "name"')
+        set_d = curs.fetchall()
+        for i in set_d:
+            check_r = re.compile(i[0], re.I)
+            if check_r.search(flask.request.form.get('id', None)):
+                return re_error('/error/8')
+
+        if len(flask.request.form.get('id', None)) > 32:
+            return re_error('/error/7')
+
+        curs.execute("select id from user where id = ?", [flask.request.form.get('id', None)])
+        if curs.fetchall():
+            return re_error('/error/6')
+
+        hashed = pw_encode(flask.request.form.get('pw', None))
+        
+        curs.execute('select data from other where name = "email_have"')
+        sql_data = curs.fetchall()
+        if sql_data and sql_data[0][0] != '':
+            flask.session['c_id'] = flask.request.form.get('id', None)
+            flask.session['c_pw'] = hashed
+            flask.session['c_key'] = ''.join(random.choice("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") for i in range(16))
+
+            return redirect('/need_email')
+        else:
+            curs.execute('select data from other where name = "encode"')
+            db_data = curs.fetchall()
+
+            curs.execute("select id from user limit 1")
+            if not curs.fetchall():
+                curs.execute("insert into user (id, pw, acl, date, encode) values (?, ?, 'owner', ?, ?)", [flask.request.form.get('id', None), hashed, get_time(), db_data[0][0]])
+
+                first = 1
+            else:
+                curs.execute("insert into user (id, pw, acl, date, encode) values (?, ?, 'user', ?, ?)", [flask.request.form.get('id', None), hashed, get_time(), db_data[0][0]])
+
+                first = 0
+
+            ip = ip_check()
+            agent = flask.request.headers.get('User-Agent')
+
+            curs.execute("insert into ua_d (name, ip, ua, today, sub) values (?, ?, ?, ?, '')", [flask.request.form.get('id', None), ip, agent, get_time()])  
+
+            flask.session['state'] = 1
+            flask.session['id'] = flask.request.form.get('id', None)
+            flask.session['head'] = ''
+                  
+            conn.commit()
+            
+            if first == 0:
+                return redirect('/change')
+            else:
+                return redirect('/setting')
+    else:        
+        contract = ''
+        
+        curs.execute('select data from other where name = "contract"')
+        data = curs.fetchall()
+        if data and data[0][0] != '':
+            contract = data[0][0] + '<hr class=\"main_hr\">'
+
+        return easy_minify(flask.render_template(skin_check(),    
+            imp = [load_lang('register'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        ''' + contract + '''
+                        <input placeholder="''' + load_lang('id') + '''" name="id" type="text">
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('password') + '''" name="pw" type="password">
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('password_confirm') + '''" name="pw2" type="password">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('save') + '''</button>
+                        <hr class=\"main_hr\">
+                        <span>''' + load_lang('http_warring') + '''</span>
+                    </form>
+                    ''',
+            menu = [['user', load_lang('return')]]
+        ))

+ 24 - 0
route/restart.py

@@ -0,0 +1,24 @@
+from .tool.func import *
+
+def restart_2(conn):
+    curs = conn.cursor()
+
+    if admin_check() != 1:
+        return re_error('/error/3')
+
+    if flask.request.method == 'POST':
+        admin_check(None, 'restart')
+
+        print('Restart')
+
+        os.execl(sys.executable, sys.executable, *sys.argv)
+    else:
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('wiki_restart'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post">
+                        <button type="submit">''' + load_lang('restart') + '''</button>
+                    </form>
+                    ''',
+            menu = [['manager', load_lang('return')]]
+        ))

+ 77 - 0
route/revert.py

@@ -0,0 +1,77 @@
+from .tool.func import *
+
+def revert_2(conn, name):
+    curs = conn.cursor()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+
+    curs.execute("select title from history where title = ? and id = ? and hide = 'O'", [name, str(num)])
+    if curs.fetchall() and admin_check(6) != 1:
+        return re_error('/error/3')
+
+    if acl_check(name) == 1:
+        return re_error('/ban')
+
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+    
+        curs.execute("select data from history where title = ? and id = ?", [name, str(num)])
+        data = curs.fetchall()
+        if data:
+            if edit_filter_do(data[0][0]) == 1:
+                return re_error('/error/21')
+
+        curs.execute("delete from back where link = ?", [name])
+        conn.commit()
+        
+        if data:                                
+            curs.execute("select data from data where title = ?", [name])
+            data_old = curs.fetchall()
+            if data_old:
+                leng = leng_check(len(data_old[0][0]), len(data[0][0]))
+                curs.execute("update data set data = ? where title = ?", [data[0][0], name])
+            else:
+                leng = '+' + str(len(data[0][0]))
+                curs.execute("insert into data (title, data) values (?, ?)", [name, data[0][0]])
+                
+            history_plus(
+                name, 
+                data[0][0], 
+                get_time(), 
+                ip_check(), 
+                flask.request.form.get('send', None) + ' (r' + str(num) + ')', 
+                leng
+            )
+
+            render_set(
+                title = name,
+                data = data[0][0],
+                num = 1
+            )
+            
+            conn.commit()
+            
+        return redirect('/w/' + url_pas(name))
+    else:
+        curs.execute("select title from history where title = ? and id = ?", [name, str(num)])
+        if not curs.fetchall():
+            return redirect('/w/' + url_pas(name))
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('revert') + ')', 0])],
+            data =  '''
+                    <form method="post">
+                        <span>r''' + flask.request.args.get('num', '0') + '''</span>
+                        <hr class=\"main_hr\">
+                        ''' + ip_warring() + '''
+                        <input placeholder="''' + load_lang('why') + '''" name="send" type="text">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button type="submit">''' + load_lang('revert') + '''</button>
+                    </form>
+                    ''',
+            menu = [['history/' + url_pas(name), load_lang('history')], ['recent_changes', load_lang('recent_change')]]
+        ))

+ 403 - 0
route/setting.py

@@ -0,0 +1,403 @@
+from .tool.func import *
+
+def setting_2(conn, num):
+    curs = conn.cursor()
+
+    if num != 0 and admin_check() != 1:
+        return re_error('/ban')
+
+    if num == 0:
+        li_list = [
+            load_lang('main_setting'),
+            load_lang('text_setting'),
+            load_lang('main_head'),
+            load_lang('main_body'),
+            'robots.txt',
+            'Google'
+        ]
+        
+        x = 0
+        
+        li_data = ''
+        
+        for li in li_list:
+            x += 1
+            li_data += '<li><a href="/setting/' + str(x) + '">' + li + '</a></li>'
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('setting'), wiki_set(), custom(), other2([0, 0])],
+            data = '<h2>' + load_lang('list') + '</h2><ul>' + li_data + '</ul>',
+            menu = [['manager', load_lang('return')]]
+        ))
+    elif num == 1:
+        i_list = ['name', 'logo', 'frontpage', 'license', 'upload', 'skin', 'edit', 'reg', 'ip_view', 'back_up', 'port', 'key', 'update', 'email_have', 'discussion', 'encode', 'host']
+        n_list = ['wiki', '', 'FrontPage', 'CC 0', '2', '', 'normal', '', '', '0', '3000', 'test', 'stable', '', 'normal', 'sha256', '0.0.0.0']
+        
+        if flask.request.method == 'POST':
+            i = 0
+            
+            for data in i_list:
+                curs.execute("update other set data = ? where name = ?", [flask.request.form.get(data, n_list[i]), data])
+                i += 1
+
+            conn.commit()
+
+            admin_check(None, 'edit_set')
+
+            return redirect('/setting/1')
+        else:
+            d_list = []
+            
+            x = 0
+            
+            for i in i_list:
+                curs.execute('select data from other where name = ?', [i])
+                sql_d = curs.fetchall()
+                if sql_d:
+                    d_list += [sql_d[0][0]]
+                else:
+                    curs.execute('insert into other (name, data) values (?, ?)', [i, n_list[x]])
+                    
+                    d_list += [n_list[x]]
+
+                x += 1
+
+            conn.commit()
+            
+            div = ''
+            acl_list = [[load_lang('member'), 'login'], [load_lang('ip'), 'normal'], [load_lang('admin'), 'admin']]
+            for i in acl_list:
+                if i[1] == d_list[6]:
+                    div = '<option value="' + i[1] + '">' + i[0] + '</option>' + div
+                else:
+                    div += '<option value="' + i[1] + '">' + i[0] + '</option>'
+
+            div4 = ''
+            for i in acl_list:
+                if i[1] == d_list[14]:
+                    div4 = '<option value="' + i[1] + '">' + i[0] + '</option>' + div4
+                else:
+                    div4 += '<option value="' + i[1] + '">' + i[0] + '</option>'
+
+            ch_1 = ''
+            if d_list[7]:
+                ch_1 = 'checked="checked"'
+
+            ch_2 = ''
+            if d_list[8]:
+                ch_2 = 'checked="checked"'
+            
+            ch_3 = ''
+            if d_list[13]:
+                ch_3 = 'checked="checked"'
+
+            div2 = load_skin(d_list[5])
+
+            div3 =''
+            if d_list[12] == 'stable':
+                div3 += '<option value="stable">stable</option>'
+                div3 += '<option value="master">master</option>'
+            else:
+                div3 += '<option value="master">master</option>'
+                div3 += '<option value="stable">stable</option>'
+                
+            div5 =''
+            encode_data = ['sha256', 'sha3']
+            for i in encode_data:
+                if d_list[15] == i:
+                    div5 = '<option value="' + i + '">' + i + '</option>' + div5
+                else:
+                    div5 += '<option value="' + i + '">' + i + '</option>'
+
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = [load_lang('main_setting'), wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            <span>''' + load_lang('wiki_name') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('wiki_name') + '''" type="text" name="name" value="''' + html.escape(d_list[0]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('wiki_logo') + ''' (HTML)</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('wiki_logo') + '''" type="text" name="logo" value="''' + html.escape(d_list[1]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('main_page') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('main_page') + '''" type="text" name="frontpage" value="''' + html.escape(d_list[2]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('bottom_text') + ''' (HTML)</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('bottom_text') + '''" type="text" name="license" value="''' + html.escape(d_list[3]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('max_file_size') + ''' [MB]</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('max_file_size') + '''" type="text" name="upload" value="''' + html.escape(d_list[4]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('backup_interval') + ' [' + load_lang('hour') + '''] (off : 0) {restart}</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('backup_interval') + '''" type="text" name="back_up" value="''' + html.escape(d_list[9]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('wiki_skin') + '''</span>
+                            <br>
+                            <br>
+                            <select name="skin">''' + div2 + '''</select>
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('default_acl') + '''</span>
+                            <br>
+                            <br>
+                            <select name="edit">''' + div + '''</select>
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('default_discussion_acl') + '''</span>
+                            <br>
+                            <br>
+                            <select name="discussion">''' + div4 + '''</select>
+                            <hr class=\"main_hr\">
+                            <input type="checkbox" name="reg" ''' + ch_1 + '''> ''' + load_lang('no_register') + '''
+                            <hr class=\"main_hr\">
+                            <input type="checkbox" name="ip_view" ''' + ch_2 + '''> ''' + load_lang('hide_ip') + '''
+                            <hr class=\"main_hr\">
+                            <input type="checkbox" name="email_have" ''' + ch_3 + '''> ''' + load_lang('email_required') + ''' {<a href="/setting/5">''' + load_lang('google_imap_required') + '''</a>}
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('wiki_host') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('wiki_host') + '''" type="text" name="host" value="''' + html.escape(d_list[16]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('wiki_port') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('wiki_port') + '''" type="text" name="port" value="''' + html.escape(d_list[10]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('wiki_secret_key') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('wiki_secret_key') + '''" type="password" name="key" value="''' + html.escape(d_list[11]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('update_branch') + '''</span>
+                            <br>
+                            <br>
+                            <select name="update">''' + div3 + '''</select>
+                            <hr class=\"main_hr\">
+                            <span>encryption method</span>
+                            <br>
+                            <br>
+                            <select name="encode">''' + div5 + '''</select>
+                            <hr class=\"main_hr\">
+                            <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['setting', load_lang('return')]]
+            ))
+    elif num == 2:
+        if flask.request.method == 'POST':
+            curs.execute("update other set data = ? where name = ?", [flask.request.form.get('contract', None), 'contract'])
+            curs.execute("update other set data = ? where name = ?", [flask.request.form.get('no_login_warring', None), 'no_login_warring'])
+            conn.commit()
+            
+            admin_check(None, 'edit_set')
+
+            return redirect('/setting/2')
+        else:
+            i_list = ['contract', 'no_login_warring']
+            n_list = ['', '']
+            d_list = []
+            
+            x = 0
+            
+            for i in i_list:
+                curs.execute('select data from other where name = ?', [i])
+                sql_d = curs.fetchall()
+                if sql_d:
+                    d_list += [sql_d[0][0]]
+                else:
+                    curs.execute('insert into other (name, data) values (?, ?)', [i, n_list[x]])
+                    
+                    d_list += [n_list[x]]
+
+                x += 1
+
+            conn.commit()
+
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = [load_lang('text_setting'), wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            <span>''' + load_lang('register_text') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('register_text') + '''" type="text" name="contract" value="''' + html.escape(d_list[0]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('non_login_alert') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('non_login_alert') + '''" type="text" name="no_login_warring" value="''' + html.escape(d_list[1]) + '''">
+                            <hr class=\"main_hr\">
+                            <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['setting', load_lang('return')]]
+            ))
+    elif num == 3 or num == 4:
+        if flask.request.method == 'POST':
+            if num == 4:
+                info_d = 'body'
+                end_r = '4'
+            else:
+                info_d = 'head'
+                end_r = '3'
+            
+            curs.execute("select name from other where name = ?", [info_d])
+            if curs.fetchall():
+                curs.execute("update other set data = ? where name = ?", [flask.request.form.get('content', ''), info_d])
+            else:
+                curs.execute("insert into other (name, data) values (?, ?)", [info_d, flask.request.form.get('content', '')])
+            
+            conn.commit()
+
+            admin_check(None, 'edit_set')
+
+            return redirect('/setting/' + end_r)
+        else:
+            if num == 4:
+                curs.execute("select data from other where name = 'body'")
+                title = '_body'
+                start = ''
+            else:
+                curs.execute("select data from other where name = 'head'")
+                title = '_head'
+                start = '<span>&lt;style&gt;CSS&lt;/style&gt;<br>&lt;script&gt;JS&lt;/script&gt;</span><hr class=\"main_hr\">'
+                
+            head = curs.fetchall()
+            if head:
+                data = head[0][0]
+            else:
+                data = ''
+
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = [load_lang(data = 'main' + title, safe = 1), wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            ''' + start + '''
+                            <textarea rows="25" name="content">''' + html.escape(data) + '''</textarea>
+                            <hr class=\"main_hr\">
+                            <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['setting', load_lang('return')]]
+            ))
+    elif num == 5:
+        if flask.request.method == 'POST':
+            curs.execute("select name from other where name = 'robot'")
+            if curs.fetchall():
+                curs.execute("update other set data = ? where name = 'robot'", [flask.request.form.get('content', None)])
+            else:
+                curs.execute("insert into other (name, data) values ('robot', ?)", [flask.request.form.get('content', None)])
+            
+            conn.commit()
+            
+            fw = open('./robots.txt', 'w')
+            fw.write(re.sub('\r\n', '\n', flask.request.form.get('content', None)))
+            fw.close()
+            
+            admin_check(None, 'edit_set')
+
+            return redirect('/setting/4')
+        else:
+            curs.execute("select data from other where name = 'robot'")
+            robot = curs.fetchall()
+            if robot:
+                data = robot[0][0]
+            else:
+                data = ''
+
+            f = open('./robots.txt', 'r')
+            lines = f.readlines()
+            f.close()
+
+            if not data or data == '':
+                data = ''.join(lines)
+
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = ['robots.txt', wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <a href="/robots.txt">(view)</a>
+                        <hr class=\"main_hr\">
+                        <form method="post">
+                            <textarea rows="25" name="content">''' + html.escape(data) + '''</textarea>
+                            <hr class=\"main_hr\">
+                            <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['setting', load_lang('return')]]
+            ))
+    elif num == 6:
+        i_list = ['recaptcha', 'sec_re', 'g_email', 'g_pass']
+
+        if flask.request.method == 'POST':
+            for data in i_list:
+                curs.execute("update other set data = ? where name = ?", [flask.request.form.get(data, ''), data])
+
+            conn.commit()
+            
+            admin_check(None, 'edit_set')
+
+            return redirect('/setting/5')
+        else:
+            n_list = ['', '', '', '']
+            d_list = []
+            
+            x = 0
+            
+            for i in i_list:
+                curs.execute('select data from other where name = ?', [i])
+                sql_d = curs.fetchall()
+                if sql_d:
+                    d_list += [sql_d[0][0]]
+                else:
+                    curs.execute('insert into other (name, data) values (?, ?)', [i, n_list[x]])
+                    
+                    d_list += [n_list[x]]
+
+                x += 1
+
+            conn.commit()
+
+            return easy_minify(flask.render_template(skin_check(), 
+                imp = ['google', wiki_set(), custom(), other2([0, 0])],
+                data =  '''
+                        <form method="post">
+                            <h2><a href="https://www.google.com/recaptcha/admin">recaptcha</a></h2>
+                            <span>''' + load_lang('recaptcha') + ''' (HTML)</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('recaptcha') + ''' (HTML)" type="text" name="recaptcha" value="''' + html.escape(d_list[0]) + '''">
+                            <hr class=\"main_hr\">
+                            <span>''' + load_lang('recaptcha') + ' (' + load_lang('secret_key') + ''')</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('recaptcha') + ' (' + load_lang('secret_key') + ''')" type="text" name="sec_re" value="''' + html.escape(d_list[1]) + '''">
+                            <hr class=\"main_hr\">
+                            <h2><a href="https://support.google.com/mail/answer/7126229">''' + load_lang('google_imap') + '</a> {' + load_lang('restart_required') + '''}</h1>
+                            <span>''' + load_lang('google_email') + '''</span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('google_email') + '''" type="text" name="g_email" value="''' + html.escape(d_list[2]) + '''">
+                            <hr class=\"main_hr\">
+                            <span><a href="https://security.google.com/settings/security/apppasswords">''' + load_lang('google_app_password') + '''</a></span>
+                            <br>
+                            <br>
+                            <input placeholder="''' + load_lang('google_app_password') + '''" type="password" name="g_pass" value="''' + html.escape(d_list[3]) + '''">
+                            <hr class=\"main_hr\">
+                            <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                        </form>
+                        ''',
+                menu = [['setting', load_lang('return')]]
+            ))
+    else:
+        return redirect()

+ 72 - 0
route/title_index.py

@@ -0,0 +1,72 @@
+from .tool.func import *
+
+def title_index_2(conn):
+    curs = conn.cursor()
+    
+    page = int(number_check(flask.request.args.get('page', '1')))
+    num = int(number_check(flask.request.args.get('num', '100')))
+    if page * num > 0:
+        sql_num = page * num - num
+    else:
+        sql_num = 0
+
+    all_list = sql_num + 1
+
+    if num > 1000:
+        return re_error('/error/3')
+
+    data = '<a href="/title_index?num=250">(250)</a> <a href="/title_index?num=500">(500)</a> <a href="/title_index?num=1000">(1000)</a>'
+
+    curs.execute("select title from data order by title asc limit ?, ?", [str(sql_num), str(num)])
+    title_list = curs.fetchall()
+    if title_list:
+        data += '<hr class=\"main_hr\"><ul>'
+
+    for list_data in title_list:
+        data += '<li>' + str(all_list) + '. <a href="/w/' + url_pas(list_data[0]) + '">' + list_data[0] + '</a></li>'        
+        all_list += 1
+
+    if page == 1:
+        count_end = []
+
+        curs.execute("select count(title) from data")
+        count = curs.fetchall()
+        if count:
+            count_end += [count[0][0]]
+        else:
+            count_end += [0]
+
+        sql_list = [load_lang('template', 1).lower() + ':', 'category:', 'user:', 'file:']
+        for sql in sql_list:
+            curs.execute("select count(title) from data where title like ?", [sql + '%'])
+            count = curs.fetchall()
+            if count:
+                count_end += [count[0][0]]
+            else:
+                count_end += [0]
+
+        count_end += [count_end[0] - count_end[1]  - count_end[2]  - count_end[3]  - count_end[4]]
+        
+        data += '''
+                </ul>
+                <hr class=\"main_hr\">
+                <ul>
+                    <li>all : ''' + str(count_end[0]) + '''</li>
+                </ul>
+                <hr class=\"main_hr\">
+                <ul>
+                    <li>''' + load_lang('template') + ' : ' + str(count_end[1]) + '''</li>
+                    <li>''' + load_lang('category') + ' : ' + str(count_end[2]) + '''</li>
+                    <li>''' + load_lang('user') + ' : ' + str(count_end[3]) + '''</li>
+                    <li>''' + load_lang('file') + ' : ' + str(count_end[4]) + '''</li>
+                    <li>other : ''' + str(count_end[5]) + '''</li>
+                '''
+
+    data += '</ul>' + next_fix('/title_index?num=' + str(num) + '&page=', page, title_list, num)
+    sub = ' (' + str(num) + ')'
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('all_document_list'), wiki_set(), custom(), other2([sub, 0])],
+        data = data,
+        menu = [['other', load_lang('return')]]
+    ))

+ 177 - 99
func.py → route/tool/func.py

@@ -1,15 +1,30 @@
+import werkzeug.routing
+import flask_compress
+import flask_reggie
+import tornado.ioloop
+import tornado.httpserver
+import tornado.wsgi
+import urllib.request
 import email.mime.text
 import urllib.request
 import sqlite3
 import hashlib
 import smtplib
 import bcrypt
+import platform
+import zipfile
+import difflib
+import shutil
+import threading
+import logging
+import random
 import flask
 import json
 import html
 import sys
 import re
 import os
+
 try:
     import css_html_js_minify
 except:
@@ -18,8 +33,10 @@ except:
 if sys.version_info < (3, 6):
     import sha3
 
-from set_mark.tool import *
-from mark import *
+from .set_mark.tool import *
+from .mark import *
+
+app_var = json.loads(open('data/app_variables.json', encoding='utf-8').read())
 
 def load_conn(data):
     global conn
@@ -53,7 +70,41 @@ def send_email(who, title, data):
 
         smtp.quit()
     except:
-        print('error : email login error')
+        print('Error : Email login error')
+
+def last_change(data):
+    json_address = re.sub("\.html$", ".json", skin_check())
+    try:
+        json_data = json.loads(open(json_address).read())
+    except:
+        json_data = 0
+
+    if json_data != 0:
+        for j_data in json_data:
+            if "class" in json_data[j_data]:
+                if "require" in json_data[j_data]:
+                    re_data = re.compile("<((?:" + j_data + ")( (?:(?!>).)*)?)>")
+                    s_data = re_data.findall(data)
+                    for i_data in s_data:
+                        e_data = 0
+
+                        for j_i_data in json_data[j_data]["require"]:
+                            re_data_2 = re.compile("( |^)" + j_i_data + " *= *[\'\"]" + json_data[j_data]["require"][j_i_data] + "[\'\"]")
+                            if not re_data_2.search(i_data[1]):
+                                re_data_2 = re.compile("( |^)" + j_i_data + "=" + json_data[j_data]["require"][j_i_data] + "(?: |$)")
+                                if not re_data_2.search(i_data[1]):
+                                    e_data = 1
+
+                                    break
+
+                        if e_data == 0:
+                            re_data_3 = re.compile("<" + i_data[0] + ">")
+                            data = re_data_3.sub("<" + i_data[0] + " class=\"" + json_data[j_data]["class"] + "\">", data)        
+                else:
+                    re_data = re.compile("<(?P<in>" + j_data + "(?: (?:(?!>).)*)?)>")
+                    data = re_data.sub("<\g<in> class=\"" + json_data[j_data]["class"] + "\">", data)        
+
+    return data
 
 def easy_minify(data, tool = None):
     try:
@@ -68,11 +119,13 @@ def easy_minify(data, tool = None):
         data = re.sub('\n +<', '\n<', data)
         data = re.sub('>(\n| )+<', '> <', data)
     
-    return data
+    return last_change(data)
 
-def render_set(title = '', data = '', num = 0):
+def render_set(title = '', data = '', num = 0, s_data = 0):
     if acl_check(title, 'render') == 1:
-        return 'http request 401.3'
+        return 'HTTP Request 401.3'
+    elif s_data == 1:
+        return data
     else:
         return namumark(title, data, num)
 
@@ -86,50 +139,11 @@ def captcha_get():
             curs.execute('select data from other where name = "sec_re"')
             sec_re = curs.fetchall()
             if sec_re and sec_re[0][0] != '':
-                data += recaptcha[0][0] + '<hr>'
+                data += recaptcha[0][0] + '<hr class=\"main_hr\">'
 
     return data
 
 def update():
-    # v3.0.5 사용자 문서, 파일 문서, 분류 문서 영어화
-    try:
-        all_rep = [['사용자:', 'user:'], ['파일:', 'file:'], ['분류:', 'category:']]
-        all_rep2 = ['data', 'history', 'acl', 'topic', 'back']
-
-        test = 0
-
-        for i in range(3):
-            for j in range(6):
-                if not j == 5:
-                    curs.execute('select title from ' + all_rep2[j] + ' where title like ?', [all_rep[i][0] + '%'])
-                else:
-                    curs.execute('select link from back where link like ?', [all_rep[i][0] + '%'])
-
-                user_rep = curs.fetchall()
-                if user_rep:
-                    for user_rep2 in user_rep:
-                        test = 1
-
-                        first = re.sub('^' + all_rep[i][0], all_rep[i][1], user_rep2[0])
-
-                        if j == 0:
-                            curs.execute("update data set title = ? where title = ?", [first, user_rep2[0]])
-                        elif j == 1:
-                            curs.execute("update history set title = ? where title = ?", [first, user_rep2[0]])
-                        elif j == 2:
-                            curs.execute("update acl set title = ? where title = ?", [first, user_rep2[0]])
-                        elif j == 3:
-                            curs.execute("update topic set title = ? where title = ?", [first, user_rep2[0]])
-                        elif j == 4:
-                            curs.execute("update back set title = ? where title = ?", [first, user_rep2[0]])
-                        elif j == 5:
-                            curs.execute("update back set link = ? where link = ?", [first, user_rep2[0]])
-
-        if test == 1:
-            print('사용자 to user, 파일 to file, 분류 to category')
-    except:
-        pass
-        
     # v3.0.8 rd, agreedis, stop 테이블 통합
     try:
         curs.execute("select title, sub, close from stop")
@@ -154,6 +168,32 @@ def update():
     except:
         pass
 
+    # Start Data Migration Code
+    app_var = json.loads(open(os.path.abspath('./data/app_variables.json'), encoding='utf-8').read())
+
+    if os.path.exists('image'):
+        os.rename('image', app_var['path_data_image'])
+
+    if os.path.exists('oauthsettings.json'):
+        os.rename('oauthsettings.json', app_var['path_oauth_setting'])
+
+    try:
+        load_oauth('discord')
+    except KeyError:
+        old_oauth_data = json.loads(open(app_var['path_oauth_setting'], encoding='utf-8').read())
+
+        if 'discord' not in old_oauth_data['_README']['support']:
+            old_oauth_data['_README']['support'] += ['discord']
+
+        old_oauth_data['discord'] = {}
+        old_oauth_data['discord']['client_id'] = ''
+        old_oauth_data['discord']['client_secret'] = ''
+
+        with open(app_var['path_oauth_setting'], 'w') as f:
+            f.write(json.dumps(old_oauth_data, sort_keys = True, indent = 4))
+
+    # -> End Data Migration Code
+
 def pw_encode(data, data2 = '', type_d = ''):
     if type_d == '':
         curs.execute('select data from other where name = "encode"')
@@ -188,17 +228,25 @@ def pw_check(data, data2, type_d = 'no', id_d = ''):
     else:
         set_data = db_data[0][0]
     
-    if set_data in ['sha256', 'sha3']:
-        data3 = pw_encode(data = data, type_d = set_data)
-        if data3 == data2:
-            re_data = 1
-        else:
-            re_data = 0
-    else:
-        if pw_encode(data, data2, 'bcrypt') == data2:
-            re_data = 1
+    while 1:
+        if set_data in ['sha256', 'sha3']:
+            data3 = pw_encode(data = data, type_d = set_data)
+            if data3 == data2:
+                re_data = 1
+            else:
+                re_data = 0
+
+            break
         else:
-            re_data = 0
+            try:
+                if pw_encode(data, data2, 'bcrypt') == data2:
+                    re_data = 1
+                else:
+                    re_data = 0
+
+                break
+            except:
+                set_data = db_data[0][0]
 
     if db_data[0][0] != set_data and re_data == 1 and id_d != '':
         curs.execute("update user set pw = ?, encode = ? where id = ?", [pw_encode(data), db_data[0][0], id_d])
@@ -228,7 +276,7 @@ def captcha_post(re_data, num = 1):
     else:
         pass
 
-def load_lang(data, num = 2):
+def load_lang(data, num = 2, safe = 0):
     if num == 1:
         curs.execute("select data from other where name = 'language'")
         rep_data = curs.fetchall()
@@ -237,9 +285,12 @@ def load_lang(data, num = 2):
         lang = json.loads(json_data)
 
         if data in lang:
-            return lang[data]
+            if safe == 1:
+                return lang[data]
+            else:
+                return html.escape(lang[data])
         else:
-            return data + ' (missing)'
+            return html.escape(data + ' (M)')
     else:
         curs.execute('select data from user_set where name = "lang" and id = ?', [ip_check()])
         rep_data = curs.fetchall()
@@ -248,14 +299,31 @@ def load_lang(data, num = 2):
                 json_data = open(os.path.join('language', rep_data[0][0] + '.json'), 'rt', encoding='utf-8').read()
                 lang = json.loads(json_data)
             except:
-                return load_lang(data, 1)
+                return load_lang(data, 1, safe)
 
             if data in lang:
-                return lang[data]
+                if safe == 1:
+                    return lang[data]
+                else:
+                    return html.escape(lang[data])
             else:
-                return load_lang(data, 1)
+                return load_lang(data, 1, safe)
         else:
-            return load_lang(data, 1)
+            return load_lang(data, 1, safe)
+
+def load_oauth(provider):
+    oauth = json.loads(open(app_var['path_oauth_setting'], encoding='utf-8').read())
+
+    return oauth[provider]
+
+def update_oauth(provider, target, content):
+    oauth = json.loads(open(app_var['path_oauth_setting'], encoding='utf-8').read())
+    oauth[provider][target] = content
+
+    with open(app_var['path_oauth_setting'], 'w') as f:
+        f.write(json.dumps(oauth, sort_keys=True, indent=4))
+
+    return 'Done'
 
 def ip_or_user(data):
     if re.search('(\.|:)', data):
@@ -266,24 +334,24 @@ def ip_or_user(data):
 def edit_help_button():
     # https://stackoverflow.com/questions/11076975/insert-text-into-textarea-at-cursor-position-javascript
     js_data =   '''
-                <script>
-                    function insert_data(name, data) {
-                        if(document.selection) { 
-                            document.getElementById(name).focus();
-
-                            sel = document.selection.createRange();
-                            sel.text = data; 
-                        } else if(document.getElementById(name).selectionStart || document.getElementById(name).selectionStart == '0') {
-                            var startPos = document.getElementById(name).selectionStart;
-                            var endPos = document.getElementById(name).selectionEnd;
-
-                            document.getElementById(name).value = document.getElementById(name).value.substring(0, startPos) + data + document.getElementById(name).value.substring(endPos, document.getElementById(name).value.length); 
-                        } else {
-                            document.getElementById(name).value += data;
-                        }
-                    }
-                </script>
-                '''
+        <script>
+            function insert_data(name, data) {
+                if(document.selection) { 
+                    document.getElementById(name).focus();
+
+                    sel = document.selection.createRange();
+                    sel.text = data; 
+                } else if(document.getElementById(name).selectionStart || document.getElementById(name).selectionStart == '0') {
+                    var startPos = document.getElementById(name).selectionStart;
+                    var endPos = document.getElementById(name).selectionEnd;
+
+                    document.getElementById(name).value = document.getElementById(name).value.substring(0, startPos) + data + document.getElementById(name).value.substring(endPos, document.getElementById(name).value.length); 
+                } else {
+                    document.getElementById(name).value += data;
+                }
+            }
+        </script>
+    '''
 
     insert_list = [['[[|]]', '[[|]]'], ['[*()]', '[*()]'], ['{{{#!}}}', '{{{#!}}}'], ['||<>||', '||<>||'], ["\\'\\'\\'", "\'\'\'"]]
 
@@ -291,16 +359,16 @@ def edit_help_button():
     for insert_data in insert_list:
         data += '<a href="javascript:void(0);" onclick="insert_data(\'content\', \'' + insert_data[0] + '\');">(' + insert_data[1] + ')</a> '
 
-    return [js_data, data + '<hr>']
+    return [js_data, data + '<hr class=\"main_hr\">']
 
 def ip_warring():
     if custom()[2] == 0:    
         curs.execute('select data from other where name = "no_login_warring"')
         data = curs.fetchall()
         if data and data[0][0] != '':
-            text_data = '<span>' + data[0][0] + '</span><hr>'
+            text_data = '<span>' + data[0][0] + '</span><hr class=\"main_hr\">'
         else:
-            text_data = '<span>' + load_lang('no_login_warring') + '</span><hr>'
+            text_data = '<span>' + load_lang('no_login_warring') + '</span><hr class=\"main_hr\">'
     else:
         text_data = ''
 
@@ -328,11 +396,11 @@ def next_fix(link, num, page, end = 50):
 
     if num == 1:
         if len(page) == end:
-            list_data += '<hr><a href="' + link + str(num + 1) + '">(' + load_lang('next') + ')</a>'
+            list_data += '<hr class=\"main_hr\"><a href="' + link + str(num + 1) + '">(' + load_lang('next') + ')</a>'
     elif len(page) != end:
-        list_data += '<hr><a href="' + link + str(num - 1) + '">(' + load_lang('previous') + ')</a>'
+        list_data += '<hr class=\"main_hr\"><a href="' + link + str(num - 1) + '">(' + load_lang('previous') + ')</a>'
     else:
-        list_data += '<hr><a href="' + link + str(num - 1) + '">(' + load_lang('previous') + ')</a> <a href="' + link + str(num + 1) + '">(' + load_lang('next') + ')</a>'
+        list_data += '<hr class=\"main_hr\"><a href="' + link + str(num - 1) + '">(' + load_lang('previous') + ')</a> <a href="' + link + str(num + 1) + '">(' + load_lang('next') + ')</a>'
 
     return list_data
 
@@ -348,7 +416,7 @@ def wiki_set(num = 1):
         if db_data and db_data[0][0] != '':
             data_list += [db_data[0][0]]
         else:
-            data_list += ['wiki']
+            data_list += ['Wiki']
 
         curs.execute('select data from other where name = "license"')
         db_data = curs.fetchall()
@@ -372,7 +440,6 @@ def wiki_set(num = 1):
             data_list += [db_data[0][0]]
         else:
             data_list += ['']
-
         return data_list
 
     if num == 2:
@@ -482,13 +549,13 @@ def ip_pas(raw_ip):
     hide = 0
 
     if re.search("(\.|:)", raw_ip):
-        if not re.search("^" + load_lang('tool', 1) + ":", raw_ip):    
+        if not re.search("^tool:", raw_ip):    
             curs.execute("select data from other where name = 'ip_view'")
             data = curs.fetchall()
             if data and data[0][0] != '':
                 ip = '<span style="font-size: 75%;">' + hashlib.md5(bytes(raw_ip, 'utf-8')).hexdigest() + '</span>'
 
-                if not admin_check('ban', None):
+                if not admin_check(1):
                     hide = 1
             else:
                 ip = raw_ip
@@ -537,7 +604,7 @@ def custom():
     else:
         user_name = load_lang('user')
 
-    return ['', '', user_icon, user_head, email, user_name, load_lang(data = '', num = 2)]
+    return ['', '', user_icon, user_head, email, user_name]
 
 def load_skin(data = ''):
     div2 = ''
@@ -579,14 +646,14 @@ def acl_check(name, tool = ''):
         acl_data = curs.fetchall()
         if acl_data:
             if acl_data[0][0] == 'user':
-                if not user_data:
+                if ip_or_user(ip):
                     return 1
 
             if acl_data[0][0] == 'admin':
-                if not user_data:
+                if ip_or_user(ip):
                     return 1
 
-                if not admin_check(5, 'view (' + name + ')') == 1:
+                if admin_check(5, 'view (' + name + ')') != 1:
                     return 1
 
         return 0
@@ -598,7 +665,7 @@ def acl_check(name, tool = ''):
         if acl_c:
             acl_n = acl_c.groups()
 
-            if admin_check(5, None) == 1:
+            if admin_check(5) == 1:
                 return 0
 
             curs.execute("select dec from acl where title = ?", ['user:' + acl_n[0]])
@@ -650,7 +717,7 @@ def acl_check(name, tool = ''):
                 if not user_data:
                     return 1
 
-                if not admin_check(5, None) == 1:
+                if not admin_check(5) == 1:
                     return 1
 
         return 0
@@ -744,8 +811,10 @@ def ban_insert(name, end, why, login, blocker):
             login = ''
 
         if end != '0':
+            end = int(number_check(end))
+
             time = datetime.datetime.now()
-            plus = datetime.timedelta(seconds = int(end))
+            plus = datetime.timedelta(seconds = end)
             r_time = (time + plus).strftime("%Y-%m-%d %H:%M:%S")
         else:
             r_time = ''
@@ -778,6 +847,15 @@ def leng_check(first, second):
         
     return all_plus
 
+def number_check(data):
+    if not data:
+        return '1'
+    else:
+        if re.search('[^0-9]', data):
+            return '1'
+        else:
+            return data
+
 def edit_filter_do(data):
     if admin_check(1, 'edit_filter pass') != 1:
         curs.execute("select regex, sub from filter")
@@ -796,7 +874,7 @@ def edit_filter_do(data):
     
     return 0
 
-def redirect(data):
+def redirect(data = '/'):
     return flask.redirect(data)
 
 def re_error(data):

+ 68 - 0
route/tool/init.py

@@ -0,0 +1,68 @@
+import os
+
+env_dict = {
+    'host'      : os.getenv('NAMU_HOST'),
+    'port'      : os.getenv('NAMU_PORT'),
+    'language'  : os.getenv('NAMU_LANG'),
+    'markup'    : os.getenv('NAMU_MARKUP'),
+    'encode'    : os.getenv('NAMU_ENCRYPT')
+}
+
+server_set_var = {
+    'host' : {
+        'display' : 'Host',
+        'require' : 'conv',
+        'default' : '0.0.0.0'
+    },
+    'port' : {
+        'display' : 'Port',
+        'require' : 'conv',
+        'default' : '3000'
+    },
+    'language' : {
+        'display' : 'Language',
+        'require' : 'select',
+        'default' : 'en-US',
+        'list' : ['ko-KR', 'en-US']
+    },
+    'markup' : {
+        'display' : 'Markup',
+        'require' : 'select',
+        'default' : 'namumark',
+        'list' : ['namumark']
+    },
+    'encode' : {
+        'display' : 'Encrypt Method',
+        'require' : 'select',
+        'default' : 'sha3',
+        'list' : ['sha3', 'sha256']
+    }
+}
+
+def init(key):
+    if env_dict[key] != None:
+        return env_dict[key]
+    else:
+        while 1:
+            if server_set_var[key]['require'] == 'select':
+                list_ = '[' + ', '.join(server_set_var[key]['list']) + '] '
+            else:
+                list_ = ''
+
+            print('{} ({}) {}:'.format(
+                server_set_var[key]['display'],
+                server_set_var[key]['default'],
+                list_
+            ), end = '')
+
+            server_set_val = input()
+            if server_set_val:
+                if server_set_var[key]['require'] == 'select':
+                    if server_set_val not in server_set_var[key]['list']:
+                        pass
+                    else:
+                        return server_set_val
+                else:
+                    return server_set_val
+            else:
+                return server_set_var[key]['default']

+ 5 - 5
mark.py → route/tool/mark.py

@@ -1,4 +1,4 @@
-from set_mark.namu import namu
+from .set_mark.namu import namu
 
 import re
 import html
@@ -50,11 +50,11 @@ def namumark(title = '', data = None, num = 0):
 
         if num == 1:
             data_num = len(data[2]) 
-            data_in_num = int(data_num / 8)
+            data_in_num = int(data_num / multiprocessing.cpu_count())
             data_in = []
 
-            for i in range(8):
-                if not i == 7:
+            for i in range(multiprocessing.cpu_count()):
+                if i != multiprocessing.cpu_count() - 1:
                     data_in += [data[2][data_in_num * i:data_in_num * (i + 1)]]
                 else:
                     data_in += [data[2][data_in_num * i:]]
@@ -68,4 +68,4 @@ def namumark(title = '', data = None, num = 0):
             
         return data[0] + data[1]
     else:
-        return 'http request 404'
+        return 'HTTP Request 404'

+ 0 - 0
set_mark/namu.py → route/tool/set_mark/namu.py


+ 0 - 0
set_mark/tool.py → route/tool/set_mark/tool.py


+ 201 - 0
route/topic.py

@@ -0,0 +1,201 @@
+from .tool.func import *
+
+def topic_2(conn, name, sub):
+    curs = conn.cursor()
+    
+    ban = topic_check(name, sub)
+    admin = admin_check(3)
+    
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        ip = ip_check()
+        today = get_time()
+        
+        if ban == 1:
+            return re_error('/ban')
+        
+        curs.execute("select id from topic where title = ? and sub = ? order by id + 0 desc limit 1", [name, sub])
+        old_num = curs.fetchall()
+        if old_num:
+            num = int(old_num[0][0]) + 1
+        else:
+            num = 1
+
+        match = re.search('^user:([^/]+)', name)
+        if match:
+            curs.execute('insert into alarm (name, data, date) values (?, ?, ?)', [match.groups()[0], ip + ' - <a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '">' + load_lang('user_discussion', 1) + '</a>', today])
+        
+        data = re.sub('\[\[((?:분류|category):(?:(?:(?!\]\]).)*))\]\]', '[br]', flask.request.form.get('content', None))
+        for rd_data in re.findall("(?:#([0-9]+))", data):
+            curs.execute("select ip from topic where title = ? and sub = ? and id = ?", [name, sub, rd_data])
+            ip_data = curs.fetchall()
+            if ip_data and ip_or_user(ip_data[0][0]) == 0:
+                curs.execute('insert into alarm (name, data, date) values (?, ?, ?)', [ip_data[0][0], ip + ' - <a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '#' + str(num) + '">' + load_lang('discussion', 1) + '</a>', today])
+            
+        data = re.sub("(?P<in>#(?:[0-9]+))", '[[\g<in>]]', data)
+
+        data = savemark(data)
+
+        rd_plus(name, sub, today)
+
+        curs.execute("insert into topic (id, title, sub, data, date, ip, block, top) values (?, ?, ?, ?, ?, ?, '', '')", [str(num), name, sub, data, today, ip])
+        conn.commit()
+        
+        return redirect('/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '#reload')
+    else:
+        curs.execute("select title from rd where title = ? and sub = ? and stop = 'O'", [name, sub])
+        close_data = curs.fetchall()
+        
+        curs.execute("select title from rd where title = ? and sub = ? and stop = 'S'", [name, sub])
+        stop_data = curs.fetchall()
+        
+        curs.execute("select id from topic where title = ? and sub = ? limit 1", [name, sub])
+        topic_exist = curs.fetchall()
+        
+        display = ''
+        all_data = ''
+        data = ''
+        number = 1
+        
+        if admin == 1 and topic_exist:
+            if close_data:
+                all_data += '<a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/tool/close">(' + load_lang('open') + ')</a> '
+            else:
+                all_data += '<a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/tool/close">(' + load_lang('close') + ')</a> '
+            
+            if stop_data:
+                all_data += '<a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/tool/stop">(' + load_lang('restart') + ')</a> '
+            else:
+                all_data += '<a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/tool/stop">(' + load_lang('stop') + ')</a> '
+
+            curs.execute("select title from rd where title = ? and sub = ? and agree = 'O'", [name, sub])
+            if curs.fetchall():
+                all_data += '<a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/tool/agree">(' + load_lang('destruction') + ')</a>'
+            else:
+                all_data += '<a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/tool/agree">(' + load_lang('agreement') + ')</a>'
+            
+            all_data += '<hr class=\"main_hr\">'
+        
+        if (close_data or stop_data) and admin != 1:
+            display = 'display: none;'
+        
+        curs.execute("select data, id, date, ip, block, top from topic where title = ? and sub = ? order by id + 0 asc", [name, sub])
+        topic = curs.fetchall()
+        
+        curs.execute("select data, id, date, ip from topic where title = ? and sub = ? and top = 'O' order by id + 0 asc", [name, sub])
+        for topic_data in curs.fetchall():                   
+            who_plus = ''
+            
+            curs.execute("select who from re_admin where what = ? order by time desc limit 1", ['notice (' + name + ' - ' + sub + '#' + topic_data[1] + ')'])
+            topic_data_top = curs.fetchall()
+            if topic_data_top:
+                who_plus += ' <span style="margin-right: 5px;">@' + topic_data_top[0][0] + ' </span>'
+                                
+            all_data += '''
+                        <table id="toron">
+                            <tbody>
+                                <tr>
+                                    <td id="toron_color_red">
+                                        <a href="#''' + topic_data[1] + '''">
+                                            #''' + topic_data[1] + '''
+                                        </a> ''' + ip_pas(topic_data[3]) + who_plus + ''' <span style="float: right;">''' + topic_data[2] + '''</span>
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <td>''' + render_set(data = topic_data[0]) + '''</td>
+                                </tr>
+                            </tbody>
+                        </table>
+                        <br>
+                        '''    
+
+        for topic_data in topic:
+            user_write = topic_data[0]
+
+            if number == 1:
+                start = topic_data[3]
+
+            if topic_data[4] == 'O':
+                blind_data = 'id="toron_color_grey"'
+                
+                if admin != 1:
+                    curs.execute("select who from re_admin where what = ? order by time desc limit 1", ['blind (' + name + ' - ' + sub + '#' + str(number) + ')'])
+                    who_blind = curs.fetchall()
+                    if who_blind:
+                        user_write = '[[user:' + who_blind[0][0] + ']] ' + load_lang('hide')
+                    else:
+                        user_write = load_lang('hide')
+            else:
+                blind_data = ''
+
+            user_write = render_set(data = user_write)
+            ip = ip_pas(topic_data[3])
+            
+            curs.execute('select acl from user where id = ?', [topic_data[3]])
+            user_acl = curs.fetchall()
+            if user_acl and user_acl[0][0] != 'user':
+                ip += ' <a href="javascript:void(0);" title="' + load_lang('admin') + '">★</a>'
+
+            if admin == 1 or blind_data == '':
+                ip += ' <a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/admin/' + str(number) + '">(' + load_lang('discussion_tool') + ')</a>'
+
+            curs.execute("select end from ban where block = ?", [topic_data[3]])
+            if curs.fetchall():
+                ip += ' <a href="javascript:void(0);" title="' + load_lang('blocked') + '">†</a>'
+                    
+            if topic_data[5] == '1':
+                color = '_blue'
+            elif topic_data[3] == start:
+                color = '_green'
+            else:
+                color = ''
+                
+            if user_write == '':
+                user_write = '<br>'
+                         
+            all_data += '''
+                        <table id="toron">
+                            <tbody>
+                                <tr>
+                                    <td id="toron_color''' + color + '''">
+                                        <a href="javascript:void(0);" id="''' + str(number) + '">#' + str(number) + '</a> ' + ip + '''</span>
+                                    </td>
+                                </tr>
+                                <tr ''' + blind_data + '''>
+                                    <td>''' + user_write + '''</td>
+                                </tr>
+                            </tbody>
+                        </table>
+                        <br>
+                        '''
+            number += 1
+
+        if ban != 1 or admin == 1:
+            data += '''
+                    <div id="plus"></div>
+                    <script type="text/javascript" src="/views/main_css/topic_reload.js"></script>
+                    <script>topic_load("''' + name + '''", "''' + sub + '''");</script>
+                    <a id="reload" href="javascript:void(0);" onclick="location.href.endsWith(\'#reload\')? location.reload(true):location.href=\'#reload\'">(''' + load_lang('reload') + ''')</a>
+                    <form style="''' + display + '''" method="post">
+                    <br>
+                    <textarea style="height: 100px;" name="content"></textarea>
+                    <hr class=\"main_hr\">
+                    ''' + captcha_get()
+            
+            if display == '':
+                data += ip_warring()
+
+            data += '''
+                        <button type="submit">''' + load_lang('send') + '''</button>
+                    </form>
+                    '''
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('discussion') + ')', 0])],
+            data = '<h2 id="topic_top_title">' + sub + '</h2>' + all_data + data,
+            menu = [['topic/' + url_pas(name), load_lang('list')]]
+        ))

+ 77 - 0
route/topic_admin.py

@@ -0,0 +1,77 @@
+from .tool.func import *
+
+def topic_admin_2(conn, name, sub, num):
+    curs = conn.cursor()
+
+    curs.execute("select block, ip, date from topic where title = ? and sub = ? and id = ?", [name, sub, str(num)])
+    data = curs.fetchall()
+    if not data:
+        return redirect('/topic/' + url_pas(name) + '/sub/' + url_pas(sub))
+
+    ban = ''
+
+    if admin_check(3) == 1:
+        ban +=  '''
+                </ul>
+                <br>
+                <h2>''' + load_lang('admin_tool') + '''</h2>
+                <ul>
+                '''
+        is_ban = '<li><a href="/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '/b/' + str(num) + '">'
+
+        if data[0][0] == 'O':
+            is_ban += load_lang('hide_release')
+        else:
+            is_ban += load_lang('hide')
+        
+        is_ban +=   '''
+                        </a>
+                    </li>
+                    <li>
+                        <a href="/topic/''' + url_pas(name) + '/sub/' + url_pas(sub) + '/notice/' + str(num) + '''">
+                    '''
+
+        curs.execute("select id from topic where title = ? and sub = ? and id = ? and top = 'O'", [name, sub, str(num)])
+        if curs.fetchall():
+            is_ban += load_lang('notice_release')
+        else:
+            is_ban += load_lang('notice') + ''
+        
+        is_ban += '</a></li></ul>'
+        ban += '<li><a href="/ban/' + url_pas(data[0][1]) + '">'
+
+        curs.execute("select end from ban where block = ?", [data[0][1]])
+        if curs.fetchall():
+            ban += load_lang('ban_release')
+        else:
+            ban += load_lang('ban')
+        
+        ban += '</a></li>' + is_ban
+
+    ban +=  '''
+            </ul>
+            <br>
+            <h2>''' + load_lang('other_tool') + '''</h2>
+            <ul>
+                <li>
+                    <a href="/topic/''' + url_pas(name) + '/sub/' + url_pas(sub) + '/raw/' + str(num) + '''">raw</a>
+                </li>
+            '''
+    ban = '<li>' + load_lang('time') + ' : ' + data[0][2] + '</li>' + ban
+    
+    if ip_or_user(data[0][1]) == 1:
+        ban = '<li>' + load_lang('writer') + ' : ' + data[0][1] + ' <a href="/record/' + url_pas(data[0][1]) + '">(' + load_lang('record') + ')</a></li>' + ban
+    else:
+        ban =   '''
+                <li>
+                    ''' + load_lang('writer') + ' : <a href="/w/user:' + data[0][1] + '">' + data[0][1] + '</a> <a href="/record/' + url_pas(data[0][1]) + '">(' + load_lang('record') + ''')</a>
+                </li>
+                ''' + ban
+
+    ban = '<h2>' + load_lang('state') + '</h2><ul>' + ban
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('discussion_tool'), wiki_set(), custom(), other2([' (' + str(num) + ')', 0])],
+        data = ban,
+        menu = [['topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '#' + str(num), load_lang('return')]]
+    ))

+ 55 - 0
route/topic_stop.py

@@ -0,0 +1,55 @@
+from .tool.func import *
+
+def topic_stop_2(conn, name, sub, tool):
+    curs = conn.cursor()
+
+    if tool == 'close':
+        set_list = [
+            'O', 
+            'S', 
+            load_lang('close', 1), 
+            load_lang('open', 1)
+        ]
+    elif tool == 'stop':
+        set_list = [
+            '', 
+            'O', 
+            load_lang('stop', 1), 
+            load_lang('restart', 1)
+        ]
+    elif tool == 'agree':
+        pass
+    else:
+        return redirect('/topic/' + url_pas(name) + '/sub/' + url_pas(sub))
+
+    if admin_check(3, 'topic ' + tool + ' (' + name + ' - ' + sub + ')') != 1:
+        return re_error('/error/3')
+
+    ip = ip_check()
+    time = get_time()
+    
+    curs.execute("select id from topic where title = ? and sub = ? order by id + 0 desc limit 1", [name, sub])
+    topic_check = curs.fetchall()
+    if topic_check:
+        if tool == 'agree':
+            curs.execute("select title from rd where title = ? and sub = ? and agree = 'O'", [name, sub])
+            if curs.fetchall():
+                curs.execute("insert into topic (id, title, sub, data, date, ip, block, top) values (?, ?, ?, '" + load_lang('agreement', 1) + " X', ?, ?, '', '1')", [str(int(topic_check[0][0]) + 1), name, sub, time, ip])
+                curs.execute("update rd set agree = '' where title = ? and sub = ?", [name, sub])
+            else:
+                curs.execute("insert into topic (id, title, sub, data, date, ip, block, top) values (?, ?, ?, '" + load_lang('agreement', 1) + " O', ?, ?, '', '1')", [str(int(topic_check[0][0]) + 1), name, sub, time, ip])
+                curs.execute("update rd set agree = 'O' where title = ? and sub = ?", [name, sub])
+        else:
+            curs.execute("select title from rd where title = ? and sub = ? and stop = ?", [name, sub, set_list[0]])
+            if curs.fetchall():
+                curs.execute("insert into topic (id, title, sub, data, date, ip, block, top) values (?, ?, ?, ?, ?, ?, '', '1')", [str(int(topic_check[0][0]) + 1), name, sub, set_list[3], time, ip])
+                curs.execute("update rd set stop = '' where title = ? and sub = ?", [name, sub])
+            else:
+                curs.execute("insert into topic (id, title, sub, data, date, ip, block, top) values (?, ?, ?, ?, ?, ?, '', '1')", [str(int(topic_check[0][0]) + 1), name, sub, set_list[2], time, ip])
+                curs.execute("update rd set stop = ? where title = ? and sub = ?", [set_list[0], name, sub])
+        
+        rd_plus(name, sub, time)
+        
+        conn.commit()
+        
+    return redirect('/topic/' + url_pas(name) + '/sub/' + url_pas(sub))    

+ 23 - 0
route/topic_top.py

@@ -0,0 +1,23 @@
+from .tool.func import *
+
+def topic_top_2(conn, name, sub, num):
+    curs = conn.cursor()
+    
+    if admin_check(3, 'notice (' + name + ' - ' + sub + '#' + str(num) + ')') != 1:
+        return re_error('/error/3')
+
+    curs.execute("select title from topic where title = ? and sub = ? and id = ?", [name, sub, str(num)])
+    if curs.fetchall():
+        curs.execute("select top from topic where id = ? and title = ? and sub = ?", [str(num), name, sub])
+        top_data = curs.fetchall()
+        if top_data:
+            if top_data[0][0] == 'O':
+                curs.execute("update topic set top = '' where title = ? and sub = ? and id = ?", [name, sub, str(num)])
+            else:
+                curs.execute("update topic set top = 'O' where title = ? and sub = ? and id = ?", [name, sub, str(num)])
+        
+        rd_plus(name, sub, get_time())
+
+        conn.commit()
+
+    return redirect('/topic/' + url_pas(name) + '/sub/' + url_pas(sub) + '#' + str(num))        

+ 92 - 0
route/upload.py

@@ -0,0 +1,92 @@
+from .tool.func import *
+
+def upload_2(conn):
+    curs = conn.cursor()
+
+    if ban_check() == 1:
+        return re_error('/ban')
+    
+    if flask.request.method == 'POST':
+        if captcha_post(flask.request.form.get('g-recaptcha-response', '')) == 1:
+            return re_error('/error/13')
+        else:
+            captcha_post('', 0)
+
+        data = flask.request.files.get('f_data', None)
+        if not data:
+            return re_error('/error/9')
+
+        if int(wiki_set(3)) * 1024 * 1024 < flask.request.content_length:
+            return re_error('/error/17')
+        
+        value = os.path.splitext(data.filename)[1]
+        if not value in ['.jpeg', '.jpg', '.gif', '.png', '.webp', '.JPEG', '.JPG', '.GIF', '.PNG', '.WEBP']:
+            return re_error('/error/14')
+    
+        if flask.request.form.get('f_name', None):
+            name = flask.request.form.get('f_name', None) + value
+        else:
+            name = data.filename
+        
+        piece = os.path.splitext(name)
+        if re.search('[^ㄱ-힣0-9a-zA-Z_\- ]', piece[0]):
+            return re_error('/error/22')
+
+        e_data = sha224(piece[0]) + piece[1]
+
+        curs.execute("select title from data where title = ?", ['file:' + name])
+        if curs.fetchall():
+            return re_error('/error/16')
+            
+        ip = ip_check()
+
+        if flask.request.form.get('f_lice', None):
+            lice = flask.request.form.get('f_lice', None)
+        else:
+            if custom()[2] == 0:
+                lice = ip
+            else:
+                lice = '[[user:' + ip + ']]'
+            
+        if os.path.exists(os.path.join(app_var['path_data_image'], e_data)):
+            os.remove(os.path.join(app_var['path_data_image'], e_data))
+            
+            data.save(os.path.join(app_var['path_data_image'], e_data))
+        else:
+            data.save(os.path.join(app_var['path_data_image'], e_data))
+            
+        curs.execute("select title from data where title = ?", ['file:' + name])
+        if curs.fetchall(): 
+            curs.execute("delete from data where title = ?", ['file:' + name])
+        
+        curs.execute("insert into data (title, data) values (?, ?)", ['file:' + name, '[[file:' + name + ']][br][br]{{{[[file:' + name + ']]}}}[br][br]' + lice])
+        curs.execute("insert into acl (title, dec, dis, why, view) values (?, 'admin', '', '', '')", ['file:' + name])
+
+        history_plus(
+            'file:' + name, '[[file:' + name + ']][br][br]{{{[[file:' + name + ']]}}}[br][br]' + lice,
+            get_time(), 
+            ip, 
+            '(upload)',
+            '0'
+        )
+        
+        conn.commit()
+        
+        return redirect('/w/file:' + name)      
+    else:
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [load_lang('upload'), wiki_set(), custom(), other2([0, 0])],
+            data =  '''
+                    <form method="post" enctype="multipart/form-data" accept-charset="utf8">
+                        <input type="file" name="f_data">
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('name') + '''" name="f_name" type="text">
+                        <hr class=\"main_hr\">
+                        <input placeholder="''' + load_lang('license') + '''" name="f_lice" type="text">
+                        <hr class=\"main_hr\">
+                        ''' + captcha_get() + '''
+                        <button id="save" type="submit">''' + load_lang('save') + '''</button>
+                    </form>
+                    ''',
+            menu = [['other', load_lang('return')]]
+        ))  

+ 66 - 0
route/user_admin.py

@@ -0,0 +1,66 @@
+from .tool.func import *
+
+def user_admin_2(conn, name):
+    curs = conn.cursor()
+
+    owner = admin_check()
+    
+    curs.execute("select acl from user where id = ?", [name])
+    user = curs.fetchall()
+    if not user:
+        return re_error('/error/2')
+    else:
+        if owner != 1:
+            curs.execute('select name from alist where name = ? and acl = "owner"', [user[0][0]])
+            if curs.fetchall():
+                return re_error('/error/3')
+
+            if ip_check() == name:
+                return re_error('/error/3')
+
+    if flask.request.method == 'POST':
+        if admin_check(7, 'admin (' + name + ')') != 1:
+            return re_error('/error/3')
+
+        if owner != 1:
+            curs.execute('select name from alist where name = ? and acl = "owner"', [flask.request.form.get('select', None)])
+            if curs.fetchall():
+                return re_error('/error/3')
+
+        if flask.request.form.get('select', None) == 'X':
+            curs.execute("update user set acl = 'user' where id = ?", [name])
+        else:
+            curs.execute("update user set acl = ? where id = ?", [flask.request.form.get('select', None), name])
+        
+        conn.commit()
+        
+        return redirect('/admin/' + url_pas(name))            
+    else:
+        if admin_check(7) != 1:
+            return re_error('/error/3')            
+
+        div = '<option value="X">X</option>'
+        
+        curs.execute('select distinct name from alist order by name asc')
+        for data in curs.fetchall():
+            if user[0][0] == data[0]:
+                div += '<option value="' + data[0] + '" selected="selected">' + data[0] + '</option>'
+            else:
+                if owner != 1:
+                    curs.execute('select name from alist where name = ? and acl = "owner"', [data[0]])
+                    if not curs.fetchall():
+                        div += '<option value="' + data[0] + '">' + data[0] + '</option>'
+                else:
+                    div += '<option value="' + data[0] + '">' + data[0] + '</option>'
+        
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + load_lang('authorize') + ')', 0])],
+            data =  '''
+                    <form method="post">
+                        <select name="select">''' + div + '''</select>
+                        <hr class=\"main_hr\">
+                        <button type="submit">''' + load_lang('save') + '''</button>
+                    </form>
+                    ''',
+            menu = [['manager', load_lang('return')]]
+        ))

+ 82 - 0
route/user_ban.py

@@ -0,0 +1,82 @@
+from .tool.func import *
+
+def user_ban_2(conn, name):
+    curs = conn.cursor()
+
+    if ip_or_user(name) == 0:
+        curs.execute("select acl from user where id = ?", [name])
+        user = curs.fetchall()
+        if not user:
+            return re_error('/error/2')
+
+        if user and user[0][0] != 'user':
+            if admin_check() != 1:
+                return re_error('/error/4')
+
+    if ban_check(ip = ip_check(), tool = 'login') == 1:
+        return re_error('/ban')
+                
+    if flask.request.method == 'POST':
+        if admin_check(1, 'ban (' + name + ')') != 1:
+            return re_error('/error/3')
+
+        if flask.request.form.get('limitless', '') == '':
+            end = flask.request.form.get('second', '0')
+        else:
+            end = '0'
+
+        ban_insert(name, end, flask.request.form.get('why', ''), flask.request.form.get('login', ''), ip_check())
+
+        return redirect('/ban/' + url_pas(name))     
+    else:
+        if admin_check(1) != 1:
+            return re_error('/error/3')
+
+        curs.execute("select end, why from ban where block = ?", [name])
+        end = curs.fetchall()
+        if end:
+            now = load_lang('release')
+
+            if end[0][0] == '':
+                data = '<ul><li>' + load_lang('limitless') + '</li>'
+            else:
+                data = '<ul><li>' + load_lang('period') + ' : ' + end[0][0] + '</li>'
+                
+            curs.execute("select block from ban where block = ? and login = 'O'", [name])
+            if curs.fetchall():
+                data += '<li>' + load_lang('login_able') + '</li>'
+
+            if end[0][1] != '':
+                data += '<li>' + load_lang('why') + ' : ' + end[0][1] + '</li></ul><hr class=\"main_hr\">'
+            else:
+                data += '</ul><hr class=\"main_hr\">'
+        else:
+            if re.search("^([0-9]{1,3}\.[0-9]{1,3})$", name):
+                now = load_lang('band_ban')
+            else:
+                now = load_lang('ban')
+                
+            if ip_or_user(name) == 1:
+                plus = '<input type="checkbox" name="login"> ' + load_lang('login_able') + '<hr class=\"main_hr\">'
+            else:
+                plus = ''
+
+            data =  '''
+                    <input placeholder="''' + load_lang('second') + '''" name="second" type="text">
+                    <hr class=\"main_hr\">
+                    <input type="checkbox" name="limitless"> ''' + load_lang('limitless') + '''
+                    <hr class=\"main_hr\">
+                    <input placeholder="''' + load_lang('why') + '''" name="why" type="text">
+                    <hr class=\"main_hr\">
+                    ''' + plus
+
+        return easy_minify(flask.render_template(skin_check(), 
+            imp = [name, wiki_set(), custom(), other2([' (' + now + ')', 0])],
+            data =  '''
+                    <form method="post">
+                        ''' + data + '''
+                        <button type="submit">''' + now + '''</button>
+                    </form>
+                    ''',
+            menu = [['manager', load_lang('return')]]
+        ))   

+ 92 - 0
route/user_check.py

@@ -0,0 +1,92 @@
+from .tool.func import *
+
+def user_check_2(conn, name):
+    curs = conn.cursor()
+
+    curs.execute("select acl from user where id = ? or id = ?", [name, flask.request.args.get('plus', '-')])
+    user = curs.fetchall()
+    if user and user[0][0] != 'user':
+        if admin_check() != 1:
+            return re_error('/error/4')
+
+    if admin_check(4, 'check (' + name + ')') != 1:
+        return re_error('/error/3')
+        
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+    
+    if flask.request.args.get('plus', None):
+        end_check = 1
+    
+        if ip_or_user(name) == 1:
+            if ip_or_user(flask.request.args.get('plus', None)) == 1:
+                curs.execute("select name, ip, ua, today from ua_d where ip = ? or ip = ? order by today desc limit ?, '50'", [name, flask.request.args.get('plus', None), sql_num])
+            else:
+                curs.execute("select name, ip, ua, today from ua_d where ip = ? or name = ? order by today desc limit ?, '50'", [name, flask.request.args.get('plus', None), sql_num])
+        else:
+            if ip_or_user(flask.request.args.get('plus', None)) == 1:
+                curs.execute("select name, ip, ua, today from ua_d where name = ? or ip = ? order by today desc limit ?, '50'", [name, flask.request.args.get('plus', None), sql_num])
+            else:
+                curs.execute("select name, ip, ua, today from ua_d where name = ? or name = ? order by today desc limit ?, '50'", [name, flask.request.args.get('plus', None), sql_num])
+    else:
+        end_check = 0
+        
+        if ip_or_user(name) == 1:
+            curs.execute("select name, ip, ua, today from ua_d where ip = ? order by today desc limit ?, '50'", [name, sql_num])
+        else:
+            curs.execute("select name, ip, ua, today from ua_d where name = ? order by today desc limit ?, '50'", [name, sql_num])
+    
+    record = curs.fetchall()
+    if record:
+        if not flask.request.args.get('plus', None):
+            div = '<a href="/manager/14?plus=' + url_pas(name) + '">(' + load_lang('compare') + ')</a><hr class=\"main_hr\">'
+        else:
+            div = '<a href="/check/' + url_pas(name) + '">(' + name + ')</a> <a href="/check/' + url_pas(flask.request.args.get('plus', None)) + '">(' + flask.request.args.get('plus', None) + ')</a><hr class=\"main_hr\">'
+
+        div +=  '''
+                <table id="main_table_set">
+                    <tbody>
+                        <tr>
+                            <td id="main_table_width">''' + load_lang('name') + '''</td>
+                            <td id="main_table_width">ip</td>
+                            <td id="main_table_width">''' + load_lang('time') + '''</td>
+                        </tr>
+                '''
+        
+        for data in record:
+            if data[2]:
+                ua = data[2]
+            else:
+                ua = '<br>'
+
+            div +=  '''
+                    <tr>
+                        <td>''' + ip_pas(data[0]) + '''</td>
+                        <td>''' + ip_pas(data[1]) + '''</td>
+                        <td>''' + data[3] + '''</td>
+                    </tr>
+                    <tr>
+                        <td colspan="3">''' + ua + '''</td>
+                    </tr>
+                    '''
+        
+        div +=  '''
+                    </tbody>
+                </table>
+                '''
+    else:
+        return re_error('/error/2')
+        
+    if end_check == 1:
+        div += next_fix('/check/' + url_pas(name) + '?plus=' + flask.request.args.get('plus', None) + '&num=', num, record)
+    else:
+        div += next_fix('/check/' + url_pas(name) + '?num=', num, record)
+            
+    return easy_minify(flask.render_template(skin_check(),    
+        imp = [load_lang('check'), wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['manager', load_lang('return')]]
+    ))

+ 101 - 0
route/user_info.py

@@ -0,0 +1,101 @@
+from .tool.func import *
+
+def user_info_2(conn):
+    curs = conn.cursor()
+
+    ip = ip_check()
+    
+    curs.execute("select acl from user where id = ?", [ip])
+    data = curs.fetchall()
+    if ban_check() == 0:
+        if data:
+            if data[0][0] != 'user':
+                acl = data[0][0]
+            else:
+                acl = load_lang('member')
+        else:
+            acl = load_lang('normal')
+    else:
+        acl = load_lang('blocked')
+
+        match = re.search("^([0-9]{1,3}\.[0-9]{1,3})", ip)
+        if match:
+            match = match.groups()[0]
+        else:
+            match = '-'
+
+        curs.execute("select end, login, band from ban where block = ? or block = ?", [ip, match])
+        block_data = curs.fetchall()
+        if block_data:
+            if block_data[0][0] != '':
+                acl += ' (' + load_lang('period') + ' : ' + block_data[0][0] + ')'
+            else:
+                acl += ' (' + load_lang('limitless') + ')'        
+
+            if block_data[0][1] != '':
+                acl += ' (' + load_lang('login_able') + ')'
+
+            if block_data[0][2] == 'O':
+                acl += ' (' + load_lang('band_blocked') + ')'
+            
+    if custom()[2] != 0:
+        ip_user = '<a href="/w/user:' + ip + '">' + ip + '</a>'
+        
+        plus =  '''
+                <li><a href="/logout">''' + load_lang('logout') + '''</a></li>
+                <li><a href="/change">''' + load_lang('user_setting') + '''</a></li>
+                '''
+        
+        curs.execute('select name from alarm where name = ? limit 1', [ip_check()])
+        if curs.fetchall():
+            plus2 = '<li><a href="/alarm">' + load_lang('alarm') + ' (O)</a></li>'
+        else:
+            plus2 = '<li><a href="/alarm">' + load_lang('alarm') + '</a></li>'
+
+        plus2 += '<li><a href="/watch_list">' + load_lang('watchlist') + '</a></li>'
+        plus3 = '<li><a href="/acl/user:' + url_pas(ip) + '">' + load_lang('user_document_acl') + '</a></li>'
+    else:
+        ip_user = ip
+        
+        plus =  '''
+                <li><a href="/login">''' + load_lang('login') + '''</a></li>
+                <li><a href="/register">''' + load_lang('register') + '''</a></li>
+                '''
+        plus2 = ''
+        plus3 = ''
+
+        curs.execute("select data from other where name = 'email_have'")
+        test = curs.fetchall()
+        if test and test[0][0] != '':
+            plus += '<li><a href="/pass_find">' + load_lang('password_search') + '</a></li>'
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('user') + ' ' + load_lang('tool'), wiki_set(), custom(), other2([0, 0])],
+        data =  '''
+                <h2>''' + load_lang('state') + '''</h2>
+                <ul>
+                    <li>''' + ip_user + ''' <a href="/record/''' + url_pas(ip) + '''">(''' + load_lang('record') + ''')</a></li>
+                    <li>''' + load_lang('state') + ''' : ''' + acl + '''</li>
+                </ul>
+                <br>
+                <h2>''' + load_lang('login') + '''</h2>
+                <ul>
+                    ''' + plus + '''
+                </ul>
+                <br>
+                <h2>''' + load_lang('tool') + '''</h2>
+                <ul>
+                    ''' + plus3 + '''
+                    <li><a href="/custom_head">''' + load_lang('user_head') + '''</a></li>
+                </ul>
+                <br>
+                <h2>''' + load_lang('other') + '''</h2>
+                <ul>
+                ''' + plus2 + '''
+                <li>
+                    <a href="/count">''' + load_lang('count') + '''</a>
+                </li>
+                </ul>
+                ''',
+        menu = 0
+    ))

+ 57 - 0
route/user_log.py

@@ -0,0 +1,57 @@
+from .tool.func import *
+
+def user_log_2(conn):
+    curs = conn.cursor()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+        
+    list_data = '<ul>'
+
+    admin_one = admin_check(1)
+    
+    curs.execute("select id, date from user order by date desc limit ?, '50'", [str(sql_num)])
+    user_list = curs.fetchall()
+    for data in user_list:
+        if admin_one == 1:
+            curs.execute("select block from ban where block = ?", [data[0]])
+            if curs.fetchall():
+                ban_button = ' <a href="/ban/' + url_pas(data[0]) + '">(' + load_lang('ban_release') + ')</a>'
+            else:
+                ban_button = ' <a href="/ban/' + url_pas(data[0]) + '">(' + load_lang('ban') + ')</a>'
+        else:
+            ban_button = ''
+            
+        list_data += '<li>' + ip_pas(data[0]) + ban_button
+        
+        if data[1] != '':
+            list_data += ' (' + data[1] + ')'
+
+        list_data += '</li>'
+
+    if num == 1:
+        curs.execute("select count(id) from user")
+        user_count = curs.fetchall()
+        if user_count:
+            count = user_count[0][0]
+        else:
+            count = 0
+
+        list_data +=    '''
+            </ul>
+            <hr class=\"main_hr\">
+            <ul>
+                <li>all : ''' + str(count) + '''</li>
+            </ul>
+        '''
+
+    list_data += next_fix('/user_log?num=', num, user_list)
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('member_list'), wiki_set(), custom(), other2([0, 0])],
+        data = list_data,
+        menu = [['other', load_lang('return')]]
+    ))

+ 32 - 0
route/user_tool.py

@@ -0,0 +1,32 @@
+from .tool.func import *
+
+def user_tool_2(conn, name):
+    curs = conn.cursor()
+    
+    data =  '''
+            <h2>''' + load_lang('tool') + '''</h2>
+            <ul>
+                <li><a href="/record/''' + url_pas(name) + '''">''' + load_lang('record') + '''</a></li>
+            </ul>
+            '''
+            
+    if admin_check(1) == 1:
+        curs.execute("select block from ban where block = ?", [name])
+        if curs.fetchall():
+            ban_name = load_lang('ban_release')
+        else:
+            ban_name = load_lang('ban')
+    
+        data += '''
+                <h2>''' + load_lang('admin') + '''</h2>
+                <ul>
+                    <li><a href="/ban/''' + url_pas(name) + '''">''' + ban_name + '''</a></li>
+                    <li><a href="/check/''' + url_pas(name) + '''">''' + load_lang('check') + '''</a></li>
+                </ul>
+                '''
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [name, wiki_set(), custom(), other2([' (' + load_lang('tool') + ')', 0])],
+        data = data,
+        menu = 0
+    ))

+ 55 - 0
route/user_topic_list.py

@@ -0,0 +1,55 @@
+from .tool.func import *
+
+def user_topic_list_2(conn, name):
+    curs = conn.cursor()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+    
+    one_admin = admin_check(1)
+
+    div =   '''
+            <table id="main_table_set">
+                <tbody>
+                    <tr>
+                        <td id="main_table_width">''' + load_lang('discussion_name') + '''</td>
+                        <td id="main_table_width">''' + load_lang('writer') + '''</td>
+                        <td id="main_table_width">''' + load_lang('time') + '''</td>
+                    </tr>
+            '''
+    
+    curs.execute("select title, id, sub, ip, date from topic where ip = ? order by date desc limit ?, '50'", [name, str(sql_num)])
+    data_list = curs.fetchall()
+    for data in data_list:
+        title = html.escape(data[0])
+        sub = html.escape(data[2])
+        
+        if one_admin == 1:
+            curs.execute("select * from ban where block = ?", [data[3]])
+            if curs.fetchall():
+                ban = ' <a href="/ban/' + url_pas(data[3]) + '">(' + load_lang('release') + ')</a>'
+            else:
+                ban = ' <a href="/ban/' + url_pas(data[3]) + '">(' + load_lang('ban') + ')</a>'
+        else:
+            ban = ''
+            
+        div += '<tr><td><a href="/topic/' + url_pas(data[0]) + '/sub/' + url_pas(data[2]) + '#' + data[1] + '">' + title + '#' + data[1] + '</a> (' + sub + ')</td>'
+        div += '<td>' + ip_pas(data[3]) + ban + '</td><td>' + data[4] + '</td></tr>'
+
+    div += '</tbody></table>'
+    div += next_fix('/topic_record/' + url_pas(name) + '?num=', num, data_list)      
+    
+    curs.execute("select end from ban where block = ?", [name])
+    if curs.fetchall():
+        sub = ' (' + load_lang('blocked') + ')'
+    else:
+        sub = 0 
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('discussion_record'), wiki_set(), custom(), other2([sub, 0])],
+        data = div,
+        menu = [['other', load_lang('other')], ['user', load_lang('user')], ['count/' + url_pas(name), load_lang('count')], ['record/' + url_pas(name), load_lang('record')]]
+    ))

+ 25 - 0
route/watch_list.py

@@ -0,0 +1,25 @@
+from .tool.func import *
+
+def watch_list_2(conn):
+    curs = conn.cursor()
+
+    div = 'limit : 10<hr class=\"main_hr\">'
+    
+    if custom()[2] == 0:
+        return redirect('/login')
+
+    curs.execute("select title from scan where user = ?", [ip_check()])
+    data = curs.fetchall()
+    for data_list in data:
+        div += '<li><a href="/w/' + url_pas(data_list[0]) + '">' + data_list[0] + '</a> <a href="/watch_list/' + url_pas(data_list[0]) + '">(' + load_lang('delete') + ')</a></li>'
+
+    if data:
+        div = '<ul>' + div + '</ul><hr class=\"main_hr\">'
+
+    div += '<a href="/manager/13">(' + load_lang('add') + ')</a>'
+
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [load_lang('watchlist'), wiki_set(), custom(), other2([0, 0])],
+        data = div,
+        menu = [['manager', load_lang('return')]]
+    ))

+ 24 - 0
route/watch_list_name.py

@@ -0,0 +1,24 @@
+from .tool.func import *
+
+def watch_list_name_2(conn, name):
+    curs = conn.cursor()
+    
+    if custom()[2] == 0:
+        return redirect('/login')
+
+    ip = ip_check()
+
+    curs.execute("select count(title) from scan where user = ?", [ip])
+    count = curs.fetchall()
+    if count and count[0][0] > 9:
+        return redirect('/watch_list')
+
+    curs.execute("select title from scan where user = ? and title = ?", [ip, name])
+    if curs.fetchall():
+        curs.execute("delete from scan where user = ? and title = ?", [ip, name])
+    else:
+        curs.execute("insert into scan (user, title) values (?, ?)", [ip, name])
+    
+    conn.commit()
+
+    return redirect('/watch_list')

+ 35 - 0
route/xref.py

@@ -0,0 +1,35 @@
+from .tool.func import *
+
+def xref_2(conn, name):
+    curs = conn.cursor()
+
+    num = int(number_check(flask.request.args.get('num', '1')))
+    if num * 50 > 0:
+        sql_num = num * 50 - 50
+    else:
+        sql_num = 0
+        
+    div = '<ul>'
+    
+    curs.execute("select link, type from back where title = ? and not type = 'cat' and not type = 'no' order by link asc limit ?, '50'", [name, str(sql_num)])
+    data_list = curs.fetchall()
+    for data in data_list:
+        div += '<li><a href="/w/' + url_pas(data[0]) + '">' + data[0] + '</a>'
+        
+        if data[1]:                
+            div += ' (' + data[1] + ')'
+        
+        curs.execute("select title from back where title = ? and type = 'include'", [data[0]])
+        db_data = curs.fetchall()
+        if db_data:
+            div += ' <a id="inside" href="/xref/' + url_pas(data[0]) + '">(' + load_lang('backlink') + ')</a>'
+
+        div += '</li>'
+      
+    div += '</ul>' + next_fix('/xref/' + url_pas(name) + '?num=', num, data_list)
+    
+    return easy_minify(flask.render_template(skin_check(), 
+        imp = [name, wiki_set(), custom(), other2([' (' + load_lang('backlink') + ')', 0])],
+        data = div,
+        menu = [['w/' + url_pas(name), load_lang('return')]]
+    ))

+ 0 - 6
views/easter_egg.html

@@ -1,6 +0,0 @@
-<iframe width="560" height="315" src="https://www.youtube.com/embed/O6BJiije6m4" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
-<br>
-<br>
-<div>
-    노라조와 영접하십셔.
-</div>

+ 2 - 1
views/main_css/main.css

@@ -31,4 +31,5 @@ s:hover, strike:hover, del:hover { color: gray; background-color: gainsboro; tex
 #main_table_width_half { width: 50%; }
 #main_table_width_quarter { width: 25%; }
 #redirect { border: 1px solid; padding: 10px; }
-body { word-break: break-all; overflow: scroll; }
+body { word-break: break-all; overflow: scroll; }
+hr.main_hr { border: none; }

+ 56 - 0
views/main_css/oauth.css

@@ -0,0 +1,56 @@
+.oauth-wrapper {
+    display: inline-block;
+}
+
+.oauth-list {
+    list-style: none;
+    padding: 0;
+}
+
+.oauth-list li {
+    display: inline-block;
+}
+
+.oauth-btn {
+    position: relative;
+    margin-left: 20px;
+    border: none;
+    display: inline-block;
+    padding: 25px;
+    padding-left: 75px;
+    border-radius: 10px;
+    color: #fff;
+}
+
+.oauth-btn a {
+    position: relative;
+}
+
+.oauth-btn.oauth-btn-facebook {
+    background-color: #3b5998;
+}
+
+.oauth-btn.oauth-btn-naver {
+    background-color: #00d337;
+}
+
+.oauth-btn-logo {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 10px;
+    height: 10px;
+    margin: 20px;
+    margin-left: 25px;
+    padding: 10px;
+    background-size: cover;
+    background-position: center center;
+}
+
+.oauth-btn-logo.oauth-btn-facebook {
+    background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJNOSA4aC0zdjRoM3YxMmg1di0xMmgzLjY0MmwuMzU4LTRoLTR2LTEuNjY3YzAtLjk1NS4xOTItMS4zMzMgMS4xMTUtMS4zMzNoMi44ODV2LTVoLTMuODA4Yy0zLjU5NiAwLTUuMTkyIDEuNTgzLTUuMTkyIDQuNjE1djMuMzg1eiIvPjwvc3ZnPg==);
+}
+
+.oauth-btn-logo.oauth-btn-naver {
+    background-image : url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEzLDI3VjIyOS45NzJIOTEuNTRWMTI2LjcwNmw3NC44NDQsMTAzLjI4NUwyNDQsMjI5Ljk3MlYyNy4wMDlIMTY2LjM4NFYxMjguNDg2TDkyLjQ2NCwyNy4wMDlaIi8+PC9zdmc+)
+}

+ 1 - 1
views/main_css/topic_reload.js

@@ -27,7 +27,7 @@ function topic_load(name, sub) {
                     console.log(xhr.responseText);
                     console.log(url);
 
-                    doc_data.innerText += '(new topic)\n\n';
+                    doc_data.innerText += '(New)\n\n';
 
                     clearInterval(test);
                 }

+ 14 - 7
views/neo_yousoro/css/main.css

@@ -3,7 +3,8 @@ body {
     word-break: break-all;
     word-wrap: break-word;
     margin: 0;
-    font-family: '나눔고딕', 'nanumgothic', "Nanum Gothic","KoPub Dotum","Malgun Gothic","맑은 고딕",sans-serif;
+    font-family: "나눔고딕", "nanumgothic", "Nanum Gothic", "KoPub Dotum", "Malgun Gothic", "맑은 고딕", "sans-serif";
+    overflow: auto;
 }
 
 ul {
@@ -50,8 +51,8 @@ li {
 #top {
     width: 100%;
     border-bottom: 2px solid gainsboro;
-    background: #fff;
-    padding: 10px;
+    background: skyblue;
+    padding: 15px;
     padding-right: 0;
     padding-left: 0;
     position: relative;
@@ -68,6 +69,7 @@ li {
     background: white;
     border-left: 2px solid gainsboro;
     border-right: 2px solid gainsboro;
+    min-height: 350px;
 }
 
 #bottom {
@@ -274,6 +276,7 @@ h6 {
     right: 0;
     background: white;
     border-left: 2px solid gainsboro;
+    z-index: 1;
 }
 
 #mobile_menu_main {
@@ -338,14 +341,14 @@ input {
 #go_toc {
     display: inline-block;
     padding: 10px;
-    border-left: 2px solid white;
+    border-left: 2px solid gainsboro;
     width: 25px;
 }
 
 #go_top {
     display: inline-block;
     padding: 10px;
-    border-right: 2px solid white;
+    border-right: 2px solid gainsboro;
     width: 25px;
 }
 
@@ -361,7 +364,11 @@ input {
     position: fixed;
     bottom: 0;
     right: 0;
-    border: 2px solid white;
-    background: gainsboro;
+    border: 2px solid gainsboro;
+    background: skyblue;
     text-align: center;
+}
+
+#nav_bar a {
+    color: black;
 }

+ 11 - 6
views/neo_yousoro/index.html

@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8">
@@ -5,9 +6,10 @@
         <link rel="stylesheet" href="/views/main_css/main.css">
         <link rel="stylesheet" href="/views/neo_yousoro/css/main.css">
         <script src="/views/neo_yousoro/js/main.js"></script>
+        <script src="/views/neo_yousoro/js/skin_set.js"></script>
         <link   rel="stylesheet"
-                href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
-                integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
+                href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
+                integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr"
                 crossorigin="anonymous">
         <link rel="shortcut icon" href="/views/main_css/file/favicon.ico">
         {{imp[1][5]|safe}}
@@ -31,7 +33,7 @@
                             <div id="recent_cel" class="cel_in_cel" style="display: none;">
                                 <a href="/recent_changes">
                                     <i class="fas fa-sync-alt"></i>
-                                    {{'change'|load_lang}}
+                                    {{'edit'|load_lang}}
                                 </a>
                                 <hr>
                                 <a href="/recent_discuss">
@@ -60,7 +62,7 @@
                                 <hr>
                                 <a href="/skin_set">
                                     <i class="fas fa-cog"></i>
-                                    {{'skin'|load_lang}} {{'setting'|load_lang}}
+                                    {{'skin_setting'|load_lang}}
                                 </a>
                             </div>
                         </div>
@@ -108,12 +110,12 @@
                         </a>
                     </div>
                     <h2>{{'recent'|load_lang}}</h2>
-                    <li><a href="/recent_changes">{{'change'|load_lang}}</a></li>
+                    <li><a href="/recent_changes">{{'edit'|load_lang}}</a></li>
                     <li><a href="/recent_discuss">{{'discussion'|load_lang}}</a></li>
                     <h2>{{'other'|load_lang}}</h2>
                     <li><a href="/random">{{'random'|load_lang}}</a></li>
                     <li><a href="/other">{{'tool'|load_lang}}</a></li>
-                    <li><a href="/skin_set">{{'skin'|load_lang}} {{'setting'|load_lang}}</a></li>
+                    <li><a href="/skin_set">{{'skin_setting'|load_lang}}</a></li>
                     <h2>{{'user'|load_lang}}</h2>
                     <li><a href="/user">{{imp[2][5]}}</a></li>
                 </div>
@@ -159,6 +161,9 @@
         <div id="bottom">
             <div id="bottom_main">
                 {{imp[1][1]|safe}}
+                <br>
+                <br>
+                <a href="https://github.com/2du/openNAMU"><img id="b_logo" src="/views/main_css/file/s_logo.png"></a>
             </div>
         </div>
         <div id="nav_bar">

+ 0 - 70
views/neo_yousoro/js/main.js

@@ -1,37 +1,3 @@
-// 쿠키 생성
-function setCookie(name, value, expiredays) {
-    var cookie = name + "=" + escape(value) + "; path=/;"
-    if (typeof expiredays != 'undefined') {
-        var todayDate = new Date();
-        todayDate.setDate(todayDate.getDate() + expiredays);
-        cookie += "expires=" + todayDate.toGMTString() + ";"
-    }
-    document.cookie = cookie;
-}
- 
-// 쿠키 획득
-function getCookie(name) {
-    name += "=";
-    var cookie = document.cookie;
-    var startIdx = cookie.indexOf(name);
-    if (startIdx != -1) {
-        startIdx += name.length;
-        var endIdx = cookie.indexOf(";", startIdx);
-        if (endIdx == -1) {
-            endIdx = cookie.length;
-            return unescape(cookie.substring(startIdx, endIdx));
-        }
-    }
-    return null;
-}
- 
-// 쿠키 삭제
-function deleteCookie(name) {
-    setCookie(name, "", -1);
-}
-
-// http://vip00112.tistory.com/33
-
 function opening(data) {
     var element = document.getElementById(data);
     if(element.style.display == 'none') {
@@ -39,40 +5,4 @@ function opening(data) {
     } else {
         element.style.display = 'none';
     }
-}
-
-function get_post() {
-    check = document.getElementById('strike');
-    if(check.checked == true) {
-        setCookie("set_strike", "1");
-    } else {
-        deleteCookie("set_strike");
-    }
-    
-    window.location.reload(true);
-}
-
-head_data = document.querySelector('head');
-if(getCookie("set_strike") == "1") {
-    head_data.innerHTML += '<style>s { display: none; }';
-}
-
-window.onload = function () {
-    if(window.location.pathname == '/skin_set') {
-        document.getElementById("main_top").innerHTML = '<h1>skin setting</h1>';
-        data = document.getElementById("main_data")
-        
-        set_data = {};
-        if(getCookie("set_strike") == "1") {
-            set_data["strike"] = "checked";
-        } 
-        
-        data.innerHTML =    `
-                            <input ` + set_data["strike"] + ` type="checkbox" id="strike" name="strike" value="strike"> remove strikethrough
-                            <hr>
-                            <button onclick="get_post();">Save</button>
-                            `;
-                            
-        document.title = document.title.replace(/.*(\- .*)$/, "skin setting $1");
-    }
 }

+ 69 - 0
views/neo_yousoro/js/skin_set.js

@@ -0,0 +1,69 @@
+// 쿠키 생성
+function setCookie(name, value, expiredays) {
+    var cookie = name + "=" + escape(value) + "; path=/;"
+    if (typeof expiredays != 'undefined') {
+        var todayDate = new Date();
+        todayDate.setDate(todayDate.getDate() + expiredays);
+        cookie += "expires=" + todayDate.toGMTString() + ";"
+    }
+    document.cookie = cookie;
+}
+ 
+// 쿠키 획득
+function getCookie(name) {
+    name += "=";
+    var cookie = document.cookie;
+    var startIdx = cookie.indexOf(name);
+    if (startIdx != -1) {
+        startIdx += name.length;
+        var endIdx = cookie.indexOf(";", startIdx);
+        if (endIdx == -1) {
+            endIdx = cookie.length;
+            return unescape(cookie.substring(startIdx, endIdx));
+        }
+    }
+    return null;
+}
+ 
+// 쿠키 삭제
+function deleteCookie(name) {
+    setCookie(name, "", -1);
+}
+
+// http://vip00112.tistory.com/33
+
+function get_post() {
+    check = document.getElementById('strike');
+    if(check.checked == true) {
+        setCookie("set_strike", "1");
+    } else {
+        deleteCookie("set_strike");
+    }
+    
+    window.location.reload(true);
+}
+
+head_data = document.querySelector('head');
+if(getCookie("set_strike") == "1") {
+    head_data.innerHTML += '<style>s { display: none; }';
+}
+
+window.onload = function () {
+    if(window.location.pathname == '/skin_set') {
+        document.getElementById("main_top").innerHTML = '<h1>Skin setting</h1>';
+        data = document.getElementById("main_data")
+        
+        set_data = {};
+        if(getCookie("set_strike") == "1") {
+            set_data["strike"] = "checked";
+        } 
+        
+        data.innerHTML = ' \
+            <input ' + set_data["strike"] + ' type="checkbox" id="strike" name="strike" value="strike"> Remove strikethrough \
+            <hr> \
+            <button onclick="get_post();">Save</button> \
+        ';
+                            
+        document.title = document.title.replace(/.*(\- .*)$/, "skin setting $1");
+    }
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů