Selaa lähdekoodia

히스토리 리셋을 위한 조치

잉여개발기 1 vuosi sitten
sitoutus
6afdf94edf
100 muutettua tiedostoa jossa 7338 lisäystä ja 0 poistoa
  1. 36 0
      .dockerignore
  2. 21 0
      .github/ISSUE_TEMPLATE.md
  3. 4 0
      .github/PULL_REQUEST_TEMPLATE.md
  4. BIN
      .github/logo.png
  5. 40 0
      .gitignore
  6. 10 0
      Docker-Install.md
  7. 22 0
      Dockerfile
  8. 22 0
      Dockerfile.arm64v8
  9. 22 0
      Dockerfile.arm64v8.ko
  10. 22 0
      Dockerfile.ko
  11. 29 0
      LICENSE
  12. 952 0
      app.py
  13. 47 0
      create_service.sh
  14. 0 0
      data/test
  15. 5 0
      docker_build.bat
  16. 274 0
      emergency_tool.py
  17. 801 0
      lang/en-US.json
  18. 30 0
      lang/help_tool.py
  19. 706 0
      lang/ko-KR.json
  20. 40 0
      readme-en.md
  21. 50 0
      readme.md
  22. 3 0
      requirements-optional.txt
  23. 15 0
      requirements.txt
  24. 228 0
      route/__init__.py
  25. 8 0
      route/api_image_view.py
  26. 65 0
      route/api_skin_info.py
  27. 79 0
      route/api_user_info.py
  28. 18 0
      route/api_version.py
  29. 39 0
      route/bbs_delete.py
  30. 41 0
      route/bbs_make.py
  31. 195 0
      route/bbs_w.py
  32. 30 0
      route/bbs_w_comment_tool.py
  33. 66 0
      route/bbs_w_delete.py
  34. 156 0
      route/bbs_w_edit.py
  35. 31 0
      route/bbs_w_hide.py
  36. 46 0
      route/bbs_w_pinned.py
  37. 216 0
      route/bbs_w_post.py
  38. 42 0
      route/bbs_w_tool.py
  39. 385 0
      route/edit.py
  40. 16 0
      route/edit_backlink_reset.py
  41. 67 0
      route/edit_delete.py
  42. 47 0
      route/edit_delete_file.py
  43. 39 0
      route/edit_delete_multiple.py
  44. 265 0
      route/edit_move.py
  45. 112 0
      route/edit_request.py
  46. 88 0
      route/edit_revert.py
  47. 162 0
      route/edit_upload.py
  48. 94 0
      route/filter_all.py
  49. 281 0
      route/filter_all_add.py
  50. 34 0
      route/filter_all_delete.py
  51. 121 0
      route/give_admin_groups.py
  52. 32 0
      route/give_delete_admin_group.py
  53. 183 0
      route/give_user_ban.py
  54. 74 0
      route/give_user_fix.py
  55. 9 0
      route/go_api_bbs.py
  56. 4 0
      route/go_api_bbs_list.py
  57. 9 0
      route/go_api_bbs_w.py
  58. 10 0
      route/go_api_bbs_w_comment.py
  59. 10 0
      route/go_api_bbs_w_comment_one.py
  60. 14 0
      route/go_api_bbs_w_set.py
  61. 12 0
      route/go_api_bbs_w_tabom.py
  62. 7 0
      route/go_api_func_auth.py
  63. 24 0
      route/go_api_func_ip.py
  64. 9 0
      route/go_api_func_ip_menu.py
  65. 13 0
      route/go_api_func_language.py
  66. 11 0
      route/go_api_func_llm.py
  67. 9 0
      route/go_api_func_search.py
  68. 7 0
      route/go_api_func_sha224.py
  69. 16 0
      route/go_api_give_auth.py
  70. 7 0
      route/go_api_list_acl.py
  71. 6 0
      route/go_api_list_auth.py
  72. 10 0
      route/go_api_list_history.py
  73. 6 0
      route/go_api_list_markup.py
  74. 8 0
      route/go_api_list_old_page.py
  75. 11 0
      route/go_api_list_recent_block.py
  76. 17 0
      route/go_api_list_recent_change.py
  77. 17 0
      route/go_api_list_recent_discuss.py
  78. 10 0
      route/go_api_list_recent_edit_request.py
  79. 7 0
      route/go_api_list_title_index.py
  80. 13 0
      route/go_api_setting.py
  81. 137 0
      route/go_api_topic.py
  82. 9 0
      route/go_api_topic_list.py
  83. 11 0
      route/go_api_user_rankup.py
  84. 15 0
      route/go_api_user_setting_editor.py
  85. 7 0
      route/go_api_w_page_view.py
  86. 4 0
      route/go_api_w_random.py
  87. 10 0
      route/go_api_w_raw.py
  88. 93 0
      route/go_api_w_render.py
  89. 8 0
      route/go_api_w_set_reset.py
  90. 10 0
      route/go_api_w_watch_list.py
  91. 9 0
      route/go_api_w_xref.py
  92. 34 0
      route/go_main_func_easter_egg.py
  93. 39 0
      route/list_acl.py
  94. 26 0
      route/list_admin.py
  95. 52 0
      route/list_admin_auth_use.py
  96. 34 0
      route/list_admin_group.py
  97. 65 0
      route/list_image_file.py
  98. 32 0
      route/list_long_page.py
  99. 30 0
      route/list_no_link.py
  100. 26 0
      route/list_please.py

+ 36 - 0
.dockerignore

@@ -0,0 +1,36 @@
+__pycache__
+/app_session
+
+data/set.json
+data/mysql.json
+data/oauthsettings.json
+data/version.json
+
+route/tool/set_mark/custom.py
+
+images
+
+.vscode
+.vs
+goorm.manifest
+.DS_Store
+
+*.db
+*.db-shm
+*.db-wal
+*.db-journal
+
+robots.txt
+custom.py
+404.html
+
+views/liberty
+views/buma
+views/before_namu
+views/acme
+views/sl_open
+views/nitori
+views/jsonnamu
+
+sitemap.xml
+sitemap_0.xml

+ 21 - 0
.github/ISSUE_TEMPLATE.md

@@ -0,0 +1,21 @@
+## 환경 (Environment)
+* 운영체제 (OS) :
+* 파이썬 버전 (Python version) :
+* 오픈나무 버전 (openNAMU version) :
+* 사용하는 브랜치 (Branch that you use) :
+* 스킨 (Skin) : 
+* 스킨 버전 (Skin version) : 
+
+<!-- 무언가 작동 안할 때는 캐시 초기화, 재시작을 먼저 해보세요. -->
+<!-- Try initializing the cache, restart first when something isn't working. -->
+
+## 설명 (Explanation)
+
+## 오류 내용 (Error contents)
+
+## 스크린샷 (Screenshot)
+
+<!-- 다 작성해주시면 오류 해결에 좀 더 빠른 시간이 소요됩니다. -->
+<!-- If you fill it out, it will take faster time to resolve the error. -->
+
+<!-- 완전 뜬금없는 내용이 아닌 한 단순 질문 올리셔도 됩니다. -->

+ 4 - 0
.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,4 @@
+<!--
+stable, beta, dev branch로 요청을 보내지 말아주세요. 개발은 dont_use branch에서 이루어집니다.
+Don't request merge your commit to stable, beta, dev branch, please request to dont_use branch.
+-->

BIN
.github/logo.png


+ 40 - 0
.gitignore

@@ -0,0 +1,40 @@
+__pycache__
+/app_session
+
+data/set.json
+data/mysql.json
+data/oauthsettings.json
+data/version.json
+
+route/tool/set_mark/custom.py
+
+images
+
+.vscode
+.vs
+goorm.manifest
+.DS_Store
+
+*.db
+*.db-shm
+*.db-wal
+*.db-journal
+file
+
+robots.txt
+custom.py
+404.html
+
+views/liberty
+views/buma
+views/before_namu
+views/acme
+views/sl_open
+views/nitori
+views/jsonnamu
+
+route_go/bin/*
+!route_go/bin/test
+
+sitemap.xml
+sitemap_0.xml

+ 10 - 0
Docker-Install.md

@@ -0,0 +1,10 @@
+## Installation
+```
+docker pull opennamu/opennamu
+```
+
+## Start
+```
+docker run -p 3000:3000 -v data:/app/data --name opennamu opennamu/opennamu
+docker run -p <host-port>:3000 -v <host-data_directory>:/app/data --name <docker-containername> opennamu/opennamu
+```

+ 22 - 0
Dockerfile

@@ -0,0 +1,22 @@
+FROM python:3.11.10-slim
+
+MAINTAINER 2du <min08101@naver.com>
+MAINTAINER hoparkgo9ma <me@ho9.me>
+
+ENV NAMU_DB_TYPE sqlite
+ENV NAMU_DB data
+ENV NAMU_HOST 0.0.0.0
+ENV NAMU_PORT 3000
+ENV NAMU_GOLANGPORT 3001
+ENV NAMU_LANG en-US
+ENV NAMU_MARKUP namumark
+ENV NAMU_ENCRYPT sha3
+ENV NAMU_DOCKER O
+
+ADD . /app
+WORKDIR /app
+
+RUN pip install -r requirements.txt
+EXPOSE 3000
+
+CMD [ "python", "./app.py" ]

+ 22 - 0
Dockerfile.arm64v8

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

+ 22 - 0
Dockerfile.arm64v8.ko

@@ -0,0 +1,22 @@
+FROM arm64v8/python:3.11.10-slim
+
+MAINTAINER 2du <min08101@naver.com>
+MAINTAINER hoparkgo9ma <me@ho9.me>
+
+ENV NAMU_DB_TYPE sqlite
+ENV NAMU_DB data
+ENV NAMU_HOST 0.0.0.0
+ENV NAMU_PORT 3000
+ENV NAMU_GOLANGPORT 3001
+ENV NAMU_LANG ko-KR
+ENV NAMU_MARKUP namumark
+ENV NAMU_ENCRYPT sha3
+ENV NAMU_DOCKER O
+
+ADD . /app
+WORKDIR /app
+
+RUN pip install -r requirements.txt
+EXPOSE 3000
+
+CMD [ "python", "./app.py" ]

+ 22 - 0
Dockerfile.ko

@@ -0,0 +1,22 @@
+FROM python:3.11.10-slim
+
+MAINTAINER 2du <min08101@naver.com>
+MAINTAINER hoparkgo9ma <me@ho9.me>
+
+ENV NAMU_DB_TYPE sqlite
+ENV NAMU_DB data
+ENV NAMU_HOST 0.0.0.0
+ENV NAMU_PORT 3000
+ENV NAMU_GOLANGPORT 3001
+ENV NAMU_LANG ko-KR
+ENV NAMU_MARKUP namumark
+ENV NAMU_ENCRYPT sha3
+ENV NAMU_DOCKER O
+
+ADD . /app
+WORKDIR /app
+
+RUN pip install -r requirements.txt
+EXPOSE 3000
+
+CMD [ "python", "./app.py" ]

+ 29 - 0
LICENSE

@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2017-2021, surplus-dev
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 952 - 0
app.py

@@ -0,0 +1,952 @@
+# Init
+import os
+import re
+import signal
+import atexit
+import logging
+
+from route.tool.func import *
+from route import *
+
+args = sys.argv
+run_mode = ''
+if len(args) > 1:
+    run_mode = args[1]
+
+    if not run_mode in ('dev'):
+        run_mode = ''
+
+# Init-Version
+with open('version.json', encoding = 'utf8') as file_data:
+    version_list = orjson.loads(file_data.read())
+
+# Init-DB
+data_db_set = class_check_json()
+do_db_set(data_db_set)
+
+with class_temp_db() as m_conn:
+    m_conn.execute('pragma journal_mode = DELETE')
+
+with get_db_connect(init_mode = True) as conn:
+    curs = conn.cursor()
+
+    setup_tool = ''
+    try:
+        curs.execute(db_change('select data from other where name = "ver"'))
+    except:
+        setup_tool = 'init'
+
+    if setup_tool != 'init':
+        ver_set_data = curs.fetchall()
+        if ver_set_data:
+            if int(version_list['c_ver']) > int(ver_set_data[0][0]):
+                setup_tool = 'update'
+            else:
+                setup_tool = 'normal'
+        else:
+            setup_tool = 'init'
+
+    if setup_tool != 'normal' and run_mode != 'dev':
+        file_name = linux_exe_chmod()
+        local_file_path = os.path.join("route_go", "bin", file_name)
+
+        if os.path.exists(local_file_path):
+            print('Remove Old Binary')
+            os.remove(local_file_path)
+
+        download_url = version_list["bin_link"] + file_name
+
+        print('Download New Binary File')
+        response = requests.get(download_url, stream = True)
+        if response.status_code == 200:
+            with open(local_file_path, 'wb') as file:
+                for chunk in response.iter_content(chunk_size = 8192):
+                    file.write(chunk)
+
+            print('Complete Download')
+
+    if data_db_set['type'] == 'mysql':
+        try:
+            curs.execute(db_change('create database ' + data_db_set['name'] + ' default character set utf8mb4'))
+        except:
+            try:
+                curs.execute(db_change('alter database ' + data_db_set['name'] + ' character set utf8mb4'))
+            except:
+                pass
+
+        conn.select_db(data_db_set['name'])
+    else:
+        conn.execute('pragma journal_mode = DELETE')
+
+    if setup_tool != 'normal':
+        create_data = get_db_table_list()
+        for create_table in create_data:
+            for create in ['test'] + create_data[create_table]:
+                db_pass = 0
+
+                try:
+                    curs.execute(db_change('select ' + create + ' from ' + create_table + ' limit 1'))
+                    db_pass = 1
+                except:
+                    pass
+
+                field_text = 'longtext' if data_db_set['type'] == 'mysql' else 'text'
+
+                if db_pass == 0:
+                    try:
+                        curs.execute(db_change('create table ' + create_table + '(test ' + field_text + ' default (""))'))
+                        db_pass = 1
+                    except Exception as e:
+                        # print(e)
+                        pass
+
+                if db_pass == 0:
+                    try:
+                        curs.execute(db_change('create table ' + create_table + '(test ' + field_text + ' default "")'))
+                        db_pass = 1
+                    except Exception as e:
+                        # print(e)
+                        pass
+
+                if db_pass == 0:
+                    try:
+                        curs.execute(db_change('create table ' + create_table + '(test ' + field_text + ')'))
+                        db_pass = 1
+                    except Exception as e:
+                        # print(e)
+                        pass
+
+                if db_pass == 0:
+                    try:
+                        curs.execute(db_change("alter table " + create_table + " add column " + create + " " + field_text + " default ('')"))
+                        db_pass = 1
+                    except Exception as e:
+                        # print(e)
+                        pass
+
+                if db_pass == 0:
+                    try:
+                        curs.execute(db_change("alter table " + create_table + " add column " + create + " " + field_text + " default ''"))
+                        db_pass = 1
+                    except Exception as e:
+                        # print(e)
+                        pass
+
+                if db_pass == 0:
+                    try:
+                        curs.execute(db_change("alter table " + create_table + " add column " + create + " " + field_text))
+                        db_pass = 1
+                    except Exception as e:
+                        # print(e)
+                        pass
+
+                if db_pass == 0:
+                    raise
+        try:
+            curs.execute(db_change("create index history_index on history (title, ip)"))
+        except:
+            pass
+
+        if setup_tool == 'update':
+            update(conn, int(ver_set_data[0][0]), data_db_set)
+        else:
+            set_init(conn)
+
+    set_init_always(conn, version_list['c_ver'], run_mode)
+
+    # Init-Route
+    class EverythingConverter(werkzeug.routing.PathConverter):
+        def __init__(self, map):
+            super(EverythingConverter, self).__init__(map)
+            self.regex = r'.*?'
+
+        def to_python(self, value):
+            return re.sub(r'^\\\.', '.', value)
+
+    class RegexConverter(werkzeug.routing.BaseConverter):
+        def __init__(self, url_map, *items):
+            super(RegexConverter, self).__init__(url_map)
+            self.regex = items[0]
+
+    app = flask.Flask(__name__, template_folder = './')
+
+    app.config['JSON_AS_ASCII'] = False
+    app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
+    app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
+    if run_mode == 'dev':
+        app.config['TEMPLATES_AUTO_RELOAD'] = True
+        app.config['DEBUG'] = True
+        app.config['ENV'] = 'development'
+
+    log = logging.getLogger('waitress')
+    log.setLevel(logging.ERROR)
+
+    app.jinja_env.filters['md5_replace'] = md5_replace
+    app.jinja_env.filters['load_lang'] = load_lang
+    app.jinja_env.filters['cut_100'] = cut_100
+
+    app.url_map.converters['everything'] = EverythingConverter
+    app.url_map.converters['regex'] = RegexConverter
+
+    curs.execute(db_change('select data from other where name = "key"'))
+    sql_data = curs.fetchall()
+    app.secret_key = sql_data[0][0]
+
+    # Init-DB_Data
+    server_set = {}
+    server_set_var = get_init_set_list()
+    server_set_env = {
+        'host' : os.getenv('NAMU_HOST'),
+        'golang_port' : os.getenv('NAMU_GOLANGPORT'),
+        'port' : os.getenv('NAMU_PORT'),
+        'language' : os.getenv('NAMU_LANG'),
+        'markup' : os.getenv('NAMU_MARKUP'),
+        'encode' : os.getenv('NAMU_ENCRYPT')
+    }
+    for i in server_set_var:
+        curs.execute(db_change('select data from other where name = ?'), [i])
+        server_set_val = curs.fetchall()
+        if server_set_val:
+            server_set_val = server_set_val[0][0]
+        elif server_set_env[i] != None:
+            server_set_val = server_set_env[i]
+
+            curs.execute(db_change('insert into other (name, data, coverage) values (?, ?, "")'), [i, server_set_env[i]])
+        else:
+            if 'list' in server_set_var[i]:
+                print(server_set_var[i]['display'] + ' (' + server_set_var[i]['default'] + ') [' + ', '.join(server_set_var[i]['list']) + ']' + ' : ', end = '')
+            else:
+                print(server_set_var[i]['display'] + ' (' + server_set_var[i]['default'] + ') : ', end = '')
+
+            server_set_val = input()
+            if server_set_val == '':
+                server_set_val = server_set_var[i]['default']
+            elif server_set_var[i]['require'] == 'select':
+                if not server_set_val in server_set_var[i]['list']:
+                    server_set_val = server_set_var[i]['default']
+
+            curs.execute(db_change('insert into other (name, data, coverage) values (?, ?, "")'), [i, server_set_val])
+
+        print(server_set_var[i]['display'] + ' : ' + server_set_val)
+
+        server_set[i] = server_set_val
+        
+with class_temp_db() as m_conn:
+    m_curs = m_conn.cursor()
+        
+    for for_a in server_set:
+        m_curs.execute('insert into temp (name, data) values (?, ?)', ['setup_' + for_a, server_set[for_a]])
+
+###
+
+if platform.system() == 'Linux':
+    if platform.machine() in ["AMD64", "x86_64"]:
+        cmd = [os.path.join(".", "route_go", "bin", "main.amd64.bin")]
+    else:
+        cmd = [os.path.join(".", "route_go", "bin", "main.arm64.bin")]
+elif platform.system() == 'Darwin':
+    cmd = [os.path.join(".", "route_go", "bin", "main.mac.arm64.bin")]
+else:
+    if platform.machine() in ["AMD64", "x86_64"]:
+        cmd = [os.path.join(".", "route_go", "bin", "main.amd64.exe")]
+    else:
+        cmd = [os.path.join(".", "route_go", "bin", "main.arm64.exe")]
+        
+if run_mode != '':
+    cmd += [run_mode]
+
+def golang_process_check():
+    with class_temp_db() as m_conn:
+        m_curs = m_conn.cursor()
+        
+        m_curs.execute('select data from temp where name = "setup_golang_port"')
+        db_data = m_curs.fetchall()
+        db_data = db_data[0][0] if db_data else "3001"
+        
+        while True:
+            try:
+                response = requests.post('http://localhost:' + db_data + '/', data = "test {}")
+                if response.status_code == 200:
+                    print('Golang turn on')
+                    break
+            except requests.ConnectionError:
+                print('Wait golang...')
+                time.sleep(1)
+
+golang_process = subprocess.Popen(cmd)
+golang_process_check()
+
+###
+
+def back_up(data_db_set):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+    
+        try:
+            curs.execute(db_change('select data from other where name = "back_up"'))
+            back_time = curs.fetchall()
+            back_time = float(number_check(back_time[0][0], True)) if back_time and back_time[0][0] != '' else 0
+
+            curs.execute(db_change('select data from other where name = "backup_count"'))
+            back_up_count = curs.fetchall()
+            back_up_count = int(number_check(back_up_count[0][0])) if back_up_count and back_up_count[0][0] != '' else 3
+
+            if back_time != 0:
+                curs.execute(db_change('select data from other where name = "backup_where"'))
+                back_up_where = curs.fetchall()
+                back_up_where = back_up_where[0][0] if back_up_where and back_up_where[0][0] != '' else data_db_set['name'] + '.db'
+
+                print('Back up state : ' + str(back_time) + ' hours')
+                print('Back up directory : ' + back_up_where)
+                if back_up_count != 0:
+                    print('Back up max number : ' + str(back_up_count))
+
+                    file_dir = os.path.split(back_up_where)[0]
+                    file_dir = '.' if file_dir == '' else file_dir
+                    
+                    file_name = os.path.split(back_up_where)[1]
+                    file_name = re.sub(r'\.db$', '_[0-9]{14}.db', file_name)
+
+                    backup_file = [for_a for for_a in os.listdir(file_dir) if re.search('^' + file_name + '$', for_a)]
+                    backup_file = sorted(backup_file)
+                    
+                    if len(backup_file) >= back_up_count:
+                        remove_dir = os.path.join(file_dir, backup_file[0])
+                        os.remove(remove_dir)
+                        print('Back up : Remove (' + remove_dir + ')')
+
+                now_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+                new_file_name = re.sub(r'\.db$', '_' + now_time + '.db', back_up_where)
+                shutil.copyfile(
+                    data_db_set['name'] + '.db', 
+                    new_file_name
+                )
+
+                print('Back up : OK (' + new_file_name + ')')
+            else:
+                print('Back up state : Turn off')
+
+                back_time = 1
+        except Exception as e:
+            print('Back up : Error')
+            print(e)
+
+            back_time = 1
+
+        threading.Timer(60 * 60 * back_time, back_up, [data_db_set]).start()
+
+def do_every_day():
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+        
+        # 오늘의 날짜 불러오기
+        time_today = get_time().split()[0]
+    
+        # vote 관리
+        curs.execute(db_change('select id, type from vote where type = "open" or type = "n_open"'))
+        for for_a in curs.fetchall():
+            curs.execute(db_change('select data from vote where id = ? and name = "end_date" and type = "option"'), [for_a[0]])
+            db_data = curs.fetchall()
+            if db_data:
+                time_db = db_data[0][0].split()[0]
+                if time_today > time_db:
+                    curs.execute(db_change("update vote set type = ? where user = '' and id = ? and type = ?"), ['close' if for_a[1] == 'open' else 'n_close', for_a[0], for_a[1]])
+
+        # ban 관리
+        curs.execute(db_change("update rb set ongoing = '' where end < ? and end != '' and ongoing = '1'"), [get_time()])
+
+        # auth 관리
+        curs.execute(db_change('select id, data from user_set where name = "auth_date"'))
+        db_data = curs.fetchall()
+        for for_a in db_data:
+            time_db = for_a[1].split()[0]
+            if time_today > time_db:
+                curs.execute(db_change("update user_set set data = 'user' where id = ? and name = 'acl'"), [for_a[0]])
+                curs.execute(db_change('delete from user_set where name = "auth_date" and id = ?'), [for_a[0]])
+                
+        # acl 관리
+        curs.execute(db_change("select doc_name, doc_rev, set_data from data_set where set_name = 'acl_date'"))
+        db_data = curs.fetchall()
+        for for_a in db_data:
+            time_db = for_a[2].split()[0]
+            if time_today > time_db:
+                curs.execute(db_change("delete from acl where title = ? and type = ?"), [for_a[0], for_a[1]])
+                curs.execute(db_change("delete from data_set where doc_name = ? and doc_rev = ? and set_name = 'acl_date'"), [for_a[0], for_a[1]])
+                
+        # ua 관리
+        curs.execute(db_change('select data from other where name = "ua_expiration_date"'))
+        db_data = curs.fetchall()
+        if db_data and db_data[0][0] != '':
+            time_db = int(number_check(db_data[0][0]))
+            
+            time_calc = datetime.date.today() - datetime.timedelta(days = time_db)
+            time_calc = time_calc.strftime('%Y-%m-%d %H:%M:%S')
+            
+            curs.execute(db_change("delete from ua_d where today < ?"), [time_calc])
+            
+        # auth history 관리
+        curs.execute(db_change('select data from other where name = "auth_history_expiration_date"'))
+        db_data = curs.fetchall()
+        if db_data and db_data[0][0] != '':
+            time_db = int(number_check(db_data[0][0]))
+            
+            time_calc = datetime.date.today() - datetime.timedelta(days = time_db)
+            time_calc = time_calc.strftime('%Y-%m-%d %H:%M:%S')
+            
+            curs.execute(db_change("delete from re_admin where time < ?"), [time_calc])
+
+        # 사이트맵 생성 관리
+        curs.execute(db_change('select data from other where name = "sitemap_auto_make"'))
+        db_data = curs.fetchall()
+        if db_data and db_data[0][0] != '':
+            main_setting_sitemap(1)
+
+            print('Make sitemap')
+
+        # 칭호 관리
+        curs.execute(db_change("select id from user_set where name = 'user_title' and data = '✅'"))
+        for for_a in curs.fetchall():
+            if acl_check('', 'all_admin_auth', '', for_a[0]) == 1:
+                curs.execute(db_change("update user_set set data = '☑️' where name = 'user_title' and data = '✅' and id = ?"), [for_a[0]])
+
+        threading.Timer(60 * 60 * 24, do_every_day).start()
+
+def auto_do_something(data_db_set):
+    if data_db_set['type'] == 'sqlite':
+        back_up(data_db_set)
+
+    do_every_day()
+
+auto_do_something(data_db_set)
+
+print('Now running... http://localhost:' + server_set['port'])
+
+@app.before_request
+def before_request_func():
+    with class_temp_db() as m_conn:
+        m_curs = m_conn.cursor()
+        
+        m_curs.execute('select data from temp where name = "wiki_access_password"')
+        db_data = m_curs.fetchall()
+        if db_data:
+            access_password = db_data[0][0]
+            input_password = flask.request.cookies.get('opennamu_wiki_access', ' ')
+            if url_pas(access_password) != input_password:
+                with get_db_connect() as conn:
+                    return '''
+                        <script>
+                            "use strict";
+                            function opennamu_do_wiki_access() {
+                                let password = document.getElementById('wiki_access').value;
+                                document.cookie = 'opennamu_wiki_access=' + encodeURIComponent(password) + '; path=/;';
+                                history.go(0);
+                            }
+                        </script>
+                        <h2>''' + get_lang(conn, 'error_password_require_for_wiki_access') + '''</h2>
+                        <input type="password" id="wiki_access">
+                        <input type="submit" onclick="opennamu_do_wiki_access();">
+                    '''
+
+# Init-custom
+if os.path.exists('custom.py'):
+    from custom import custom_run
+    custom_run('error', app)
+
+# Func
+# Func-inter_wiki
+app.route('/filter/inter_wiki', defaults = { 'tool' : 'inter_wiki' })(filter_all)
+app.route('/filter/inter_wiki/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'inter_wiki' })(filter_all_add)
+app.route('/filter/inter_wiki/add/<everything:name>', methods = ['POST', 'GET'], defaults = { 'tool' : 'inter_wiki' })(filter_all_add)
+app.route('/filter/inter_wiki/del/<everything:name>', defaults = { 'tool' : 'inter_wiki' })(filter_all_delete)
+
+app.route('/filter/outer_link', defaults = { 'tool' : 'outer_link' })(filter_all)
+app.route('/filter/outer_link/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'outer_link' })(filter_all_add)
+app.route('/filter/outer_link/add/<everything:name>', methods = ['POST', 'GET'], defaults = { 'tool' : 'outer_link' })(filter_all_add)
+app.route('/filter/outer_link/del/<everything:name>', defaults = { 'tool' : 'outer_link' })(filter_all_delete)
+
+app.route('/filter/document', defaults = { 'tool' : 'document' })(filter_all)
+app.route('/filter/document/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'document' })(filter_all_add)
+app.route('/filter/document/add/<everything:name>', methods = ['POST', 'GET'], defaults = { 'tool' : 'document' })(filter_all_add)
+app.route('/filter/document/del/<everything:name>', defaults = { 'tool' : 'document' })(filter_all_delete)
+
+app.route('/filter/edit_top', defaults = { 'tool' : 'edit_top' })(filter_all)
+app.route('/filter/edit_top/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'edit_top' })(filter_all_add)
+app.route('/filter/edit_top/add/<everything:name>', methods = ['POST', 'GET'], defaults = { 'tool' : 'edit_top' })(filter_all_add)
+app.route('/filter/edit_top/del/<everything:name>', defaults = { 'tool' : 'edit_top' })(filter_all_delete)
+
+app.route('/filter/image_license', defaults = { 'tool' : 'image_license' })(filter_all)
+app.route('/filter/image_license/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'image_license' })(filter_all_add)
+app.route('/filter/image_license/del/<everything:name>', defaults = { 'tool' : 'image_license' })(filter_all_delete)
+
+app.route('/filter/template', defaults = { 'tool' : 'template' })(filter_all)
+app.route('/filter/template/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'template' })(filter_all_add)
+app.route('/filter/template/add/<everything:name>', methods = ['POST', 'GET'], defaults = { 'tool' : 'template' })(filter_all_add)
+app.route('/filter/template/del/<everything:name>', defaults = { 'tool' : 'template' })(filter_all_delete)
+
+app.route('/filter/edit_filter', defaults = { 'tool' : 'edit_filter' })(filter_all)
+app.route('/filter/edit_filter/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'edit_filter' })(filter_all_add)
+app.route('/filter/edit_filter/add/<everything:name>', methods = ['POST', 'GET'], defaults = { 'tool' : 'edit_filter' })(filter_all_add)
+app.route('/filter/edit_filter/del/<everything:name>', defaults = { 'tool' : 'edit_filter' })(filter_all_delete)
+
+app.route('/filter/email_filter', defaults = { 'tool' : 'email_filter' })(filter_all)
+app.route('/filter/email_filter/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'email_filter' })(filter_all_add)
+app.route('/filter/email_filter/del/<everything:name>', defaults = { 'tool' : 'email_filter' })(filter_all_delete)
+
+app.route('/filter/file_filter', defaults = { 'tool' : 'file_filter' })(filter_all)
+app.route('/filter/file_filter/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'file_filter' })(filter_all_add)
+app.route('/filter/file_filter/del/<everything:name>', defaults = { 'tool' : 'file_filter' })(filter_all_delete)
+
+app.route('/filter/name_filter', defaults = { 'tool' : 'name_filter' })(filter_all)
+app.route('/filter/name_filter/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'name_filter' })(filter_all_add)
+app.route('/filter/name_filter/del/<everything:name>', defaults = { 'tool' : 'name_filter' })(filter_all_delete)
+
+app.route('/filter/extension_filter', defaults = { 'tool' : 'extension_filter' })(filter_all)
+app.route('/filter/extension_filter/add', methods = ['POST', 'GET'], defaults = { 'tool' : 'extension_filter' })(filter_all_add)
+app.route('/filter/extension_filter/del/<everything:name>', defaults = { 'tool' : 'extension_filter' })(filter_all_delete)
+
+# Func-list
+app.route('/list/document/old', defaults = { 'set_type' : 'old' })(list_old_page)
+app.route('/list/document/old/<int:num>', defaults = { 'set_type' : 'old' })(list_old_page)
+
+app.route('/list/document/new', defaults = { 'set_type' : 'new' })(list_old_page)
+app.route('/list/document/new/<int:num>', defaults = { 'set_type' : 'new' })(list_old_page)
+
+app.route('/list/document/no_link')(list_no_link)
+app.route('/list/document/no_link/<int:num>')(list_no_link)
+
+app.route('/list/document/acl')(list_acl)
+app.route('/list/document/acl/<int:arg_num>')(list_acl)
+
+app.route('/list/document/need')(list_please)
+app.route('/list/document/need/<int:arg_num>')(list_please)
+
+app.route('/list/document/all')(list_title_index)
+app.route('/list/document/all/<int:num>')(list_title_index)
+
+app.route('/list/document/long')(list_long_page)
+app.route('/list/document/long/<int:arg_num>')(list_long_page)
+
+app.route('/list/document/short', defaults = { 'tool' : 'short_page' })(list_long_page)
+app.route('/list/document/short/<int:arg_num>', defaults = { 'tool' : 'short_page' })(list_long_page)
+
+app.route('/list/file')(list_image_file)
+app.route('/list/file/<int:arg_num>')(list_image_file)
+app.route('/list/image', defaults = { 'do_type' : 1 })(list_image_file)
+app.route('/list/image/<int:arg_num>', defaults = { 'do_type' : 1 })(list_image_file)
+
+app.route('/list/admin')(list_admin)
+
+app.route('/list/admin/auth_use', methods = ['POST', 'GET'])(list_admin_auth_use)
+app.route('/list/admin/auth_use_page/<int:arg_num>/<everything:arg_search>', methods = ['POST', 'GET'])(list_admin_auth_use)
+
+app.route('/list/user')(list_user)
+app.route('/list/user/<int:arg_num>')(list_user)
+
+app.route('/list/user/check_submit/<name>')(list_user_check_submit)
+app.route('/list/user/check/<name>')(list_user_check)
+app.route('/list/user/check/<name>/<do_type>')(list_user_check)
+app.route('/list/user/check/<name>/<do_type>/<int:arg_num>')(list_user_check)
+app.route('/list/user/check/<name>/<do_type>/<int:arg_num>/<plus_name>')(list_user_check)
+app.route('/list/user/check/delete/<name>/<ip>/<time>/<do_type>', methods = ['POST', 'GET'])(list_user_check_delete)
+
+# Func-auth
+app.route('/auth/give', methods = ['POST', 'GET'])(give_auth)
+app.route('/auth/give_total', methods = ['POST', 'GET'])(give_auth)
+app.route('/auth/give/<user_name>', methods = ['POST', 'GET'])(give_auth)
+
+app.route('/auth/ban', methods = ['POST', 'GET'])(give_user_ban)
+app.route('/auth/ban/multiple', methods = ['POST', 'GET'], defaults = { 'ban_type' : 'multiple' })(give_user_ban)
+app.route('/auth/ban/<everything:name>', methods = ['POST', 'GET'])(give_user_ban)
+app.route('/auth/ban_cidr/<everything:name>', methods = ['POST', 'GET'], defaults = { 'ban_type' : 'cidr' })(give_user_ban)
+app.route('/auth/ban_regex/<everything:name>', methods = ['POST', 'GET'], defaults = { 'ban_type' : 'regex' })(give_user_ban)
+
+# /auth/list
+# /auth/list/add/<name>
+# /auth/list/delete/<name>
+app.route('/auth/list')(list_admin_group_2)
+app.route('/auth/list/add/<name>', methods = ['POST', 'GET'])(give_admin_groups)
+app.route('/auth/list/delete/<name>', methods = ['POST', 'GET'])(give_delete_admin_group_2)
+
+app.route('/auth/give/fix/<user_name>', methods = ['POST', 'GET'])(give_user_fix)
+
+app.route('/app_submit', methods = ['POST', 'GET'])(recent_app_submit_2)
+
+# /auth/history
+app.route('/recent_block')(list_recent_block)
+app.route('/recent_block/all')(list_recent_block)
+app.route('/recent_block/all/<int:num>')(list_recent_block)
+app.route('/recent_block/all/<int:num>/<everything:why>')(list_recent_block)
+app.route('/recent_block/user/<user_name>', defaults = { 'tool' : 'user' })(list_recent_block)
+app.route('/recent_block/user/<user_name>/<int:num>', defaults = { 'tool' : 'user' })(list_recent_block)
+app.route('/recent_block/admin/<user_name>', defaults = { 'tool' : 'admin' })(list_recent_block)
+app.route('/recent_block/admin/<user_name>/<int:num>', defaults = { 'tool' : 'admin' })(list_recent_block)
+app.route('/recent_block/regex', defaults = { 'tool' : 'regex' })(list_recent_block)
+app.route('/recent_block/regex/<int:num>', defaults = { 'tool' : 'regex' })(list_recent_block)
+app.route('/recent_block/cidr', defaults = { 'tool' : 'cidr' })(list_recent_block)
+app.route('/recent_block/cidr/<int:num>', defaults = { 'tool' : 'cidr' })(list_recent_block)
+app.route('/recent_block/private', defaults = { 'tool' : 'private' })(list_recent_block)
+app.route('/recent_block/private/<int:num>', defaults = { 'tool' : 'private' })(list_recent_block)
+app.route('/recent_block/ongoing', defaults = { 'tool' : 'ongoing' })(list_recent_block)
+app.route('/recent_block/ongoing/<int:num>', defaults = { 'tool' : 'ongoing' })(list_recent_block)
+
+app.route('/recent_change')(list_recent_change)
+app.route('/recent_changes')(list_recent_change)
+app.route('/recent_change/<int:num>/<set_type>')(list_recent_change)
+
+app.route('/recent_discuss', defaults = { 'tool' : 'normal' })(list_recent_discuss)
+app.route('/recent_discuss/<int:num>/<tool>')(list_recent_discuss)
+
+# Func-history
+app.route('/recent_edit_request')(recent_edit_request)
+
+app.route('/record/<name>', defaults = { 'tool' : 'record' })(recent_change)
+app.route('/record/<int:num>/<set_type>/<name>', defaults = { 'tool' : 'record' })(recent_change)
+
+app.route('/record/reset/<name>', methods = ['POST', 'GET'])(recent_record_reset)
+app.route('/record/topic/<name>')(recent_record_topic)
+
+app.route('/record/bbs/<name>', defaults = { 'tool' : 'record' })(bbs_w)
+app.route('/record/bbs_comment/<name>', defaults = { 'tool' : 'comment_record' })(bbs_w)
+
+app.route('/history/<everything:doc_name>', methods = ['POST', 'GET'])(list_history)
+app.route('/history_page/<int:num>/<set_type>/<everything:doc_name>', methods = ['POST', 'GET'])(list_history)
+
+app.route('/history_tool/<int(signed = True):rev>/<everything:name>')(recent_history_tool)
+app.route('/history_delete/<int(signed = True):rev>/<everything:name>', methods = ['POST', 'GET'])(recent_history_delete)
+app.route('/history_hidden/<int(signed = True):rev>/<everything:name>')(recent_history_hidden)
+app.route('/history_send/<int(signed = True):rev>/<everything:name>', methods = ['POST', 'GET'])(recent_history_send)
+app.route('/history_reset/<everything:name>', methods = ['POST', 'GET'])(recent_history_reset)
+app.route('/history_add/<everything:name>', methods = ['POST', 'GET'])(recent_history_add)
+
+# Func-view
+app.route('/xref/<everything:name>')(view_xref)
+app.route('/xref_page/<int:num>/<everything:name>')(view_xref)
+app.route('/xref_this/<everything:name>', defaults = { 'xref_type' : 2 })(view_xref)
+app.route('/xref_this_page/<int:num>/<everything:name>', defaults = { 'xref_type' : 2 })(view_xref)
+
+app.route('/doc_watch_list/<int:num>/<everything:name>')(w_watch_list)
+app.route('/doc_star_doc/<int:num>/<everything:name>', defaults = { 'do_type' : 'star_doc' })(w_watch_list)
+
+app.route('/raw/<everything:name>')(view_w_raw)
+app.route('/raw_acl/<everything:name>', defaults = { 'doc_acl' : 'on' })(view_w_raw)
+app.route('/raw_rev/<int(signed = True):rev>/<everything:name>')(view_w_raw)
+
+app.route('/diff/<int(signed = True):num_a>/<int(signed = True):num_b>/<everything:name>')(view_diff)
+
+app.route('/down/<everything:name>')(view_down)
+
+app.route('/acl_multiple', defaults = { 'multiple' : True }, methods = ['POST', 'GET'])(view_set)
+app.route('/acl/<everything:name>', methods = ['POST', 'GET'])(view_set)
+
+app.route('/w_from/<everything:name>', defaults = { 'do_type' : 'from' })(view_w)
+app.route('/w/<everything:name>')(view_w)
+
+app.route('/random')(view_random)
+
+# Func-edit
+app.route('/edit/<everything:name>', methods = ['POST', 'GET'])(edit)
+app.route('/edit_from/<everything:name>', methods = ['POST', 'GET'], defaults = { 'do_type' : 'load' })(edit)
+app.route('/edit_section/<int:section>/<everything:name>', methods = ['POST', 'GET'])(edit)
+
+app.route('/edit_request/<everything:name>', methods = ['POST', 'GET'])(edit_request)
+app.route('/edit_request_from/<everything:name>', defaults = { 'do_type' : 'from' }, methods = ['POST', 'GET'])(edit_request)
+
+# app.route('/edit_request_rev/<int:rev>/<everything:name>', methods = ['POST', 'GET'])(edit_request)
+
+app.route('/upload', methods = ['POST', 'GET'])(edit_upload)
+
+# 개편 예정
+app.route('/xref_reset/<everything:name>')(edit_backlink_reset)
+
+app.route('/delete/<everything:name>', methods = ['POST', 'GET'])(edit_delete)
+app.route('/delete_file/<everything:name>', methods = ['POST', 'GET'])(edit_delete_file)
+app.route('/delete_multiple', methods = ['POST', 'GET'])(edit_delete_multiple)
+
+app.route('/revert/<int:num>/<everything:name>', methods = ['POST', 'GET'])(edit_revert)
+
+app.route('/move/<everything:name>', methods = ['POST', 'GET'])(edit_move)
+app.route('/move_all')(edit_move_all)
+
+# Func-topic
+app.route('/topic/<everything:name>', methods = ['POST', 'GET'])(topic_list)
+app.route('/topic_page/<int:page>/<everything:name>', methods = ['POST', 'GET'])(topic_list)
+
+app.route('/thread/<int:topic_num>', methods = ['POST', 'GET'])(topic)
+app.route('/thread/0/<everything:doc_name>', defaults = { 'topic_num' : '0' }, methods = ['POST', 'GET'])(topic)
+
+app.route('/thread/<int:topic_num>/tool')(topic_tool)
+app.route('/thread/<int:topic_num>/setting', methods = ['POST', 'GET'])(topic_tool_setting)
+app.route('/thread/<int:topic_num>/acl', methods = ['POST', 'GET'])(topic_tool_acl)
+app.route('/thread/<int:topic_num>/delete', methods = ['POST', 'GET'])(topic_tool_delete)
+app.route('/thread/<int:topic_num>/change', methods = ['POST', 'GET'])(topic_tool_change)
+
+app.route('/thread/<int:topic_num>/comment/<int:num>/tool')(topic_comment_tool)
+app.route('/thread/<int:topic_num>/comment/<int:num>/notice')(topic_comment_notice)
+app.route('/thread/<int:topic_num>/comment/<int:num>/blind')(topic_comment_blind)
+app.route('/thread/<int:topic_num>/comment/<int:num>/raw')(view_raw)
+app.route('/thread/<int:topic_num>/comment/<int:num>/delete', methods = ['POST', 'GET'])(topic_comment_delete)
+
+# Func-user
+app.route('/change', methods = ['POST', 'GET'])(user_setting)
+app.route('/change/key')(user_setting_key)
+app.route('/change/key/delete')(user_setting_key_delete)
+app.route('/change/pw', methods = ['POST', 'GET'])(user_setting_pw)
+app.route('/change/head', methods = ['GET', 'POST'], defaults = { 'skin_name' : '' })(user_setting_head)
+app.route('/change/head/<skin_name>', methods = ['GET', 'POST'])(user_setting_head)
+app.route('/change/head_reset', methods = ['GET', 'POST'])(user_setting_head_reset)
+app.route('/change/skin_set')(user_setting_skin_set)
+app.route('/change/top_menu', methods = ['GET', 'POST'])(user_setting_top_menu)
+app.route('/change/user_name', methods = ['GET', 'POST'])(user_setting_user_name)
+app.route('/change/user_name/<user_name>', methods = ['GET', 'POST'])(user_setting_user_name)
+# 하위 호환용 S
+app.route('/skin_set')(user_setting_skin_set)
+# 하위 호환용 E
+app.route('/change/skin_set/main', methods = ['POST', 'GET'])(user_setting_skin_set_main)
+
+app.route('/user')(user_info)
+app.route('/user/<name>')(user_info)
+
+app.route('/challenge', methods = ['GET', 'POST'])(user_challenge)
+app.route('/rankup')(user_rankup)
+
+app.route('/edit_filter/<name>', methods = ['GET', 'POST'])(user_edit_filter)
+
+app.route('/count')(user_count)
+app.route('/count/<name>')(user_count)
+
+app.route('/alarm')(user_alarm)
+app.route('/alarm/delete')(user_alarm_delete)
+app.route('/alarm/delete/<int:id>')(user_alarm_delete)
+
+app.route('/watch_list', defaults = { 'tool' : 'watch_list' })(user_watch_list)
+app.route('/watch_list/<everything:name>', defaults = { 'tool' : 'watch_list' })(user_watch_list_name)
+app.route('/watch_list_from/<everything:name>', defaults = { 'tool' : 'watch_list_from' })(user_watch_list_name)
+
+app.route('/star_doc', defaults = { 'tool' : 'star_doc' })(user_watch_list)
+app.route('/star_doc/<everything:name>', defaults = { 'tool' : 'star_doc' })(user_watch_list_name)
+app.route('/star_doc_from/<everything:name>', defaults = { 'tool' : 'star_doc_from' })(user_watch_list_name)
+
+# 개편 보류중 S
+app.route('/change/email', methods = ['POST', 'GET'])(user_setting_email_2)
+app.route('/change/email/delete')(user_setting_email_delete)
+app.route('/change/email/check', methods = ['POST', 'GET'])(user_setting_email_check_2)
+# 개편 보류중 E
+
+# Func-login
+# 개편 예정
+
+# login -> login/2fa -> login/2fa/email with login_id
+# register -> register/email -> regiter/email/check with reg_id
+# pass_find -> pass_find/email with find_id
+
+app.route('/login', methods = ['POST', 'GET'])(login_login_2)
+app.route('/login/2fa', methods = ['POST', 'GET'])(login_login_2fa_2)
+app.route('/register', methods = ['POST', 'GET'])(login_register_2)
+app.route('/register/email', methods = ['POST', 'GET'])(login_register_email_2)
+app.route('/register/email/check', methods = ['POST', 'GET'])(login_register_email_check_2)
+app.route('/register/submit', methods = ['POST', 'GET'])(login_register_submit_2)
+
+app.route('/login/find')(login_find)
+app.route('/login/find/key', methods = ['POST', 'GET'])(login_find_key)
+app.route('/login/find/email', methods = ['POST', 'GET'], defaults = { 'tool' : 'pass_find' })(login_find_email)
+app.route('/login/find/email/check', methods = ['POST', 'GET'], defaults = { 'tool' : 'check_key' })(login_find_email_check)
+app.route('/logout')(login_logout)
+
+# Func-vote
+app.route('/vote/<int:num>', methods = ['POST', 'GET'])(vote_select)
+app.route('/vote/end/<int:num>')(vote_end)
+app.route('/vote/close/<int:num>')(vote_close)
+app.route('/vote', defaults = { 'list_type' : 'normal' })(vote_list)
+app.route('/vote/list', defaults = { 'list_type' : 'normal' })(vote_list)
+app.route('/vote/list/<int:num>', defaults = { 'list_type' : 'normal' })(vote_list)
+app.route('/vote/list/close', defaults = { 'list_type' : 'close' })(vote_list)
+app.route('/vote/list/close/<int:num>', defaults = { 'list_type' : 'close' })(vote_list)
+app.route('/vote/add', methods = ['POST', 'GET'])(vote_add)
+
+# Func-bbs
+app.route('/bbs/main')(bbs_main)
+app.route('/bbs/make', methods = ['POST', 'GET'])(bbs_make)
+app.route('/bbs/in/<int:bbs_num>')(bbs_in)
+app.route('/bbs/in/<int:bbs_num>/<int:page>')(bbs_in)
+# app.route('/bbs/blind/<int:bbs_num>', methods = ['POST', 'GET'])(bbs_hide)
+app.route('/bbs/delete/<int:bbs_num>', methods = ['POST', 'GET'])(bbs_delete)
+app.route('/bbs/set/<int:bbs_num>', methods = ['POST', 'GET'])(bbs_w_set)
+app.route('/bbs/edit/<int:bbs_num>', methods = ['POST', 'GET'])(bbs_w_edit)
+app.route('/bbs/w/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_post)
+# app.route('/bbs/blind/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_hide)
+app.route('/bbs/pinned/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_pinned)
+app.route('/bbs/delete/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_delete)
+app.route('/bbs/raw/<int:bbs_num>/<int:post_num>')(view_raw)
+app.route('/bbs/tool/<int:bbs_num>/<int:post_num>')(bbs_w_tool)
+app.route('/bbs/edit/<int:bbs_num>/<int:post_num>', methods = ['POST', 'GET'])(bbs_w_edit)
+app.route('/bbs/tool/<int:bbs_num>/<int:post_num>/<comment_num>')(bbs_w_comment_tool)
+app.route('/bbs/raw/<int:bbs_num>/<int:post_num>/<comment_num>')(view_raw)
+app.route('/bbs/edit/<int:bbs_num>/<int:post_num>/<comment_num>', methods = ['POST', 'GET'])(bbs_w_edit)
+app.route('/bbs/delete/<int:bbs_num>/<int:post_num>/<comment_num>', methods = ['POST', 'GET'])(bbs_w_delete)
+
+# Func-api
+## v1 API
+app.route('/api/render', methods = ['POST'])(api_w_render)
+app.route('/api/render/<tool>', methods = ['POST'])(api_w_render)
+
+app.route('/api/raw_exist/<everything:name>', defaults = { 'exist_check' : 'on' })(api_w_raw)
+app.route('/api/raw_rev/<int(signed = True):rev>/<everything:name>')(api_w_raw)
+app.route('/api/raw/<everything:name>')(api_w_raw)
+
+app.route('/api/xref/<int:page>/<everything:name>')(api_w_xref)
+app.route('/api/xref_this/<int:page>/<everything:name>', defaults = { 'xref_type' : '2' })(api_w_xref)
+
+app.route('/api/random')(api_w_random)
+
+app.route('/api/bbs/w/<sub_code>')(api_bbs_w)
+app.route('/api/bbs/w/comment/<sub_code>')(api_bbs_w_comment)
+app.route('/api/bbs/w/comment_one/<sub_code>')(api_bbs_w_comment_one)
+
+app.route('/api/version', defaults = { 'version_list' : version_list })(api_version)
+app.route('/api/skin_info')(api_skin_info)
+app.route('/api/skin_info/<name>')(api_skin_info)
+app.route('/api/user_info/<user_name>')(api_user_info)
+
+app.route('/api/thread/<int:topic_num>/<int:s_num>/<int:e_num>')(api_topic)
+app.route('/api/thread/<int:topic_num>/<tool>')(api_topic)
+app.route('/api/thread/<int:topic_num>')(api_topic)
+
+app.route('/api/search/<everything:name>')(api_func_search)
+app.route('/api/search_page/<int:num>/<everything:name>')(api_func_search)
+app.route('/api/search_data/<everything:name>', defaults = { 'search_type' : 'data' })(api_func_search)
+app.route('/api/search_data_page/<int:num>/<everything:name>', defaults = { 'search_type' : 'data' })(api_func_search)
+
+app.route('/api/recent_change')(api_list_recent_change)
+app.route('/api/recent_changes')(api_list_recent_change)
+app.route('/api/recent_change/<int:limit>')(api_list_recent_change)
+app.route('/api/recent_change/<int:limit>/<set_type>/<int:num>')(api_list_recent_change)
+
+app.route('/api/recent_edit_request')(api_list_recent_edit_request)
+app.route('/api/recent_edit_request/<int:limit>/<set_type>/<int:num>')(api_list_recent_edit_request)
+
+app.route('/api/recent_discuss/<set_type>/<int:limit>')(api_list_recent_discuss)
+app.route('/api/recent_discuss/<int:limit>')(api_list_recent_discuss)
+app.route('/api/recent_discuss')(api_list_recent_discuss)
+
+app.route('/api/lang', methods = ['POST'])(api_func_language)
+app.route('/api/lang/<data>')(api_func_language)
+app.route('/api/sha224/<everything:data>')(api_func_sha224)
+app.route('/api/ip/<everything:data>')(api_func_ip)
+
+app.route('/api/image/<everything:name>')(api_image_view)
+
+## v2 API
+app.route('/api/v2/recent_edit_request/<set_type>/<int:num>', defaults = { 'limit' : 50 })(api_list_recent_edit_request)
+app.route('/api/v2/recent_change/<set_type>/<int:num>', defaults = { 'legacy' : '', 'limit' : 50 })(api_list_recent_change)
+app.route('/api/v2/recent_discuss/<set_type>/<int:num>', defaults = { 'legacy' : '', 'limit' : 50 })(api_list_recent_discuss)
+app.route('/api/v2/recent_block/<set_type>/<int:num>')(api_list_recent_block)
+app.route('/api/v2/recent_block/<set_type>/<int:num>/<everything:why>')(api_list_recent_block)
+app.route('/api/v2/recent_block_user/<set_type>/<int:num>/<user_name>')(api_list_recent_block)
+app.route('/api/v2/recent_block_user/<set_type>/<int:num>/<user_name>/<everything:why>')(api_list_recent_block)
+app.route('/api/v2/list/document/old/<int:num>', defaults = { 'set_type' : 'old' })(api_list_old_page)
+app.route('/api/v2/list/document/new/<int:num>', defaults = { 'set_type' : 'new' })(api_list_old_page)
+app.route('/api/v2/list/document/<int:num>')(api_list_title_index)
+app.route('/api/v2/list/auth')(api_list_auth)
+app.route('/api/v2/list/markup')(api_list_markup)
+app.route('/api/v2/list/acl/<data_type>')(api_list_acl)
+app.route('/api/v2/history/<int:num>/<set_type>/<everything:doc_name>')(api_list_history)
+
+app.route('/api/v2/topic/<int:num>/<set_type>/<everything:name>')(api_topic_list)
+
+app.route('/api/v2/bbs')(api_bbs_list)
+app.route('/api/v2/bbs/main')(api_bbs)
+app.route('/api/v2/bbs/set/<int:bbs_num>/<name>', methods = ['GET', 'PUT'])(api_bbs_w_set)
+app.route('/api/v2/bbs/in/<int:bbs_num>/<int:page>')(api_bbs)
+app.route('/api/v2/bbs/w/<sub_code>', defaults = { 'legacy' : '' })(api_bbs_w)
+app.route('/api/v2/bbs/w/tabom/<sub_code>', methods = ['GET', 'POST'])(api_bbs_w_tabom)
+app.route('/api/v2/bbs/w/comment/<sub_code>/<tool>', defaults = { 'legacy' : '' })(api_bbs_w_comment)
+app.route('/api/v2/bbs/w/comment_one/<sub_code>/<tool>', defaults = { 'legacy' : '' })(api_bbs_w_comment_one)
+
+app.route('/api/v2/doc_star_doc/<int:num>/<everything:name>', defaults = { 'do_type' : 'star_doc' })(api_w_watch_list)
+app.route('/api/v2/doc_watch_list/<int:num>/<everything:name>')(api_w_watch_list)
+app.route('/api/v2/set_reset/<everything:name>')(api_w_set_reset)
+app.route('/api/v2/page_view/<everything:name>')(api_w_page_view)
+
+app.route('/api/v2/setting/<name>', methods = ['GET', 'PUT'])(api_setting)
+
+app.route('/api/v2/auth')(api_func_auth)
+app.route('/api/v2/auth/<user_name>')(api_func_auth)
+app.route('/api/v2/auth/give', methods = ['PATCH'])(api_give_auth)
+
+app.route('/api/v2/user/rankup', methods = ['GET', 'PATCH'])(api_user_rankup)
+app.route('/api/v2/user/setting/editor', methods = ['GET', 'POST', 'DELETE'])(api_user_setting_editor)
+
+app.route('/api/v2/ip/<everything:data>', methods = ['GET', 'POST'])(api_func_ip)
+app.route('/api/v2/ip_menu/<everything:ip>', defaults = { 'option' : 'user' }, methods = ['GET', 'POST'])(api_func_ip_menu)
+app.route('/api/v2/user_menu/<everything:ip>')(api_func_ip_menu)
+app.route('/api/v2/lang', defaults = { 'legacy' : '' }, methods = ['POST'])(api_func_language)
+
+# Func-main
+# 여기도 전반적인 조정 시행 예정
+app.route('/other')(main_tool_other)
+app.route('/manager', methods = ['POST', 'GET'])(main_tool_admin)
+app.route('/manager/<int:num>', methods = ['POST', 'GET'])(main_tool_redirect)
+app.route('/manager/<int:num>/<everything:add_2>', methods = ['POST', 'GET'])(main_tool_redirect)
+app.route('/redirect_to/<int:n>')(main_redirect)
+
+app.route('/search', methods=['POST'])(main_search)
+app.route('/search/<everything:name>', methods = ['POST', 'GET'])(main_search_deep)
+app.route('/search_page/<int:num>/<everything:name>', methods = ['POST', 'GET'])(main_search_deep)
+app.route('/search_data/<everything:name>', defaults = { 'search_type' : 'data' }, methods = ['POST', 'GET'])(main_search_deep)
+app.route('/search_data_page/<int:num>/<everything:name>', defaults = { 'search_type' : 'data' }, methods = ['POST', 'GET'])(main_search_deep)
+app.route('/goto', methods=['POST'])(main_search_goto)
+app.route('/goto/<everything:name>', methods=['GET', 'POST'])(main_search_goto)
+
+app.route('/setting')(main_setting)
+app.route('/setting/main', methods = ['POST', 'GET'])(main_setting_main)
+app.route('/setting/main/logo', methods = ['POST', 'GET'])(main_setting_main_logo)
+app.route('/setting/top_menu', methods = ['POST', 'GET'])(main_setting_top_menu)
+app.route('/setting/phrase', methods = ['POST', 'GET'])(main_setting_phrase)
+app.route('/setting/head', defaults = { 'num' : 3 }, methods = ['POST', 'GET'])(main_setting_head)
+app.route('/setting/head/<skin_name>', defaults = { 'num' : 3 }, methods = ['POST', 'GET'])(main_setting_head)
+app.route('/setting/body/top', defaults = { 'num' : 4 }, methods = ['POST', 'GET'])(main_setting_head)
+app.route('/setting_preview/body/top', defaults = { 'num' : 4, 'set_preview' : 1 }, methods = ['POST'])(main_setting_head)
+app.route('/setting/body/bottom', defaults = { 'num' : 7 }, methods = ['POST', 'GET'])(main_setting_head)
+app.route('/setting_preview/body/bottom', defaults = { 'num' : 7, 'set_preview' : 1 }, methods = ['POST'])(main_setting_head)
+app.route('/setting/robot', methods = ['POST', 'GET'])(main_setting_robot)
+app.route('/setting/external', methods = ['POST', 'GET'])(main_setting_external)
+app.route('/setting/sitemap', methods = ['POST', 'GET'])(main_setting_sitemap)
+app.route('/setting/sitemap_set', methods = ['POST', 'GET'])(main_setting_sitemap_set)
+app.route('/setting/skin_set', methods = ['POST', 'GET'])(main_setting_skin_set)
+app.route('/setting/404_page', methods = ['POST', 'GET'])(setting_404_page)
+
+app.route('/easter_egg')(main_func_easter_egg)
+
+# views -> view
+app.route('/view/<path:name>')(main_view)
+app.route('/views/<path:name>')(main_view)
+app.route('/image/<path:name>')(main_view_image)
+# 조정 계획 중
+app.route('/<regex("[^.]+\\.(?:txt|xml|ico)"):data>')(main_view_file)
+
+app.route('/shutdown', methods = ['POST', 'GET'])(main_sys_shutdown)
+app.route('/restart', methods = ['POST', 'GET'])(main_sys_restart)
+app.route('/update', methods = ['POST', 'GET'])(main_sys_update)
+
+app.errorhandler(404)(main_func_error_404)
+
+def terminate_golang():
+    if golang_process.poll() is None:
+        golang_process.terminate()
+
+def signal_handler(signal, frame):
+    terminate_golang()
+    os._exit(0)
+
+signal.signal(signal.SIGTERM, signal_handler)
+signal.signal(signal.SIGINT, signal_handler)
+
+atexit.register(terminate_golang)
+
+if __name__ == "__main__":
+    waitress.serve(
+        app,
+        host = server_set['host'],
+        port = int(server_set['port']),
+        clear_untrusted_proxy_headers = True
+    )

+ 47 - 0
create_service.sh

@@ -0,0 +1,47 @@
+#!/bin/Bash
+
+#1단계 : 작업 디렉토리 & 서비스 명 받기
+read -p "설치된 디렉토리 (ex: /mnt/openNAMU) : " working_directory
+echo $working_directory
+
+read -p "원하는 서비스명 (ex: opennamu): " service_name
+echo $service_name
+
+read -p "설명 (ex: OpenNAMU 서비스입니다): " description
+echo $description
+
+read -p "로그위치 (ex: /var/log/openNAMU.log) : " log_path
+echo $log_path
+
+
+#2단계 : 파일제작
+cat <<EOF > /lib/systemd/system/${service_name}.service
+[Unit]
+Description=$description
+
+[Service]
+Type=simple
+
+WorkingDirectory=$working_directory
+ExecStart=/usr/bin/python3 $working_directory/app.py
+Restart=on-failure
+PIDFile=/run/$service_name.pid
+
+#rsyslog 사용
+#StandardOutput=syslog
+#StandardError=syslog
+#SyslogIdentifier=$service_name
+
+#systemctl 245 이후 로깅
+StandardOutput=append:$log_path
+StandardError=append:$log_path
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+
+#3단계 : 서비스 확인
+systemctl daemon-reload 
+systemctl start $service_name
+systemctl status $service_name

+ 0 - 0
data/test


+ 5 - 0
docker_build.bat

@@ -0,0 +1,5 @@
+docker build . -t opennamu/opennamu:dev
+docker build -t opennamu/opennamu:dev-ko -f Dockerfile.ko .
+docker login
+docker push opennamu/opennamu:dev
+docker push opennamu/opennamu:dev-ko

+ 274 - 0
emergency_tool.py

@@ -0,0 +1,274 @@
+# Load
+import time
+import os
+import platform
+import urllib
+import zipfile
+import urllib.request
+
+from route.tool.func import *
+
+while True:
+    data_db_load = input('Load DB (Y) [Y, N] : ')
+    data_db_load = data_db_load.upper()
+    if data_db_load in ('Y', 'N'):
+        break
+
+if data_db_load == 'Y':
+    data_db_set = class_check_json()
+    do_db_set(data_db_set)
+
+    load_db = get_db_connect()
+
+    conn = load_db.__enter__()
+    curs = conn.cursor()
+else:
+    print('You can use [9, 11, 19]')
+
+# Main
+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. Set db version')
+print('9. Delete set.json')
+print('10. Change name')
+print('11. Delete mysql.json')
+print('14. Delete Main <HEAD>')
+print('15. Give owner')
+print('16. Delete 2FA password')
+print('17. Change markup')
+print('18. Change wiki access password')
+print('19. Forced update')
+print('20. Change domain')
+print('21. Change TLS')
+print('22. Delete body top')
+print('23. Delete body bottom')
+print('24. SQLite to MySQL')
+print('25. Recalc exist data_set')
+print('26. Change update branch')
+print('27. Change golang port')
+
+what_i_do = input('Insert selection number (EX : 9) : ')
+if what_i_do == '1':
+    try:
+        go_num = int(input('Count (100) : '))
+    except ValueError:
+        go_num = 100
+
+    curs.execute(db_change('select count(*) from data'))
+    count = curs.fetchall()
+    count = count[0][0] if count else 0
+
+    num = 0
+    title = []
+    print('Load...')
+
+    while num == 0 or len(title) == 100:
+        curs.execute(db_change('select title from data limit ?, 100'), [num])
+        title = curs.fetchall()
+
+        print('Rest : ' + str(count - num))
+        print('Start : ' + title[0][0])
+        time.sleep(1)
+
+        for name in title:
+            num += 1
+            if num % go_num == 0:
+                print(str(num) + ' : ' + name[0])
+
+            curs.execute(db_change("select data from data where title = ?"), [name[0]])
+            data = curs.fetchall()
+
+            render_lang_data = {
+                'toc' : 'toc',
+                'category' : 'category'
+            }
+
+            class_do_render(conn, render_lang_data, '').do_render(name[0], data[0][0], 'backlink')
+elif what_i_do == '2':
+    curs.execute(db_change("delete from other where name = 'recaptcha'"))
+    curs.execute(db_change("delete from other where name = 'sec_re'"))
+elif what_i_do == '3':
+    user_data = input('IP or Name : ')
+
+    curs.execute(
+        db_change(
+            "insert into rb (block, end, today, blocker, why, band, ongoing, login) "
+            "values (?, ?, ?, ?, ?, ?, '', '')"
+        ),
+        [
+            user_data,
+            'release',
+            get_time(),
+            'tool:emergency',
+            '',
+            '',
+        ]
+    )
+
+    curs.execute(db_change("update rb set ongoing = '' where block = ?"), [user_data])
+elif what_i_do == '4':
+    host = input('Host : ')
+
+    curs.execute(db_change("update other set data = ? where name = 'host'"), [host])
+elif what_i_do == '5':
+    port = int(input('Port : '))
+
+    curs.execute(db_change("update other set data = ? where name = 'port'"), [port])
+elif what_i_do == '6':
+    skin = input('Skin name : ')
+
+    curs.execute(db_change("update other set data = ? where name = 'skin'"), [skin])
+elif what_i_do == '7':
+    user_name = input('User name : ')
+    user_pw = input('User password : ')
+
+    hashed = pw_encode(conn, user_pw)
+    curs.execute(db_change("update user_set set data = ? where id = ? and name = 'pw'"), [
+        hashed,
+        user_name
+    ])
+elif what_i_do == '8':
+    new_ver = input('Insert version (0000000) : ')
+
+    if new_ver == '':
+        new_ver = '0000000'
+
+    curs.execute(db_change("update other set data = ? where name = 'ver'"), [new_ver])
+elif what_i_do == '9':
+    if os.path.exists(os.path.join('data', 'set.json')):
+        os.remove(os.path.join('data', 'set.json'))
+elif what_i_do == '10':
+    user_name = input('User name : ')
+    new_name = input('New name : ')
+
+    curs.execute(
+        db_change("update user_set set id = ? where id = ?"),
+        [new_name, user_name]
+    )
+elif what_i_do == '11':
+    if os.path.exists(os.path.join('data', 'mysql.json')):
+        os.remove(os.path.join('data', 'mysql.json'))
+elif what_i_do == '14':
+    curs.execute(db_change('delete from other where name = "head"'))
+elif what_i_do == '15':
+    user_name = input('User name : ')
+
+    curs.execute(db_change("update user_set set data = 'owner' where id = ? and name = 'acl'"), [user_name])
+elif what_i_do == '16':
+    user_name = input('User name : ')
+
+    curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [user_name])
+    if curs.fetchall():
+        curs.execute(db_change("update user_set set data = '' where name = '2fa' and id = ?"), [user_name])
+elif what_i_do == '17':
+    markup = input('Markup name : ')
+
+    curs.execute(db_change("update other set data = ? where name = 'markup'"), [markup])
+elif what_i_do == '18':
+    wiki_access_password = input('Password : ')
+
+    curs.execute(db_change("update other set data = ? where name = 'wiki_access_password'"), [wiki_access_password])
+elif what_i_do == '19':
+    curs.execute(db_change('select data from other where name = "update"'))
+    up_data = curs.fetchall()
+    up_data = up_data[0][0] if up_data and up_data[0][0] in ['stable', 'beta', 'dev', 'dont_use'] else 'stable'
+
+    if platform.system() == 'Linux' or platform.system() == 'Darwin':
+        ok = []
+        ok += [os.system('git remote rm origin')]
+        ok += [os.system('git remote add origin https://github.com/opennamu/opennamu.git')]
+        ok += [os.system('git fetch --depth=1 origin ' + up_data)]
+        ok += [os.system('git reset --hard origin/' + up_data)]
+        for for_a in ok[1:]:
+            if for_a != 0:
+                print('Error : update failed')
+                
+                break
+    elif platform.system() == 'Windows':
+        os.system('rd /s /q route')
+        
+        urllib.request.urlretrieve('https://github.com/opennamu/opennamu/archive/' + up_data + '.zip', 'update.zip')
+        
+        zipfile.ZipFile('update.zip').extractall('')
+        
+        ok = os.system('xcopy /y /s /r opennamu-' + up_data + ' .')
+        if ok == 0:
+            os.system('rd /s /q opennamu-' + up_data)
+            os.system('del update.zip')
+        else:
+            print('Error : update failed')
+elif what_i_do == '20':
+    domain = input('Domain (EX : 2du.pythonanywhere.com) : ')
+
+    curs.execute(db_change('delete from other where name = "domain"'))
+    curs.execute(db_change('insert into other (name, data, coverage) values ("domain", ?, "")'), [domain])
+elif what_i_do == '21':
+    tls_v = input('TLS (http) [http, https] : ')
+    if not tls_v in ['http', 'https']:
+        tls_v = 'http'
+
+    curs.execute(db_change('delete from other where name = "http_select"'))
+    curs.execute(db_change('insert into other (name, data, coverage) values ("http_select", ?, "")'), [tls_v])
+elif what_i_do == '22':
+    curs.execute(db_change('delete from other where name = "body"'))
+elif what_i_do == '23':
+    curs.execute(db_change('delete from other where name = "bottom_body"'))
+elif what_i_do == '24':
+    load_db = get_db_connect('mysql')
+    mysql_conn = load_db.__enter__()
+    mysql_curs = mysql_conn.cursor()
+    
+    load_db = get_db_connect('sqlite')
+    sqlite_conn = load_db.__enter__()
+    sqlite_curs = sqlite_conn.cursor()
+
+    create_data = get_db_table_list()
+    for create_table in create_data:
+        create = ['test'] + create_data[create_table]
+
+        create_r = ', '.join(['%s' for _ in create])
+        create = ', '.join(create)
+        
+        mysql_curs.execute(db_change('delete from ' + create_table))
+        
+        sqlite_curs.execute(db_change('select ' + create + ' from ' + create_table))
+        db_data = sqlite_curs.fetchall()
+        if db_data:
+            mysql_curs.executemany("insert into " + create_table + " (" + create + ") values (" + create_r + ")", db_data)
+elif what_i_do == '25':
+    curs.execute(db_change("select distinct doc_name from data_set where doc_rev = 'not_exist' or doc_rev = ''"))
+    for for_a in curs.fetchall():
+        data_set_exist = ''
+        
+        curs.execute(db_change("select title from data where title = ?"), [for_a[0]])
+        if not curs.fetchall():
+            data_set_exist = 'not_exist'
+
+        curs.execute(db_change("update data_set set doc_rev = ? where doc_name = ? and (doc_rev = '' or doc_rev = 'not_exist')"), [data_set_exist, for_a[0]])
+elif what_i_do == '26':
+    up_data = input('Insert branch name (beta) [stable, beta, dev] : ')
+
+    if not up_data in ['stable', 'beta', 'dev', 'dont_use']:
+        up_data = 'beta'
+
+    curs.execute(db_change('delete from other where name = "update"'))
+    curs.execute(db_change('insert into other (name, data, coverage) values ("update", ?, "")'), [up_data])
+elif what_i_do == '27':
+    port = int(input('Port : '))
+
+    curs.execute(db_change("update other set data = ? where name = 'golang_port'"), [port])
+else:
+    raise ValueError(what_i_do)
+
+if data_db_load == 'Y':
+    try:
+        conn.commit()
+    except:
+        pass
+
+print('OK')

+ 801 - 0
lang/en-US.json

@@ -0,0 +1,801 @@
+{
+    "language_tag": "en-US",
+
+    "_comment_" : "No category",
+    "recent_edit_request" : "Recent edit request",
+    "all_function_authority" : "All function authority",
+    "bbs_authority" : "BBS authority",
+    "treat_as_admin_authority" : "Treat as admin authority",
+    "backup_count" : "Maximum number of backup files",
+    "template_var_1" : "Template variable 1",
+    "template_var_2" : "Template variable 2",
+    "template_var_3" : "Template variable 3",
+    "sidebar" : "Sidebar",
+    "document_make_acl" : "Document create ACL",
+    "document_edit_request_acl" : "Document edit request ACL",
+    "up_to_level_3" : "Level 3 or higher",
+    "up_to_level_10" : "Level 10 or higher",
+    "document_top" : "Top of document phrases",
+    "document_editor_top" : "Top of document editor phrases",
+    "cidr" : "CIDR",
+    "option" : "Option",
+    "edit_request_able" : "Edit request available",
+    "date" : "Date",
+    "wiki_load_ip_select" : "Header to get IP",
+    "auto_login" : "Auto login",
+    "view_hide_user_name_authority" : "View hidden username and IP authority",
+    "view_user_watchlist_authority" : "View user watchlist authority",
+    "doc_watch_list_view_authority" : "View the list of people who have added document to the watchlist authority",
+    "bbs_view_authority" : "BBS view authority",
+    "bbs_comment_authority" : "BBS comment authority",
+    "bbs_edit_authority" : "BBS edit authority",
+    "user_analyze_authority" : "Analyze user authority",
+    "still_use_auth_error" : "Someone still has this authority",
+    "xss_data_include_error" : "You cannot enter characters that can be used for XSS.",
+    "nothing_authority" : "Ineffective authority",
+    "multiple_authorize" : "Multiple authorize",
+    "auth_to_auth" : "Auth group to auth group",
+    "vote_management_authority" : "Vote management authority",
+    "bbs_management_authority" : "BBS management authority",
+    "discuss_view_authority" : "Discussion view authority",
+    "page_view" : "Page views",
+    "discuss_make_new_thread_authority" : "Create a new discussion authority",
+    "multiple_move" : "Multiple move documents",
+    "url" : "URL",
+    "resolution" : "Resolution",
+    "file_delete_with_document" : "Delete document and file at the same time",
+    "method" : "Method",
+    "edit_filter_pass_authority" : "Contents filter pass authority",
+    "edit_filter_view_authority" : "Contents filter view authority",
+    "post_view_acl" : "Post view ACL",
+    "post_comment_acl" : "Post comment ACL",
+    "remove_hidden" : "Don't show hide content",
+    "link_count" : "Number of links",
+    "data_missing" : "No information",
+    "not_use_view_count" : "Disable page view",
+    "login_able_and_regsiter_disable" : "Login-able and Registration-disable",
+    "private" : "Private",
+    "upvote" : "Upvote",
+
+    "_comment_" : "Common",
+    "data" : "Data",
+    "volume" : "Volume",
+    "server" : "Server",
+    "filter" : "Filter",
+    "delete" : "Delete",
+    "notice" : "Notice",
+    "add" : "Add",
+    "license" : "License",
+    "etc" : "Etc",
+    "name" : "Name",
+    "regex" : "Regex",
+    "id" : "ID",
+    "list" : "List",
+    "main" : "Main",
+    "return" : "Return",
+    "skin" : "Skin",
+    "save" : "Save",
+    "secret_key" : "Secret key",
+    "public_key" : "Public key",
+    "host" : "Host",
+    "port" : "Port",
+    "restart" : "Restart",
+    "shutdown" : "Shutdown",
+    "document_name" : "Documents name",
+    "discussion_name" : "Discussions topic",
+    "user_name" : "Username",
+    "go" : "Go",
+    "document" : "Document",
+    "discussion" : "Discussion",
+    "backlink" : "Backlink",
+    "link" : "Link",
+    "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 document",
+    "file" : "File",
+    "writer" : "Writer",
+    "editor" : "Editor",
+    "hide" : "Hide",
+    "check" : "Check",
+    "destruction" : "Destruction",
+    "tool" : "Tool",
+    "position" : "Position",
+    "recent" : "Recently",
+    "password" : "Password",
+    "login" : "Login",
+    "logout" : "Logout",
+    "register" : "Registration",
+    "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",
+    "explanation" : "Explanation",
+    "default" : "Default",
+    "lastest" : "Lastest",
+    "type" : "Type",
+    "in_progress" : "In progress",
+    "start" : "Start",
+    "pass" : "Passing",
+    "file_name" : "File name",
+    "pinned" : "Pinned",
+    "markup" : "Markup",
+    "title" : "Title",
+    "reference" : "Reference",
+    "icon" : "Icon",
+    "view" : "View",
+    "content" : "Content",
+    "on" : "On",
+    "off" : "Off",
+    "unset" : "Unset",
+    "extension" : "Extension",
+    "empty" : "Empty",
+    "domain" : "Domain",
+    "result" : "Result",
+    "not_working" : "Not working",
+    "alpha" : "Alpha",
+    "beta" : "Beta",
+    "example" : "Example",
+    "reset" : "Reset",
+    "top" : "Top",
+    "bottom" : "Bottom",
+    "use" : "Use",
+    "spread" : "Speard",
+    "popup" : "Popup",
+    "popover" : "Popover",
+    "trace" : "Trace",
+    "level" : "Level",
+    "challenge" : "Challenges",
+    "ua" : "UA",
+    "day" : "Day(s)",
+    "backup" : "Backup",
+    
+    "_comment_" : "Time",
+    "second" : "Second(s)",
+    "hour" : "Hour(s)",
+    "limitless" : "Limitless",
+    "time" : "Time",
+    "period" : "Period",
+    "end" : "End",
+    
+    "_comment_" : "User",
+    "user" : "User",
+    "admin" : "Administrator",
+    "owner" : "Owner",
+    "ip" : "IP",
+    "member" : "Member",
+    
+    "_comment_" : "Ban",
+    "ban" : "Block",
+    "blocked" : "Blocked user",
+    "release" : "Unblock",
+    "completely_ban" : "Completely block",
+    "dont_come_this_site" : "Site access block",
+    
+    "_comment_" : "Func",
+    "wiki_restart" : "Restart wiki engine",
+    "wiki_shutdown" : "Shutdown wiki engine",
+    "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" : "Block(s) record",
+    "edit_filter_rule" : "Contents filter rule",
+    "move_history" : "History of moveing",
+    "other_tool" : "Other tools",
+    "admin_tool" : "Administrators tools",
+    "check_user" : "Check users login history",
+    "compare_target" : "Comparison target name",
+    "authorize" : "Authorize",
+    "indexing" : "Database Indexing",
+    "hide_release" : "Unhide",
+    "pinned_release" : "Unpinned",
+    "discussion_tool" : "Discussion tools",
+    "discussion_raw" : "Discussion raw",
+    "connection" : "Connection",
+    "new_connection" : "Connect...",
+    "user_setting" : "User settings",
+    "now_password" : "Current password",
+    "new_password" : "New password",
+    "password_confirm" : "Confirm password",
+    "password_search" : "Password finder",
+    "login_able" : "Login-able",
+    "band_ban" : "Range block",
+    "band_blocked" : "Range blocked",
+    "under_category" : "Sub-category",
+    "count" : "Number of Contributions",
+    "alarm" : "Notifications(s)",
+    "user_document" : "User document",
+    "user_head" : "Custom <HEAD>",
+    "encryption_method" : "Encryption method",
+    "check_key" : "Check authentication key",
+    "reset_user_ok" : "Check Success",
+    "name_or_ip_or_regex_or_cidr" : "Username or IP or Regex or CIDR",
+    "ban_period" : "Period to block",
+    "not_sure" : "Not sure",
+    "use_push_alarm" : "Enable Push Notification",
+    "edit_button_paragraph" : "Paragraph",
+    "password_change" : "Change password",
+    "email_change" : "Change email",
+    "document_setting" : "Document setting",
+    "mutiple_document_setting" : "Multiple document setting",
+    "acl_thread_change" : "Change thread ACL",
+    "user_tool" : "Users tools",
+    "skin_info" : "Skin information",
+    "closed_discussion" : "Closed discussions",
+    "agreed_discussion" : "Agreed discussions",
+    "history_delete" : "Delete history",
+    "direct_input" : "Direct input",
+    "acl_record" : "ACL record",
+    "last_edit_time" : "Last edited time",
+    "admin_group" : "Auth group(s)",
+    "topic_setting" : "Discussion settings",
+    "old_page" : "Document(s) modified a long time ago",
+    "skin_set" : "Skin setting(s)",
+    "many_delete" : "Document bulk delete",
+    "edit_request" : "Edit request",
+    "edit_request_check" : "Check edit request",
+    "accept_edit_request" : "Accept edit request",
+    "history_add" : "Add history",
+    "all_register_num" : "The number of application forms",
+    "replace_move" : "Interchange",
+    "merge_move" : "Merging documents",
+    "add_admin_group" : "Add auth group",
+    "add_watchlist" : "Add watchlist",
+    "blocked_user" : "Blocked user",
+    "blocked_admin" : "Blocked administrator",
+    "category_title" : "Documents under this category",
+    "topic_name_change" : "Change discussion name",
+    "topic_acl_setting" : "Discussion ACL setting",
+    "topic_acl" : "Discussion ACL",
+    "delete_admin_group": "Delete auth group",
+    "main_skin_set" : "Main skin settings",
+    "reset_backlink" : "Reset backlink",
+    "link_in_this" : "Links in this document",
+    "star_doc" : "Document(s) of interest",
+    "add_star_doc" : "Add document(s) of interest",
+    "simple_check" : "Simple check",
+    "add_user" : "Add user",
+    "2fa" : "2FA",
+    "2fa_off" : "2FA disabled",
+    "2fa_password" : "2FA password",
+    "2fa_password_change" : "Change 2FA password",
+    "history_reset" : "Document history reset",
+    "record_reset" : "User edit log reset",
+    "today_doc" : "Todays document",
+    "send_edit" : "Modify reason",
+    "password_instead_key" : "Authentication key",
+    "key_change" : "Change key",
+    "key_delete" : "Delete key",
+    "email_delete" : "Delete email",
+    "user_title" : "User title",
+    "multiple_ban" : "Multiple ban",
+    "dont_move" : "Don't move",
+    "file_delete" : "File delete",
+    "added_menu" : "Added menu",
+    "document_set" : "Document settings",
+    "user_added_menu" : "User added menu",
+    "move_redirect_make" : "Redirect document generation (Only if possible)",
+    "redirect" : "Redirect",
+    "new_doc" : "New document(s)",
+    "table" : "Table",
+    "table_transparent" : "Table transparency",
+    "recaptcha_pass_acl" : "CAPTCHA Pass ACL",
+    "recaptcha_one_check_five_pass_acl" : "Passing the CAPTCHA once will result in 5 unimplemented ACL",
+    "user_fix" : "Fix user",
+    "main_user_name" : "Main user name",
+    "change_user_name" : "Change main user name",
+    "sub_user_name" : "Sub user name",
+    "start_with_search" : "Search from the first letter",
+    "challenge_and_level_manage" : "Challenges and level management",
+    "bbs_record" : "BBS post(s) record",
+    "bbs_comment_record" : "BBS comment(s) record",
+    "load_temp_save" : "Temporary save",
+    "load_temp_save_load" : "Load temporary save",
+    "bbs_comment_delete" : "Delete BBS comment",
+    "bbs_post_pinned" : "Pin BBS post",
+    "bbs_post_delete" : "Delete BBS post",
+    "outer_link_filter_list" : "External link auto image list",
+    "html_or_link" : "HTML or link",
+    "template_document_list" : "Template document(s) list",
+    "no_link_document_list" : "List of document(s) without link",
+    "new_page" : "List of new document(s)",
+    
+    "_comment_" : "BBS",
+    "bbs" : "BBS",
+    "bbs_main" : "BBS main",
+    "bbs_name" : "BBS name",
+    "bbs_make" : "Create BBS",
+    "order" : "Order",
+    "comment_base" : "Comment based",
+    "thread_base" : "Thread based",
+    "bbs_set" : "BBS set",
+    "comment" : "Comment",
+    "reply" : "Reply",
+    "post_edit" : "Modify post",
+    "post_add" : "Add post",
+    "last_comment_time" : "last comment time",
+    "bbs_comment_tool" : "BBS comment tool(s)",
+    "bbs_comment_edit" : "BBS comment edit",
+    "bbs_post_tool" : "BBS post tool(s)",
+    
+    "_comment_" : "BBS ACL",
+    "bbs_view_acl" : "BBS ACL to view posts",
+    "bbs_acl" : "BBS ACL",
+    "bbs_edit_acl" : "BBS ACL to write post",
+    "bbs_comment_acl" : "BBS ACL to write comment",
+    
+    "_comment_" : "Edit",
+    "load" : "Load another document",
+    "turn_off_monaco" : "Turn on/off monaco editor",
+    
+    "_comment_" : "Render",
+    "toc" : "TOC",
+    "category" : "Category",
+    
+    "_comment_" : "Search",
+    "search_document_name" : "Search document name",
+    "search_document_data" : "Search document data",
+    
+    "_comment_" : "Main skin set part",
+    "render" : "Render",
+    "strike" : "Strike",
+    "bold" : "Bold",
+    "footnote" : "Footnote",
+    "include_link" : "Include link",
+    "image" : "Image",
+    "exter_link" : "Exter link",
+    "link_delimiter" : "Link delimiter",
+    "force_darkmode" : "Force darkmode",
+    "font_size" : "Font size",
+    "image_paste" : "Paste Image by Ctrl + C and V",
+    "monaco_editor" : "Monaco editor",
+    "footnote_render" : "Footnote rendering",
+    "footnote_number" : "Footnote number output",
+    "only_number" : "Only number",
+    "footnote_real_num_view" : "Footnote original number view",
+    "category_change_title" : "Don't rename documents in category",
+    "table_scroll" : "Use scroll in tables",
+    "list_view_change" : "Change how the number list is viewed",
+    "view_joke" : "Show contents of joke macro",
+    "math_scroll" : "Use scroll in math macros",
+    "view_history" : "Use trace in view",
+    
+    "_comment_" : "Main skin set option",
+    "change_to_normal" : "Change to plain text.",
+    "change_to_link" : "Change to Link.",
+    "click_load" : "Load by click.",
+    "all_off" : "Turn off all",
+    "in_content" : "Turn off automatic TOC",
+    "self_tab" : "Open a link in this tab",
+    
+    "_comment_" : "Main skin set footnote",
+    "only_korean" : "Only Korean is available",
+    "unavailable_in_monaco" : "Not available in Monaco editor.",
+    
+    "_comment_" : "Filter list",
+    "interwiki_list" : "Interwiki(s) list",
+    "email_filter_list" : "Email filter(s) list",
+    "id_filter_list" : "Username filter(s) list",
+    "edit_filter_list" : "Contents filter(s) list",
+    "file_filter_list" : "File name filter(s) list",
+    "edit_tool_list" : "Edit tool(s) list",
+    "image_license_list" : "Image license(s) list",
+    "extension_filter_list" : "Extension filter(s) list",
+    "document_filter_list" : "Document filter(s) list",
+    
+    "_comment_" : "Filter add",
+    "interwiki_add" : "Add Interwiki",
+    "edit_filter_add" : "Add contents filter",
+    "id_filter_add" : "Add username filter",
+    "email_filter_add" : "Add email filter",
+    "file_filter_add" : "Add file name filter",
+    "edit_tool_add" : "Add edit tool",
+    "image_license_add" : "Add image license",
+    "extension_filter_add" : "Add extension filter",
+    "document_filter_add" : "Add document filter",
+    
+    "_comment_" : "Setting",
+    "setting" : "Setting",
+    "restart_required" : "Restart required",
+    "adsense_setting" : "Adsense settings",
+    "adsense_enable" : "Adsense enable",
+    "skin_setting" : "Skin settings",
+    "main_acl_setting" : "Default ACL settings",
+    "top_menu_setting" : "Added menu setting",
+    "main_skin_set_default" : "Main skin setting(s) default",
+
+    "_comment_" : "Setting 404 page",
+    "404_page_setting" : "404 page setting",
+    "404_page" : "Use the input below as 404 page",
+    "404_file" : "Use 404.html in root folder",
+
+    "_comment_" : "Setting list",
+    "main_setting" : "Main settings",
+    "text_setting" : "Text settings",
+    "main_head" : "Global <HEAD>",
+    "main_body" : "Top of body",
+    "main_bottom_body" : "Bottom of body",
+    "ext_api_req_set" : "External API required setting",
+    
+    "_comment_" : "Setting main",
+    "basic_set" : "Basic settings",
+    "design_set" : "Design-related settings",
+    "login_set" : "Login-related settings",
+    "server_set" : "Server-related settings",
+    "edit_set" : "Edit-related settings",
+    "communication_set" : "Communication-related settings",
+    "render_set" : "Rendering-related settings",
+    "namumark_fully_compatible_mode" : "namumark design compatible mode",
+    "wiki_name" : "Wikis name",
+    "wiki_logo" : "Wikis logo",
+    "main_page" : "Main page",
+    "bottom_text" : "License text",
+    "max_file_size" : "Max file size",
+    "backup_interval" : "Backup Cycles",
+    "wiki_skin" : "Wikis default Skin",
+    "no_register" : "Disable registration",
+    "hide_ip" : "Hide IP address",
+    "wiki_host" : "Wiki host address",
+    "wiki_port" : "Wiki port number",
+    "wiki_secret_key" : "Wiki secret key",
+    "email_required" : "Request e-mail when registration",
+    "smtp_setting_required" : "Email SMTP setting required",
+    "update_branch" : "Branch to import updates",
+    "slow_edit" : "Continuous edit limit",
+    "slow_edit_acl" : "Continuous edit limit excluded target ACL",
+    "requires_approval" : "Requires approval for registration",
+    "approval_question": "Registeration questions",
+    "backup_where" : "Backup location",
+    "ua_get_off" : "Turn off UA collection",
+    "edit_bottom_compulsion" : "Edit reason required",
+    "edit_bottom_compulsion_acl" : "Edit reason required excluded target ACL",
+    "enable_comment_function" : "Using the comment function",
+    "enable_challenge_function" : "Using the challenge function",
+    "tls_method" : "TLS method",
+    "title_max_length" : "Documents title maximum length",
+    "title_topic_max_length" : "Discussions topic maximum length",
+    "password_min_length" : "Password minimum length",
+    "set_wiki_access_password_need" : "Password required for wiki access",
+    "set_wiki_access_password" : "Wiki access password",
+    "set_history_recording_off" : "Stop recording history",
+    "link_case_insensitive" : "Link case insensitive",
+    "hide_user_name" : "Hide member name",
+    "move_with_redirect" : "Create a redirect when moving a document",
+    "slow_thread" : "Continuous thread upload limit",
+    "edit_timeout" : "render timeout",
+    "display_level_in_user_name" : "Displaying levels in username",
+    "ua_expiration_date" : "UA expiration deadline",
+    "document_content_max_length" : "Maximum document length",
+    
+    "_comment_" : "Text",
+    "register_text" : "Terms of registration",
+    "non_login_alert" : "Non-login alert",
+    "edit_bottom_text" : "Editing textarea bottom notice",
+    "copyright_checkbox_text" : "Copyright Agreenment text as checkbox",
+    "check_key_text" : "Check authentication key notice",
+    "email_title" : "Email subject",
+    "email_text" : "Email content",
+    "email_insert_text" : "Email input box text",
+    "password_search_text" : "Password finder text",
+    "reset_user_text" : "Password reset complete text",
+    "error_401" : "ACL view limited document notice",
+    "error_404" : "Missing document notice",
+    "edit_help" : "Editing textarea phrase",
+    "upload_help" : "File upload phrase",
+    "upload_default" : "File upload other Default",
+    "topic_text" : "Discussion textarea phrase",
+    "phrase_user_page_admin" : "Administrator user page phrase",
+    "phrase_user_page_owner" : "Onwer user page phrase",
+    "phrase_old_page_warning" : "Warning on previous revision document visit",
+    
+    "_comment_" : "Ext_API",
+    "recaptcha" : "reCAPTCHA",
+    "hcaptcha" : "hCAPTCHA",
+    "captcha" : "CAPTCHA",
+    "email_setting" : "Email settings",
+    
+    "_comment_" : "SMTP",
+    "smtp_setting" : "Email SMTP settings",
+    "smtp_server" : "SMTP Server address",
+    "smtp_security": "SMTP Connection security",
+    "smtp_port" : "SMTP Server port",
+    "smtp_username" : "SMTP Username",
+    "smtp_password" : "SMTP Password",
+    
+    "_comment_" : "OAuth",
+    "oauth" : "OAuth",
+    "oauth_client_id" : "OAuth client ID",
+    
+    "_comment_" : "Top memu",
+    "enter_top_menu_setting" : "Enter name in the upper line and URL in the lower line.",
+    
+    "_comment_" : "Sitemap",
+    "sitemap_management" : "sitemap.xml management",
+    "stiemap_exclude_domain" : "Exclude domain",
+    "stiemap_exclude_user_page" : "Exclude user page(s)",
+    "stiemap_exclude_file_page" : "Exclude file page(s)",
+    "stiemap_exclude_category_page" : "Exclude category page(s)",
+    "sitemap_manual_create" : "Create sitemap manually",
+    "sitemap_auto_make" : "Create sitemap automatically",
+    
+    "_comment_" : "List",
+    "discussion_list" : "Discussion(s) list",
+    "admin_list" : "Administrator(s) list",
+    "member_list" : "Member(s) list",
+    "admin_group_list" : "Auth group(s) list",
+    "all_document_list" : "All document(s) list",
+    "watchlist" : "Watchlist",
+    "image_file_list" : "Image file(s) list",
+    "short_page" : "Short page(s) list",
+    "long_page" : "Long page(s) list",
+    "bbs_help" : "BBS editor help",
+    "bbs_comment_help" : "BBS comment editor help",
+    "outdated_doc_warning" : "Out of date document warning",
+
+    "_comment_" : "Auth use list",
+    "authority_use_list" : "Authority use list",
+    "authority_use_list_off" : "Authority use list off",
+    "authority_use_list_expiration_date" : "Authority use list expiration deadline",
+    
+    "_comment_" : "ACL document list",
+    "acl_document_list" : "ACL document(s) list",
+    "acl_required" : "Required ACL",
+    
+    "_comment_" : "Auth list",
+    "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",
+    "admin_authority" : "Admin authority",
+    "user_authority" : "User authority",
+    "admin_default_feature_authority" : "Admin default feature authority",
+    "captcha_pass_authority" : "CAPTCHA pass authority",
+    "ip_authority" : "IP authority",
+    "view_authority" : "Document view authority",
+    "document_authority" : "Document authority",
+    "edit_authority" : "Document edit authority",
+    "move_authority" : "Document move authority",
+    "new_make_authority" : "Document create authority",
+    "delete_authority" : "Document delete authority",
+    "edit_request_authority" : "Edit request authority",
+    "discuss_authority" : "Discussion authority",
+    "upload_authority" : "File upload authority",
+    "vote_authority" : "Vote authority",
+    "captcha_one_check_five_pass_authority" : "Passing the CAPTCHA once will result in 5 unimplemented authority",
+    "user_name_bold_authority" : "Mark user as admin authority",
+    "multiple_upload_authority" : "Upload multiple files authority",
+    "slow_edit_pass_authority" : "Continuous edit limit pass authority",
+    "edit_bottom_compulsion_pass_authority" : "Edit reason required pass authority",
+    
+    "_comment_" : "Record",
+    "edit_record" : "Edit record",
+    "discussion_record" : "Discussion record",
+    
+    "_comment_" : "Topic",
+    "make_new_topic" : "Make new discussion",
+    "topic_tool" : "Discussion management tools",
+    "topic_state" : "Discussion status",
+    "topic_delete" : "Delete this thread",
+    "topic_view_acl" : "Discussion View ACL",
+    "topic_normal" : "Normal",
+    "topic_stop" : "Stop",
+    "topic_close" : "Close",
+    "topic_agree" : "Discussion Agreed",
+    
+    "_comment_" : "Topic set",
+    "topic_change_agree" : "Transition to a consensus discussion",
+    "topic_progress" : "Discussion progress",
+    "topic_associate" : "Associate discussion with other features",
+    "topic_link_vote" : "Link votes to discussion",
+    "topic_insert_vote_number" : "Number of the vote",
+    
+    "_comment_" : "Topic state",
+    "topic_state_change_normal" : "The admin normalized the discussion.",
+    "topic_state_change_stop" : "The admin has stopped the discussion.",
+    "topic_state_change_close" : "The admin has closed the discussion.",
+    "topic_state_change_agree" : "The admin has approved the discussion agreement.",
+    "topic_state_change_disagree" : "The admin has broken the agreement of the discussion.",
+    
+    "_comment_" : "Period",
+    "1_day" : "1 day",
+    "5_day" : "5 days",
+    "30_day" : "30 days",
+    "180_day" : "180 days",
+    "360_day" : "360 days",
+    
+    "_comment_" : "ACL",
+    "admin_acl" : "Administrators, owners",
+    "member_acl" : "Members",
+    "50_edit_acl" : "Members with 50 or more document edits, administrators, owners",
+    "all_acl" : "All users exclude blocked users",
+    "email_acl" : "Members with email, administrators, owners",
+    "owner_acl" : "Owners",
+    "before_acl" : "Those who have edited this document before, administrators, owners",
+    "ban_acl" : "All users include blocked users",
+    "ban_admin_acl" : "Blocked users, administrators, owners",
+    "30_day_acl" : "Members 30 days after registration, administrators, owners",
+    "90_day_acl" : "Members 90 days after registration, administrators, owners",
+    "not_all_acl" : "All prohibited",
+    
+    "_comment_" : "Set name",
+    "document_acl" : "Document ACL",
+    "document_edit_acl" : "Document edit ACL",
+    "document_move_acl" : "Document move ACL",
+    "document_delete_acl" : "Document delete ACL",
+    "discussion_acl" : "Discussion(s) ACL",
+    "thread_acl" : "Thread ACL",
+    "view_acl" : "Reading ACL",
+    "user_document_acl" : "User document ACL",
+    "upload_acl" : "Upload ACL",
+    "edit_request_acl" : "Edit request ACL",
+    "many_upload_acl" : "Upload multiple files ACL",
+    "vote_acl" : "Vote ACL",
+    
+    "_comment_" : "Application list",
+    "application_list": "Application list",
+    "application_time": "Application time",
+    "answer": "Answer",
+    "approve": "Approve",
+    "decline": "Decline",
+    "new_application" : "A new registration application exists.",
+    "approve_or_decline": "Approve or reject",
+    "no_applications_now" : "There are no applications.",
+    "approval_requirement_disabled": "Approval requirement is disabled now. You can enable this feature on settings",
+    
+    "_comment_" : "Vote",
+    "vote_list" : "Vote list(s)",
+    "add_vote" : "Add vote",
+    "open_vote" : "Public vote",
+    "not_open_vote" : "Private vote",
+    "close_vote_list" : "Closed vote list(s)",
+    "open_vote_list" : "Open vote list(s)",
+    "vote" : "Vote",
+    "result_vote" : "Result of a vote",
+    "1_line_1_q" : "1 option per line.",
+    "close_vote" : "Close vote",
+    "re_open_vote" : "Reopen vote",
+    
+    "_comment_" : "Long",
+    "application_submitted": "Registration application successful",
+    "waiting_for_approval": "Application for registration has been made successfully. Please wait for approval by administrators.",
+    "ie_no_data_required" : "Operation cannot continue because all required data has not been collected.",
+    "user_reset_sign" : "Your account information has changed like this.",
+    "default_edit_help" : "Describe it here",
+    "markup_enabled" : "Markup enabled",
+    "many_delete_help" : "Please write down the name one by one on the line.",
+    "name_or_ip_or_regex_or_cidr_multiple" : "Please write down the username or IP or Regex or CIDR one by one on the line.",
+    "sqlite_only" : "SQLite only",
+    "linux_only" : "Linux OS only",
+    "approval_question_visible_only_when_approval_on" : "Approval questions are visible only when approval requirement is on",
+    "msg_whatchlist_lmt": "You can add as many as",
+    "enter_html" : "Please enter HTML",
+    "exp_edit_conflict" : "Editing conflict occurred",
+    "same_ip_exist" : "Same IP exists",
+    
+    "_comment_" : "Error",
+    "update_error" : "Auto update is not supported.",
+    "inter_error" : "Internal error.",
+    "authority_error" : "Insufficient privileges.",
+    "no_login_error" : "You are not logged in.",
+    "no_exist_user_error" : "The account does not exist.",
+    "no_admin_block_error" : "You cannot block or check administrators.",
+    "error_skin_set" : "The skin you are using does not support individual settings.",
+    "error_skin_set_old" : "Some older skins may only work by using the old version link.",
+    "same_id_exist_error" : "There are users using the same username.",
+    "long_id_error" : "Username must be shorter than 128 characters.",
+    "id_char_error" : "XSS characters(inequality symbol, quotation marks) and . and : are not available.",
+    "file_exist_error" : "The file does not exist.",
+    "password_error" : "Password does not match.",
+    "recaptcha_error" : "Pass the reCAPTCHA.",
+    "file_extension_error" : "Only files with the specified extension can be uploaded.",
+    "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): ",
+    "move_error" : "An error occurred while moving. All or part of it has not been moved.",
+    "password_diffrent_error" : "Reconfirm password and input password are different.",
+    "edit_filter_error" : "Censored by contents filter.",
+    "file_name_error" : "You cannot use dots in the file name.",
+    "topic_long_error" : "The discussions topic must not exceed 256 characters.",
+    "email_error" : "No one has this email.",
+    "regex_error" : "There is an error in the regular expression.",
+    "decument_404_error" : "This document does not exist.",
+    "func_404_error" : "This URL does not have the correct function.",
+    "fast_edit_error" : "You can edit another document after this period (Second(s)) : ",
+    "too_many_dec_error" : "This feature is not supported because there are too many documents.",
+    "application_not_found" : "Application not found",
+    "invalid_password_error" : "The Password or ID is invalid.",
+    "watchlist_overflow_error": "You cannot add more than ten documents.",
+    "copyright_disagreed" : "You have to agree to the copyright noticement to contribute.",
+    "email_send_error" : "Email transfer failed.",
+    "restart_fail_error" : "Restart failed. Please try restarting manually.",
+    "same_email_error" : "There are users using the same email.",
+    "input_email_error" : "There is a problem with the input value.",
+    "error_edit_send_request" : "Entering a reason is required.",
+    "error_title_length_too_long" : "Documents title or Discussion topic length is too long. Maximum number of characters : ",
+    "error_password_length_too_short" : "Password length is too short. Minimum number of characters : ",
+    "error_password_require_for_wiki_access" : "A password is required to access the wiki.",
+    "timeout_error" : "Rendering time is too long. Maximum rendering time (Second(s)) : ",
+    "password_same_as_id_error": "The password cannot be the same as the user ID.",
+    
+    "_comment_" : "Warning",
+    "http_warning" : "Warning: If you are not on HTTPS connection, your information can be leaked. The users themselves have responsibility to any problems that happen because of this.",
+    "user_head_warning" : "User data will be deleted if you close the browser or when you login.",
+    "no_login_warning" : "You are not logged in. Your current IP address will be logged within editing or discussing until you log in.",
+    "update_warning" : "Manual updates are recommended if your version is 0.2 or lower than the latest version. For Windows, the contents of the route folder will be deleted.",
+    "delete_warning" : "If you erase this, it's hard to restore it, so please be careful.",
+    "user_css_warning" : "If you have a problem using this, connect here.",
+    "main_css_warning" : "If you have a problem using this, use the emergency tool.",
+    "not_support_skin_warning" : "It does not work on skins that do not support this feature.",
+    "old_page_warning" : "This page is out of date.",
+    "backup_warning" : "Warning. Files with the same name may be erased.",
+    
+    "_comment_" : "Challenge",
+    "challenge_title_register" : "Hello, World!",
+    "challenge_info_register" : "Registration<br><br>Reward : 🌳 title",
+    
+    "challenge_title_first_contribute" : "Well begun is half done.",
+    "challenge_info_first_contribute" : "Make your 1st contribution<br><br>Reward : 🔰 title and 500 exp",
+    
+    "challenge_title_tenth_contribute" : "Boys, Be Ambitious!",
+    "challenge_info_tenth_contribute" : "Make your 10th contribution<br><br>Reward : 📝 title and 1000 exp",
+
+    "challenge_title_hundredth_contribute" : "Knowledge is power.",
+    "challenge_info_hundredth_contribute" : "Make your 100th contribution<br><br>보상 : 🖊️ title and 3000 exp",
+
+    "challenge_title_thousandth_contribute" : "Rome wasn't built in a day.",
+    "challenge_info_thousandth_contribute" : "Make your 1000th contribution<br><br>Reward : 🏅 title and 10000 exp",
+    
+    "challenge_title_first_discussion" : "Truth is generally the best vindication against slander.",
+    "challenge_info_first_discussion" : "Post the 1st discussion thread<br><br>Reward : 💬 title and 500 exp",
+
+    "challenge_title_tenth_discussion" : "I shall either find a way or make one.",
+    "challenge_info_tenth_discussion" : "Post the 10th discussion thread<br><br>Reward : 💡 title and 1000 exp",
+
+    "challenge_title_hundredth_discussion" : "When all think alike, no one thinks very much.",
+    "challenge_info_hundredth_discussion" : "Post the 100th discussion thread<br><br>Reward : 📢 title and 3000 exp",
+
+    "challenge_title_thousandth_discussion" : "I think, therefore I am.",
+    "challenge_info_thousandth_discussion" : "Post the 1000th discussion thread<br><br>Reward : 📜 title and 10000 exp",
+
+    "challenge_title_admin" : "If you want to test a man's character, give him power.",
+    "challenge_info_admin" : "Be the administrator.<br><br>Reward : ☑️ title and 10000 exp"
+}

+ 30 - 0
lang/help_tool.py

@@ -0,0 +1,30 @@
+import re
+import json
+
+o_json = json.loads(open('en-US.json', encoding = 'utf8').read())
+
+print('n_name : ', end = '')
+n_name = input()
+n_json = json.loads(open(n_name + '.json', encoding = 'utf8').read())
+
+print()
+for i in list(n_json):
+    if not i in o_json:
+        del n_json[i]
+
+for i in list(o_json):
+    if not re.search(r'^_', i[0]):
+        if not i in n_json:
+            print('o_title : ' + i)
+            print('o_text : ' + o_json[i])
+
+            print('n_text : ', end = '')
+            n_text = input()
+
+            n_json = {**n_json, **{i : n_text}}
+
+n_data = json.dumps(n_json, indent = 4, ensure_ascii = False)
+
+f = open(n_name + '.json', "w", encoding='utf8')
+f.write(n_data)
+f.close()

+ 706 - 0
lang/ko-KR.json

@@ -0,0 +1,706 @@
+{
+    "language_tag": "ko-KR",
+    "sitemap_manual_create": "사이트맵 수동 생성",
+    "sitemap_auto_make": "사이트맵 자동 생성",
+    "delete_admin_group": "권한 그룹 삭제",
+    "document_name": "문서명",
+    "non_login_alert": "비로그인 알림",
+    "hide": "숨김",
+    "discussion_acl": "토론들 ACL",
+    "ongoing": "진행 중",
+    "user_tool": "사용자 도구",
+    "compare_target": "비교 대상 이름",
+    "email_title": "이메일 제목",
+    "acl_required": "ACL 필요",
+    "blocked": "차단된 사용자",
+    "authorization_authority": "권한 부여 권한",
+    "open_discussion": "열린 토론",
+    "move_history": "이동 기록",
+    "register": "회원 가입",
+    "reset_user_text": "암호 초기화 완료 문구",
+    "no_register": "가입 불가능",
+    "view_acl": "읽기 ACL",
+    "notice": "알림",
+    "login": "로그인",
+    "smtp_setting": "이메일 SMTP 설정",
+    "smtp_server": "SMTP 서버 주소",
+    "smtp_security": "SMTP 보안 프로토콜",
+    "smtp_port": "SMTP 서버 포트",
+    "close": "닫기",
+    "closed": "닫힘",
+    "start": "시작",
+    "encryption_method": "비밀번호 암호화 방식",
+    "why": "사유",
+    "user_check_authority": "사용자 검사 권한",
+    "second": "초",
+    "load": "불러오기",
+    "user_discussion": "사용자 토론",
+    "upper": "상위",
+    "email": "이메일",
+    "closed_discussion": "닫힌 토론",
+    "acl": "ACL",
+    "history_hide_authority": "역사 숨김 권한",
+    "go": "이동",
+    "document_acl": "문서 ACL",
+    "secret_key": "비밀 키",
+    "raw": "원본",
+    "file": "파일",
+    "state": "상태",
+    "authorize": "권한 부여",
+    "check_user": "사용자 검사",
+    "email_error": "해당 이메일을 가진 사용자가 존재하지 않습니다.",
+    "version": "버전",
+    "open": "열기",
+    "update_branch": "업데이트를 가져올 브랜치",
+    "edit_filter_error": "편집 필터에 의해 금지된 단어가 사용되었습니다.",
+    "ip": "IP",
+    "pinned_release": "고정 해제",
+    "next": "다음",
+    "password_change": "비밀번호 변경",
+    "hour": "시간",
+    "connect": "연결",
+    "edit_record": "편집 기록",
+    "preview": "미리보기",
+    "view": "보기",
+    "no_login_error": "비로그인 상태입니다.",
+    "upload": "파일 올리기",
+    "user_name": "사용자 이름",
+    "search": "검색",
+    "etc": "기타",
+    "edit_filter": "편집 필터",
+    "no_exist_user_error": "계정이 존재하지 않습니다.",
+    "compare": "비교",
+    "password_error": "비밀번호가 올바르지 않습니다.",
+    "default": "기본값",
+    "wiki_name": "위키 이름",
+    "acl_document_list": "ACL 문서 목록",
+    "connection": "연결",
+    "random": "무작위 문서",
+    "filter": "필터",
+    "band_blocked": "대역 차단됨",
+    "password_diffrent_error": "입력한 비밀번호와 비밀번호 확인이 서로 다릅니다.",
+    "180_day": "180일",
+    "markup_enabled": "문법 사용 가능",
+    "send": "전송",
+    "not_sure": "확실하지 않음",
+    "file_name_error": "파일명에는 점을 사용할 수 없습니다.",
+    "pass": "넘기기",
+    "recaptcha_error": "CAPTCHA를 통과하세요.",
+    "file_capacity_error": "최대 파일 크기 (MB) : ",
+    "setting": "설정",
+    "end": "끝",
+    "error": "오류",
+    "ban": "차단",
+    "email_insert_text": "이메일 입력란 문구",
+    "check_key_text": "인증키 검사 문구",
+    "other_tool": "기타 도구",
+    "recent_discussion": "최근 토론",
+    "record": "기록",
+    "owner_authority": "소유자 권한",
+    "wiki_port": "위키 포트",
+    "limitless": "무기한",
+    "normal": "일반",
+    "reset_user_ok": "검사 성공",
+    "in_progress": "진행 중",
+    "under_category": "하위 분류",
+    "range": "대역",
+    "edit_filter_rule": "편집 필터 규칙",
+    "hide_ip": "IP 주소 숨기기",
+    "topic_tool": "토론 관리 도구",
+    "user_document": "사용자 문서",
+    "id": "아이디",
+    "no_login_warning": "비로그인 상태입니다. 편집 시 지금 접속한 IP가 기록됩니다.",
+    "decument_404_error": "이 문서는 존재하지 않습니다.",
+    "key": "키",
+    "lastest": "최신",
+    "now_password": "현재 비밀번호",
+    "same_file_error": "동일한 이름의 파일이 존재합니다.",
+    "same_id_exist_error": "이미 동일한 이름의 계정이 존재합니다.",
+    "max_file_size": "최대 파일 크기",
+    "new_password": "새 비밀번호",
+    "authority_use_list": "권한 사용 목록",
+    "alarm": "알림",
+    "main_body": "본문 상단",
+    "all_document_list": "모든 문서 목록",
+    "update": "업데이트",
+    "login_able": "로그인 허용",
+    "discussion_tool": "토론 도구",
+    "regex": "정규표현식",
+    "file_filter_add": "파일명 필터 추가",
+    "wiki_restart": "위키 엔진 재시작",
+    "discussion_raw": "토론 댓글 원본",
+    "main_setting": "메인 설정",
+    "password": "비밀번호",
+    "update_error": "자동 업데이트가 지원되지 않습니다.",
+    "need_document": "필요한 문서들",
+    "sub": "하위",
+    "template": "템플릿 문서",
+    "user_document_acl": "사용자 문서 ACL",
+    "password_confirm": "비밀번호 확인",
+    "pinned": "고정",
+    "edit_filter_add": "편집 필터 추가",
+    "ban_authority": "차단 권한",
+    "file_extension_error": "지정된 확장자의 파일만 올릴 수 있습니다.",
+    "host": "호스트",
+    "email_text": "이메일 내용",
+    "recent": "최근",
+    "wiki_host": "위키 호스트",
+    "error_404": "존재하지 않는 문서 문구",
+    "member_list": "사용자 목록",
+    "register_text": "회원 가입 정책",
+    "interwiki_list": "인터위키 목록",
+    "email_change": "이메일 변경",
+    "edit_button_paragraph": "문단",
+    "check": "검사",
+    "admin": "관리자",
+    "edit_filter_list": "편집 필터 목록",
+    "wiki_skin": "위키 스킨",
+    "admin_group": "권한 그룹",
+    "all": "전체",
+    "error_skin_set": "사용 중인 스킨은 스킨 설정 기능을 지원하지 않습니다.",
+    "error_skin_set_old": "일부 오래된 스킨은 구버전 링크를 이용해야만 작동할 수도 있습니다.",
+    "member": "가입자",
+    "backlink": "역링크",
+    "no_admin_block_error": "관리자를 검사하거나 차단할 수 없습니다.",
+    "recaptcha": "reCAPTCHA",
+    "create": "생성",
+    "restart": "재시작",
+    "edit_bottom_text": "편집창 하단 문구",
+    "copyright_checkbox_text": "저작권 동의 문구(체크박스 형태)",
+    "authority": "권한",
+    "document": "문서",
+    "move_error": "이동하는 과정 중 오류가 발생하였습니다. 전부 또는 일부가 이동되지 않았습니다.",
+    "skin_setting": "스킨 설정",
+    "discussion_list": "토론 목록",
+    "restart_required": "재시작 필요",
+    "save": "저장",
+    "delete": "삭제",
+    "file_exist_error": "파일이 존재하지 않습니다.",
+    "text_setting": "문구 설정",
+    "document_acl_authority": "문서 ACL 관리 권한",
+    "wiki_logo": "위키 로고",
+    "smtp_setting_required": "이메일 SMTP 설정 필요",
+    "adsense_setting": "애드센스 설정",
+    "smtp_username": "SMTP 아이디",
+    "previous": "이전",
+    "name_or_ip_or_regex_or_cidr": "사용자 이름 혹은 IP 주소 혹은 정규표현식 혹은 CIDR",
+    "editor": "편집자",
+    "wiki_secret_key": "위키 비밀 키",
+    "band_ban": "대역 차단",
+    "http_warning": "경고: HTTPS 연결을 사용하지 않는다면 개인정보가 유출될 수 있습니다. 이 사항에 의해 입는 피해는 사용자에게 책임이 있음을 알려드립니다.",
+    "update_warning": "최신 버전보다 0.2 버전 이상 낮은 경우 수동 업데이트를 권장합니다. 윈도우의 경우 route 폴더의 내용이 사라집니다.",
+    "discussion_name": "토론 제목",
+    "discussion": "토론",
+    "main": "메인",
+    "server": "서버",
+    "admin_tool": "관리 도구",
+    "interwiki_add": "인터위키 추가",
+    "return": "돌아가기",
+    "logout": "로그아웃",
+    "bottom_text": "라이선스 내용",
+    "30_day": "30일",
+    "topic_long_error": "토론 제목은 256자를 넘을 수 없습니다.",
+    "error_401": "ACL 읽기 제한 문서 문구",
+    "history": "역사",
+    "admin_group_list": "권한 그룹 목록",
+    "time": "시간",
+    "password_search_text": "비밀번호 찾기 문구",
+    "agreed_discussion": "합의된 토론",
+    "explanation": "설명",
+    "name": "이름",
+    "period": "기간",
+    "writer": "작성자",
+    "long_id_error": "ID는 128자보다 짧아야 합니다.",
+    "edit_record_error": "사유는 500자를 넘을 수 없습니다.",
+    "revert": "복원",
+    "discussion_authority": "토론 관리 권한",
+    "user_setting": "사용자 설정",
+    "admin_list": "관리자 목록",
+    "add": "추가",
+    "check_key": "인증 키 확인",
+    "file_filter_list": "파일명 필터 목록",
+    "5_day": "5일",
+    "email_filter_list": "이메일 필터 목록",
+    "user_reset_sign": "사용자의 계정 정보가 다음과 같이 변경되었습니다.",
+    "ban_period": "차단 기간",
+    "use_push_alarm": "푸쉬 알림 사용",
+    "email_filter_add": "이메일 필터 추가",
+    "authority_error": "권한이 부족합니다.",
+    "type": "유형",
+    "document_setting": "문서 설정",
+    "mutiple_document_setting": "다중 문서 설정",
+    "owner": "소유자",
+    "password_search": "비밀번호 찾기",
+    "file_name": "파일명",
+    "close_discussion": "닫힌 토론",
+    "language": "언어",
+    "id_char_error": "XSS 문자(부등호, 따옴표)와 .과 :는 사용 불가능합니다.",
+    "id_filter_add": "ID 필터 추가",
+    "skin": "스킨",
+    "user_head": "사용자 <HEAD>",
+    "agreement": "동의",
+    "stop": "중지",
+    "application_submitted": "회원 가입 신청 완료",
+    "waiting_for_approval": "회원 가입 신청이 정상적으로 접수됐습니다. 관리자의 승인을 기다려주세요.",
+    "ie_no_data_required": "이 기능을 수행하기 위해 필요한 데이터가 제공되지 않았습니다.",
+    "port": "포트",
+    "reload": "갱신",
+    "indexing": "DB 인덱싱",
+    "topic_state": "토론 상태",
+    "watchlist": "주시 목록",
+    "main_page": "대문",
+    "user": "사용자",
+    "skin_info": "스킨 정보",
+    "new_connection": "연결...",
+    "discussion_record": "토론 참여 기록",
+    "move": "이동",
+    "recent_change": "최근 편집",
+    "destruction": "취소",
+    "count": "기여 횟수",
+    "main_head": "메인 <HEAD>",
+    "recent_ban": "차단 내역",
+    "smtp_password": "SMTP 비밀번호",
+    "id_filter_list": "ID 필터 목록",
+    "other": "기타",
+    "edit": "편집",
+    "user_head_warning": "비로그인시 브라우저를 닫거나 로그인시 사용자가 지정한 정보는 삭제됩니다.",
+    "email_required": "가입시 이메일 요구",
+    "1_day": "1일",
+    "regex_error": "정규표현식에 오류가 있습니다.",
+    "backup_interval": "백업 주기",
+    "license": "라이선스",
+    "hide_release": "숨김 해제",
+    "360_day": "360일",
+    "inter_error": "내부 오류.",
+    "tool": "도구",
+    "adsense_enable": "애드센스 사용",
+    "list": "목록",
+    "release": "차단 해제",
+    "category": "분류",
+    "history_delete": "역사 삭제",
+    "markup": "문법",
+    "title": "제목",
+    "edit_tool_list": "편집 도구 목록",
+    "edit_tool_add": "편집 도구 추가",
+    "image_license_list": "이미지 라이선스 목록",
+    "image_license_add": "이미지 라이선스 추가",
+    "direct_input": "직접 입력",
+    "acl_record": "ACL 기록",
+    "main_bottom_body": "본문 하단",
+    "reference": "참고",
+    "last_edit_time": "최근 수정 시각",
+    "link": "링크",
+    "icon": "아이콘",
+    "topic_setting": "토론 설정",
+    "old_page": "편집된 지 오래된 문서",
+    "skin_set": "스킨 설정",
+    "edit_help": "편집 창 문구",
+    "default_edit_help": "이곳에 내용을 입력해주세요.",
+    "many_delete": "다중 문서 삭제",
+    "many_delete_help": "한 줄에 이름을 한 개씩 적어주세요.",
+    "content": "내용",
+    "upload_acl": "파일 올리기 ACL",
+    "topic_delete": "토론 삭제",
+    "edit_request": "편집 요청",
+    "edit_request_check": "편집 요청 검사",
+    "sqlite_only": "SQLite만",
+    "off": "끄기",
+    "slow_edit": "편집 속도 제한 시간",
+    "requires_approval": "가입시 승인 필요",
+    "approval_question": "회원 가입 질문",
+    "public_key": "공개 키",
+    "fast_edit_error": "편집 속도가 너무 빠릅니다. 제한 (초) : ",
+    "main_acl_setting": "기본 ACL 설정",
+    "edit_request_acl": "편집 요청 ACL",
+    "application_list": "가입신청 목록",
+    "application_time": "가입신청 일시",
+    "answer": "답변",
+    "approve": "승인",
+    "decline": "거절",
+    "approve_or_decline": "승인 및 거절",
+    "history_add": "역사 추가",
+    "too_many_dec_error": "문서 수가 너무 많아서 지원하지 않는 기능입니다.",
+    "approval_question_visible_only_when_approval_on": "회원 가입 질문은 가입시 승인필요 설정이 활성화됐을때만 보여집니다. 이 설정은 메인 설정에서 활성하실 수 있습니다.",
+    "no_applications_now": "회원 가입 신청이 없습니다.",
+    "application_not_found": "존재하지 않는 회원 가입 신청입니다.",
+    "approval_requirement_disabled": "현재 가입시 승인필요 설정이 비활성화되어있습니다. 필요시 설정에서 활성화할 수 있습니다.",
+    "all_register_num": "모든 가입 신청자의 수",
+    "replace_move": "서로 바꾸기",
+    "merge_move": "문서 병합",
+    "add_admin_group": "권한 그룹 추가",
+    "add_watchlist": "주시 문서 추가",
+    "blocked_user": "차단된 사용자",
+    "blocked_admin": "차단한 관리자",
+    "invalid_password_error": "비밀번호 또는 아이디가 없습니다.",
+    "accept_edit_request": "편집 요청 승인",
+    "msg_whatchlist_lmt": "최대 추가 가능 갯수",
+    "watchlist_overflow_error": "추가 한도를 초과했으므로 더 이상 추가할 수 없습니다. 필요없는 항목을 삭제하십시오.",
+    "unset": "미설정",
+    "copyright_disagreed": "저작권 동의문구에 동의하여야 합니다.",
+    "category_title": "이 분류 아래 문서들",
+    "extension_filter_list": "확장자 필터 목록",
+    "extension_filter_add": "확장자 필터 추가",
+    "extension": "확장자",
+    "topic_name_change": "토론 제목 변경",
+    "topic_acl_setting": "토론 ACL 설정",
+    "topic_acl": "토론 ACL",
+    "main_skin_set": "기본 스킨 설정",
+    "image_file_list": "이미지 파일 목록",
+    "many_upload_acl": "다중 파일 올리기 ACL",
+    "reset_backlink": "역링크 초기화",
+    "link_in_this": "이 문서의 링크",
+    "star_doc": "관심 문서",
+    "add_star_doc": "관심 문서 추가",
+    "enter_html": "HTML을 입력하세요.",
+    "exp_edit_conflict": "편집 충돌 발생",
+    "backup_where": "백업 위치",
+    "empty": "빈칸",
+    "email_send_error": "이메일 전송이 실패했습니다.",
+    "sitemap_management": "sitemap.xml 관리",
+    "same_ip_exist": "동일한 IP가 존재합니다.",
+    "restart_fail_error": "재시작이 실패했습니다. 수동 재시작을 이용해주세요.",
+    "domain": "도메인",
+    "simple_check": "간편 검사",
+    "add_user": "계정 추가",
+    "result": "결과",
+    "vote_list": "투표 목록",
+    "add_vote": "투표 추가",
+    "open_vote": "공개 투표",
+    "not_open_vote": "비밀 투표",
+    "close_vote_list": "닫힌 투표 목록",
+    "vote": "투표",
+    "result_vote": "투표 결과",
+    "1_line_1_q": "1줄당 1개의 선택지를 쓰세요.",
+    "open_vote_list": "열린 투표 목록",
+    "close_vote": "투표 닫기",
+    "re_open_vote": "투표 다시 열기",
+    "on": "켜기",
+    "2fa": "2차 인증",
+    "2fa_password": "2차 비밀번호",
+    "2fa_password_change": "2차 비밀번호 변경",
+    "vote_acl": "투표 ACL",
+    "upload_help": "파일 올리기 문구",
+    "upload_default": "파일 기타란 기본값",
+    "same_email_error": "동일한 이메일을 사용하는 사용자가 있습니다.",
+    "input_email_error": "입력 값에 문제가 있습니다.",
+    "short_page": "짧은 문서 목록",
+    "long_page": "긴 문서 목록",
+    "ua_get_off": "UA 수집 끄기",
+    "ext_api_req_set": "외부 API 필요 설정",
+    "oauth": "OAuth",
+    "basic_set": "기본 설정",
+    "design_set": "디자인 관련 설정",
+    "login_set": "로그인 관련 설정",
+    "server_set": "서버 관련 설정",
+    "edit_set": "편집 관련 설정",
+    "wiki_shutdown": "위키 엔진 종료",
+    "shutdown": "종료",
+    "delete_warning": "지울 경우 복구하기 어려우니 신중하게 결정하세요.",
+    "history_reset": "문서 역사 초기화",
+    "record_reset": "사용자 편집 기록 초기화",
+    "email_setting": "이메일 설정",
+    "oauth_client_id": "OAuth 클라이언트 ID",
+    "today_doc": "오늘의 문서",
+    "send_edit": "편집 사유 수정",
+    "document_filter_list": "문서명 필터 목록",
+    "document_filter_add": "문서명 필터 추가",
+    "password_instead_key": "대체 인증키",
+    "key_change": "키 변경",
+    "key_delete": "키 삭제",
+    "email_delete": "이메일 삭제",
+    "not_working": "작동 안함",
+    "topic_text": "토론 기본 문구",
+    "hcaptcha": "hCAPTCHA",
+    "captcha": "CAPTCHA",
+    "make_new_topic": "새 토론 생성",
+    "old_page_warning": "이 문서는 오래되었습니다.",
+    "challenge_title_register": "Hello, World!",
+    "challenge_info_register": "가입을 하세요.<br><br>보상 : 🌳 칭호",
+    "challenge_title_first_contribute": "시작이 반이다.",
+    "challenge_info_first_contribute": "첫 기여를 하세요.<br><br>보상 : 🔰 칭호와 500 경험치",
+    "challenge_title_tenth_contribute": "소년이여, 야망을 가져라!",
+    "challenge_info_tenth_contribute": "10번째 기여를 하세요.<br><br>보상 : 📝 칭호와 1000 경험치",
+    "challenge_title_hundredth_contribute": "아는 것이 힘이다.",
+    "challenge_info_hundredth_contribute": "100번째 기여를 하세요.<br><br>보상 : 🖊️ 칭호와 3000 경험치",
+    "challenge_title_thousandth_contribute": "대기만성",
+    "challenge_info_thousandth_contribute": "1000번째 기여를 하세요.<br><br>보상 : 🏅 칭호와 10000 경험치",
+    "challenge_title_first_discussion": "진실은 보통 모함에 맞서는 최고의 해명이다.",
+    "challenge_info_first_discussion": "1번째 토론 스레드를 올리세요.<br><br>보상 : 💬 칭호와 500 경험치",
+    "challenge_title_tenth_discussion": "내가 길을 찾아내거나 직접 길을 만들겠다.",
+    "challenge_info_tenth_discussion": "10번째 토론 스레드를 올리세요.<br><br>보상 : 💡 칭호와 1000 경험치",
+    "challenge_title_hundredth_discussion": "모두가 비슷한 생각을 한다는 것은, 아무도 생각하고 있지 않다는 말이다.",
+    "challenge_info_hundredth_discussion": "100번째 토론 스레드를 올리세요.<br><br>보상 : 📢 칭호와 3000 경험치",
+    "challenge_title_thousandth_discussion": "나는 생각한다. 고로 존재한다.",
+    "challenge_info_thousandth_discussion": "1000번째 토론 스레드를 올리세요.<br><br>보상 : 📜 칭호와 10000 경험치",
+    "challenge_title_admin": "왕후장상의 씨가 어찌 따로 있단 말이냐!",
+    "challenge_info_admin": "관리자가 되세요.<br><br>보상 : ☑️ 칭호와 10000 경험치",
+    "challenge": "도전과제",
+    "user_title": "칭호",
+    "alpha": "알파",
+    "beta": "베타",
+    "example": "예시",
+    "communication_set": "커뮤니케이션 기능 설정",
+    "edit_bottom_compulsion": "편집 사유 필수",
+    "enable_comment_function": "댓글 기능 사용",
+    "enable_challenge_function": "도전과제 기능 사용",
+    "error_edit_send_request": "편집 사유가 필요합니다.",
+    "tls_method": "TLS 방식",
+    "title_max_length": "문서 제목 최대 길이",
+    "title_topic_max_length": "토론 제목 최대 길이",
+    "error_title_length_too_long": "문서 제목이나 토론 제목의 길이가 너무 깁니다. 최대 글자 수 : ",
+    "thread_acl": "스레드 ACL",
+    "password_min_length": "비밀번호 최소 길이",
+    "error_password_length_too_short": "비밀번호 길이가 너무 짧습니다. 최소 글자 수 : ",
+    "phrase_user_page_admin": "관리자인 사용자 문서 문구",
+    "phrase_user_page_owner": "소유자인 사용자 문서 문구",
+    "error_password_require_for_wiki_access": "위키에 접속하려면 비밀번호가 필요합니다.",
+    "set_wiki_access_password_need": "위키 접속시 비밀번호 필요",
+    "set_wiki_access_password": "위키 접속 비밀번호",
+    "set_history_recording_off": "역사 기록 중지",
+    "multiple_ban": "다중 차단",
+    "name_or_ip_or_regex_or_cidr_multiple": "한 줄에 IP나 정규표현식이나 CIDR을 한 개씩 적어주세요.",
+    "dont_move": "이동하지 않음",
+    "stiemap_exclude_domain": "도메인 제외",
+    "stiemap_exclude_user_page": "사용자 문서 제외",
+    "stiemap_exclude_file_page": "파일 문서 제외",
+    "stiemap_exclude_category_page": "분류 문서 제외",
+    "search_document_name": "문서명 검색",
+    "search_document_data": "문서 내용 검색",
+    "admin_acl": "관리자, 소유자",
+    "member_acl": "가입자",
+    "50_edit_acl": "기여 횟수가 50회 이상인 가입자, 관리자, 소유자",
+    "all_acl": "전체 사용자 (차단된 사용자 제외)",
+    "email_acl": "이메일을 등록한 가입자, 관리자, 소유자",
+    "owner_acl": "소유자",
+    "before_acl": "전에 이 문서를 편집한 적 있는 사람, 관리자, 가입자",
+    "ban_acl": "전체 사용자 (차단된 사용자 포함)",
+    "ban_admin_acl": "차단된 사용자, 관리자, 소유자",
+    "30_day_acl": "가입 후 30일이 지난 가입자, 관리자, 소유자",
+    "90_day_acl": "가입 후 90일이 지난 가입자, 관리자, 소유자",
+    "not_all_acl": "전부 금지",
+    "document_move_acl": "문서 이동 ACL",
+    "document_delete_acl": "문서 삭제 ACL",
+    "document_edit_acl": "문서 편집 ACL",
+    "phrase_old_page_warning": "이전 리비전 문서 방문시 경고문",
+    "toc": "목차",
+    "topic_view_acl": "토론 보기 ACL",
+    "file_delete": "파일 삭제",
+    "topic_change_agree": "합의가 완료된 토론으로 전환",
+    "topic_progress": "토론 진행",
+    "topic_associate": "다른 기능과 토론 연계",
+    "topic_link_vote": "토론과 투표 연계",
+    "topic_insert_vote_number": "투표의 번호",
+    "topic_state_change_normal": "관리자가 토론을 정상화 했습니다.",
+    "topic_state_change_stop": "관리자가 토론을 일시 중지 했습니다.",
+    "topic_state_change_close": "관리자가 토론을 닫았습니다.",
+    "topic_state_change_agree": "관리자가 토론의 합의를 승인했습니다.",
+    "topic_state_change_disagree": "관리자가 토론의 합의를 파기했습니다.",
+    "topic_normal": "일반",
+    "topic_stop": "중지",
+    "topic_close": "닫힘",
+    "turn_off_monaco": "모나코 에디터 켜기/끄기",
+    "topic_agree": "토론 합의 완료",
+    "user_css_warning": "만약 사용하다가 문제가 생기면 여기로 접속하세요.",
+    "main_css_warning": "만약 사용하다가 문제가 생기면 이머전시 툴을 사용하세요.",
+    "reset": "초기화",
+    "namumark_fully_compatible_mode": "나무마크 디자인 호환 모드",
+    "added_menu": "추가 메뉴",
+    "top_menu_setting": "추가 메뉴 설정",
+    "user_added_menu": "사용자 추가 메뉴",
+    "enter_top_menu_setting": "윗 줄에는 이름을 쓰고 아랫 줄에는 URL을 입력하세요.",
+    "not_support_skin_warning": "이 기능을 미지원하는 스킨에서는 작동하지 않습니다.",
+    "acl_thread_change": "스레드 ACL 변경",
+    "new_application": "새로운 가입 신청이 있습니다.",
+    "top": "상단",
+    "bottom": "하단",
+    "use": "사용",
+    "change_to_normal": "일반 글자로 변경",
+    "change_to_link": "링크로 변경",
+    "click_load": "클릭시 로드",
+    "all_off": "전부 끄기",
+    "in_content": "자동 목차 끄기",
+    "self_tab": "현재 탭에서 링크 열기",
+    "only_korean": "한국어만 사용 가능",
+    "unavailable_in_monaco": "모나코 에디터 상에서 사용 불가",
+    "render": "렌더링",
+    "strike": "취소선",
+    "bold": "굵게",
+    "footnote": "각주",
+    "include_link": "틀 링크",
+    "image": "이미지",
+    "exter_link": "외부 링크",
+    "link_delimiter": "링크 구분자",
+    "force_darkmode": "강제 다크모드",
+    "font_size": "글자 크기",
+    "image_paste": "이미지 붙여넣기 (컨트롤 C + V로)",
+    "monaco_editor": "모나코 에디터",
+    "document_set": "문서 설정",
+    "move_redirect_make": "넘겨주기 문서 생성 (가능한 경우에만)",
+    "slow_edit_acl": "편집 속도 제한 시간 제외 대상 ACL",
+    "edit_bottom_compulsion_acl": "편집 사유 필수 제외 대상 ACL",
+    "main_skin_set_default": "기본 스킨 설정 기본값",
+    "spread": "펼침",
+    "popup": "팝업",
+    "popover": "팝오버",
+    "user_fix": "사용자 수정",
+    "2fa_off": "2FA 끄기",
+    "bbs": "게시판",
+    "bbs_main": "게시판 메인",
+    "bbs_name": "게시판 이름",
+    "bbs_make": "게시판 생성",
+    "order": "순번",
+    "comment_base": "댓글 기반",
+    "thread_base": "스레드 기반",
+    "bbs_set": "게시판 설정",
+    "bbs_view_acl": "게시판 읽기 ACL",
+    "bbs_acl": "게시판 ACL",
+    "bbs_edit_acl": "게시판 글 작성 ACL",
+    "bbs_comment_acl": "게시판 댓글 작성 ACL",
+    "sub_user_name": "보조 사용자 이름",
+    "render_set": "렌더링 관련 설정",
+    "link_case_insensitive": "링크 대소문자 구분 안함",
+    "hide_user_name": "가입자 이름 숨기기",
+    "comment": "댓글",
+    "reply": "대댓글",
+    "timeout_error": "렌더링 시간이 너무 오래 걸립니다. 최대 실행 시간 (초) : ",
+    "footnote_render": "각주 렌더링",
+    "footnote_number": "각주 번호 출력",
+    "only_number": "숫자만",
+    "footnote_real_num_view": "각주 실제 번호 보기",
+    "post_edit": "게시글 수정",
+    "post_add": "게시글 추가",
+    "main_user_name": "메인 사용자명",
+    "change_user_name": "메인 사용자명 변경",
+    "last_comment_time": "마지막 댓글 시간",
+    "bbs_comment_tool": "게시판 댓글 도구",
+    "bbs_comment_edit": "게시판 댓글 수정",
+    "move_with_redirect": "문서 이동시 넘겨주기 문서 생성",
+    "slow_thread": "토론 올리기 제한 시간",
+    "edit_timeout": "렌더링 제한 시간",
+    "linux_only": "리눅스 OS 전용",
+    "bbs_post_tool": "게시판 게시글 도구",
+    "position": "위치",
+    "category_change_title": "분류 내 문서명 변경 금지",
+    "table_scroll": "표에 스크롤 사용",
+    "data": "데이터",
+    "volume": "용량",
+    "list_view_change": "숫자 리스트의 보기 방식 변경",
+    "view_joke": "농담 매크로 내용 보이기",
+    "math_scroll": "Math 매크로에서 스크롤 사용",
+    "trace": "추적",
+    "view_history": "문서 열람 추적 사용",
+    "start_with_search": "첫 글자부터 검색",
+    "backup_warning": "경고. 동일한 이름의 파일이 있는 경우 지워질 수 있습니다.",
+    "level": "레벨",
+    "challenge_and_level_manage": "도전과제와 레벨 관리",
+    "display_level_in_user_name": "사용자 이름에 레벨 표시",
+    "ua": "UA",
+    "day": "일",
+    "ua_expiration_date": "UA 만료 기한",
+    "backup": "백업",
+    "document_content_max_length": "문서 최대 길이",
+    "bbs_record": "게시판 작성글 목록",
+    "bbs_comment_record": "게시판 작성댓글 목록",
+    "load_temp_save": "임시 저장",
+    "load_temp_save_load": "임시 저장 불러오기",
+    "bbs_comment_delete": "게시판 댓글 삭제",
+    "bbs_post_pinned": "게시판 게시글 고정",
+    "bbs_post_delete": "게시판 게시글 삭제",
+    "outer_link_filter_list": "외부 링크 자동 이미지 목록",
+    "html_or_link": "HTML 혹은 링크",
+    "template_document_list": "템플릿 문서 목록",
+    "no_link_document_list": "링크가 없는 문서 목록",
+    "authority_use_list_off": "권한 사용 목록 끄기",
+    "authority_use_list_expiration_date": "권한 사용 목록 만료 기한",
+    "bbs_help": "게시판 편집기 도움말",
+    "bbs_comment_help": "게시판 댓글 편집기 도움말",
+    "outdated_doc_warning": "오래된 문서 경고",
+    "redirect": "넘겨주기",
+    "new_doc": "새 문서",
+    "table": "표",
+    "table_transparent": "표 투명화",
+    "recaptcha_pass_acl": "CAPTCHA 통과 ACL",
+    "recaptcha_one_check_five_pass_acl": "CAPTCHA 1회 통과시 5회 미시행 ACL",
+    "func_404_error": "이 URL에 맞는 기능이 없습니다.",
+    "new_page": "새로운 문서 목록",
+    "admin_authority": "관리자 권한",
+    "user_authority": "사용자 권한",
+    "admin_default_feature_authority": "관리자 기본 기능 권한",
+    "captcha_pass_authority": "CAPTCHA 넘기기 권한",
+    "ip_authority": "IP 권한",
+    "view_authority": "문서 보기 권한",
+    "document_authority": "문서 권한",
+    "edit_authority": "문서 편집 권한",
+    "move_authority": "문서 이동 권한",
+    "new_make_authority": "문서 생성 권한",
+    "delete_authority": "문서 삭제 권한",
+    "edit_request_authority": "편집 요청 권한",
+    "discuss_authority": "토론 권한",
+    "upload_authority": "파일 올리기 권한",
+    "vote_authority": "투표 권한",
+    "captcha_one_check_five_pass_authority": "한번 CAPTCHA 인증시 5번 넘기기 권한",
+    "user_name_bold_authority": "관리자로 표시 권한",
+    "multiple_upload_authority": "다중 파일 올리기 권한",
+    "slow_edit_pass_authority": "편집 속도 제한 넘기기 권한",
+    "edit_bottom_compulsion_pass_authority": "편집 사유 필수 넘기기 권한",
+    "404_page_setting": "404 페이지 설정",
+    "404_page": "아래의 입력을 404 페이지로 사용",
+    "404_file": "루트 폴더 안의 404.html 사용",
+    "completely_ban": "완전 차단",
+    "dont_come_this_site": "사이트 접근 차단",
+    "recent_edit_request": "최근 편집 요청",
+    "all_function_authority": "모든 기능 권한",
+    "bbs_authority": "BBS 권한",
+    "treat_as_admin_authority": "관리자로 취급 권한",
+    "backup_count": "최대 백업 파일 갯수",
+    "template_var_1": "템플릿 변수 1",
+    "template_var_2": "템플릿 변수 2",
+    "template_var_3": "템플릿 변수 3",
+    "sidebar": "사이드바",
+    "document_make_acl": "문서 생성 ACL",
+    "document_edit_request_acl": "문서 편집 요청 ACL",
+    "up_to_level_3": "레벨 3 이상",
+    "up_to_level_10": "레벨 10 이상",
+    "document_top": "문서 상단 문구",
+    "document_editor_top": "문서 편집기 상단 문구",
+    "cidr": "CIDR",
+    "option": "옵션",
+    "edit_request_able": "편집 요청 가능",
+    "date": "날짜",
+    "wiki_load_ip_select" : "IP를 가져올 헤더",
+    "auto_login" : "자동 로그인",
+    "view_hide_user_name_authority" : "숨겨진 사용자 이름과 IP 보기",
+    "view_user_watchlist_authority" : "사용자 주시 목록 보기",
+    "doc_watch_list_view_authority" : "문서를 주시 목록에 추가한 사람 목록 보기",
+    "bbs_view_authority" : "BBS 보기 권한",
+    "bbs_comment_authority" : "BBS 댓글 권한",
+    "bbs_edit_authority" : "BBS 편집 권한",
+    "user_analyze_authority" : "사용자 분석 권한",
+    "still_use_auth_error" : "이 권한을 보유한 사람이 아직 존재합니다.",
+    "xss_data_include_error" : "XSS에 이용될 수 있는 문자는 입력할 수 없습니다.",
+    "nothing_authority" : "아무 기능 없는 권한",
+    "multiple_authorize" : "다중 권한 부여",
+    "auth_to_auth" : "권한 그룹에서 권한 그룹으로",
+    "vote_management_authority" : "투표 관리 권한",
+    "bbs_management_authority" : "BBS 관리 권한",
+    "discuss_view_authority" : "토론 보기 권한",
+    "page_view" : "조회수",
+    "discuss_make_new_thread_authority" : "새 토론 만들기 권한",
+    "multiple_move" : "다중 문서 이동",
+    "file_delete_with_document" : "문서와 파일 동시 삭제",
+    "url" : "URL",
+    "resolution" : "해상도",
+    "method" : "방법",
+    "edit_filter_pass_authority" : "편집 필터 통과 권한",
+    "edit_filter_view_authority" : "편집 필터 보기 권한",
+    "post_view_acl" : "게시글 보기 ACL",
+    "post_comment_acl" : "게시글 댓글 ACL",
+    "remove_hidden": "숨겨진 내용 보이지 않기",
+    "link_count" : "링크 갯수",
+    "data_missing" : "정보 없음",
+    "not_use_view_count" : "조회수 기능 사용 안함",
+    "login_able_and_regsiter_disable" : "로그인 허용 및 회원가입 비허용",
+    "private" : "비공개",
+    "password_same_as_id_error": "비밀번호는 사용자 ID와 같을 수 없습니다.",
+    "upvote" : "추천"
+}

+ 40 - 0
readme-en.md

@@ -0,0 +1,40 @@
+[(en-US)](./readme-en.md) | [(ko-KR)](./readme.md)
+
+# openNAMU
+[![Up to Python 3.8](https://img.shields.io/badge/python->=%203.8-blue.svg)](https://python.org)
+[![LICENSE](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
+
+![](https://raw.githubusercontent.com/openNAMU/openNAMU/beta/.github/logo.png)
+
+openNAMU is a Python-based wiki engine. 
+
+## Getting Started
+openNAMU is based upon Python, and it requires a Python environment.
+
+[Here](https://2du.pythonanywhere.com/w/en%3AInstall) you can see the guide.
+
+## Clone
+You can clone this repository by entering the following command at the terminal (command prompt):
+ * `git clone -b stable https://github.com/openNAMU/openNAMU.git`
+ * `git clone -b beta https://github.com/openNAMU/openNAMU.git`
+ * `git clone -b dev https://github.com/openNAMU/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/openNAMU/openNAMU/issues/new)
+
+openNAMU is a complete open source project. Add new features and create pull requests. [(Create Pull Requests)](https://github.com/openNAMU/openNAMU/compare)
+
+## Lisence
+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)
+ * [highlight.js](https://highlightjs.org/)
+ * [KaTeX](https://katex.org/)
+ * [Feather](https://feathericons.com/)
+ * [go.mod](https://github.com/openNAMU/openNAMU/blob/beta/route_go/go.mod)
+
+## Etc.
+ * Owner rights are granted to the first registor.
+ * [Test Server](http://2du.pythonanywhere.com)
+ * [Contributors](https://github.com/openNAMU/openNAMU/graphs/contributors)

+ 50 - 0
readme.md

@@ -0,0 +1,50 @@
+[(en-US)](./readme-en.md) | [(ko-KR)](./readme.md)
+
+# 오픈나무
+[![Python 3.8 이상](https://img.shields.io/badge/python->=%203.8-blue.svg)](https://python.org)
+[![라이선스](https://img.shields.io/badge/license-BSD%203--Clause-lightgrey.svg)](./LICENSE)
+
+![](https://raw.githubusercontent.com/openNAMU/openNAMU/beta/.github/logo.png)
+
+오픈나무는 파이썬 기반의 위키 엔진입니다.
+
+## 시작하기
+오픈나무는 파이썬 환경에서 동작하는 파이썬 애플리케이션으로, 파이썬 환경을 필요로 합니다.
+
+[여기](https://2du.pythonanywhere.com/w/설치법)를 눌러 설치 가이드를 볼 수 있습니다.
+
+### 클론
+아래 명령을 터미널(명령 프롬프트)에 입력하여 본 리포지토리를 클론할 수 있습니다.
+ * 일반: `git clone -b stable https://github.com/openNAMU/openNAMU.git`
+ * 베타: `git clone -b beta https://github.com/openNAMU/openNAMU.git`
+ * 개발: `git clone -b dev https://github.com/openNAMU/openNAMU.git`
+
+## 기여
+오픈나무에는 확인되지 않은 버그가 존재할 수 있습니다. 이를 보고해주시면 오픈나무의 발전을 도울 수 있습니다. [여기](https://github.com/openNAMU/openNAMU/issues/new)를 눌러 버그를 보고해주세요.
+
+오픈나무는 오픈소스 프로젝트입니다. 원한다면 직접 코드를 수정하고 [Pull Request](https://github.com/openNAMU/openNAMU/compare)를 보낼 수 있습니다.
+
+## 라이선스
+오픈나무 프로젝트는 [BSD 3-Clause License](./LICENSE) [(ko-KR)](https://www.olis.or.kr/license/Detailselect.do?lId=1092)이며, 오픈나무 프로젝트를 사용하고자 한다면 라이선스를 준수해야 합니다. 자세한 내용은 문서를 참고하세요.
+
+### 포함된 외부 프로젝트
+ * [Quotes icon - Dave Gandy](http://www.flaticon.com/free-icon/quote-left_25672)
+ * [highlight.js](https://highlightjs.org/)
+ * [KaTeX](https://katex.org/)
+ * [Feather](https://feathericons.com/)
+ * [go.mod](https://github.com/openNAMU/openNAMU/blob/beta/route_go/go.mod)
+
+### 도움을 주신 분들
+ * [Team Croatia](https://github.com/TeamCroatia)
+ * Basix
+ * Efrit
+ * 기타 여러 사람들
+
+## 지원 문법
+ * 나무마크 (NamuMark)
+ * 마크다운 (Markdown) (Beta)
+
+## 기타
+ * 첫 가입자에게 소유자 권한이 부여됩니다.
+ * [테스트 서버](http://2du.pythonanywhere.com)
+ * [기여자 목록](https://github.com/openNAMU/openNAMU/graphs/contributors)

+ 3 - 0
requirements-optional.txt

@@ -0,0 +1,3 @@
+mysqlclient
+
+orjson

+ 15 - 0
requirements.txt

@@ -0,0 +1,15 @@
+pip
+
+flask
+flask[async]
+waitress
+
+aiohttp
+asyncio
+requests
+
+diff-match-patch
+
+pillow
+
+pymysql

+ 228 - 0
route/__init__.py

@@ -0,0 +1,228 @@
+from route.api_image_view import api_image_view
+from route.api_skin_info import api_skin_info
+from route.api_user_info import api_user_info
+from route.api_version import api_version
+
+from route.bbs_w_edit import bbs_w_edit
+from route.bbs_make import bbs_make
+# from route.bbs_w_hide import bbs_w_hide
+from route.bbs_w_pinned import bbs_w_pinned
+from route.bbs_w_delete import bbs_w_delete
+from route.bbs_w import bbs_w
+from route.bbs_delete import bbs_delete
+# from route.bbs_hide import bbs_hide
+from route.bbs_w_post import bbs_w_post
+from route.bbs_w_comment_tool import bbs_w_comment_tool
+from route.bbs_w_tool import bbs_w_tool
+
+from route.edit import edit
+from route.edit_backlink_reset import edit_backlink_reset
+from route.edit_delete import edit_delete
+from route.edit_delete_file import edit_delete_file
+from route.edit_delete_multiple import edit_delete_multiple
+from route.edit_move import edit_move
+from route.edit_revert import edit_revert
+from route.edit_upload import edit_upload
+from route.edit_request import edit_request
+
+from route.filter_all import filter_all
+from route.filter_all_add import filter_all_add
+from route.filter_all_delete import filter_all_delete
+
+from route.give_admin_groups import give_admin_groups
+from route.give_delete_admin_group import give_delete_admin_group_2
+from route.give_user_ban import give_user_ban
+from route.give_user_fix import give_user_fix
+
+from route.list_acl import list_acl
+from route.list_admin import list_admin
+from route.list_admin_auth_use import list_admin_auth_use
+from route.list_admin_group import list_admin_group_2
+from route.list_image_file import list_image_file
+from route.list_long_page import list_long_page
+from route.list_no_link import list_no_link
+from route.list_please import list_please
+from route.list_title_index import list_title_index
+from route.list_user import list_user
+from route.list_user_check import list_user_check
+from route.list_user_check_delete import list_user_check_delete
+
+from route.login_find import login_find
+from route.login_find_email import login_find_email
+from route.login_find_email_check import login_find_email_check
+from route.login_find_key import login_find_key
+from route.login_login import login_login_2
+from route.login_login_2fa import login_login_2fa_2
+from route.login_login_2fa_email import login_login_2fa_email_2
+from route.login_logout import login_logout
+
+from route.login_register import login_register_2
+from route.login_register_email import login_register_email_2
+from route.login_register_email_check import login_register_email_check_2
+from route.login_register_submit import login_register_submit_2
+
+from route.main_func_error_404 import main_func_error_404
+
+from route.main_search import main_search
+from route.main_search_deep import main_search_deep
+from route.main_search_goto import main_search_goto
+
+from route.main_setting import main_setting
+from route.main_setting_external import main_setting_external
+from route.main_setting_head import main_setting_head
+from route.main_setting_main import main_setting_main
+from route.main_setting_main_logo import main_setting_main_logo
+from route.main_setting_phrase import main_setting_phrase
+from route.main_setting_robot import main_setting_robot
+from route.main_setting_sitemap import main_setting_sitemap
+from route.main_setting_sitemap_set import main_setting_sitemap_set
+from route.main_setting_skin_set import main_setting_skin_set
+from route.main_setting_top_menu import main_setting_top_menu
+
+from route.main_sys_restart import main_sys_restart
+from route.main_sys_shutdown import main_sys_shutdown
+from route.main_sys_update import main_sys_update
+
+from route.main_tool_admin import main_tool_admin
+from route.main_tool_other import main_tool_other
+from route.main_tool_redirect import main_tool_redirect
+
+from route.main_view import main_view
+from route.main_view_file import main_view_file
+from route.main_view_image import main_view_image
+
+from route.recent_app_submit import recent_app_submit_2
+
+from route.recent_change import recent_change
+from route.recent_edit_request import recent_edit_request
+from route.recent_history_add import recent_history_add
+from route.recent_history_delete import recent_history_delete
+from route.recent_history_hidden import recent_history_hidden
+from route.recent_history_reset import recent_history_reset
+from route.recent_history_send import recent_history_send
+from route.recent_history_tool import recent_history_tool
+from route.recent_record_reset import recent_record_reset
+from route.recent_record_topic import recent_record_topic
+
+from route.topic import topic
+from route.topic_comment_blind import topic_comment_blind
+from route.topic_comment_delete import topic_comment_delete
+from route.topic_comment_notice import topic_comment_notice
+from route.topic_comment_tool import topic_comment_tool
+from route.topic_tool import topic_tool
+from route.topic_tool_acl import topic_tool_acl
+from route.topic_tool_change import topic_tool_change
+from route.topic_tool_delete import topic_tool_delete
+from route.topic_tool_setting import topic_tool_setting
+
+from route.user_alarm import user_alarm
+from route.user_alarm_delete import user_alarm_delete
+from route.user_challenge import user_challenge
+from route.user_count import user_count
+from route.user_info import user_info
+from route.user_edit_filter import user_edit_filter
+
+from route.user_setting import user_setting
+from route.user_setting_email import user_setting_email_2
+from route.user_setting_email_check import user_setting_email_check_2
+from route.user_setting_email_delete import user_setting_email_delete
+from route.user_setting_head import user_setting_head
+from route.user_setting_head_reset import user_setting_head_reset
+from route.user_setting_key import user_setting_key
+from route.user_setting_key_delete import user_setting_key_delete
+from route.user_setting_pw import user_setting_pw
+from route.user_setting_skin_set import user_setting_skin_set
+from route.user_setting_skin_set_main import user_setting_skin_set_main
+from route.user_setting_top_menu import user_setting_top_menu
+from route.user_setting_user_name import user_setting_user_name
+
+from route.user_watch_list import user_watch_list
+from route.user_watch_list_name import user_watch_list_name
+
+from route.view_set import view_set
+from route.view_diff import view_diff
+from route.view_down import view_down
+from route.view_raw import view_raw
+from route.view_w_raw import view_w_raw
+from route.view_w import view_w
+from route.view_xref import view_xref
+from route.view_random import view_random
+
+from route.vote_add import vote_add
+from route.vote_close import vote_close
+from route.vote_end import vote_end
+from route.vote_list import vote_list
+from route.vote_select import vote_select
+
+from route.n_list_recent_change import list_recent_change
+from route.n_list_recent_discuss import list_recent_discuss
+from route.n_list_recent_block import list_recent_block
+from route.n_list_old_page import list_old_page
+from route.n_list_user_check_submit import list_user_check_submit
+from route.n_list_history import list_history
+
+from route.n_w_watch_list import w_watch_list
+
+from route.n_user_rankup import user_rankup
+
+from route.n_topic_list import topic_list
+
+from route.n_give_auth import give_auth
+
+from route.n_main_redirect import main_redirect
+
+from route.n_setting_404_page import setting_404_page
+
+from route.n_bbs_main import bbs_main
+from route.n_bbs_in import bbs_in
+from route.n_bbs_w_set import bbs_w_set
+
+from route.n_edit_move_all import edit_move_all
+
+from route.go_api_func_llm import api_func_llm
+from route.go_api_func_language import api_func_language
+from route.go_api_func_sha224 import api_func_sha224
+from route.go_api_func_ip import api_func_ip
+from route.go_api_func_ip_menu import api_func_ip_menu
+from route.go_api_func_auth import api_func_auth
+
+from route.go_api_func_search import api_func_search
+
+from route.go_api_give_auth import api_give_auth
+
+from route.go_api_list_recent_change import api_list_recent_change
+from route.go_api_list_recent_discuss import api_list_recent_discuss
+from route.go_api_list_recent_block import api_list_recent_block
+from route.go_api_list_recent_edit_request import api_list_recent_edit_request
+from route.go_api_list_old_page import api_list_old_page
+from route.go_api_list_title_index import api_list_title_index
+from route.go_api_list_acl import api_list_acl
+from route.go_api_list_auth import api_list_auth
+from route.go_api_list_markup import api_list_markup
+from route.go_api_list_history import api_list_history
+
+from route.go_api_bbs import api_bbs
+from route.go_api_bbs_list import api_bbs_list
+from route.go_api_bbs_w import api_bbs_w
+from route.go_api_bbs_w_set import api_bbs_w_set
+from route.go_api_bbs_w_tabom import api_bbs_w_tabom
+from route.go_api_bbs_w_comment import api_bbs_w_comment
+from route.go_api_bbs_w_comment_one import api_bbs_w_comment_one
+
+from route.go_api_setting import api_setting
+
+from route.go_api_topic import api_topic
+from route.go_api_topic_list import api_topic_list
+
+from route.go_api_user_rankup import api_user_rankup
+from route.go_api_user_setting_editor import api_user_setting_editor
+
+from route.go_api_w_raw import api_w_raw
+from route.go_api_w_random import api_w_random
+from route.go_api_w_xref import api_w_xref
+from route.go_api_w_watch_list import api_w_watch_list
+from route.go_api_w_render import api_w_render
+from route.go_api_w_set_reset import api_w_set_reset
+from route.go_api_w_page_view import api_w_page_view
+
+from route.go_main_func_easter_egg import main_func_easter_egg

+ 8 - 0
route/api_image_view.py

@@ -0,0 +1,8 @@
+from .tool.func import *
+
+def api_image_view(name = 'Test'):
+    with get_db_connect() as conn:
+        if os.path.exists(os.path.join(load_image_url(conn), name)):
+            return flask.jsonify({ "exist" : "1" })
+        else:
+            return flask.jsonify({})

+ 65 - 0
route/api_skin_info.py

@@ -0,0 +1,65 @@
+import urllib.request
+
+from .tool.func import *
+
+def api_skin_info(name = ''):
+    with get_db_connect() as conn:
+        name = skin_check(conn) if name == '' else './views/' + name + '/index.html'
+
+        if not flask.request.args.get('all', None):
+            json_address = re.sub(r"(((?!\.|\/).)+)\.html$", "info.json", name)
+            try:
+                json_data = orjson.loads(open(json_address, encoding='utf8').read())
+            except:
+                json_data = None
+
+            if json_data:
+                return flask.jsonify(json_data)
+            else:
+                return flask.jsonify({}), 404
+        else:
+            a_data = {}
+            d_link_data = {
+                "ACME" : "https://raw.githubusercontent.com/openNAMU/openNAMU-Skin-ACME/master/info.json",
+                "Liberty" : "https://raw.githubusercontent.com/openNAMU/openNAMU-Skin-Liberty/master/info.json",
+                "Before Namu" : "https://raw.githubusercontent.com/openNAMU/openNAMU-Skin-Before_Namu/master/info.json"
+            }
+
+            for i in load_skin(conn, skin_check(conn, 1), 1):
+                json_address = re.sub(r"(((?!\.|\/).)+)\.html$", "info.json", './views/' + i + '/index.html')
+                try:
+                    json_data = orjson.loads(open(json_address, encoding='utf8').read())
+                except:
+                    json_data = None
+
+                if json_data:
+                    if i == skin_check(conn, 1):
+                        json_data = {**json_data, **{ "main" : "true" }}
+
+                    if "info_link" in json_data:
+                        info_link = json_data["info_link"]
+                    elif json_data["name"] in d_link_data:
+                        info_link = d_link_data[json_data["name"]]
+                    else:
+                        info_link = 0
+
+                    if info_link != 0:
+                        try:
+                            get_data = urllib.request.urlopen(info_link)
+                        except:
+                            get_data = None
+
+                        if get_data and get_data.getcode() == 200:
+                            try:
+                                get_data = orjson.loads(get_data.read().decode())
+                            except:
+                                get_data = {}
+
+                            if "skin_ver" in get_data:
+                                json_data = {**json_data, **{ "lastest_version" : {
+                                    "skin_ver" : get_data["skin_ver"]
+                                }}}
+
+                    a_data = {**a_data, **{ i : json_data }}
+
+            return flask.jsonify(a_data)

+ 79 - 0
route/api_user_info.py

@@ -0,0 +1,79 @@
+from .tool.func import *
+
+def api_user_info(user_name = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        data_result = {}
+        
+        # name part
+        data_result['render'] = ip_pas(user_name)
+        
+        # auth part
+        curs.execute(db_change("select data from user_set where id = ? and name = 'acl'"), [user_name])
+        db_data = curs.fetchall()
+        if db_data:
+            data_result['auth'] = db_data[0][0]
+        elif ip_or_user(user_name) == 1:
+            data_result['auth'] = 'ip'
+        else:
+            data_result['auth'] = 'user'
+
+        curs.execute(db_change("select data from user_set where id = ? and name = 'auth_date'"), [user_name])
+        db_data = curs.fetchall()
+        if db_data:
+            data_result['auth_date'] = db_data[0][0]
+        else:
+            data_result['auth_date'] = '0'
+
+        level_data = level_check(conn, user_name)
+        data_result['level'] = level_data[0]
+        data_result['exp'] = level_data[1]
+        data_result['max_exp'] = level_data[2]
+            
+        # ban part
+        ban = ban_check(user_name)
+        if ban[0] == 0:
+            data_result['ban'] = '0'
+        else:
+            data_result['ban'] = ban
+        
+        # user document part
+        curs.execute(db_change("select title from data where title = ?"), ['user:' + user_name])
+        if curs.fetchall():
+            data_result['document'] = '1'
+        else:
+            data_result['document'] = '0'
+
+        # user title part
+        curs.execute(db_change('select data from user_set where name = "user_title" and id = ?'), [user_name])
+        db_data = curs.fetchall()
+        if db_data:
+            data_result['user_title'] = db_data[0][0]
+        else:
+            data_result['user_title'] = ''
+
+        lang_data_list = [
+            'user_name',
+            'authority',
+            'state',
+            'member',
+            'normal',
+            'blocked',
+            'type',
+            'regex',
+            'period',
+            'limitless',
+            'login_able',
+            'why',
+            'band_blocked',
+            'ip',
+            'ban',
+            'level',
+            'option',
+            'edit_request_able',
+            'cidr'
+        ]
+        lang_data = { for_a : get_lang(conn, for_a) for for_a in lang_data_list }
+                
+        return flask.jsonify({ 'data' : data_result, 'language' : lang_data })

+ 18 - 0
route/api_version.py

@@ -0,0 +1,18 @@
+from .tool.func import *
+
+def api_version(version_list):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change('select data from other where name = "update"'))
+        up_data = curs.fetchall()
+        up_data = up_data[0][0] if up_data and up_data[0][0] in ['stable', 'beta', 'dev'] else 'stable'
+
+        json_data = {
+            "version" : version_list['r_ver'], 
+            "db_version" : version_list['c_ver'],
+            "skin_version" : version_list['s_ver'],
+            "build" : up_data
+        }
+
+        return flask.jsonify(json_data)

+ 39 - 0
route/bbs_delete.py

@@ -0,0 +1,39 @@
+from .tool.func import *
+
+def bbs_delete(bbs_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num])
+        db_data = curs.fetchall()
+        if not db_data:
+            return redirect(conn, '/bbs/main')
+        
+        bbs_name = db_data[0][0]
+        
+        bbs_num_str = str(bbs_num)
+
+        if acl_check('', 'owner_auth', '', '') == 1:
+            return redirect(conn, '/bbs/in/' + bbs_num_str)
+        
+        if bbs_num_str == 0:
+            return redirect(conn, '/bbs/in/' + bbs_num_str)
+        
+        if flask.request.method == 'POST':
+            curs.execute(db_change('delete from bbs_data where set_id = ?'), [bbs_num_str])
+            curs.execute(db_change('delete from bbs_set where set_id = ?'), [bbs_num_str])
+            curs.execute(db_change('delete from bbs_data where set_id like ?'), [bbs_num_str + '-%'])
+            
+            return redirect(conn, '/bbs/main')
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'bbs_delete'), wiki_set(conn), wiki_custom(conn), wiki_css(['(' + bbs_name + ')', 0])],
+                data = render_simple_set(conn, '''
+                    <form method="post">
+                        <span>''' + get_lang(conn, 'delete_warning') + '''</span>
+                        <hr class="main_hr">
+                        <button type="submit">''' + get_lang(conn, 'delete') + '''</button>
+                    </form>
+                '''),
+                menu = [['bbs/set/' + bbs_num_str, get_lang(conn, 'return')]]
+            ))

+ 41 - 0
route/bbs_make.py

@@ -0,0 +1,41 @@
+from .tool.func import *
+
+def bbs_make():   
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        if acl_check('', 'owner_auth', '', '') == 1:
+            return re_error(conn, 3)
+        
+        if flask.request.method == 'POST':
+            curs.execute(db_change('select set_id from bbs_set where set_name = "bbs_name" order by set_id + 0 desc'))
+            db_data = curs.fetchall()
+
+            bbs_num = str(int(db_data[0][0]) + 1) if db_data else '1'
+            bbs_name = flask.request.form.get('bbs_name', 'test')
+            bbs_type = flask.request.form.get('bbs_type', 'comment')
+            bbs_type = bbs_type if bbs_type in ['comment', 'thread'] else 'comment'
+
+            curs.execute(db_change("insert into bbs_set (set_name, set_code, set_id, set_data) values ('bbs_name', '', ?, ?)"), [bbs_num, bbs_name])
+            curs.execute(db_change("insert into bbs_set (set_name, set_code, set_id, set_data) values ('bbs_type', '', ?, ?)"), [bbs_num, bbs_type])
+
+            return redirect(conn, '/bbs/main')
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'bbs_make'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+                data = '''
+                    <form method="post">
+                        <input placeholder="''' + get_lang(conn, 'bbs_name') + '''" name="bbs_name">
+                        <hr class="main_hr">
+                        
+                        <select name="bbs_type">
+                            <option value="comment">''' + get_lang(conn, 'comment_base') + '''</option>
+                            <option value="thread">''' + get_lang(conn, 'thread_base') + '''</option>
+                        </select>
+                        <hr class="main_hr">
+                        
+                        <button type="submit">''' + get_lang(conn, 'save') + '''</button>
+                    </form>
+                ''',
+                menu = [['bbs/main', get_lang(conn, 'return')]]
+            ))

+ 195 - 0
route/bbs_w.py

@@ -0,0 +1,195 @@
+from .tool.func import *
+
+def bbs_w(bbs_num = '', tool = 'bbs', page = 1, name = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+        
+        data = ''
+        title_name = ''
+        sub = ''
+        bbs_name_dict = {}
+
+        admin_auth = acl_check(tool = 'owner_auth')
+        admin_auth = 1 if admin_auth == 0 else 0
+
+        if tool == 'bbs':
+            curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num])
+            db_data = curs.fetchall()
+            if not db_data:
+                return redirect(conn, '/bbs/main')
+        
+            bbs_name = db_data[0][0]
+            bbs_num_str = str(bbs_num)
+
+            title_name = bbs_name
+            sub = '(' + get_lang(conn, 'bbs') + ')'
+            menu = [['bbs/main', get_lang(conn, 'return')], ['bbs/edit/' + bbs_num_str, get_lang(conn, 'add')], ['bbs/set/' + bbs_num_str, get_lang(conn, 'bbs_set')]]
+        elif tool == 'record':
+            curs.execute(db_change('select set_data, set_id from bbs_set where set_name = "bbs_name"'))
+            db_data = curs.fetchall()
+            bbs_name_dict = { for_a[1] : for_a[0] for for_a in db_data } if db_data else {}
+            
+            title_name = name
+            sub = '(' + get_lang(conn, 'bbs_record') + ')'
+            menu = [['user/' + url_pas(name), get_lang(conn, 'user_tool')]]
+        elif tool == 'comment_record':
+            curs.execute(db_change('select set_data, set_id from bbs_set where set_name = "bbs_name"'))
+            db_data = curs.fetchall()
+            bbs_name_dict = { for_a[1] : for_a[0] for for_a in db_data } if db_data else {}
+            
+            title_name = name
+            sub = '(' + get_lang(conn, 'bbs_comment_record') + ')'
+            menu = [['user/' + url_pas(name), get_lang(conn, 'user_tool')]]
+        else:
+            curs.execute(db_change('select set_data, set_id from bbs_set where set_name = "bbs_name"'))
+            db_data = curs.fetchall()
+            if db_data:
+                data += '<ul>'
+                for for_a in db_data:
+                    bbs_name_dict[for_a[1]] = for_a[0]
+
+                    curs.execute(db_change('select set_data from bbs_set where set_name = "bbs_type" and set_id = ?'), [for_a[1]])
+                    db_data_2 = curs.fetchall()
+                    bbs_type = db_data_2[0][0] if db_data_2 else 'comment'
+
+                    if bbs_type == 'thread':
+                        bbs_type = get_lang(conn, 'thread_base')
+                    else:
+                        bbs_type = get_lang(conn, 'comment_base')
+                    
+                    curs.execute(db_change('select set_data from bbs_data where set_id = ? and set_name = "date" order by set_code + 0 desc limit 1'), [for_a[1]])
+                    db_data_2 = curs.fetchall()
+                    last_date = ('(' + db_data_2[0][0] + ')') if db_data_2 else ''
+
+                    data += '<li>'
+                    data += '<a href="/bbs/in/' + for_a[1] + '">' + html.escape(for_a[0]) + '</a> (' + bbs_type + ') ' + last_date
+                    data += '</li>'
+
+                data += '</ul>'
+            
+            data += '<hr class="main_hr">'
+
+            title_name = get_lang(conn, 'bbs_main')
+            menu = [['other', get_lang(conn, 'other_tool')]] + ([['bbs/make', get_lang(conn, 'add')]] if admin_auth == 1 else [])
+
+        if tool == 'comment_record':
+            data += '''
+                <table id="main_table_set">
+                    <tr id="main_table_top_tr">
+                        <td id="main_table_width">''' + get_lang(conn, 'editor') + '''</td>
+                        <td id="main_table_width">''' + get_lang(conn, 'time') + '''</td>
+                        <td id="main_table_width">''' + get_lang(conn, 'comment') + '''</td>
+                    </tr>
+            '''
+        else:
+            data += '''
+                <table id="main_table_set">
+                    <tr id="main_table_top_tr">
+                        <td id="main_table_width">''' + get_lang(conn, 'editor') + '''</td>
+                        <td id="main_table_width">''' + get_lang(conn, 'time') + '''</td>
+                        <td id="main_table_width">''' + get_lang(conn, 'last_comment_time') + '''</td>
+                    </tr>
+            '''
+
+        if tool == 'bbs':
+            curs.execute(db_change('select set_code, set_id, set_name from bbs_data where set_name = "pinned" and set_id like ? order by set_data desc'), [bbs_num])
+            db_data = curs.fetchall()
+            db_data = list(db_data) if db_data else []
+            
+            curs.execute(db_change('select set_code, set_id from bbs_data where set_name = "title" and set_id like ? order by set_code + 0 desc'), [bbs_num])
+            db_data_2 = curs.fetchall()
+            db_data += list(db_data_2) if db_data_2 else []
+        elif tool == 'record':
+            try:
+                curs.execute(db_change('select set_code, set_id, set_data from bbs_data where set_name = "date" and (set_code, set_id) in (select set_code, set_id from bbs_data where set_name = "user_id" and set_data = ?) as sub_query order by set_data desc limit 50'), [name])
+            except:
+                curs.execute(db_change('select set_code, set_id from bbs_data where set_name = "user_id" and set_data = ? order by set_data desc limit 50'), [name])
+
+            db_data = curs.fetchall()
+        elif tool == 'comment_record':
+            try:
+                curs.execute(db_change('select set_code, set_id, set_data from bbs_data where set_name = "comment_date" and (set_code, set_id) in (select set_code, set_id from bbs_data where set_name = "comment_user_id" and set_data = ?) as sub_query order by set_data desc limit 50'), [name])
+            except:
+                curs.execute(db_change('select set_code, set_id from bbs_data where set_name = "comment_user_id" and set_data = ? order by set_data desc limit 50'), [name])
+            
+            db_data = curs.fetchall()
+        else:
+            curs.execute(db_change('select set_code, set_id, set_data from bbs_data where set_name = "date" order by set_data desc limit 50'))
+            db_data = curs.fetchall()
+
+        for for_b in db_data:
+            curs.execute(db_change('select set_name, set_data, set_code, set_id from bbs_data where set_code = ? and set_id = ?'), [for_b[0], for_b[1]])
+            db_data = curs.fetchall()
+            db_data = list(db_data) if db_data else []
+
+            temp_dict = { for_a[0] : for_a[1] for for_a in db_data }
+
+            bbs_name_select = ''
+            bbs_split = for_b[1].split('-')
+            if tool == 'comment_record':
+                bbs_name_select = '(' + bbs_name_dict[bbs_split[0]] + ')'
+            elif tool != 'bbs':
+                bbs_name_select = '(' + bbs_name_dict[for_b[1]] + ')'
+
+            if tool == 'bbs':
+                notice = 1 if len(for_b) > 2 else 0
+            else:
+                notice = 0
+
+            if tool == 'comment_record':
+                curs.execute(db_change('select set_name, set_data, set_code, set_id from bbs_data where set_name = "title" and set_code = ? and set_id = ?'), [bbs_split[1], bbs_split[0]])
+                db_data = curs.fetchall()
+                db_data = list(db_data) if db_data else []
+                for for_a in db_data:
+                    temp_dict[for_a[0]] = for_a[1]
+            
+                comment_link = ''
+                if len(bbs_split) > 2:
+                    comment_link = '-'.join(bbs_split[2:])
+                    
+                comment_link += ('-' + for_b[0] if comment_link != '' else for_b[0])
+                    
+                data += '''
+                    <tr>
+                        <td>''' + ip_pas(temp_dict['comment_user_id']) + '''</td>
+                        <td>''' + temp_dict['comment_date'] + '''</td>
+                        <td>''' + ('#' + comment_link) + '''</td>
+                    </tr>
+                    <tr>
+                        <td colspan="3">
+                            <a href="/bbs/w/''' + bbs_split[0] + '/' + bbs_split[1] + '#' + comment_link + '">' + html.escape(temp_dict['title']) + '''</a> 
+                            ''' + bbs_name_select + '''
+                        </td>
+                    </tr>
+                '''
+            else:
+                curs.execute(db_change('select count(*) from bbs_data where set_name = "comment_date" and (set_id = ? or set_id like ?) order by set_code + 0 desc'), [for_b[1] + '-' + for_b[0], for_b[1] + '-' + for_b[0] + '-%'])
+                db_data = curs.fetchall()
+                comment_count = str(db_data[0][0]) if db_data else '0'
+
+                curs.execute(db_change('select set_data from bbs_data where set_name = "comment_date" and (set_id = ? or set_id like ?) order by set_data desc limit 1'), [for_b[1] + '-' + for_b[0], for_b[1] + '-' + for_b[0] + '-%'])
+                db_data = curs.fetchall()
+                last_comment_date = db_data[0][0] if db_data else '0'
+            
+                data += '''
+                    <tr class="''' + ('opennamu_comment_color_red' if notice == 1 else '') + '''">
+                        <td>''' + ip_pas(temp_dict['user_id']) + '''</td>
+                        <td>''' + temp_dict['date'] + '''</td>
+                        <td>''' + last_comment_date + '''</td>
+                    </tr>
+                    <tr>
+                        <td colspan="3">
+                            <a href="/bbs/w/''' + for_b[1] + '/' + for_b[0] + '">' + html.escape(temp_dict['title']) + '''</a> 
+                            (''' + comment_count + ''') 
+                            ''' + bbs_name_select + '''
+                        </td>
+                    </tr>
+                '''
+                
+        data += '</table>'
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [title_name, wiki_set(conn), wiki_custom(conn), wiki_css([sub, 0])],
+            data = data,
+            menu = menu
+        ))

+ 30 - 0
route/bbs_w_comment_tool.py

@@ -0,0 +1,30 @@
+from .tool.func import *
+
+def bbs_w_comment_tool(bbs_num = '', post_num = '', comment_num = ''):
+    with get_db_connect() as conn:
+        data = ''
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+        
+        data += '''
+            <h2>''' + get_lang(conn, 'tool') + '''</h2>
+            <ul>
+                <li><a href="/bbs/raw/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '/' + url_pas(comment_num) + '">' + get_lang(conn, 'raw') + '''</a></li>
+                <li><a href="/bbs/edit/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '/' + url_pas(comment_num) + '">' + get_lang(conn, 'edit') + '''</a></li>
+            </ul>
+        '''
+
+        if acl_check('', 'owner_auth', '', '') != 1:
+            data += '''
+                <h3>''' + get_lang(conn, 'owner') + '''</h2>
+                <ul>
+                    <li><a href="/bbs/delete/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '/' + url_pas(comment_num) + '">' + get_lang(conn, 'delete') + '''</a></li>
+                </ul>
+            '''
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'bbs_comment_tool'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = data,
+            menu = [['bbs/w/' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '#' + url_pas(comment_num), get_lang(conn, 'return')]]
+        ))

+ 66 - 0
route/bbs_w_delete.py

@@ -0,0 +1,66 @@
+from .tool.func import *
+
+from .go_api_bbs_w import api_bbs_w
+
+def bbs_w_delete(bbs_num = '', post_num = '', comment_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num])
+        db_data = curs.fetchall()
+        if not db_data:
+            return redirect(conn, '/bbs/main')
+
+        bbs_name = db_data[0][0]
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+
+        if acl_check('', 'owner_auth', '') == 1:
+            return redirect(conn, '/bbs/in/' + bbs_num_str)
+        
+        temp_dict = orjson.loads(api_bbs_w(bbs_num_str + '-' + post_num_str).data)
+        if not 'user_id' in temp_dict:
+            return redirect(conn, '/bbs/main')
+        
+        if flask.request.method == 'POST':
+            if comment_num == '':
+                curs.execute(db_change('delete from bbs_data where set_code = ? and set_id = ?'), [post_num_str, bbs_num_str])
+                curs.execute(db_change('delete from bbs_set where set_code = ? and set_id = ?'), [post_num_str, bbs_num_str])
+                curs.execute(db_change('delete from bbs_data where set_id = ? or set_id like ?'), [bbs_num_str + '-' + post_num_str, bbs_num_str + '-' + post_num_str + '-%'])
+                
+                return redirect(conn, '/bbs/in/' + bbs_num_str)
+            else:
+                comment_num_split = comment_num.split('-')
+                
+                set_id = bbs_num_str + '-' + post_num_str
+                set_id_sub = '-'.join(comment_num_split[:-1])
+                if set_id_sub != '':
+                    set_id += '-' + set_id_sub
+
+                set_code = comment_num_split[len(comment_num_split) - 1]
+
+                curs.execute(db_change("update bbs_data set set_data = '' where set_code = ? and set_id = ?"), [set_code, set_id])
+                
+                return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str)
+        else:
+            sub = '(' + bbs_name + ')'
+            sub += ' (' + post_num_str + ')'
+            
+            name = get_lang(conn, 'bbs_comment_delete')
+            if comment_num == '':
+                name = get_lang(conn, 'bbs_post_delete')
+            else:
+                sub += ' (' + comment_num + ')'
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css([sub, 0])],
+                data = render_simple_set(conn, '''
+                    <form method="post">
+                        <span>''' + get_lang(conn, 'delete_warning') + '''</span>
+                        <hr class="main_hr">
+                        <button type="submit">''' + get_lang(conn, 'delete') + '''</button>
+                    </form>
+                '''),
+                menu = [['bbs/w/' + bbs_num_str + '/' + post_num_str, get_lang(conn, 'return')]]
+            ))

+ 156 - 0
route/bbs_w_edit.py

@@ -0,0 +1,156 @@
+from .tool.func import *
+
+from .go_api_bbs_w import api_bbs_w
+from .go_api_bbs_w_comment_one import api_bbs_w_comment_one
+
+from .edit import edit_editor
+
+async def bbs_w_edit(bbs_num = '', post_num = '', comment_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+
+        ip = ip_check()
+
+        curs.execute(db_change('select set_id from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num_str])
+        if not curs.fetchall():
+            return redirect(conn, '/bbs/main')
+        
+        if comment_num != '':
+            temp_dict = orjson.loads((await api_bbs_w_comment_one(bbs_num_str + '-' + post_num_str + '-' + comment_num)).get_data(as_text = True))
+            if 'comment_user_id' in temp_dict:
+                if not temp_dict['comment_user_id'] == ip and acl_check('', 'owner_auth', '', '') == 1:
+                    return re_error(conn, 0)
+            else:
+                return redirect(conn, '/bbs/main')
+        elif post_num != '':
+            temp_dict = orjson.loads(api_bbs_w(bbs_num_str + '-' + post_num_str).data)
+            if 'user_id' in temp_dict:
+                if not temp_dict['user_id'] == ip and acl_check('', 'owner_auth', '', '') == 1:
+                    return re_error(conn, 0)
+            else:
+                return redirect(conn, '/bbs/main')
+            
+        if acl_check(bbs_num_str, 'bbs_edit') == 1:
+            return redirect(conn, '/bbs/set/' + bbs_num_str)
+        
+        i_list = ['post_view_acl', 'post_comment_acl']
+
+        if flask.request.method == 'POST':
+            if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                return re_error(conn, 13)
+        
+            if post_num == '':
+                curs.execute(db_change('select set_code from bbs_data where set_name = "title" and set_id = ? order by set_code + 0 desc'), [bbs_num_str])
+                db_data = curs.fetchall()
+                id_data = str(int(db_data[0][0]) + 1) if db_data else '1'
+            else:
+                id_data = post_num_str
+
+            title = flask.request.form.get('title', 'test')
+            title = 'test' if title == '' else title
+            data = flask.request.form.get('content', '')
+            if data == '':
+                # re_error로 대체 예정
+                return redirect(conn, '/bbs/in/' + bbs_num_str)
+            
+            if do_edit_filter(conn, title) == 1:
+                return re_error(conn, 21)
+
+            if do_edit_filter(conn, data) == 1:
+                return re_error(conn, 21)
+            
+            date = get_time()
+
+            if comment_num != '':
+                sub_code = (bbs_num_str + '-' + post_num_str + '-' + comment_num).split('-')
+                sub_code_last = ''
+                if len(sub_code) > 2:
+                    sub_code_last = sub_code[len(sub_code) - 1]
+                    del sub_code[len(sub_code) - 1]
+                    
+                sub_code = '-'.join(sub_code)
+
+                curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'comment' and set_code = ? and set_id = ?"), [data, sub_code_last, sub_code])
+            elif post_num == '':
+                curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('title', ?, ?, ?)"), [id_data, bbs_num_str, title])
+                curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('data', ?, ?, ?)"), [id_data, bbs_num_str, data])
+                curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('date', ?, ?, ?)"), [id_data, bbs_num_str, date])
+                curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('user_id', ?, ?, ?)"), [id_data, bbs_num_str, ip])
+            else:
+                curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'title' and set_code = ? and set_id = ?"), [title, post_num, bbs_num_str])
+                curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'data' and set_code = ? and set_id = ?"), [data, id_data, bbs_num_str])
+                curs.execute(db_change("update bbs_data set set_data = ? where set_name = 'date' and set_code = ? and set_id = ?"), [date, id_data, bbs_num_str])
+
+            if comment_num != '':
+                return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + id_data + '#' + url_pas(comment_num))
+            else:
+                return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + id_data)
+        else:
+            option_display = ''
+
+            if comment_num != '':
+                temp_dict = orjson.loads((await api_bbs_w_comment_one(bbs_num_str + '-' + post_num_str + '-' + comment_num)).get_data(as_text = True))
+
+                title = ''
+                data = temp_dict['comment']
+                option_display = 'display: none;'
+            elif post_num == '':
+                title = ''
+                data = ''
+            else:
+                temp_dict = orjson.loads(api_bbs_w(bbs_num_str + '-' + post_num_str).data)
+
+                title = temp_dict['title']
+                data = temp_dict['data']
+
+            acl_div = ['' for _ in range(0, len(i_list))]
+            acl_list = get_acl_list()
+            for for_a in range(0, len(i_list)):
+                for data_list in acl_list:
+                    acl_div[for_a] += '<option value="' + data_list + '">' + (data_list if data_list != '' else 'normal') + '</option>'
+    
+            editor_top_text = '<a href="/filter/edit_filter">(' + get_lang(conn, 'edit_filter_rule') + ')</a>'
+
+            if editor_top_text != '':
+                editor_top_text += '<hr class="main_hr">'
+
+            if comment_num != '':
+                bbs_title = get_lang(conn, 'bbs_comment_edit')
+            elif post_num == '':
+                bbs_title = get_lang(conn, 'post_add')
+            else:
+                bbs_title = get_lang(conn, 'post_edit')
+    
+            return easy_minify(conn, flask.render_template(skin_check(conn), 
+                imp = [bbs_title, wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+                data =  editor_top_text + '''
+                    <form method="post">                        
+                        <input style="''' + option_display + '''" placeholder="''' + get_lang(conn, 'title') + '''" name="title" value="''' + html.escape(title) + '''">
+                        <hr style="''' + option_display + '''" class="main_hr">
+
+                        ''' + edit_editor(conn, ip, data, 'bbs') + '''
+
+                        <!--
+                        <div style="''' + option_display + '''">
+                            ''' + render_simple_set(conn, '''
+                                <hr class="main_hr">
+                                <a href="/acl/TEST#exp">(''' + get_lang(conn, 'reference') + ''')</a>
+                                <h2>''' + get_lang(conn, 'acl') + '''</h2>
+                                <h3>''' + get_lang(conn, 'post_view_acl') + '''</h3>
+                                <select name="post_view_acl">''' + acl_div[0] + '''</select>
+
+                                <h4>''' + get_lang(conn, 'post_comment_acl') + '''</h4>
+                                <select name="post_comment_acl">''' + acl_div[1] + '''</select>
+
+                                <h2>''' + get_lang(conn, 'markup') + '''</h2>
+                                ''' + get_lang(conn, 'not_working') + '''
+                            ''') + '''
+                        </div>
+                        -->
+                    </form>
+                ''',
+                menu = [['bbs/in/' + bbs_num_str, get_lang(conn, 'return')]]
+            ))

+ 31 - 0
route/bbs_w_hide.py

@@ -0,0 +1,31 @@
+from .tool.func import *
+
+def bbs_w_hide(bbs_num = '', post_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num])
+        db_data = curs.fetchall()
+        if not db_data:
+            return redirect(conn, '/bbs/main')
+
+        bbs_name = db_data[0][0]
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+
+        if acl_check('', 'bbs_auth', '', '') == 1:
+            return redirect(conn, '/bbs/in/' + bbs_num_str)
+        
+        if flask.request.method == 'POST':
+            pass
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'bbs_post_hide'), wiki_set(conn), wiki_custom(conn), wiki_css(['(' + bbs_name + ')' + ' (' + post_num_str + ')', 0])],
+                data = render_simple_set(conn, '''
+                    <form method="post">
+                        <button type="submit">''' + get_lang(conn, 'hide') + '''</button>
+                    </form>
+                '''),
+                menu = [['bbs/w/' + bbs_num_str + '/' + post_num_str, get_lang(conn, 'return')]]
+            ))

+ 46 - 0
route/bbs_w_pinned.py

@@ -0,0 +1,46 @@
+from .tool.func import *
+
+from .go_api_bbs_w import api_bbs_w
+
+def bbs_w_pinned(bbs_num = '', post_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num])
+        db_data = curs.fetchall()
+        if not db_data:
+            return redirect(conn, '/bbs/main')
+
+        bbs_name = db_data[0][0]
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+
+        if acl_check('', 'bbs_auth', '', '') == 1:
+            return redirect(conn, '/bbs/in/' + bbs_num_str)
+        
+        temp_dict = orjson.loads(api_bbs_w(bbs_num_str + '-' + post_num_str).data)
+        if not 'user_id' in temp_dict:
+            return redirect(conn, '/bbs/main')
+        
+        if flask.request.method == 'POST':
+            curs.execute(db_change('select set_data from bbs_data where set_code = ? and set_id = ? and set_name = "pinned"'), [post_num_str, bbs_num_str])
+            if not curs.fetchall():
+                curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('pinned', ?, ?, ?)"), [post_num_str, bbs_num_str, get_time()])
+            else:
+                curs.execute(db_change('delete from bbs_data where set_code = ? and set_id = ? and set_name = "pinned"'), [post_num_str, bbs_num_str])
+            
+            return redirect(conn, '/bbs/in/' + bbs_num_str)
+        else:
+            curs.execute(db_change('select set_data from bbs_data where set_code = ? and set_id = ? and set_name = "pinned"'), [post_num_str, bbs_num_str])
+            pinned = get_lang(conn, 'pinned') if not curs.fetchall() else get_lang(conn, 'pinned_release')
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'bbs_post_pinned'), wiki_set(conn), wiki_custom(conn), wiki_css(['(' + bbs_name + ')' + ' (' + post_num_str + ')', 0])],
+                data = render_simple_set(conn, '''
+                    <form method="post">
+                        <button type="submit">''' + pinned + '''</button>
+                    </form>
+                '''),
+                menu = [['bbs/w/' + bbs_num_str + '/' + post_num_str, get_lang(conn, 'return')]]
+            ))

+ 216 - 0
route/bbs_w_post.py

@@ -0,0 +1,216 @@
+from .tool.func import *
+
+from .go_api_bbs_w import api_bbs_w
+from .go_api_bbs_w_comment import api_bbs_w_comment
+
+from .go_api_topic import api_topic_thread_make, api_topic_thread_pre_render
+
+from .edit import edit_editor
+
+async def bbs_w_post_comment(conn, user_id, sub_code, comment_num, bbs_num_str, post_num_str):
+    comment_data = ''
+    comment_select = ''
+
+    comment_count = 0
+    comment_add_count = 0
+
+    thread_data = orjson.loads((await api_bbs_w_comment(sub_code)).get_data(as_text = True))
+
+    for temp_dict in thread_data:
+        if temp_dict['comment_user_id'] != '':
+            color = 'default'
+            if user_id == temp_dict['comment_user_id']:
+                color = 'green'
+
+            sub_code_check = re.sub(r'^[0-9]+-[0-9]+-', '', temp_dict['id'] + '-' + temp_dict['code'])
+            margin_count = sub_code_check.count('-')
+
+            if margin_count == 0:
+                comment_count += 1
+            else:
+                comment_add_count += 1
+
+            date = ''
+            date += '<a href="javascript:opennamu_change_comment(\'' + sub_code_check + '\');">(' + get_lang(conn, 'comment') + ')</a> '
+            date += '<a href="/bbs/tool/' + bbs_num_str + '/' + post_num_str + '/' + sub_code_check + '">(' + get_lang(conn, 'tool') + ')</a> '
+            date += temp_dict['comment_date']
+
+            comment_data += '<span style="padding-left: 20px;"></span>' * margin_count
+            comment_data += api_topic_thread_make(
+                ip_pas(temp_dict['comment_user_id']),
+                date,
+                render_set(conn, doc_data = temp_dict['comment']),
+                sub_code_check,
+                color = color,
+                add_style = 'width: calc(100% - ' + str(margin_count * 20) + 'px);'
+            )
+
+            comment_default = ''
+            if comment_num == sub_code_check:
+                comment_default = 'selected'
+
+            comment_select += '<option value="' + sub_code_check + '" ' + comment_default + '>' + sub_code_check + '</option>'
+
+    return (comment_data, comment_select, comment_count, comment_add_count)
+
+async def bbs_w_post(bbs_num = '', post_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_name"'), [bbs_num])
+        db_data_3 = curs.fetchall()
+        if not db_data_3:
+            return redirect(conn, '/bbs/main')
+        
+        bbs_name = db_data_3[0][0]
+
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+        bbs_comment_acl = acl_check(bbs_num_str, 'bbs_comment')
+        ip = ip_check()
+
+        temp_dict = orjson.loads(api_bbs_w(bbs_num_str + '-' + post_num_str).data)
+        if temp_dict == {}:
+            return redirect(conn, '/bbs/main')
+        
+        curs.execute(db_change('select set_data from bbs_set where set_id = ? and set_name = "bbs_type"'), [bbs_num])
+        db_data_2 = curs.fetchall()
+        if not db_data_2:
+            return redirect(conn, '/bbs/main')
+        else:
+            if flask.request.method == 'POST':
+                if db_data_2[0][0] == 'thread':
+                    if bbs_comment_acl == 1:
+                        return redirect(conn, '/bbs/set/' + bbs_num_str)
+                    
+                    if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                        return re_error(conn, 13)
+
+                    set_id = bbs_num_str + '-' + post_num_str
+
+                    curs.execute(db_change('select set_code from bbs_data where set_name = "comment" and set_id = ? order by set_code + 0 desc'), [set_id])
+                    db_data_4 = curs.fetchall()
+                    id_data = str(int(db_data_4[0][0]) + 1) if db_data_4 else '1'
+
+                    data = flask.request.form.get('content', '')
+                    if data == '':
+                        # re_error로 대체 예정
+                        return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str)
+                    
+                    data = data.replace('\r', '')
+                    data = api_topic_thread_pre_render(conn, data, id_data, ip, set_id, bbs_name, temp_dict['title'], 'post')
+                    
+                    date = get_time()
+
+                    curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('comment', ?, ?, ?)"), [id_data, set_id, data])
+                    curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('comment_date', ?, ?, ?)"), [id_data, set_id, date])
+                    curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('comment_user_id', ?, ?, ?)"), [id_data, set_id, ip])
+
+                    add_alarm(temp_dict['user_id'], ip, 'BBS <a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '#' + id_data + '">' + html.escape(bbs_name) + ' - ' + html.escape(temp_dict['title']) + '#' + id_data + '</a>')
+
+                    return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str + '#' + id_data)
+                else:
+                    if bbs_comment_acl == 1:
+                        return redirect(conn, '/bbs/set/' + bbs_num_str)
+                    
+                    if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                        return re_error(conn, 13)
+                    
+                    select = flask.request.form.get('comment_select', '0')
+                    select = '' if select == '0' else select
+
+                    comment_user_name = ''
+
+                    if select != '':
+                        select_split = select.split('-')
+                        if len(select_split) < 2:
+                            curs.execute(db_change('select set_data from bbs_data where set_name = "comment_user_id" and set_id = ? and set_code = ? limit 1'), [bbs_num_str + '-' + post_num_str, select_split[0]])    
+                            db_data_6 = curs.fetchall()
+                            if not db_data_6:
+                                # re_error로 변경 예정
+                                return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str)
+                            else:
+                                set_id = bbs_num_str + '-' + post_num_str + '-' + select_split[0]
+                                comment_user_name = db_data_6[0][0]
+                        else:
+                            curs.execute(db_change('select set_data from bbs_data where set_name = "comment_user_id" and set_id = ? and set_code = ? limit 1'), [bbs_num_str + '-' + post_num_str + '-' + '-'.join(select_split[0:len(select_split) - 1]), select_split[len(select_split) - 1]])
+                            db_data_7 = curs.fetchall()
+                            if not db_data_7:
+                                return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str)
+                            else:
+                                set_id = bbs_num_str + '-' + post_num_str + '-' + '-'.join(select_split)
+                                comment_user_name = db_data_7[0][0]
+                    else:
+                        set_id = bbs_num_str + '-' + post_num_str
+
+                    curs.execute(db_change('select set_code from bbs_data where set_name = "comment" and set_id = ? order by set_code + 0 desc limit 1'), [set_id])
+                    db_data_5 = curs.fetchall()
+                    id_data = str(int(db_data_5[0][0]) + 1) if db_data_5 else '1'
+
+                    data = flask.request.form.get('content', '')
+                    if data == '':
+                        # re_error로 대체 예정
+                        return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str)
+
+                    date = get_time()
+
+                    curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('comment', ?, ?, ?)"), [id_data, set_id, data])
+                    curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('comment_date', ?, ?, ?)"), [id_data, set_id, date])
+                    curs.execute(db_change("insert into bbs_data (set_name, set_code, set_id, set_data) values ('comment_user_id', ?, ?, ?)"), [id_data, set_id, ip])
+                
+                    if set_id == '':
+                        end_id = id_data
+                    else:
+                        set_id = re.sub(r'^[0-9]+-[0-9]+-?', '', set_id)
+                        set_id += '-' if set_id != '' else ''
+                        end_id = set_id + id_data
+
+                    add_alarm(temp_dict['user_id'], ip, 'BBS <a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '#' + end_id + '">' + html.escape(bbs_name) + ' - ' + html.escape(temp_dict['title']) + '#' + end_id + '</a>')
+                    if comment_user_name != '':
+                        add_alarm(comment_user_name, ip, 'BBS <a href="/bbs/w/' + bbs_num_str + '/' + post_num_str + '#' + end_id + '">' + html.escape(bbs_name) + ' - ' + html.escape(temp_dict['title']) + '#' + end_id + '</a>')
+
+                    return redirect(conn, '/bbs/w/' + bbs_num_str + '/' + post_num_str + '#' + end_id)
+            else:
+                if acl_check(bbs_num_str, 'bbs_view') == 1:
+                    return re_error(conn, 0)
+
+                date = ''
+                date += '<a href="javascript:opennamu_change_comment(\'0\');">(' + get_lang(conn, 'comment') + ')</a> '
+                date += temp_dict['date']
+
+                data = ''
+                data += '<h2>' + html.escape(temp_dict['title']) + '</h2>'
+                data += api_topic_thread_make(
+                    ip_pas(temp_dict['user_id']),
+                    date,
+                    render_set(conn, doc_data = temp_dict['data']),
+                    '0',
+                    color = 'red'
+                )
+
+                data += '' + \
+                    '<div id="opennamu_bbs_w_post"></div>' + \
+                    '<script defer src="/views/main_css/js/route/topic.js' + cache_v() + '"></script>' + \
+                    '<script defer src="/views/main_css/js/route/bbs_w_post.js' + cache_v() + '"></script>' + \
+                    '<script>window.addEventListener("DOMContentLoaded", function() { opennamu_load_comment(); });</script>' + \
+                ''
+
+                bbs_comment_form = ''
+                if bbs_comment_acl == 0:
+                    bbs_comment_form += '''
+                        <div id="opennamu_bbs_w_post_tabom"></div>
+                        <div id="opennamu_bbs_w_post_select"></div>
+                        ''' + edit_editor(conn, ip, '', 'bbs_comment') + '''
+                    '''
+
+                data += '''
+                    <form method="post">
+                        ''' + bbs_comment_form + '''
+                    </form>
+                '''
+
+                return easy_minify(conn, flask.render_template(skin_check(conn),
+                    imp = [bbs_name, wiki_set(conn), wiki_custom(conn), wiki_css(['(' + get_lang(conn, 'bbs') + ')', 0])],
+                    data = data,
+                    menu = [['bbs/in/' + bbs_num_str, get_lang(conn, 'return')], ['bbs/edit/' + bbs_num_str + '/' + post_num_str, get_lang(conn, 'edit')], ['bbs/tool/' + bbs_num_str + '/' + post_num_str, get_lang(conn, 'tool')]]
+                ))

+ 42 - 0
route/bbs_w_tool.py

@@ -0,0 +1,42 @@
+from .tool.func import *
+
+def bbs_w_tool(bbs_num = '', post_num = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        data = ''
+        
+        bbs_num_str = str(bbs_num)
+        post_num_str = str(post_num)
+        
+        data += '''
+            <h2>''' + get_lang(conn, 'tool') + '''</h2>
+            <ul>
+                <li><a href="/bbs/raw/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '">' + get_lang(conn, 'raw') + '''</a></li>
+            </ul>
+        '''
+
+        if acl_check('', 'bbs_auth', '', '') != 1:
+            curs.execute(db_change('select set_data from bbs_data where set_code = ? and set_id = ? and set_name = "pinned"'), [post_num_str, bbs_num_str])
+            pinned = get_lang(conn, 'pinned') if not curs.fetchall() else get_lang(conn, 'pinned_release')
+
+            data += '''
+                <h3>''' + get_lang(conn, 'admin') + '''</h3>
+                <ul>
+                    <!-- <li><a href="/bbs/blind/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '">' + get_lang(conn, 'hide') + '''</a></li> -->
+                    <li><a href="/bbs/pinned/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '">' + pinned + '''</a></li>
+                </ul>
+            '''
+
+            data += '''
+                <h3>''' + get_lang(conn, 'owner') + '''</h2>
+                <ul>
+                    <li><a href="/bbs/delete/''' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str) + '">' + get_lang(conn, 'delete') + '''</a></li>
+                </ul>
+            '''
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'bbs_post_tool'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = data,
+            menu = [['bbs/w/' + url_pas(bbs_num_str) + '/' + url_pas(post_num_str), get_lang(conn, 'return')]]
+        ))

+ 385 - 0
route/edit.py

@@ -0,0 +1,385 @@
+import multiprocessing
+
+from .tool.func import *
+
+from .view_set import view_set_markup
+
+def edit_render_set(name, content):
+    with get_db_connect() as conn:
+        render_set(conn, 
+            doc_name = name,
+            doc_data = content
+        )
+
+# https://stackoverflow.com/questions/13821156/timeout-function-using-threading-in-python-does-not-work
+def edit_timeout(func, args = (), timeout = 3):
+    pool = multiprocessing.Pool(processes = 1)
+    result = pool.apply_async(func, args = args)
+    try:
+        result.get(timeout = timeout)
+    except multiprocessing.TimeoutError:
+        pool.terminate()
+        return 1
+    else:
+        pool.close()
+        pool.join()
+        return 0
+        
+def edit_editor(conn, ip, data_main = '', do_type = 'edit', addon = '', name = ''):
+    curs = conn.cursor()
+
+    monaco_editor_top = ''
+    div = ''
+
+    if do_type == 'edit':
+        curs.execute(db_change('select data from other where name = "edit_help"'))
+        sql_d = curs.fetchall()
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and set_name = 'document_top'"), [name])
+        body = curs.fetchall()
+        div = body[0][0] if body else ''
+    elif do_type == 'bbs':
+        curs.execute(db_change('select data from other where name = "bbs_help"'))
+        sql_d = curs.fetchall()
+    elif do_type == 'bbs_comment':
+        curs.execute(db_change('select data from other where name = "bbs_comment_help"'))
+        sql_d = curs.fetchall()
+    else:
+        curs.execute(db_change('select data from other where name = "topic_text"'))
+        sql_d = curs.fetchall()
+
+    if do_type == 'bbs_comment':
+        do_type = 'thread'
+    elif do_type == 'bbs':
+        do_type = 'edit'
+            
+    p_text = html.escape(sql_d[0][0]) if sql_d and sql_d[0][0] != '' else get_lang(conn, 'default_edit_help')
+    
+    monaco_editor_top += '<a href="javascript:opennamu_do_editor_temp_save();">(' + get_lang(conn, 'load_temp_save') + ')</a> <a href="javascript:opennamu_do_editor_temp_save_load();">(' + get_lang(conn, 'load_temp_save_load') + ')</a>'
+    monaco_editor_top += '<hr class="main_hr">'
+    
+    darkmode = flask.request.cookies.get('main_css_darkmode', '0')
+    monaco_thema = 'vs-dark' if darkmode == '1' else ''
+    
+    monaco_on = get_main_skin_set(conn, flask.session, 'main_css_monaco', ip)
+    editor_display = ['style="display: none;"' for _ in range(3)]
+    if monaco_on == 'use':
+        editor_display[1] = ''
+    else:
+        editor_display[0] = ''
+
+    # 에디터 선택창
+    monaco_editor_top += '<select onclick="do_sync_monaco_and_textarea();" id="opennamu_select_editor" onchange="opennamu_edit_turn_off_monaco();">'
+    monaco_editor_top += '<option value="default" ' + ('selected' if editor_display[0] == '' else '') + '>' + get_lang(conn, 'default') + '</option>'
+    monaco_editor_top += '<option value="monaco" ' + ('selected' if editor_display[1] == '' else '') + '>' + get_lang(conn, 'monaco_editor') + '</option>'
+    monaco_editor_top += '</select> '
+
+    # 문법 선택창
+    if do_type == 'edit':
+        monaco_editor_top += view_set_markup(conn, document_name = name, addon = 'id="opennamu_editor_markup" onclick="opennamu_do_sync_monaco_markup();"')
+    else:
+        monaco_editor_top += view_set_markup(conn, addon = 'id="opennamu_editor_markup" onclick="opennamu_do_sync_monaco_markup();"', disable = 'disabled')
+
+    textarea_size = 'opennamu_textarea_500' if do_type == 'edit' else 'opennamu_textarea_100'
+
+    out_field = captcha_get(conn) + ip_warning(conn) + addon
+    if out_field != '':
+        out_field += '<hr class="main_hr">'
+
+    return '''
+        <textarea style="display: none;" id="opennamu_edit_origin" name="doc_data_org">''' + html.escape(data_main) + '''</textarea>
+        <div>
+            ''' + monaco_editor_top + '''
+            <hr class="main_hr">
+            ''' + edit_button(conn) + '''
+            <div id="opennamu_editor_user_button"></div>
+        </div>
+        
+        ''' + div + '''
+
+        <div id="opennamu_monaco_editor" class="''' + textarea_size + '''" ''' + editor_display[1] + '''></div>
+        <textarea id="opennamu_edit_textarea" class="''' + textarea_size + '''" ''' + editor_display[0] + ''' name="content" placeholder="''' + p_text + '''">''' + html.escape(data_main) + '''</textarea>
+        <hr class="main_hr">
+        ''' + out_field + '''
+        
+        <script>
+            window.addEventListener('DOMContentLoaded', function() {
+                do_stop_exit();
+                do_paste_image();
+                do_monaco_init("''' + monaco_thema + '''");
+                opennnamu_do_user_editor();
+            });
+        </script>
+                        
+        <button id="opennamu_save_button" type="submit" onclick="do_stop_exit_release();">''' + get_lang(conn, 'send') + '''</button>
+        <button id="opennamu_preview_button" type="button" onclick="opennamu_do_editor_preview();">''' + get_lang(conn, 'preview') + '''</button>
+        <hr class="main_hr">
+
+        <div id="opennamu_preview_area"></div>
+    '''
+
+def edit(name = 'Test', section = 0, do_type = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+    
+        ip = ip_check()
+
+        edit_req_mode = 0
+        if acl_check(name, 'document_edit') == 1:
+            edit_req_mode = 1
+            if acl_check(name, 'document_edit_request') == 1:
+                return redirect(conn, '/raw_acl/' + url_pas(name))
+            
+        if do_title_length_check(conn, name) == 1:
+            return re_error(conn, 38)
+        
+        curs.execute(db_change("select id from history where title = ? order by id + 0 desc"), [name])
+        doc_ver = curs.fetchall()
+        doc_ver = doc_ver[0][0] if doc_ver else '0'
+
+        if doc_ver == '0':
+            if acl_check(name, 'document_make_acl') == 1:
+                edit_req_mode = 1
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and doc_rev = ? and set_name = 'edit_request_data'"), [name, doc_ver])
+        if curs.fetchall():
+            return redirect(conn, '/edit_request_from/' + url_pas(name))
+        
+        section = '' if section == 0 else section
+        post_ver = flask.request.form.get('ver', '')
+        if flask.request.method == 'POST':
+            edit_repeat = 'error' if post_ver != doc_ver else 'post'
+        else:
+            edit_repeat = 'get'
+        
+        if edit_repeat == 'post':
+            if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                return re_error(conn, 13)
+    
+            if do_edit_slow_check(conn) == 1:
+                return re_error(conn, 24)
+    
+            today = get_time()
+            content = flask.request.form.get('content', '').replace('\r', '')
+            send = flask.request.form.get('send', '')
+            agree = flask.request.form.get('copyright_agreement', '')
+            
+            if do_edit_filter(conn, content) == 1:
+                return re_error(conn, 21)
+            
+            if do_edit_filter(conn, send) == 1:
+                return re_error(conn, 21)
+
+            if do_edit_send_check(conn, send) == 1:
+                return re_error(conn, 37)
+
+            if do_edit_text_bottom_check_box_check(conn, agree) == 1:
+                return re_error(conn, 29)
+            
+            curs.execute(db_change("select data from data where title = ?"), [name])
+            db_data = curs.fetchall()
+            if db_data:
+                o_data = db_data[0][0].replace('\r', '')
+
+                if section != '':
+                    if flask.request.form.get('doc_section_edit_apply', 'X') != 'X':
+                        if flask.request.form.get('doc_section_data_where', '') != '':
+                            data_match_where = flask.request.form.get('doc_section_data_where', '').split(',')
+                            if len(data_match_where) == 2:
+                                data_match_a = int(number_check(data_match_where[0]))
+                                if data_match_where[1] != 'inf':
+                                    data_match_b = int(number_check(data_match_where[1]))
+                                else:
+                                    data_match_b = 'inf'
+
+                                try:
+                                    if data_match_b != 'inf':
+                                        content = o_data[ : data_match_a] + content + o_data[data_match_b : ]
+                                    else:
+                                        content = o_data[ : data_match_a] + content
+                                except:
+                                    pass
+    
+                leng = leng_check(len(o_data), len(content))
+            else:
+                leng = '+' + str(len(content))
+
+            curs.execute(db_change("select data from other where name = 'document_content_max_length'"))
+            db_data_3 = curs.fetchall()
+            if db_data_3 and db_data_3[0][0] != '':
+                if int(number_check(db_data_3[0][0])) < len(content):
+                    return re_error(conn, 44)
+
+            curs.execute(db_change("select data from other where name = 'edit_timeout'"))
+            db_data_2 = curs.fetchall()
+            db_data_2 = number_check(db_data_2[0][0]) if db_data_2 and db_data_2[0][0] != '' else ''
+            if db_data_2 != '' and platform.system() in ('Linux', 'Darwin'):
+                timeout = edit_timeout(edit_render_set, (name, content), timeout = int(db_data_2))
+            else:
+                timeout = 0
+
+            if timeout == 1:
+                return re_error(conn, 41)
+            
+            if edit_req_mode == 0:
+                # 진짜 기록 부분
+                curs.execute(db_change("delete from data where title = ?"), [name])
+                curs.execute(db_change("insert into data (title, data) values (?, ?)"), [name, content])
+        
+                curs.execute(db_change("select id from user_set where name = 'watchlist' and data = ?"), [name])
+                for scan_user in curs.fetchall():
+                    add_alarm(scan_user[0], ip, '<a href="/w/' + url_pas(name) + '">' + html.escape(name) + '</a>')
+                        
+                history_plus(conn, 
+                    name,
+                    content,
+                    today,
+                    ip,
+                    send,
+                    leng
+                )
+                
+                render_set(conn, 
+                    doc_name = name,
+                    doc_data = content,
+                    data_type = 'backlink'
+                )
+                
+                section = (('#edit_load_' + str(section)) if section != '' else '')
+                return redirect(conn, '/w/' + url_pas(name) + section)
+            else:
+                curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, ?, 'edit_request_data', ?)"), [name, doc_ver, content])
+                curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, ?, 'edit_request_user', ?)"), [name, doc_ver, ip])
+                curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, ?, 'edit_request_date', ?)"), [name, doc_ver, today])
+                curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, ?, 'edit_request_send', ?)"), [name, doc_ver, send])
+                curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, ?, 'edit_request_leng', ?)"), [name, doc_ver, leng])
+                curs.execute(db_change("insert into data_set (doc_name, doc_rev, set_name, set_data) values (?, ?, 'edit_request_doing', ?)"), [name, doc_ver, today])
+
+                curs.execute(db_change("select id from user_set where name = 'watchlist' and data = ?"), [name])
+                for scan_user in curs.fetchall():
+                    add_alarm(scan_user[0], ip, '<a href="/edit_request/' + url_pas(name) + '">' + html.escape(name) + '</a> edit_request')
+            
+                return redirect(conn, '/edit_request_from/' + url_pas(name))
+        else:
+            editor_top_text = ''
+
+            doc_section_edit_apply = 'X'
+            data_section = ''
+            data_section_where = ''
+
+            if edit_repeat == 'get':
+                if do_type == 'load':
+                    if flask.session and 'edit_load_document' in flask.session:
+                        load_title = flask.session['edit_load_document']
+                    else:
+                        load_title = 0
+                else:
+                    load_title = 0
+                
+                if load_title == 0 and section == '':
+                    load_title = name
+                    editor_top_text += '<a href="/manager/15/' + url_pas(name) + '">(' + get_lang(conn, 'load') + ')</a> '
+                elif section != '':
+                    load_title = name
+                    
+                curs.execute(db_change("select data from data where title = ?"), [load_title])
+                db_data = curs.fetchall()
+                data = db_data[0][0] if db_data else ''
+                data = data.replace('\r', '')
+
+                if section != '':
+                    curs.execute(db_change('select data from other where name = "markup"'))
+                    db_data = curs.fetchall()
+                    db_data = db_data[0][0] if db_data else 'namumark'
+                    if db_data in ('namumark', 'namumark_beta'):
+                        count = 1
+                        data_section = '\n' + data + '\n'
+                        
+                        while 1:
+                            data_match_re = r'\n((={1,6})(#?) ?([^\n]+))\n'
+                            data_match = re.search(data_match_re, data_section)
+                            if not data_match:
+                                data_section = ''
+
+                                break
+                            elif count > section:
+                                data_section = ''
+
+                                break
+
+                            if section == count:
+                                data_section_sub = data_section
+                                data_section_sub = re.sub(data_match_re, ('.' * (len(data_match.group(0)) - 1)) + '\n', data_section_sub, 1)
+
+                                data_match_plus = re.search(data_match_re, data_section_sub)
+                                if data_match_plus:
+                                    data_section = data[data_match.span()[0] : data_match_plus.span()[0] - 1]
+                                    data_section_where = str(data_match.span()[0]) + ',' + str(data_match_plus.span()[0] - 1)
+                                else:
+                                    data_section = data[data_match.span()[0] : ]
+                                    data_section_where = str(data_match.span()[0]) + ',inf'
+
+                                doc_section_edit_apply = 'O'
+
+                                break
+                            else:
+                                data_section = re.sub(data_match_re, ('.' * (len(data_match.group(0)) - 1)) + '\n', data_section, 1)
+
+                            count += 1
+            else:
+                data = flask.request.form.get('content', '')
+                data = data.replace('\r', '')
+                
+                data_section_where = flask.request.form.get('doc_section_data_where', '')
+                doc_section_edit_apply = flask.request.form.get('doc_section_edit_apply', '')
+
+                doc_ver = flask.request.form.get('ver', '')
+
+                warning_edit = get_lang(conn, 'exp_edit_conflict') + ' '
+    
+                if flask.request.form.get('ver', '0') == '0':
+                    warning_edit += '<a href="/raw/' + url_pas(name) + '">(r' + doc_ver + ')</a>'
+                else:
+                    warning_edit += '' + \
+                        '<a href="/diff/' + flask.request.form.get('ver', '1') + '/' + doc_ver + '/' + url_pas(name) + '">' + \
+                            '(r' + doc_ver + ')' + \
+                        '</a>' + \
+                    ''
+    
+                warning_edit += '<hr class="main_hr">'
+                editor_top_text = warning_edit + editor_top_text
+
+            if data_section == '':
+                data_section = data
+    
+            editor_top_text += '<a href="/filter/edit_filter">(' + get_lang(conn, 'edit_filter_rule') + ')</a>'
+    
+            if editor_top_text != '':
+                editor_top_text += '<hr class="main_hr">'
+
+            sub_menu = ' (' + str(section) + ')' if section != '' else ''
+            sub_title = '(' + get_lang(conn, 'edit_request') + ')' if edit_req_mode == 1 else '(' + get_lang(conn, 'edit') + ')'
+
+            return easy_minify(conn, flask.render_template(skin_check(conn), 
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css([sub_title + sub_menu, 0])],
+                data = editor_top_text + '''
+                    <form method="post">
+                        <textarea style="display: none;" name="doc_section_data_where">''' + data_section_where + '''</textarea>
+                        <input style="display: none;" name="doc_section_edit_apply" value="''' + doc_section_edit_apply + '''">
+
+                        <input style="display: none;" id="opennamu_editor_doc_name" value="''' + html.escape(name) + '''">
+                        <input style="display: none;" name="ver" value="''' + doc_ver + '''">
+                        
+                        <input placeholder="''' + get_lang(conn, 'why') + '''" name="send">
+                        <hr class="main_hr">
+                        
+                        ''' + edit_editor(conn, ip, data_section, addon = get_edit_text_bottom_check_box(conn) + get_edit_text_bottom(conn) , name = name) + '''
+                    </form>
+                ''',
+                menu = [
+                    ['w/' + url_pas(name), get_lang(conn, 'return')],
+                    ['delete/' + url_pas(name), get_lang(conn, 'delete')], 
+                    ['move/' + url_pas(name), get_lang(conn, 'move')], 
+                    ['upload', get_lang(conn, 'upload')]
+                ]
+            ))

+ 16 - 0
route/edit_backlink_reset.py

@@ -0,0 +1,16 @@
+from .tool.func import *
+
+def edit_backlink_reset(name = 'Test'):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change("select data from data where title = ?"), [name])
+        old = curs.fetchall()
+        if old:
+            render_set(conn, 
+                doc_name = name,
+                doc_data = old[0][0],
+                data_type = 'backlink'
+            )
+
+        return redirect(conn, '/xref/' + url_pas(name))

+ 67 - 0
route/edit_delete.py

@@ -0,0 +1,67 @@
+from .tool.func import *
+
+def edit_delete(name):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        ip = ip_check()
+        if acl_check(name, 'document_delete') == 1:
+            return re_error(conn, 0)
+
+        curs.execute(db_change("select title from data where title = ?"), [name])
+        if not curs.fetchall():
+            return redirect(conn, '/w/' + url_pas(name))
+
+        if flask.request.method == 'POST':
+            if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                return re_error(conn, 13)
+
+            if do_edit_slow_check(conn) == 1:
+                return re_error(conn, 24)
+            
+            send = flask.request.form.get('send', '')
+            agree = flask.request.form.get('copyright_agreement', '')
+            
+            if do_edit_send_check(conn, send) == 1:
+                return re_error(conn, 37)
+            
+            if do_edit_text_bottom_check_box_check(conn, agree) == 1:
+                return re_error(conn, 29)
+
+            curs.execute(db_change("select data from data where title = ?"), [name])
+            data = curs.fetchall()
+            if data:
+                today = get_time()
+                leng = '-' + str(len(data[0][0]))
+
+                history_plus(conn, 
+                    name,
+                    '',
+                    today,
+                    ip,
+                    send,
+                    leng,
+                    mode = 'delete'
+                )
+
+                curs.execute(db_change("select title, link from back where title = ? and not type = 'cat' and not type = 'no'"), [name])
+                for data in curs.fetchall():
+                    curs.execute(db_change("insert into back (title, link, type, data) values (?, ?, 'no', '')"), [data[0], data[1]])
+
+                curs.execute(db_change("delete from back where link = ?"), [name])
+                curs.execute(db_change("delete from data where title = ?"), [name])
+
+            return redirect(conn, '/w/' + url_pas(name))
+        else:            
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css(['(' + get_lang(conn, 'delete') + ')', 0])],
+                data = '''
+                    <form method="post">
+                        <input placeholder="''' + get_lang(conn, 'why') + '''" name="send">
+                        <hr class="main_hr">
+                        ''' + captcha_get(conn) + ip_warning(conn) + get_edit_text_bottom_check_box(conn) + get_edit_text_bottom(conn)  + '''
+                        <button type="submit">''' + get_lang(conn, 'delete') + '''</button>
+                    </form>
+                ''',
+                menu = [['w/' + url_pas(name), get_lang(conn, 'return')]]
+            ))

+ 47 - 0
route/edit_delete_file.py

@@ -0,0 +1,47 @@
+from .tool.func import *
+
+from .edit_delete import edit_delete
+
+def edit_delete_file(name = 'test.jpg'):
+    with get_db_connect() as conn:
+        if acl_check('', 'owner_auth', '', '') != 0:
+            return re_error(conn, 0)
+
+        mime_type = re.search(r'([^.]+)$', name)
+        mime_type_str = 'jpg'
+        if mime_type:
+            mime_type_str = mime_type.group(1)
+
+        file_name = re.sub(r'\.([^.]+)$', '', name)
+        file_name = re.sub(r'^file:', '', file_name)
+
+        file_all_name = sha224_replace(file_name) + '.' + mime_type_str
+        file_directory = os.path.join(load_image_url(conn), file_all_name)
+
+        if not os.path.exists(file_directory):
+            return redirect(conn, '/w/' + url_pas(name))
+
+        if flask.request.method == 'POST':
+            acl_check(tool = 'owner_auth', memo = 'file del (' + name + ')')
+            os.remove(file_directory)
+
+            if flask.request.form.get('with_doc', '') != '':
+                edit_delete(name)
+
+            return redirect(conn, '/w/' + url_pas(name))
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css(['(' + get_lang(conn, 'file_delete') + ')', 0])],
+                data = '''
+                    <form method="post">
+                        <img src="/image/''' + url_pas(file_all_name) + '''">
+                        <hr class="main_hr">
+                        <a href="/image/''' + url_pas(file_all_name) + '''">/image/''' + url_pas(file_all_name) + '''</a>
+                        <hr class="main_hr">
+                        <label><input name="with_doc" type="checkbox" checked> ''' + get_lang(conn, 'file_delete_with_document') + '''</label>
+                        <hr class="main_hr">
+                        <button type="submit">''' + get_lang(conn, 'file_delete') + '''</button>
+                    </form>
+                ''',
+                menu = [['w/' + url_pas(name), get_lang(conn, 'return')]]
+            ))

+ 39 - 0
route/edit_delete_multiple.py

@@ -0,0 +1,39 @@
+from .tool.func import *
+
+from .edit_delete import edit_delete
+
+def edit_delete_multiple():
+    with get_db_connect() as conn:
+        if acl_check('', 'acl_auth', '', '') == 1:
+            return re_error(conn, 0)
+
+        if flask.request.method == 'POST':
+            send = flask.request.form.get('send', '')
+            agree = flask.request.form.get('copyright_agreement', '')
+            
+            if do_edit_send_check(conn, send) == 1:
+                return re_error(conn, 37)
+            
+            if do_edit_text_bottom_check_box_check(conn, agree) == 1:
+                return re_error(conn, 29)
+            
+            all_title = re.findall(r'([^\n]+)\n', flask.request.form.get('content', '').replace('\r', '') + '\n')
+            for name in all_title:
+                edit_delete(name)
+
+            return redirect(conn, '/recent_change')
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'many_delete'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+                data = '''
+                    <form method="post">
+                        <textarea class="opennamu_textarea_500" placeholder="''' + get_lang(conn, 'many_delete_help') + '''" name="content"></textarea>
+                        <hr class="main_hr">
+                        <input placeholder="''' + get_lang(conn, 'why') + '''" name="send" type="text">
+                        <hr class="main_hr">
+                        ''' + captcha_get(conn) + ip_warning(conn) + get_edit_text_bottom_check_box(conn) + get_edit_text_bottom(conn)  + '''
+                        <button type="submit">''' + get_lang(conn, 'delete') + '''</button>
+                    </form>
+                ''',
+                menu = [['manager/1', get_lang(conn, 'return')]]
+            ))

+ 265 - 0
route/edit_move.py

@@ -0,0 +1,265 @@
+from .tool.func import *
+
+def edit_move(name):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        if acl_check(name, 'document_move') == 1:
+            return re_error(conn, 0)
+        
+        if do_title_length_check(conn, name) == 1:
+            return re_error(conn, 38)
+
+        if flask.request.method == 'POST':
+            move_title = flask.request.form.get('title', 'test')
+            if acl_check(move_title) == 1:
+                return re_error(conn, 0)
+
+            if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                return re_error(conn, 13)
+
+            if do_edit_slow_check(conn) == 1:
+                return re_error(conn, 24)
+            
+            send = flask.request.form.get('send', '')
+            agree = flask.request.form.get('copyright_agreement', '')
+
+            time = get_time()
+            ip = ip_check()
+            
+            has_error = 0
+
+            move_option = flask.request.form.get('move_option', 'none')
+            move_option_topic = flask.request.form.get('move_topic_option', 'none')
+            document_set_option = flask.request.form.get('document_set_option', 'none')
+            
+            if do_edit_send_check(conn, send) == 1:
+                return re_error(conn, 37)
+            
+            if do_edit_text_bottom_check_box_check(conn, agree) == 1:
+                return re_error(conn, 29)
+
+            # 역링크 관련 패치 해야할 듯
+
+            # 문서 이동 파트 S
+            curs.execute(db_change("select title from history where title = ?"), [move_title])
+            if curs.fetchall():
+                if move_option == 'merge' and acl_check(tool = 'owner_auth', memo = 'merge documents (' + name + ') (' + move_title + ')') != 1:
+                    curs.execute(db_change("select data from data where title = ?"), [move_title])
+                    data = curs.fetchall()
+                    if data:
+                        curs.execute(db_change("delete from data where title = ?"), [move_title])
+                        curs.execute(db_change("delete from back where link = ?"), [move_title])
+
+                    curs.execute(db_change("select data from data where title = ?"), [name])
+                    data = curs.fetchall()
+                    data_in = data[0][0] if data else ''
+
+                    curs.execute(db_change("update data set title = ? where title = ?"), [move_title, name])
+                    curs.execute(db_change("update back set link = ? where link = ?"), [move_title, name])
+
+                    # 역링크 S
+                    # 문서 합치기이므로 기존 문서 쪽은 no 역링크 생성, 이동하는 곳에는 no 역링크 제거
+                    curs.execute(db_change("select distinct link from back where title = ?"), [name])
+                    backlink = [[for_a[0], name, 'no', ''] for for_a in curs.fetchall()]
+                    curs.executemany(db_change("insert into back (link, title, type, data) values (?, ?, ?, ?)"), backlink)
+                    curs.execute(db_change("delete from back where title = ? and type = 'no'"), [move_title])
+                    # 역링크 E
+
+                    curs.execute(db_change("select id from history where title = ? order by id + 0 desc limit 1"), [move_title])
+                    num = curs.fetchall()[0][0]
+
+                    curs.execute(db_change("select id from history where title = ? order by id + 0 asc"), [name])
+                    data = curs.fetchall()
+                    for move in data:
+                        curs.execute(db_change("update rc set title = ?, id = ? where title = ? and id = ?"), [move_title, str(int(num) + int(move[0])), name, move[0]])
+                        curs.execute(db_change("update history set title = ?, id = ? where title = ? and id = ?"), [move_title, str(int(num) + int(move[0])), name, move[0]])
+
+                    history_plus(conn, 
+                        move_title, 
+                        data_in, 
+                        time, 
+                        ip, 
+                        send, 
+                        '0',
+                        t_check = '<a>' + name + '</a> ↔ <a>' + move_title + '</a>',
+                        mode = 'move'
+                    )
+                elif move_option == 'reverse':
+                    # 전체적인 구조 변경 필요
+                    # 중간 문서 거치지 않고 불러와서 바로 변경하도록
+                    # 문서 이동 말고 나머지도 그렇게 변경 필요함
+                    i = 0
+                    var_name = ''
+                    while var_name == '':
+                        temp_title = 'test ' + load_random_key() + ' ' + str(i)
+                        curs.execute(db_change("select title from history where title = ? limit 1"), [temp_title])
+                        if not curs.fetchall():
+                            var_name = temp_title
+                        else:
+                            i += 1
+
+                    for title_name in [[name, var_name], [move_title, name], [var_name, move_title]]:
+                        curs.execute(db_change("update data set title = ? where title = ?"), [title_name[1], title_name[0]])
+                        curs.execute(db_change("update back set link = ? where link = ?"), [title_name[1], title_name[0]])
+
+                        curs.execute(db_change("update history set title = ? where title = ?"), [title_name[1], title_name[0]])
+                        curs.execute(db_change("update rc set title = ? where title = ?"), [title_name[1], title_name[0]])
+
+                    for title_name in [[name, move_title], [move_title, name]]:
+                        curs.execute(db_change("select data from data where title = ?"), [name])
+                        data = curs.fetchall()
+                        data_in = data[0][0] if data else ''
+
+                        history_plus(conn, 
+                            title_name[0], 
+                            data_in, 
+                            time, 
+                            ip, 
+                            send, 
+                            '0',
+                            t_check = '<a>' + title_name[0] + '</a> ⇋ <a>' + title_name[1] + '</a>',
+                            mode = 'move'
+                        )
+                elif move_option != 'none':
+                    has_error = 1
+            elif move_option != 'none':
+                curs.execute(db_change("select data from data where title = ?"), [name])
+                data = curs.fetchall()
+                data_in = data[0][0] if data else ''
+
+                curs.execute(db_change("update data set title = ? where title = ?"), [move_title, name])
+                curs.execute(db_change("update back set link = ? where link = ?"), [move_title, name])
+
+                # 역링크 S
+                # 문서 합치기 쪽 역링크와 동일하게
+                curs.execute(db_change("select distinct link from back where title = ?"), [name])
+                backlink = [[for_a[0], name, 'no', ''] for for_a in curs.fetchall()]
+                curs.executemany(db_change("insert into back (link, title, type, data) values (?, ?, ?, ?)"), backlink)
+                curs.execute(db_change("delete from back where title = ? and type = 'no'"), [move_title])
+                # 역링크 E
+
+                # 역사와 최근 변경 이동 S
+                curs.execute(db_change("update history set title = ? where title = ?"), [move_title, name])
+                curs.execute(db_change("update rc set title = ? where title = ?"), [move_title, name])
+                # 역사와 최근 변경 이동 E
+
+                history_plus(conn, 
+                    move_title, 
+                    data_in, 
+                    time, 
+                    ip, 
+                    send,
+                    '0',
+                    t_check = '<a>' + name + '</a> → <a>' + move_title + '</a>',
+                    mode = 'move'
+                )
+
+            # 문서 이동 파트 E
+            
+            # 토론 이동 파트 S
+            curs.execute(db_change("select title from rd where title = ?"), [move_title])
+            if curs.fetchall():
+                if move_option_topic == 'merge' and acl_check(tool = 'owner_auth', memo = 'merge document\'s topics (' + name + ') (' + move_title + ')') != 1:
+                    curs.execute(db_change("update rd set title = ? where title = ?"), [move_title, name])
+                elif move_option_topic == 'reverse':
+                    i = 0
+                    var_name = ''
+                    while var_name == '':
+                        temp_title = 'test ' + load_random_key() + ' ' + str(i)
+                        curs.execute(db_change("select title from rd where title = ? limit 1"), [temp_title])
+                        if not curs.fetchall():
+                            var_name = temp_title
+                        else:
+                            i += 1
+                    
+                    for title_name in [[name, var_name], [move_title, name], [var_name, move_title]]:
+                        curs.execute(db_change("update rd set title = ? where title = ?"), [title_name[1], title_name[0]])
+                else:
+                    has_error = 1
+            elif move_option_topic != 'none':
+                curs.execute(db_change("update rd set title = ? where title = ?"), [move_title, name])
+
+            # 토론 이동 파트 E
+
+            # data_set 이동 파트 S
+            if document_set_option == 'reverse':
+                i = 0
+                var_name = ''
+                while var_name == '':
+                    temp_title = 'test ' + load_random_key() + ' ' + str(i)
+                    curs.execute(db_change("select title from history where title = ? limit 1"), [temp_title])
+                    if not curs.fetchall():
+                        var_name = temp_title
+                    else:
+                        i += 1
+                
+                for title_name in [[name, var_name], [move_title, name], [var_name, move_title]]:
+                    curs.execute(db_change("update data_set set doc_name = ? where doc_name = ?"), [title_name[1], title_name[0]])
+            elif document_set_option == 'normal':
+                curs.execute(db_change("delete from data_set where doc_name = ?"), [move_title])
+                curs.execute(db_change("delete from acl where title = ?"), [move_title])
+
+                curs.execute(db_change("update data_set set doc_name = ? where doc_name = ?"), [move_title, name])
+                curs.execute(db_change("update acl set title = ? where title = ?"), [move_title, name])
+
+            # data_set 이동 파트 E
+
+            if has_error == 0:
+                return redirect(conn, '/w/' + url_pas(move_title))
+            else:
+                return re_error(conn, 19)
+        else:
+            owner_auth = acl_check(tool = 'owner_auth')
+            owner_auth = 1 if owner_auth == 0 else 0
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css(['(' + get_lang(conn, 'move') + ')', 0])],
+                data = '''
+                    <form method="post">
+                        <span>''' + get_lang(conn, 'document_name') + '''</span>
+                        <hr class="main_hr">
+                        <input value="''' + name + '''" name="title" type="text">
+                        <hr class="main_hr">
+                        
+                        <input placeholder="''' + get_lang(conn, 'why') + '''" name="send" type="text">
+                        <hr class="main_hr">
+                        
+                        <h2>''' + get_lang(conn, 'document') + '''</h2>
+                        <select name="move_option">
+                            <option value="normal"> ''' + get_lang(conn, 'normal') + '''</option>
+                            <option value="none"> ''' + get_lang(conn, 'dont_move') + '''</option>
+                            <option value="reverse"> ''' + get_lang(conn, 'replace_move') + '''</option>
+                            ''' + ('<option value="merge"> ' + get_lang(conn, 'merge_move') + '</option>' if owner_auth == 1 else '') + '''
+                        </select>
+                        <hr class="main_hr">
+                        <!-- <label><input type="checkbox" name="move_redirect_make"> ''' + get_lang(conn, 'move_redirect_make') + '''</label>
+                        <hr class="main_hr"> -->
+                        
+                        <h2>''' + get_lang(conn, 'discussion') + '''</h2>
+                        <select name="move_topic_option">
+                            <option value="none"> ''' + get_lang(conn, 'dont_move') + '''</option>
+                            <option value="normal"> ''' + get_lang(conn, 'normal') + '''</option>
+                            <option value="reverse"> ''' + get_lang(conn, 'replace_move') + '''</option>
+                            ''' + ('<option value="merge"> ' + get_lang(conn, 'merge_move') + '</option>' if owner_auth == 1 else '') + '''
+                        </select>
+                        <hr class="main_hr">
+
+                        ''' + ((
+                            '''<h2>''' + get_lang(conn, 'document_set') + '''</h2>
+                            <select name="document_set_option">
+                                <option value="none"> ''' + get_lang(conn, 'dont_move') + '''</option>
+                                <option value="normal"> ''' + get_lang(conn, 'normal') + '''</option>
+                                <option value="reverse"> ''' + get_lang(conn, 'replace_move') + '''</option>
+                            </select>
+                            <hr class="main_hr">
+                            '''
+                        ) if owner_auth == 1 else '') + '''
+
+                        ''' + captcha_get(conn) + ip_warning(conn) + get_edit_text_bottom_check_box(conn) + get_edit_text_bottom(conn)  + '''
+                        
+                        <button type="submit">''' + get_lang(conn, 'move') + '''</button>
+                    </form>
+                ''',
+                menu = [['w/' + url_pas(name), get_lang(conn, 'return')], ['move_all', get_lang(conn, 'multiple_move')]]
+            ))

+ 112 - 0
route/edit_request.py

@@ -0,0 +1,112 @@
+from .tool.func import *
+
+from .view_diff import view_diff_do
+
+def edit_request(name = 'Test', do_type = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        disabled = ""
+        if acl_check(name, 'document_edit') == 1:
+            disabled = "disabled"
+
+        curs.execute(db_change("select id from history where title = ? order by id + 0 desc"), [name])
+        doc_ver = curs.fetchall()
+        doc_ver = doc_ver[0][0] if doc_ver else '0'
+
+        if doc_ver == '0':
+            if acl_check(name, 'document_make_acl') == 1:
+                disabled = "disabled"
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and doc_rev = ? and set_name = 'edit_request_data'"), [name, doc_ver])
+        db_data = curs.fetchall()
+        if not db_data:
+            return redirect(conn, '/edit/' + url_pas(name))
+        
+        edit_request_data = db_data[0][0]
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and doc_rev = ? and set_name = 'edit_request_user'"), [name, doc_ver])
+        db_data = curs.fetchall()
+        edit_request_user = db_data[0][0] if db_data else ''
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and doc_rev = ? and set_name = 'edit_request_date'"), [name, doc_ver])
+        db_data = curs.fetchall()
+        edit_request_date = db_data[0][0] if db_data else ''
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and doc_rev = ? and set_name = 'edit_request_send'"), [name, doc_ver])
+        db_data = curs.fetchall()
+        edit_request_send = db_data[0][0] if db_data else ''
+
+        curs.execute(db_change("select set_data from data_set where doc_name = ? and doc_rev = ? and set_name = 'edit_request_leng'"), [name, doc_ver])
+        db_data = curs.fetchall()
+        edit_request_leng = db_data[0][0] if db_data else ''
+
+        if flask.request.method == 'POST':
+            if disabled != "":
+                return redirect(conn, '/w/' + url_pas(name))
+            
+            curs.execute(db_change("select id from user_set where name = 'watchlist' and data = ?"), [name])
+            for scan_user in curs.fetchall():
+                add_alarm(scan_user[0], edit_request_user, '<a href="/w/' + url_pas(name) + '">' + html.escape(name) + '</a>')
+
+            if flask.request.form.get('check', '') == 'Y':
+                curs.execute(db_change("delete from data where title = ?"), [name])
+                curs.execute(db_change("insert into data (title, data) values (?, ?)"), [name, edit_request_data])
+                        
+                history_plus(conn, 
+                    name,
+                    edit_request_data,
+                    edit_request_date,
+                    edit_request_user,
+                    edit_request_send,
+                    edit_request_leng,
+                    mode = 'edit_request'
+                )
+                
+                render_set(conn, 
+                    doc_name = name,
+                    doc_data = edit_request_data,
+                    data_type = 'backlink'
+                )
+            else:
+                history_plus(conn, 
+                    name,
+                    edit_request_data,
+                    edit_request_date,
+                    edit_request_user,
+                    edit_request_send,
+                    '0',
+                    mode = 'edit_request'
+                )
+                
+            if do_type == 'from':
+                return redirect(conn, '/edit/' + url_pas(name))
+            else:
+                return redirect(conn, '/w/' + url_pas(name))
+        else:
+            curs.execute(db_change("select data from data where title = ?"), [name])
+            db_data = curs.fetchall()
+            old_data = db_data[0][0] if db_data else ''
+
+            result = view_diff_do(old_data, edit_request_data, 'r' + doc_ver, get_lang(conn, 'edit_request'))
+
+            return easy_minify(conn, flask.render_template(skin_check(conn), 
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css(['(' + get_lang(conn, 'edit_request_check') + ')', 0])],
+                data = '''
+                    <div id="opennamu_get_user_info">''' + html.escape(edit_request_user) + '''</div>
+                    <hr class="main_hr">
+                    ''' + edit_request_date + '''
+                    <hr class="main_hr">
+                    <input readonly value="''' + html.escape(edit_request_send) + '''">
+                    <hr class="main_hr">
+                    ''' + result + '''
+                    <hr class="main_hr">
+                    <form method="post">
+                        <button ''' + disabled + ''' id="opennamu_save_button" type="submit" name="check" value="Y">''' + get_lang(conn, 'approve') + '''</button>
+                        <button ''' + disabled + ''' id="opennamu_preview_button" type="submit" name="check" value="">''' + get_lang(conn, 'decline') + '''</button>
+                        <hr class="main_hr">
+                        <textarea readonly class="opennamu_textarea_500">''' + html.escape(edit_request_data) + '''</textarea>
+                    </form>
+                ''',
+                menu = 0
+            ))

+ 88 - 0
route/edit_revert.py

@@ -0,0 +1,88 @@
+from .tool.func import *
+
+def edit_revert(name, num):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change("select title from history where title = ? and id = ? and hide = 'O'"), [name, str(num)])
+        if curs.fetchall() and acl_check(tool = 'hidel_auth') == 1:
+            return re_error(conn, 3)
+
+        if acl_check(name, 'document_edit') == 1:
+            return re_error(conn, 0)
+        
+        curs.execute(db_change("select data from history where title = ? and id = ?"), [name, str(num)])
+        data = curs.fetchall()
+        if not data:
+            return redirect(conn, '/w/' + url_pas(name))
+
+        if flask.request.method == 'POST':
+            if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                return re_error(conn, 13)
+
+            if do_edit_slow_check(conn) == 1:
+                return re_error(conn, 24)
+            
+            send = flask.request.form.get('send', '')
+            agree = flask.request.form.get('copyright_agreement', '')
+            
+            if do_edit_send_check(conn, send) == 1:
+                return re_error(conn, 37)
+            
+            if do_edit_text_bottom_check_box_check(conn, agree) == 1:
+                return re_error(conn, 29)
+
+            if do_edit_filter(conn, data[0][0]) == 1:
+                return re_error(conn, 21)
+            
+            curs.execute(db_change("select data from other where name = 'document_content_max_length'"))
+            db_data = curs.fetchall()
+            if db_data and db_data[0][0] != '':
+                if int(number_check(db_data[0][0])) < len(data[0][0]):
+                    return re_error(conn, 44)
+
+            curs.execute(db_change("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(db_change("update data set data = ? where title = ?"), [data[0][0], name])
+            else:
+                leng = '+' + str(len(data[0][0]))
+                curs.execute(db_change("insert into data (title, data) values (?, ?)"), [name, data[0][0]])
+
+            history_plus(conn, 
+                name,
+                data[0][0],
+                get_time(),
+                ip_check(),
+                flask.request.form.get('send', ''),
+                leng,
+                t_check = 'r' + str(num),
+                mode = 'revert'
+            )
+
+            render_set(conn, 
+                doc_name = name,
+                doc_data = data[0][0],
+                data_type = 'backlink'
+            )
+
+            return redirect(conn, '/w/' + url_pas(name))
+        else:
+            if data:
+                preview = '<hr class="main_hr"><pre>' + html.escape(data[0][0]) + '</pre>'
+            else:
+                preview = ''
+            
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css(['(r' + str(num) + ') (' + get_lang(conn, 'revert') + ')', 0])],
+                data = '''
+                    <form method="post">
+                        <input placeholder="''' + get_lang(conn, 'why') + '''" name="send" type="text">
+                        <hr class="main_hr">
+                        ''' + captcha_get(conn) + ip_warning(conn) + get_edit_text_bottom_check_box(conn) + get_edit_text_bottom(conn)  + '''
+                        <button type="submit">''' + get_lang(conn, 'revert') + '''</button>
+                    </form>
+                ''' + preview,
+                menu = [['history/' + url_pas(name), get_lang(conn, 'history')], ['recent_changes', get_lang(conn, 'recent_change')]]
+            ))

+ 162 - 0
route/edit_upload.py

@@ -0,0 +1,162 @@
+from .tool.func import *
+
+def edit_upload():
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        if acl_check('', 'upload') == 1:
+            return re_error(conn, 0)
+        
+        curs.execute(db_change('select data from other where name = "upload"'))
+        db_data = curs.fetchall()
+        file_max = number_check(db_data[0][0]) if db_data and db_data[0][0] != '' else '2'
+        file_max = int(file_max)
+
+        if flask.request.method == 'POST':
+            if captcha_post(conn, flask.request.form.get('g-recaptcha-response', flask.request.form.get('g-recaptcha', ''))) == 1:
+                return re_error(conn, 13)
+
+            file_data = flask.request.files.getlist("f_data[]")
+            file_len = len(file_data)
+
+            file_size_all = flask.request.content_length
+            if file_size_all == None:
+                file_size_all = 0
+
+            if (file_max * 1000 * 1000 * file_len) < file_size_all or file_size_all == 0:
+                return re_error(conn, 17)
+
+            if file_len == 1:
+                file_num = None
+            else:
+                if acl_check('', 'many_upload') == 1:
+                    return re_error(conn, 0)
+
+                file_num = 1
+
+            for data in file_data:
+                file_name = data.filename if data.filename else ''
+                if file_name == '':
+                    return re_error(conn, 9)
+                
+                value_tmp = os.path.splitext(file_name)
+                value = ''
+                if len(value_tmp) >= 2:
+                    value = value_tmp[1]
+
+                curs.execute(db_change("select html from html_filter where kind = 'extension'"))
+                extension = [i[0].lower() for i in curs.fetchall()]
+                if not re.sub(r'^\.', '', value).lower() in extension:
+                    return re_error(conn, 14)
+
+                name = ''
+                if flask.request.form.get('f_name', None):
+                    name = flask.request.form.get('f_name', '') + (' ' + str(file_num) if file_num else '') + value
+                else:
+                    name = file_name
+
+                piece = os.path.splitext(name)
+                if re.search(r'\.', piece[0]):
+                    return re_error(conn, 22)
+
+                e_data = sha224_replace(piece[0]) + piece[1]
+
+                curs.execute(db_change("select title from data where title = ?"), ['file:' + name])
+                if curs.fetchall():
+                    return re_error(conn, 16)
+
+                curs.execute(db_change("select html from html_filter where kind = 'file'"))
+                db_data = curs.fetchall()
+                for i in db_data:
+                    t_re = re.compile(i[0])
+                    if t_re.search(name):
+                        return redirect(conn, '/filter/file_filter')
+
+                data_url_image = load_image_url(conn)
+                if os.path.exists(os.path.join(data_url_image, e_data)):
+                    return re_error(conn, 16)
+                else:
+                    data.save(os.path.join(data_url_image, e_data))
+
+                ip = ip_check()
+                g_lice = flask.request.form.get('f_lice', '')
+                file_size = os.stat(os.path.join(data_url_image, e_data)).st_size
+                file_size = str(round(file_size / 1000, 1))
+
+                curs.execute(db_change("select data from other where name = 'markup'"))
+                db_data = curs.fetchall()
+                if db_data and db_data[0][0] == 'namumark':
+                    file_d = '' + \
+                        flask.request.form.get('f_lice_sel', 'direct_input') + '\n' + \
+                        '[[category:' + re.sub(r'\]', '_', flask.request.form.get('f_lice_sel', '')) + ']]\n' + \
+                        (g_lice if g_lice != '' else '') + \
+                    ''
+                else:
+                    file_d = '' + \
+                        flask.request.form.get('f_lice_sel', 'direct_input') + '\n' + \
+                        (g_lice if g_lice != '' else '') + \
+                    ''
+
+                curs.execute(db_change("insert into data (title, data) values (?, ?)"), ['file:' + name, file_d])
+
+                render_set(conn, 
+                    doc_name = 'file:' + name,
+                    doc_data = file_d,
+                    data_type = 'backlink'
+                )
+
+                history_plus(conn, 
+                    'file:' + name,
+                    file_d,
+                    get_time(),
+                    ip,
+                    '',
+                    '0',
+                    mode = 'upload'
+                )
+
+                if file_num:
+                    file_num += 1
+
+            return redirect(conn, '/w/file:' + name)
+        else:
+            license_list = '<option value="direct_input">' + get_lang(conn, 'direct_input') + '</option>'
+            file_name = html.escape(flask.request.args.get('name', ''))
+
+            curs.execute(db_change("select html from html_filter where kind = 'image_license'"))
+            db_data = curs.fetchall()
+            license_list += ''.join(['<option value="' + i[0] + '">' + i[0] + '</option>' for i in db_data])
+
+            curs.execute(db_change("select data from other where name = 'upload_help'"))
+            db_data = curs.fetchall()
+            upload_help = ('<hr class="main_hr">' + db_data[0][0]) if db_data and db_data[0][0] != '' else ''
+
+            curs.execute(db_change("select data from other where name = 'upload_default'"))
+            db_data = curs.fetchall()
+            upload_default = html.escape(db_data[0][0]) if db_data and db_data[0][0] != '' else ''
+            
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'upload'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+                data = '''
+                    <a href="/filter/file_filter">(''' + get_lang(conn, 'file_filter_list') + ''')</a> <a href="/filter/extension_filter">(''' + get_lang(conn, 'extension_filter_list') + ''')</a>
+                    ''' + upload_help + '''
+                    <hr class="main_hr">
+                    ''' + get_lang(conn, 'max_file_size') + ''' : ''' + str(file_max) + '''MB
+                    <hr class="main_hr">
+                    <form method="post" enctype="multipart/form-data" accept-charset="utf8">
+                        <input multiple="multiple" type="file" name="f_data[]">
+                        <hr class="main_hr">
+                        <input placeholder="''' + get_lang(conn, 'file_name') + '''" name="f_name" value="''' + file_name + '''">
+                        <hr class="main_hr">
+                        <select name="f_lice_sel">
+                            ''' + license_list + '''
+                        </select>
+                        <hr class="main_hr">
+                        <textarea class="opennamu_textarea_100" placeholder="''' + get_lang(conn, 'other') + '''" name="f_lice">''' + upload_default + '''</textarea>
+                        <hr class="main_hr">
+                        ''' + captcha_get(conn) + '''
+                        <button id="opennamu_save_button" type="submit">''' + get_lang(conn, 'save') + '''</button>
+                    </form>
+                ''',
+                menu = [['other', get_lang(conn, 'return')]]
+            ))

+ 94 - 0
route/filter_all.py

@@ -0,0 +1,94 @@
+from .tool.func import *
+
+def filter_all(tool):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        div = '<table id="main_table_set">'
+        div += '<tr id="main_table_top_tr">'
+
+        div += '<td id="main_table_width">A</td>'
+        div += '<td id="main_table_width">B</td>'
+        div += '<td id="main_table_width">C</td>'
+
+        div += '</tr>'
+
+        admin = acl_check(tool = 'owner_auth')
+        admin = 1 if admin == 0 else 0
+
+        if tool == 'edit_filter':
+            if acl_check('', 'edit_filter_view', '', '') == 1:
+                return re_error(conn, 0)
+
+        if tool == 'inter_wiki':
+            title = get_lang(conn, 'interwiki_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'inter_wiki'"))
+        elif tool == 'email_filter':
+            title = get_lang(conn, 'email_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'email'"))
+        elif tool == 'name_filter':
+            title = get_lang(conn, 'id_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'name'"))
+        elif tool == 'edit_filter':
+            title = get_lang(conn, 'edit_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'regex_filter'"))
+        elif tool == 'file_filter':
+            title = get_lang(conn, 'file_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'file'"))
+        elif tool == 'image_license':
+            title = get_lang(conn, 'image_license_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'image_license'"))
+        elif tool == 'extension_filter':
+            title = get_lang(conn, 'extension_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'extension'"))
+        elif tool == 'document':
+            title = get_lang(conn, 'document_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'document'"))
+        elif tool == 'outer_link':
+            title = get_lang(conn, 'outer_link_filter_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'outer_link'"))
+        elif tool == 'template':
+            title = get_lang(conn, 'template_document_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'template'"))
+        else:
+            title = get_lang(conn, 'edit_tool_list')
+            curs.execute(db_change("select html, plus, plus_t from html_filter where kind = 'edit_top'"))
+
+        db_data = curs.fetchall()
+        for data in db_data:
+            div += '<tr>'
+            div += '<td>'
+
+            div += html.escape(data[0])
+            if admin == 1:
+                if tool in ('inter_wiki', 'outer_link', 'edit_filter', 'document', 'edit_top', 'template'):
+                    div += ' <a href="/filter/' + tool + '/add/' + url_pas(data[0]) + '">(' + get_lang(conn, 'edit') + ')</a>'
+                    
+                div += ' <a href="/filter/' + tool + '/del/' + url_pas(data[0]) + '">(' + get_lang(conn, 'delete') + ')</a>'
+
+            div += '</td>'
+
+            if tool in ('inter_wiki', 'outer_link'):
+                if tool == 'inter_wiki':
+                    div += '<td><a class="opennamu_link_out" href="' + html.escape(data[1]) + '">' + html.escape(data[1]) + '</a></td>'
+                else:
+                    div += '<td>' + html.escape(data[1]) + '</td>'
+                
+                div += '<td>' + data[2] + '</td>'
+            else:
+                div += '<td>' + html.escape(data[1]) + '</td>'
+                div += '<td>' + html.escape(data[2]) + '</td>'
+            
+            div += '</tr>'
+
+        div += '</table>'
+
+        if admin == 1:
+            div += '<hr class="main_hr">'
+            div += '<a href="/filter/' + tool + '/add">(' + get_lang(conn, 'add') + ')</a>'
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [title, wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = div,
+            menu = [['manager/1', get_lang(conn, 'return')]]
+        ))

+ 281 - 0
route/filter_all_add.py

@@ -0,0 +1,281 @@
+from .tool.func import *
+
+def filter_all_add(tool, name = None):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        if not name and tool == 'edit_filter':
+            return redirect(conn, '/manager/9')
+
+        if flask.request.method == 'POST':
+            if acl_check('', 'owner_auth', '', '') == 1:
+                return re_error(conn, 3)
+
+            title = flask.request.form.get('title', 'test')
+            if tool in ('inter_wiki', 'outer_link'):
+                link = flask.request.form.get('link', 'test')
+                icon = flask.request.form.get('icon', '')
+                inter_type = flask.request.form.get('inter_type', '')
+
+                curs.execute(db_change("delete from html_filter where html = ? and kind = ?"), [title, tool])
+                curs.execute(db_change('insert into html_filter (html, plus, plus_t, kind) values (?, ?, ?, ?)'), [title, link, icon, tool])
+                if tool == 'inter_wiki':
+                    curs.execute(db_change("delete from html_filter where html = ? and kind = 'inter_wiki_sub'"), [title])
+                    curs.execute(db_change('insert into html_filter (html, plus, plus_t, kind) values (?, "inter_wiki_type", ?, "inter_wiki_sub")'), [title, inter_type])
+                
+                acl_check(tool = 'owner_auth', memo = tool + ' edit')
+            elif tool == 'edit_filter':
+                day = flask.request.form.get('day', '0')
+                end = 'X' if day == '0' else day
+                if end != 'X':
+                    end = re.sub(r'[^0-9]', '', end)
+                    end = str(int(number_check(end)) * 24 * 60 * 60)
+
+                content = flask.request.form.get('content', 'test')
+                try:
+                    re.compile(content)
+                except:
+                    return re_error(conn, 23)
+                
+                curs.execute(db_change("delete from html_filter where html = ? and kind = 'regex_filter'"), [name])
+                curs.execute(db_change("insert into html_filter (html, plus, plus_t, kind) values (?, ?, ?, 'regex_filter')"), [name, content, end])
+                acl_check(tool = 'owner_auth', memo = 'edit_filter edit')
+            elif tool == 'document':
+                post_name = flask.request.form.get('name', '')
+                if post_name == '':
+                    return redirect(conn, '/filter/document')
+            
+                post_acl = flask.request.form.get('acl', '')
+                post_regex = flask.request.form.get('regex', '')
+                try:
+                    re.compile(post_regex)
+                except:
+                    return re_error(conn, 23)
+                
+                curs.execute(db_change('insert into html_filter (html, kind, plus, plus_t) values (?, "document", ?, ?)'), [post_name, post_regex, post_acl])
+                acl_check(tool = 'owner_auth', memo = 'document_filter edit')
+            else:
+                plus_d = ''
+                if tool == 'name_filter':
+                    try:
+                        re.compile(title)
+                    except:
+                        return re_error(conn, 23)
+
+                    acl_check(tool = 'owner_auth', memo = 'name_filter edit')
+                    type_d = 'name'
+                elif tool == 'file_filter':
+                    try:
+                        re.compile(title)
+                    except:
+                        return re_error(conn, 23)
+
+                    acl_check(tool = 'owner_auth', memo = 'file_filter edit')
+                    type_d = 'file'
+                elif tool == 'email_filter':
+                    acl_check(tool = 'owner_auth', memo = 'email_filter edit')
+                    type_d = 'email'
+                elif tool == 'image_license':
+                    acl_check(tool = 'owner_auth', memo = 'image_license edit')
+                    type_d = 'image_license'
+                elif tool == 'extension_filter':
+                    acl_check(tool = 'owner_auth', memo = 'extension_filter edit')
+                    type_d = 'extension'
+                    plus_d = flask.request.form.get('max_file_size', '')
+                    if plus_d != '':
+                        plus_d = number_check(plus_d)
+                elif tool == 'template':
+                    acl_check(tool = 'owner_auth', memo = 'template_document edit')
+                    type_d = 'template'
+                    plus_d = flask.request.form.get('exp', 'test')
+                else:
+                    acl_check(tool = 'owner_auth', memo = 'edit_top edit')
+                    type_d = 'edit_top'
+                    plus_d = flask.request.form.get('markup', 'test')
+
+                if name:
+                    curs.execute(db_change("delete from html_filter where html = ? and kind = ?"), [name, type_d])
+
+                curs.execute(db_change('insert into html_filter (html, kind, plus, plus_t) values (?, ?, ?, ?)'), [title, type_d, plus_d, ''])
+
+            return redirect(conn, '/filter/' + tool)
+        else:
+            get_sub = 0
+            stat = 'disabled' if acl_check('', 'owner_auth', '', '') == 1 else ''
+            name = name if name else ''
+
+            if tool in ('inter_wiki', 'outer_link'):
+                value = ['', '', '']
+                if name != '':
+                    curs.execute(db_change("select html, plus, plus_t from html_filter where html = ? and kind = ?"), [name, tool])
+                    exist = curs.fetchall()
+                    value = exist[0] if exist else value
+
+                select = ''
+                if tool == 'inter_wiki':
+                    ex = 'https://namu.wiki/w/'
+
+                    select = ['', '']
+                    curs.execute(db_change("select plus_t from html_filter where kind = 'inter_wiki_sub' and html = ?"), [name])
+                    db_data = curs.fetchall()
+                    if db_data and db_data[0][0] == 'under_bar':
+                        select = ['', 'selected']
+
+                    select = '''
+                        <hr class="main_hr">
+                        ''' + get_lang(conn, 'inter_wiki_space_change') + '''
+                        <hr class="main_hr">
+                        <select name="inter_type">
+                            <option ''' + select[0] + ''' value="url_encode">%20</option>
+                            <option ''' + select[1] + ''' value="under_bar">_</option>
+                        </select>
+                    '''
+                else:
+                    ex = 'youtube.com'
+
+                title = get_lang(conn, 'interwiki_add') if tool == 'inter_wiki' else get_lang(conn, 'outer_link_add')
+                form_data = '''
+                    ''' + get_lang(conn, 'name') + '''
+                    <hr class="main_hr">
+                    <input value="''' + html.escape(value[0]) + '''" type="text" name="title">
+                    <hr class="main_hr">
+                    ''' + get_lang(conn, 'link') + ''' (EX : ''' + ex + ''')
+                    <hr class="main_hr">
+                    <input value="''' + html.escape(value[1]) + '''" type="text" name="link">
+                    <hr class="main_hr">
+                    ''' + get_lang(conn, 'icon') + ''' (''' + ('HTML' if tool == 'inter_wiki' else get_lang(conn, 'html_or_link')) + ''') (''' + get_lang(conn, 'link') + ' - EX' + ''' : /image/Test.svg)
+                    <hr class="main_hr">
+                    <input value="''' + html.escape(value[2]) + '''" type="text" name="icon">
+                    ''' + select + '''
+                '''
+            elif tool == 'edit_filter':            
+                curs.execute(db_change("select plus, plus_t from html_filter where html = ? and kind = 'regex_filter'"), [name])
+                exist = curs.fetchall()
+                if exist:
+                    textarea = exist[0][0]
+                    time_data = '' if exist[0][1] == 'X' else exist[0][1]
+                    if time_data != '':
+                        time_data = re.sub(r'[^0-9]', '', time_data)
+                        time_data = str(int(int(number_check(time_data)) / (24 * 60 * 60)))
+                else:
+                    textarea = ''
+                    time_data = ''
+
+                title = get_lang(conn, 'edit_filter_add')
+                form_data = '''
+                    <hr class="main_hr">
+                    <input placeholder="''' + get_lang(conn, 'day') + '''" name="day" type="text" value="''' + html.escape(time_data) + '''">
+                    <hr class="main_hr">
+                    <input placeholder="''' + get_lang(conn, 'regex') + '''" name="content" value="''' + html.escape(textarea) + '''" type="text">
+                '''
+            elif tool == 'name_filter':
+                title = get_lang(conn, 'id_filter_add')
+                form_data = '' + \
+                    get_lang(conn, 'regex') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(name) + '" type="text" name="title">' + \
+                ''
+            elif tool == 'file_filter':
+                title = get_lang(conn, 'file_filter_add')
+                form_data = '' + \
+                    get_lang(conn, 'regex') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(name) + '" type="text" name="title">' + \
+                ''
+            elif tool == 'email_filter':
+                title = get_lang(conn, 'email_filter_add')
+                form_data = '' + \
+                    get_lang(conn, 'email') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(name) + '" type="text" name="title">' + \
+                ''
+            elif tool == 'image_license':
+                title = get_lang(conn, 'image_license_add')
+                form_data = '' + \
+                    get_lang(conn, 'license') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(name) + '" type="text" name="title">' + \
+                ''
+            elif tool == 'extension_filter':
+                title = get_lang(conn, 'extension_filter_add')
+                form_data = '' + \
+                    get_lang(conn, 'extension') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(name) + '" type="text" name="title">' + \
+                    '<hr class="main_hr">' + \
+                    get_lang(conn, 'max_file_size') + ' (MB) (' + get_lang(conn, 'default') + ' : ' + get_lang(conn, 'empty') + ')' + \
+                    '<hr class="main_hr">' + \
+                    '<input value="" type="text" name="max_file_size">' + \
+                ''
+            elif tool == 'document':
+                acl_list = get_acl_list()
+                
+                curs.execute(db_change("select plus, plus_t from html_filter where html = ? and kind = 'document'"), [name])
+                db_data = curs.fetchall()
+                acl_list = [['selected' if db_data and db_data[0][1] == for_a else '', for_a] for for_a in acl_list]
+
+                title = get_lang(conn, 'document_filter_add')
+                form_data = '''
+                    ''' + get_lang(conn, 'name') + '''
+                    <hr class="main_hr">
+                    <input value="''' + html.escape(name) + '''" type="text" name="name">
+                    <hr class="main_hr">
+                    ''' + get_lang(conn, 'regex') + '''
+                    <hr class="main_hr">
+                    <input value="''' + (html.escape(db_data[0][0]) if db_data else '') + '''" type="text" name="regex">
+                    <hr class="main_hr">
+                    <a href="/acl/Test#exp">''' + get_lang(conn, 'acl') + '''</a>
+                    <hr class="main_hr">
+                    <select name="acl">
+                        ''' + ''.join(['<option ' + for_a[0] + ' value=' + for_a[1] + '>' + ('normal' if for_a[1] == '' else for_a[1]) + '</option>' for for_a in acl_list]) + '''
+                    </select>
+                '''
+            elif tool == 'template':
+                title = get_lang(conn, 'template_document_add')
+
+                value = ''
+                if name:
+                    curs.execute(db_change("select plus from html_filter where html = ? and kind = 'template'"), [name])
+                    exist = curs.fetchall()
+                    value = exist[0][0] if exist else '' 
+
+                form_data = '' + \
+                    get_lang(conn, 'template') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(name) + '" type="text" name="title">' + \
+                    '<hr class="main_hr">' + \
+                    get_lang(conn, 'explanation') + \
+                    '<hr class="main_hr">' + \
+                    '<input value="' + html.escape(value) + '" type="text" name="exp">' + \
+                    '<hr class="main_hr">' + \
+                ''
+            else:
+                title = get_lang(conn, 'edit_tool_add')
+                
+                value = ''
+                if name:
+                    curs.execute(db_change("select plus from html_filter where html = ? and kind = 'edit_top'"), [name])
+                    exist = curs.fetchall()
+                    value = exist[0][0] if exist else ''    
+
+                form_data = '''
+                    ''' + get_lang(conn, 'title') + '''
+                    <hr class="main_hr">
+                    <input value="''' + html.escape(name) + '''" type="text" name="title">
+                    <hr class="main_hr">
+                    ''' + get_lang(conn, 'markup') + '''
+                    <hr class="main_hr">
+                    <input value="''' + html.escape(value) + '''" type="text" name="markup">
+                '''
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [title, wiki_set(conn), wiki_custom(conn), wiki_css([get_sub, 0])],
+                data =  '''
+                        <form method="post">
+                            ''' + form_data + '''
+                            <hr class="main_hr">
+                            <button ''' + stat + ''' type="submit">''' + get_lang(conn, 'add') + '''</button>
+                        </form>
+                        ''',
+                menu = [['filter/' + tool, get_lang(conn, 'return')]]
+            ))

+ 34 - 0
route/filter_all_delete.py

@@ -0,0 +1,34 @@
+from .tool.func import *
+
+def filter_all_delete(tool, name = 'Test'):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+        
+        if acl_check(tool = 'owner_auth', memo = 'del_' + tool) == 1:
+            return re_error(conn, 3)
+
+        if tool == 'inter_wiki':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'inter_wiki'"), [name])
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'inter_wiki_sub'"), [name])
+        elif tool == 'edit_filter':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'regex_filter'"), [name])
+        elif tool == 'name_filter':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'name'"), [name])
+        elif tool == 'file_filter':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'file'"), [name])
+        elif tool == 'email_filter':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'email'"), [name])
+        elif tool == 'image_license':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'image_license'"), [name])
+        elif tool == 'extension_filter':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'extension'"), [name])
+        elif tool == 'document':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'document'"), [name])
+        elif tool == 'outer_link':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'outer_link'"), [name])
+        elif tool == 'template':
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'template'"), [name])
+        else:
+            curs.execute(db_change("delete from html_filter where html = ? and kind = 'edit_top'"), [name])
+
+        return redirect(conn, '/filter/' + tool)

+ 121 - 0
route/give_admin_groups.py

@@ -0,0 +1,121 @@
+from .tool.func import *
+
+def give_admin_groups(name = 'test'):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        acl_name_list = [
+            [1, 'owner', get_lang(conn, 'owner_authority')],
+                [2, '', get_lang(conn, 'all_function_authority')],
+                [2, 'admin', get_lang(conn, 'admin_authority')],
+                    [3, 'ban', get_lang(conn, 'ban_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'toron', get_lang(conn, 'discussion_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'check', get_lang(conn, 'user_analyze_authority')],
+                        [4, 'view_user_watchlist', get_lang(conn, 'view_user_watchlist_authority')],
+                        [4, '', get_lang(conn, 'user_check_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'acl', get_lang(conn, 'document_acl_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'hidel', get_lang(conn, 'history_hide_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'give', get_lang(conn, 'authorization_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'bbs', get_lang(conn, 'bbs_management_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'vote_fix', get_lang(conn, 'vote_management_authority')],
+                        [4, '', get_lang(conn, 'admin_default_feature_authority')],
+                    [3, 'admin_default_feature', get_lang(conn, 'admin_default_feature_authority')],
+                        [4, 'doc_watch_list_view', get_lang(conn, 'doc_watch_list_view_authority')],
+                        [4, 'treat_as_admin', get_lang(conn, 'treat_as_admin_authority')],
+                        [4, 'view_hide_user_name', get_lang(conn, 'view_hide_user_name_authority')],
+                        [4, 'user_name_bold', get_lang(conn, 'user_name_bold_authority')],
+                        [4, 'multiple_upload', get_lang(conn, 'multiple_upload_authority')],
+                        [4, 'slow_edit_pass', get_lang(conn, 'slow_edit_pass_authority')],
+                        [4, 'edit_bottom_compulsion_pass', get_lang(conn, 'edit_bottom_compulsion_pass_authority')],
+                        [4, 'edit_filter_pass', get_lang(conn, 'edit_filter_pass_authority')],
+                        [4, '', get_lang(conn, 'user_authority')],
+            [1, 'user', get_lang(conn, 'user_authority')],
+                [2, 'captcha_pass', get_lang(conn, 'captcha_pass_authority')],
+                [2, 'ip', get_lang(conn, 'ip_authority')],
+                    [3, 'document', get_lang(conn, 'document_authority')],
+                        [4, 'edit', get_lang(conn, 'edit_authority')],
+                            [5, '', get_lang(conn, 'view_authority')],
+                        [4, 'edit_request', get_lang(conn, 'edit_request_authority')],
+                            [5, '', get_lang(conn, 'view_authority')],
+                        [4, 'move', get_lang(conn, 'move_authority')],
+                            [5, '', get_lang(conn, 'view_authority')],
+                        [4, 'new_make', get_lang(conn, 'new_make_authority')],
+                            [5, '', get_lang(conn, 'view_authority')],
+                        [4, 'delete', get_lang(conn, 'delete_authority')],
+                            [5, '', get_lang(conn, 'view_authority')],
+                        [4, 'view', get_lang(conn, 'view_authority')],
+                    [3, 'discuss', get_lang(conn, 'discuss_authority')],
+                        [4, 'discuss_make_new_thread', get_lang(conn, 'discuss_make_new_thread_authority')],
+                            [5, '', get_lang(conn, 'discuss_view_authority')],
+                        [4, 'discuss_view', get_lang(conn, 'discuss_view_authority')],
+                    [3, 'upload', get_lang(conn, 'upload_authority')],
+                    [3, 'vote', get_lang(conn, 'vote_authority')],
+                    [3, 'bbs_use', get_lang(conn, 'bbs_authority')],
+                        [4, 'bbs_edit', get_lang(conn, 'bbs_edit_authority')],
+                            [5, '', get_lang(conn, 'bbs_view_authority')],
+                        [4, 'bbs_comment', get_lang(conn, 'bbs_comment_authority')],
+                            [5, '', get_lang(conn, 'bbs_view_authority')],
+                        [4, 'bbs_view', get_lang(conn, 'bbs_view_authority')],
+                    [3, 'captcha_one_check_five_pass', get_lang(conn, 'captcha_one_check_five_pass_authority')],
+                    [3, 'edit_filter_view', get_lang(conn, 'edit_filter_view_authority')],
+                    [3, 'nothing', get_lang(conn, 'nothing_authority')]
+        ]
+
+        if html.escape(name) != name:
+            return re_error(conn, 48)
+
+        if flask.request.method == 'POST':
+            if acl_check(tool = 'owner_auth', memo = 'auth list add (' + name + ')') == 1:
+                return re_error(conn, 3)
+
+            curs.execute(db_change("delete from alist where name = ?"), [name])
+            for for_a in acl_name_list:
+                if flask.request.form.get(for_a[1], 0) != 0:
+                    curs.execute(db_change("insert into alist (name, acl) values (?, ?)"), [name, for_a[1]])
+
+            curs.execute(db_change("insert into alist (name, acl) values (?, 'nothing')"), [name])
+
+            return redirect(conn, '/auth/list/add/' + url_pas(name))
+        else:
+            state = 'disabled' if acl_check('', 'owner_auth', '', '') == 1 else ''
+
+            curs.execute(db_change('select acl from alist where name = ?'), [name])
+            acl_list = curs.fetchall()
+            acl_list = [for_b[0] for for_b in acl_list]
+
+            data = '<ul>'
+            for for_a in acl_name_list:                
+                checked = ''
+                if for_a[1] in acl_list:
+                    checked = 'checked'
+                    
+                choice = '<label><input type="checkbox" ' + state + ' name="' + for_a[1] + '" ' + checked + '> ' + for_a[2] + '</label>'
+                if for_a[1] == '':
+                    choice = for_a[2]
+
+                data += '' + \
+                    '<li class="opennamu_list_1" style="margin-left: ' + str(int(for_a[0]) * 20) + 'px;">' + \
+                        choice + \
+                    '</li>'
+                ''
+
+            data += '</ul>'
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [name, wiki_set(conn), wiki_custom(conn), wiki_css(['(' + get_lang(conn, 'admin_group') + ')', 0])],
+                data = '''
+                    <form method="post">
+                        ''' + data + '''
+                        <hr class="main_hr">
+                        <button ''' + state +  ''' type="submit">''' + get_lang(conn, 'save') + '''</button>
+                    </form>
+                ''',
+                menu = [['auth/list', get_lang(conn, 'return')]]
+            ))

+ 32 - 0
route/give_delete_admin_group.py

@@ -0,0 +1,32 @@
+from .tool.func import *
+
+def give_delete_admin_group_2(name = 'test'):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        if name in get_default_admin_group():
+            return redirect(conn, '/auth/list')
+
+        if acl_check('', 'owner_auth', '', '') == 1:
+            return re_error(conn, 3)
+
+        if flask.request.method == 'POST':
+            curs.execute(db_change("select name from user_set where name = 'acl' and data = ? limit 1"), [name])
+            if not curs.fetchall():
+                acl_check(tool = 'owner_auth', memo = 'auth list delete (' + name + ')')
+
+                curs.execute(db_change("delete from alist where name = ?"), [name])
+
+                return redirect(conn, '/auth/list')
+            else:
+                return re_error(conn, 47)
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, "delete_admin_group"), wiki_set(conn), wiki_custom(conn), wiki_css(['(' + name + ')', 0])],
+                data = '' + \
+                    '<form method="post">' + \
+                        '<button type="submit">' + get_lang(conn, 'delete') + '</button>' + \
+                    '</form>' + \
+                '',
+                menu = [['auth/list', get_lang(conn, 'return')]]
+            ))

+ 183 - 0
route/give_user_ban.py

@@ -0,0 +1,183 @@
+from .tool.func import *
+
+def give_user_ban(name = None, ban_type = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        ip = ip_check()
+        
+        if ban_check(ip = ip, tool = 'login')[0] == 1:
+            if ip_or_user(ip) == 1 or acl_check(tool = 'all_admin_auth', ip = ip) != 0:
+                return re_error(conn, 0)
+        else:
+            if acl_check(tool = 'ban_auth', ip = ip) == 1:
+                return re_error(conn, 3)
+
+        if flask.request.method == 'POST':
+            end = '0'
+
+            date_select = flask.request.form.get('date_type', 'days')
+            if date_select == 'date': 
+                time_limit = flask.request.form.get('date', '')
+                if re.search(r'^[0-9]{4}-[0-9]{2}-[0-9]{2}$', time_limit):
+                    end = time_limit + ' 00:00:00'
+            else:
+                time_limit = int(number_check(flask.request.form.get('date_days', '1')))
+
+                time = datetime.datetime.now()
+                plus = datetime.timedelta(days = time_limit)
+                end = (time + plus).strftime("%Y-%m-%d %H:%M:%S")
+            
+            regex_get = flask.request.form.get('do_ban_type', '')
+            why = flask.request.form.get('why', '')
+
+            release = ''
+            login = ''
+            
+            ban_option = flask.request.form.get('ban_option', '')
+            if ban_option == 'login_able_and_regsiter_disable':
+                login = 'O'
+            elif ban_option == 'login_able':
+                login = 'L'
+            elif ban_option == 'edit_request_able':
+                login = 'E'
+            elif ban_option == 'completely_ban':
+                login = 'A'
+            elif ban_option == 'dont_come_this_site':
+                login = 'D'
+            elif ban_option == 'release':
+                release = '1'
+
+            if ban_type == 'multiple':
+                all_user = re.findall(r'([^\n]+)\n', flask.request.form.get('name', 'test').replace('\r', '') + '\n')
+            else:
+                if name:
+                    all_user = [name]
+                else:
+                    all_user = [flask.request.form.get('name', 'test')]
+
+            for name in all_user:
+                if regex_get == 'regex':
+                    type_d = 'regex'
+
+                    try:
+                        re.compile(name)
+                    except:
+                        return re_error(conn, 23)
+                elif regex_get == 'cidr':
+                    type_d = 'cidr'
+
+                    try:
+                        ipaddress.IPv4Network(name, False)
+                    except:
+                        try:
+                            ipaddress.IPv6Network(name, False)
+                        except:
+                            return re_error(conn, 45)
+                elif regex_get == 'private':
+                    type_d = 'private'
+
+                    if acl_check(tool = 'owner_auth', ip = ip) == 1:
+                        return re_error(conn, 0)
+                else:
+                    type_d = None
+
+                if regex_get != 'private':
+                    if name == ip:
+                        if acl_check(tool = 'all_admin_auth', memo = 'ban (' + name + ')') == 1:
+                            return re_error(conn, 3)
+                    else:
+                        if acl_check(tool = 'ban_auth', memo = 'ban (' + name + ')') == 1:
+                            return re_error(conn, 3)
+
+                ban_insert(conn, 
+                    name,
+                    end,
+                    why,
+                    login,
+                    ip_check(),
+                    type_d,
+                    1 if release != '' else 0
+                )
+
+            return redirect(conn, '/recent_block')
+        else:
+            if ban_type == 'multiple':
+                main_name = get_lang(conn, 'multiple_ban')
+                n_name = '<textarea class="opennamu_textarea_500" placeholder="' + get_lang(conn, 'name_or_ip_or_regex_or_cidr_multiple') + '" name="name"></textarea><hr class="main_hr">'
+            else:
+                main_name = get_lang(conn, 'ban')
+                n_name = '<input placeholder="' + get_lang(conn, 'name_or_ip_or_regex_or_cidr') + '" value="' + (name if name else '') + '" name="name"><hr class="main_hr">'
+
+            now = 0
+            
+            if ban_type == 'multiple':
+                action = 'action="/auth/ban/multiple"'
+            else:
+                action = 'action="/auth/ban"'
+                
+            date_value = ''
+            info_data = ''
+            if name:
+                curs.execute(db_change("select end from rb where block = ? and ongoing = '1'"), [name])
+                db_data = curs.fetchall()
+                if db_data and db_data[0][0] != '':
+                    date_value = db_data[0][0].split()[0]
+
+                if ban_type == '':
+                    info_data = '<div id="opennamu_get_user_info">' + html.escape(name) + '</div>'
+
+            owner_option = ''
+            if acl_check(tool = 'owner_auth', ip = ip) != 1:
+                owner_option = '<option value="private" ' + ('selected' if ban_type == 'private' else '') + '>' + get_lang(conn, 'private') + '</option>'
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [main_name, wiki_set(conn), wiki_custom(conn), wiki_css([now, 0])],
+                data = info_data + '''
+                    <form method="post" ''' + action + '''>
+                        <h2>''' + get_lang(conn, 'method') + '''</h2>
+                        ''' + n_name + '''
+        
+                        <select name="do_ban_type">
+                            <option value="normal">''' + get_lang(conn, 'normal') + '''</option>
+                            <option value="regex" ''' + ('selected' if ban_type == 'regex' else '') + '>' + get_lang(conn, 'regex') + '''</option>
+                            <option value="cidr" ''' + ('selected' if ban_type == 'cidr' else '') + '>' + get_lang(conn, 'cidr') + '''</option>
+                            ''' + owner_option + '''
+                        </select>
+                        <hr class="main_hr">
+        
+                        <select name="ban_option">
+                            <option value="">''' + get_lang(conn, 'default') + '''</option>
+                            <option value="login_able">''' + get_lang(conn, 'login_able') + '''</option>
+                            <option value="login_able_and_regsiter_disable">''' + get_lang(conn, 'login_able_and_regsiter_disable') + '''</option>
+                            <option value="edit_request_able">''' + get_lang(conn, 'edit_request_able') + '''</option>
+                            <option value="completely_ban">''' + get_lang(conn, 'completely_ban') + '''</option>
+                            <option value="dont_come_this_site">''' + get_lang(conn, 'dont_come_this_site') + '''</option>
+                            <option value="release">''' + get_lang(conn, 'release') + '''</option>
+                        </select>
+        
+                        <h2>''' + get_lang(conn, 'date') + '''</h2>
+                        <select name="date_type">
+                            <option value="date">''' + get_lang(conn, 'date') + '''</option>
+                            <option value="days">''' + get_lang(conn, 'day') + '''</option>
+                        </select>
+                        <hr class="main_hr">
+        
+                        <span>''' + get_lang(conn, 'day') + '''</span>
+                        <hr class="main_hr">
+                        <input name="date_days">
+                        <hr class="main_hr">
+
+                        <span>''' + get_lang(conn, 'date') + '''</span>
+                        <hr class="main_hr">
+                        <input type="date" value="''' + date_value + '''" name="date" pattern="\\d{4}-\\d{2}-\\d{2}">
+        
+                        <h2>''' + get_lang(conn, 'other') + '''</h2>
+                        <input placeholder="''' + get_lang(conn, 'why') + '''" name="why" type="text">
+                        <hr class="main_hr">
+        
+                        <button type="submit">''' + get_lang(conn, 'save') + '''</button>
+                    </form>
+                ''',
+                menu = [['manager', get_lang(conn, 'return')]]
+            ))   

+ 74 - 0
route/give_user_fix.py

@@ -0,0 +1,74 @@
+from .tool.func import *
+
+def give_user_fix(user_name = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        curs.execute(db_change("select data from user_set where id = ? and name = 'pw'"), [user_name])
+        if not curs.fetchall():
+            return re_error(conn, 2)
+
+        if acl_check('', 'owner_auth', '', '') == 1:
+            return re_error(conn, 3)
+
+        if flask.request.method == 'POST':
+            select = flask.request.form.get('select', '')
+
+            acl_check(tool = 'owner_auth', memo = 'user_fix (' + user_name + ') (' + select + ')')
+            if select == 'password_change':
+                password = flask.request.form.get('new_password', '')
+                check_password = flask.request.form.get('password_check', '')
+
+                if password == check_password:
+                    hashed = pw_encode(conn, password)
+                    curs.execute(db_change("update user_set set data = ? where id = ? and name = 'pw'"), [
+                        hashed,
+                        user_name
+                    ])
+                else:
+                    return re_error(conn, 20)
+            elif select == '2fa_password_change':
+                password = flask.request.form.get('new_password', '')
+                check_password = flask.request.form.get('password_check', '')
+
+                if password == check_password:
+                    hashed = pw_encode(conn, password)
+                    curs.execute(db_change('select data from user_set where name = "2fa_pw" and id = ?'), [user_name])
+                    if curs.fetchall():
+                        curs.execute(db_change("update user_set set data = ? where name = '2fa_pw' and id = ?"), [hashed, user_name])
+                    else:
+                        curs.execute(db_change("insert into user_set (name, id, data) values ('2fa_pw', ?, ?)"), [user_name, hashed])
+                else:
+                    return re_error(conn, 20)
+            elif select == '2fa_off':
+                curs.execute(db_change('select data from user_set where name = "2fa" and id = ?'), [user_name])
+                if curs.fetchall():
+                    curs.execute(db_change("update user_set set data = '' where name = '2fa' and id = ?"), [user_name])
+
+            return redirect(conn, '/user/' + url_pas(user_name))
+        else:
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'user_fix'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+                data = '''
+                    <form method="post">
+                        <div id="opennamu_get_user_info">''' + html.escape(user_name) + '''</div>
+                        <hr class="main_hr">
+                        <a href="/change/user_name/''' + url_pas(user_name) + '''">(''' + get_lang(conn, 'change_user_name') + ''')</a>
+                        <hr class="main_hr">
+                        <select name="select">
+                            <option value="password_change">''' + get_lang(conn, 'password_change') + '''</option>
+                            <option value="2fa_password_change">''' + get_lang(conn, '2fa_password_change') + '''</option>
+                            <option value="2fa_off">''' + get_lang(conn, '2fa_off') + '''</option>
+                        </select>
+                        <hr class="main_hr">
+                        ''' + get_lang(conn, 'password_change') + ''' | ''' + get_lang(conn, '2fa_password_change') + '''
+                        <hr class="main_hr">
+                        <input placeholder="''' + get_lang(conn, 'new_password') + '''" name="new_password" type="password">
+                        <hr class="main_hr">
+                        <input placeholder="''' + get_lang(conn, 'password_confirm') + '''" name="password_check" type="password">
+                        <hr class="main_hr">
+                        <button type="submit">''' + get_lang(conn, 'go') + '''</button>
+                    </form>
+                ''',
+                menu = [['manager', get_lang(conn, 'return')]]
+            ))

+ 9 - 0
route/go_api_bbs.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+async def api_bbs(bbs_num = "", page = 1):
+    other_set = {}
+    other_set["bbs_num"] = str(bbs_num)
+    other_set["page"] = str(page)
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 4 - 0
route/go_api_bbs_list.py

@@ -0,0 +1,4 @@
+from .tool.func import *
+
+async def api_bbs_list():
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name)), status = 200, mimetype = 'application/json')

+ 9 - 0
route/go_api_bbs_w.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+def api_bbs_w(sub_code = '', legacy = 'on'):
+    other_set = {}
+    other_set['ip'] = ip_check()
+    other_set["legacy"] = legacy
+    other_set['sub_code'] = sub_code
+
+    return flask.Response(response = python_to_golang_sync(sys._getframe().f_code.co_name, other_set), status = 200, mimetype = 'application/json')

+ 10 - 0
route/go_api_bbs_w_comment.py

@@ -0,0 +1,10 @@
+from .tool.func import *
+
+async def api_bbs_w_comment(sub_code = '', tool = "", legacy = 'on'):
+    other_set = {}
+    other_set["sub_code"] = sub_code
+    other_set["tool"] = tool
+    other_set["legacy"] = legacy
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 10 - 0
route/go_api_bbs_w_comment_one.py

@@ -0,0 +1,10 @@
+from .tool.func import *
+
+async def api_bbs_w_comment_one(sub_code = '', legacy = 'on', tool = ''):
+    other_set = {}
+    other_set["sub_code"] = sub_code
+    other_set["legacy"] = legacy
+    other_set["tool"] = tool
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 14 - 0
route/go_api_bbs_w_set.py

@@ -0,0 +1,14 @@
+from .tool.func import *
+
+async def api_bbs_w_set(name = 'Test', bbs_num = 1):
+    other_set = {}
+    other_set["set_name"] = name
+    other_set["set_id"] = str(bbs_num)
+    other_set["ip"] = ip_check()
+
+    func_name = sys._getframe().f_code.co_name
+    if flask.request.method == 'PUT':
+        func_name += '_put'
+        other_set['data'] = flask.request.form.get('data', 'Test')
+    
+    return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')

+ 12 - 0
route/go_api_bbs_w_tabom.py

@@ -0,0 +1,12 @@
+from .tool.func import *
+
+async def api_bbs_w_tabom(sub_code = ''):
+    other_set = {}
+    other_set["sub_code"] = sub_code
+    other_set["ip"] = ip_check()
+
+    func_name = sys._getframe().f_code.co_name
+    if flask.request.method == 'POST':
+        func_name += '_post'
+
+    return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')

+ 7 - 0
route/go_api_func_auth.py

@@ -0,0 +1,7 @@
+from .tool.func import *
+
+async def api_func_auth(user_name = ''):
+    other_set = {}
+    other_set["ip"] = ip_check() if user_name == '' else user_name
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 24 - 0
route/go_api_func_ip.py

@@ -0,0 +1,24 @@
+from .tool.func import *
+
+async def api_func_ip(data = 'Test'):
+    other_set = {}
+    other_set["ip"] = ip_check()
+
+    func_name = sys._getframe().f_code.co_name
+    if flask.request.method == 'POST':
+        func_name += '_post'
+
+        for_a = 1
+        while 1:
+            data = flask.request.form.get('data_' + str(for_a), '')
+            
+            if data == '':
+                break
+            else:
+                other_set['data_' + str(for_a)] = data
+
+            for_a += 1
+    else:
+        other_set["data"] = data
+
+    return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')

+ 9 - 0
route/go_api_func_ip_menu.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+async def api_func_ip_menu(ip = "Test", option = ""):
+    other_set = {}
+    other_set["ip"] = ip
+    other_set["my_ip"] = ip_check()
+    other_set["option"] = option
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 13 - 0
route/go_api_func_language.py

@@ -0,0 +1,13 @@
+from .tool.func import *
+
+async def api_func_language(legacy = 'on', data = 'Test'):
+    other_set = {}
+    if flask.request.method == 'POST':
+        other_set["data"] = flask.request.form.get('data', '')
+        other_set["data"] = other_set["data"].split(' ')
+    else:
+        other_set["data"] = [data]
+
+    other_set["legacy"] = legacy
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 11 - 0
route/go_api_func_llm.py

@@ -0,0 +1,11 @@
+from .tool.func import *
+
+async def api_func_llm():
+    if flask.request.method == 'POST':
+        other_set = {}
+        other_set["prompt"] = flask.request.form.get('prompt', '')
+        other_set["ip"] = ip_check()
+
+        return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')
+    else:
+        return flask.jsonify({})

+ 9 - 0
route/go_api_func_search.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+async def api_func_search(name = 'Test', search_type = 'title', num = 1):
+    other_set = {}
+    other_set["name"] = name
+    other_set["search_type"] = search_type
+    other_set["num"] = str(num)
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 7 - 0
route/go_api_func_sha224.py

@@ -0,0 +1,7 @@
+from .tool.func import *
+
+async def api_func_sha224(data = 'Test'):
+    other_set = {}
+    other_set["data"] = data
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 16 - 0
route/go_api_give_auth.py

@@ -0,0 +1,16 @@
+from .tool.func import *
+
+async def api_give_auth():
+    if flask.request.method == 'PATCH':
+        func_name = sys._getframe().f_code.co_name
+        func_name += '_patch'
+
+        other_set = {}
+        other_set["ip"] = ip_check()
+        other_set["user_name"] = flask.request.form.get('user_name', '')
+        other_set['auth'] = flask.request.form.get('auth', '')
+        other_set['change_auth'] = flask.request.form.get('change_auth', '')
+
+        return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')
+    else:
+        return flask.jsonify({}) 

+ 7 - 0
route/go_api_list_acl.py

@@ -0,0 +1,7 @@
+from .tool.func import *
+
+async def api_list_acl(data_type = ''):
+    other_set = {}
+    other_set = data_type
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 6 - 0
route/go_api_list_auth.py

@@ -0,0 +1,6 @@
+from .tool.func import *
+
+async def api_list_auth():
+    other_set = {}
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 10 - 0
route/go_api_list_history.py

@@ -0,0 +1,10 @@
+from .tool.func import *
+
+async def api_list_history(num = 1, set_type = 'normal', doc_name = 'Test'):
+    other_set = {}
+    other_set["num"] = str(num)
+    other_set["doc_name"] = doc_name
+    other_set["set_type"] = set_type
+    other_set["ip"] = ip_check()
+    
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 6 - 0
route/go_api_list_markup.py

@@ -0,0 +1,6 @@
+from .tool.func import *
+
+async def api_list_markup():
+    other_set = {}
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 8 - 0
route/go_api_list_old_page.py

@@ -0,0 +1,8 @@
+from .tool.func import *
+
+async def api_list_old_page(num = 1, set_type = 'old'):
+    other_set = {}
+    other_set["num"] = str(num)
+    other_set["set_type"] = set_type
+    
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 11 - 0
route/go_api_list_recent_block.py

@@ -0,0 +1,11 @@
+from .tool.func import *
+
+async def api_list_recent_block(num = 1, set_type = 'all', user_name = 'Test', why = ''):
+    other_set = {}
+    other_set["num"] = str(num)
+    other_set["set_type"] = set_type
+    other_set["user_name"] = user_name
+    other_set["why"] = why
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 17 - 0
route/go_api_list_recent_change.py

@@ -0,0 +1,17 @@
+from .tool.func import *
+
+async def api_list_recent_change(num = 1, set_type = 'normal', limit = 10, legacy = 'on'):
+    other_set = {}
+    other_set["num"] = str(num)
+    other_set["limit"] = str(limit)
+    other_set["set_type"] = set_type
+    other_set["legacy"] = legacy
+    other_set["ip"] = ip_check()
+
+    response = flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')
+    
+    response.headers.add("Access-Control-Allow-Origin", "*")
+    response.headers.add('Access-Control-Allow-Headers', "Content-Type")
+    response.headers.add('Access-Control-Allow-Methods', "GET")
+
+    return response

+ 17 - 0
route/go_api_list_recent_discuss.py

@@ -0,0 +1,17 @@
+from .tool.func import *
+
+async def api_list_recent_discuss(num = 1, set_type = 'normal', limit = 10, legacy = 'on'):
+    other_set = {}
+    other_set["num"] = str(num)
+    other_set["limit"] = str(limit)
+    other_set["set_type"] = set_type
+    other_set["legacy"] = legacy
+    other_set["ip"] = ip_check()
+
+    response = flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')
+    
+    response.headers.add("Access-Control-Allow-Origin", "*")
+    response.headers.add('Access-Control-Allow-Headers', "Content-Type")
+    response.headers.add('Access-Control-Allow-Methods', "GET")
+
+    return response

+ 10 - 0
route/go_api_list_recent_edit_request.py

@@ -0,0 +1,10 @@
+from .tool.func import *
+
+async def api_list_recent_edit_request(num = 1, set_type = 'normal', limit = 50):
+    other_set = {}
+    other_set["num"] = str(num)
+    other_set["limit"] = str(limit)
+    other_set["set_type"] = set_type
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 7 - 0
route/go_api_list_title_index.py

@@ -0,0 +1,7 @@
+from .tool.func import *
+
+async def api_list_title_index(num = 1):
+    other_set = {}
+    other_set["num"] = str(num)
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 13 - 0
route/go_api_setting.py

@@ -0,0 +1,13 @@
+from .tool.func import *
+
+async def api_setting(name = 'Test'):
+    other_set = {}
+    other_set["set_name"] = name
+    other_set["ip"] = ip_check()
+
+    func_name = sys._getframe().f_code.co_name
+    if flask.request.method == 'PUT':
+        func_name += '_put'
+        other_set['data'] = flask.request.form.get('data', 'Test')
+    
+    return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')

+ 137 - 0
route/go_api_topic.py

@@ -0,0 +1,137 @@
+from .tool.func import *
+
+def api_topic_thread_make(user_id, date, data, code, color = '', blind = '', add_style = '', admin_check = 1, topic_num = ''):
+    if blind == 'O':
+        if data == '':
+            color_b = 'opennamu_comment_blind'
+        else:
+            color_b = 'opennamu_comment_blind_admin'
+
+        class_b = 'opennamu_comment_blind_js opennamu_list_hidden'
+    else:
+        color_b = 'opennamu_comment_blind_not'
+        class_b = ''
+
+    admin_check_box = ''
+    if admin_check == 1 and topic_num != '':
+        admin_check_box = '<input type="checkbox" class="opennamu_blind_button" id="opennamu_blind_' + topic_num + '_' + code + '">'
+
+    return '''
+        <span class="''' + class_b + '''">
+            <table class="opennamu_comment" style="''' + add_style + '''">
+                <tr>
+                    <td class="opennamu_comment_color_''' + color + '''">
+                        ''' + admin_check_box + '''
+                        <a href="#thread_shortcut" id="''' + code + '''">#''' + code + '''</a>
+                        ''' + user_id + '''
+                        <span style="float: right;">''' + date + '''</span>
+                    </td>
+                </tr>
+                <tr>
+                    <td class="''' + color_b + ''' opennamu_comment_data_main" id="thread_''' + code + '''">
+                        ''' + data + '''
+                        <div id="opennamu_topic_req_''' + code + '''"></div>
+                    </td>
+                </tr>
+            </table>
+            <hr class="main_hr">
+        </span>
+    '''
+
+def api_topic_thread_pre_render(conn, data, num, ip, topic_num = '', name = '', sub = '', do_type = 'thread'):
+    curs = conn.cursor()
+
+    # 이거 좀 엉성해서 언젠간 손 보고 싶음
+    call_thread_regex = r"( |\n|^)(?:#([0-9]+)(?:-([0-9]+))?)( |\n|$)"
+    call_thread_count = len(re.findall(call_thread_regex, data)) * 3
+    while 1:
+        rd_data = re.search(call_thread_regex, data)
+        if call_thread_count < 0:
+            break
+        elif not rd_data:
+            break
+        else:
+            rd_data = rd_data.groups()
+
+            view_data = rd_data[1]
+            send_topic_num = topic_num
+            if rd_data[2]:
+                view_data += '-' + rd_data[2]
+                if do_type == 'thread':
+                    send_topic_num = rd_data[2]
+                else:
+                    set_id = topic_num.split('-')
+
+                    send_topic_num = set_id[0] + '-' + rd_data[2]
+                    view_data += '-' + set_id[0]
+
+            if do_type == 'thread':
+                curs.execute(db_change("select ip from topic where code = ? and id = ?"), [send_topic_num, rd_data[1]])
+            else:
+                if rd_data[1] == '0':
+                    set_id = send_topic_num.split('-')
+                    set_id = ['', ''] if len(set_id) < 2 else set_id
+
+                    curs.execute(db_change('select set_data from bbs_data where set_name = "user_id" and set_id = ? and set_code = ?'), [set_id[0], set_id[1]])
+                else:
+                    curs.execute(db_change('select set_data from bbs_data where set_name = "comment_user_id" and set_id = ? and set_code = ?'), [send_topic_num, rd_data[1]])
+
+            ip_data = curs.fetchall()
+            if ip_data and ip_or_user(ip_data[0][0]) == 0:
+                if do_type == 'thread':
+                    add_alarm(ip_data[0][0], ip, '<a href="/thread/' + topic_num + '#' + num + '">' + html.escape(name) + ' - ' + html.escape(sub) + '#' + num + '</a>')
+                else:
+                    set_id = topic_num.split('-')
+                    set_id = ['', ''] if len(set_id) < 2 else set_id
+
+                    add_alarm(ip_data[0][0], ip, 'BBS <a href="/bbs/w/' + set_id[0] + '/' + set_id[1] + '#' + num + '">' + html.escape(name) + ' - ' + html.escape(sub) + '#' + num + '</a>')
+
+            data = re.sub(call_thread_regex, rd_data[0] + '<topic_a_' + do_type + '>#' + view_data + '</topic_a_' + do_type + '>' + rd_data[3], data, 1)
+
+        call_thread_count -= 1
+
+    call_user_regex = r"( |\n|^)(?:@([^ \n]+))( |\n|$)"
+    call_user_count = len(re.findall(call_user_regex, data)) * 3
+    while 1:
+        rd_data = re.search(call_user_regex, data)
+        if call_user_count < 0:
+            break
+        elif not rd_data:
+            break
+        else:
+            rd_data = rd_data.groups()
+
+            curs.execute(db_change("select ip from history where ip = ? limit 1"), [rd_data[1]])
+            ip_data = curs.fetchall()
+            if not ip_data:
+                curs.execute(db_change("select ip from topic where ip = ? limit 1"), [rd_data[1]])
+                ip_data = curs.fetchall()
+
+            if ip_data and ip_or_user(ip_data[0][0]) == 0:
+                if do_type == 'thread':
+                    add_alarm(ip_data[0][0], ip, '<a href="/thread/' + topic_num + '#' + num + '">' + html.escape(name) + ' - ' + html.escape(sub) + '#' + num + '</a>')
+                else:
+                    set_id = topic_num.split('-')
+                    add_alarm(ip_data[0][0], ip, 'BBS <a href="/bbs/w/' + set_id[0] + '/' + set_id[1] + '#' + num + '">' + html.escape(name) + ' - ' + html.escape(sub) + '#' + num + '</a>')
+
+            data = re.sub(call_user_regex, rd_data[0] + '<topic_call>@' + rd_data[1] + '</topic_call>' + rd_data[2], data, 1)
+
+        call_user_count -= 1
+
+    return data
+
+async def api_topic(topic_num = 1, tool = 'normal', s_num = '', e_num = ''):
+    with get_db_connect() as conn:
+        topic_num = str(topic_num)
+
+        if acl_check('', 'topic_view', topic_num) != 1:
+            other_set = {}
+            other_set["topic_num"] = topic_num
+            other_set["tool"] = tool
+            other_set["s_num"] = str(s_num)
+            other_set["e_num"] = str(e_num)
+            other_set["ip"] = ip_check()
+
+            return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')
+        else:
+            return flask.jsonify({})

+ 9 - 0
route/go_api_topic_list.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+async def api_topic_list(name = 'Test', set_type = 'normal', num = 1):
+    other_set = {}
+    other_set["name"] = str(name)
+    other_set["set_type"] = set_type
+    other_set["num"] = str(num)
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 11 - 0
route/go_api_user_rankup.py

@@ -0,0 +1,11 @@
+from .tool.func import *
+
+async def api_user_rankup():
+    other_set = {}
+    other_set["ip"] = ip_check()
+    
+    func_name = sys._getframe().f_code.co_name
+    if flask.request.method == 'PATCH':
+        func_name += '_patch'
+
+    return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')

+ 15 - 0
route/go_api_user_setting_editor.py

@@ -0,0 +1,15 @@
+from .tool.func import *
+
+async def api_user_setting_editor():
+    other_set = {}
+    other_set["ip"] = ip_check()
+    
+    func_name = sys._getframe().f_code.co_name
+    if flask.request.method == 'POST':
+        func_name += '_post'
+        other_set['data'] = flask.request.form.get('data', 'Test')
+    elif flask.request.method == 'DELETE':
+        func_name += '_delete'
+        other_set['data'] = flask.request.form.get('data', 'Test')
+
+    return flask.Response(response = (await python_to_golang(func_name, other_set)), status = 200, mimetype = 'application/json')

+ 7 - 0
route/go_api_w_page_view.py

@@ -0,0 +1,7 @@
+from .tool.func import *
+
+async def api_w_page_view(name = 'Test'):
+    other_set = {}
+    other_set["doc_name"] = name
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 4 - 0
route/go_api_w_random.py

@@ -0,0 +1,4 @@
+from .tool.func import *
+
+async def api_w_random():
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name)), status = 200, mimetype = 'application/json')

+ 10 - 0
route/go_api_w_raw.py

@@ -0,0 +1,10 @@
+from .tool.func import *
+
+async def api_w_raw(name = 'Test', rev = '', exist_check = ''):
+    other_set = {}
+    other_set["name"] = name
+    other_set["rev"] = str(rev)
+    other_set["exist_check"] = exist_check
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 93 - 0
route/go_api_w_render.py

@@ -0,0 +1,93 @@
+from .tool.func import *
+
+class api_w_render_include:
+    def __init__(self, data_option):
+        self.include_change_list = data_option
+
+    def __call__(self, match):
+        match_org = match.group(0)
+        match = match.groups()
+
+        if len(match) < 3:
+            match = list(match) + ['']
+
+        if match[2] == '\\':
+            return match_org
+        else:
+            slash_add = ''
+            if match[0]:
+                if len(match[0]) % 2 == 1:
+                    slash_add = '\\' * (len(match[0]) - 1)
+                else:
+                    slash_add = match[0]
+
+            if match[1] in self.include_change_list:
+                return slash_add + self.include_change_list[match[1]]
+            else:
+                return slash_add + match[2]
+
+async def api_w_render(name = '', tool = ''):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        if flask.request.method == 'POST':
+            name = flask.request.form.get('name', '')
+            data_org = flask.request.form.get('data', '')
+            data_option = flask.request.form.get('option', '')
+
+            markup = ''
+            if tool in ('', 'from', 'include'):
+                curs.execute(db_change("select set_data from data_set where doc_name = ? and set_name = 'document_markup'"), [name])
+                db_data = curs.fetchall()
+                if db_data and db_data[0][0] != '' and db_data[0][0] != 'normal':
+                    markup = db_data[0][0]
+
+                if markup == '':
+                    curs.execute(db_change('select data from other where name = "markup"'))
+                    db_data = curs.fetchall()
+                    markup = db_data[0][0] if db_data else 'namumark'
+
+            data_type = ''
+            if tool == '':
+                data_type = 'api_view'
+            elif tool == 'from':
+                data_type = 'api_from'
+            elif tool == 'include':
+                data_type = 'api_include'
+            elif tool == 'backlink':
+                data_type = 'backlink'
+            else:
+                data_type = 'api_thread'
+
+            if markup in ('', 'namumark', 'namumark_beta') and data_option != '':
+                data_option = orjson.loads(data_option)
+
+                data_option_func = api_w_render_include(data_option)
+
+                # parameter replace
+                data_org = re.sub(r'(\\+)?@([ㄱ-힣a-zA-Z0-9_]+)=((?:\\@|[^@\n])+)@', data_option_func, data_org)
+                data_org = re.sub(r'(\\+)?@([ㄱ-힣a-zA-Z0-9_]+)@', data_option_func, data_org)
+
+                # remove end br
+                data_org = re.sub('^\n+', '', data_org)
+
+            if markup in ('', 'namumark'):
+                data_pas = render_set(conn, 
+                    doc_name = name, 
+                    doc_data = data_org, 
+                    data_type = data_type
+                )
+
+                return flask.jsonify({
+                    "data" : data_pas[0], 
+                    "js_data" : data_pas[1]
+                })
+            else:
+                other_set = {}
+                other_set["doc_name"] = name
+                other_set["render_type"] = data_type
+                other_set["data"] = data_org
+
+                return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')
+        else:
+            return flask.jsonify({})

+ 8 - 0
route/go_api_w_set_reset.py

@@ -0,0 +1,8 @@
+from .tool.func import *
+
+async def api_w_set_reset(name = 'Test'):
+    other_set = {}
+    other_set["name"] = name
+    other_set["ip"] = ip_check()
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 10 - 0
route/go_api_w_watch_list.py

@@ -0,0 +1,10 @@
+from .tool.func import *
+
+async def api_w_watch_list(name = 'Test', do_type = 'watch_list', num = 1):
+    other_set = {}
+    other_set["name"] = name
+    other_set["do_type"] = do_type
+    other_set["ip"] = ip_check()
+    other_set["num"] = str(num)
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 9 - 0
route/go_api_w_xref.py

@@ -0,0 +1,9 @@
+from .tool.func import *
+
+async def api_w_xref(name = 'Test', page = 1, xref_type = '1'):
+    other_set = {}
+    other_set["name"] = name
+    other_set["page"] = str(page)
+    other_set["do_type"] = xref_type
+
+    return flask.Response(response = (await python_to_golang(sys._getframe().f_code.co_name, other_set)), status = 200, mimetype = 'application/json')

+ 34 - 0
route/go_main_func_easter_egg.py

@@ -0,0 +1,34 @@
+from .tool.func import *
+
+def main_func_easter_egg():
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        ip = ip_check()
+        if ip_or_user(ip) == 0:
+            curs.execute(db_change('select name from user_set where id = ? and name = ?'), [ip, 'get_🥚'])
+            if not curs.fetchall():
+                curs.execute(db_change('insert into user_set (name, id, data) values ("get_🥚", ?, "Y")'), [ip])
+    
+        if platform.system() == 'Linux':
+            if platform.machine() in ["AMD64", "x86_64"]:
+                data = subprocess.Popen([os.path.join(".", "route_go", "bin", "main.amd64.bin"), sys._getframe().f_code.co_name], stdout = subprocess.PIPE).communicate()[0]
+            else:
+                data = subprocess.Popen([os.path.join(".", "route_go", "bin", "main.arm64.bin"), sys._getframe().f_code.co_name], stdout = subprocess.PIPE).communicate()[0]
+
+            data = data.decode('utf8')
+        elif platform.system() == 'Windows':
+            if platform.machine() in ["AMD64", "x86_64"]:
+                data = subprocess.Popen([os.path.join(".", "route_go", "bin", "main.amd64.exe"), sys._getframe().f_code.co_name], stdout = subprocess.PIPE).communicate()[0]
+            else:
+                data = subprocess.Popen([os.path.join(".", "route_go", "bin", "main.arm64.exe"), sys._getframe().f_code.co_name], stdout = subprocess.PIPE).communicate()[0]
+
+            data = data.decode('utf8')
+        else:
+            data = ''
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = ['Easter Egg', wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = data,
+            menu = 0
+        ))

+ 39 - 0
route/list_acl.py

@@ -0,0 +1,39 @@
+from .tool.func import *
+
+def list_acl(arg_num = 1):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        sql_num = (arg_num * 50 - 50) if arg_num * 50 > 0 else 0
+
+        div = '<ul>'
+
+        curs.execute(db_change(
+            "select distinct title, data, type from acl where data != '' and not title like 'user:%' order by title desc limit ?, 50"
+        ), [sql_num])
+        list_data = curs.fetchall()
+        for data in list_data:
+            curs.execute(db_change("select time from re_admin where what like ? order by time desc limit 1"), ['acl (' + data[0] + ')%'])
+            time_data = curs.fetchall()
+            time_data = (time_data[0][0] + ' | ') if time_data else ''
+
+            curs.execute(db_change("select data from acl where title = ? and type = 'why'"), [data[0]])
+            why_data = curs.fetchall()
+            why_data = (' | ' + why_data[0][0]) if why_data and why_data[0][0] != '' else ''
+
+            div += '' + \
+                '<li>' + \
+                    time_data + \
+                    '<a href="/acl/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a>' + \
+                    why_data + \
+                '</li>' + \
+            ''
+
+        div += '</ul>'
+        div += next_fix(conn, '/list/document/acl/', arg_num, list_data)
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'acl_document_list'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = div,
+            menu = [['other', get_lang(conn, 'return')]]
+        ))

+ 26 - 0
route/list_admin.py

@@ -0,0 +1,26 @@
+from .tool.func import *
+
+def list_admin():
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        div = '<ul>'
+
+        curs.execute(db_change(
+            "select id, data from user_set where name = 'acl' and not data = 'user'"
+        ))
+        for data in curs.fetchall():
+            name = '' + \
+                ip_pas(data[0]) + ' ' + \
+                '<a href="/auth/list/add/' + url_pas(data[1]) + '">(' + data[1] + ')</a>' + \
+            ''
+
+            div += '<li>' + name + '</li>'
+
+        div += '</ul>'
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'admin_list'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = div,
+            menu = [['other', get_lang(conn, 'return')]]
+        ))

+ 52 - 0
route/list_admin_auth_use.py

@@ -0,0 +1,52 @@
+from .tool.func import *
+
+def list_admin_auth_use(arg_num = 1, arg_search = 'normal'):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        sql_num = (arg_num * 50 - 50) if arg_num * 50 > 0 else 0
+
+        if flask.request.method == 'POST':
+            return redirect(conn, '/list/admin/auth_use_page/1/' + url_pas(flask.request.form.get('search', 'normal')))
+        else:
+            arg_search = 'normal' if arg_search == '' else arg_search
+            
+            if arg_search == 'normal':
+                curs.execute(db_change("select who, what, time from re_admin order by time desc limit ?, 50"), [sql_num])
+            else:
+                curs.execute(db_change("select who, what, time from re_admin where what like ? order by time desc limit ?, 50"), [arg_search + "%", sql_num])
+
+            list_data = '<ul>'
+
+            get_list = curs.fetchall()
+            for data in get_list:
+                do_data = data[1]
+
+                if ip_or_user(data[0]) != 0:
+                    curs.execute(db_change("select data from other where name = 'ip_view'"))
+                    db_data = curs.fetchall()
+                    ip_view = db_data[0][0] if db_data else ''
+                    ip_view = '' if acl_check(tool = 'ban_auth') != 1 else ip_view
+                    
+                    if ip_view != '':
+                        do_data = do_data.split(' ')
+                        do_data = do_data[0] if do_data[0] in ('ban') else data[1]
+
+                list_data += '<li>' + ip_pas(data[0]) + ' | ' + html.escape(do_data) + ' | ' + data[2] + '</li>'
+
+            list_data += '</ul>'
+            list_data += get_next_page_bottom(conn, '/list/admin/auth_use_page/{}/' + url_pas(arg_search), arg_num, get_list)
+
+            arg_search = html.escape(arg_search) if arg_search != 'normal' else ''
+
+            return easy_minify(conn, flask.render_template(skin_check(conn),
+                imp = [get_lang(conn, 'authority_use_list'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+                data = '''
+                    <form method="post">
+                        <input class="opennamu_width_200" name="search" placeholder="''' + get_lang(conn, 'start_with_search') + '''" value="''' + arg_search + '''">
+                        <button type="submit">''' + get_lang(conn, 'search') + '''</button>
+                    </form>
+                    <hr class="main_hr">
+                ''' + list_data,
+                menu = [['other', get_lang(conn, 'return')]]
+            ))

+ 34 - 0
route/list_admin_group.py

@@ -0,0 +1,34 @@
+from .tool.func import *
+
+def list_admin_group_2():
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        list_data = '<ul>'
+        org_acl_list = get_default_admin_group()
+
+        curs.execute(db_change("select distinct name from alist order by name asc"))
+        for data in curs.fetchall():
+            if acl_check('', 'owner_auth', '', '') != 1 and not data[0] in org_acl_list:
+                delete_admin_group = ' <a href="/auth/list/delete/' + url_pas(data[0]) + '">(' + get_lang(conn, "delete") + ')</a>'
+            else:
+                delete_admin_group = ''
+
+            list_data += '' + \
+                '<li>' + \
+                    '<a href="/auth/list/add/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a>' + \
+                    delete_admin_group + \
+                '</li>' + \
+            ''
+
+        list_data += '' + \
+            '</ul>' + \
+            '<hr class="main_hr">' + \
+            '<a href="/manager/8">(' + get_lang(conn, 'add') + ')</a>' + \
+        ''
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'admin_group_list'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = list_data,
+            menu = [['manager', get_lang(conn, 'return')]]
+        ))

+ 65 - 0
route/list_image_file.py

@@ -0,0 +1,65 @@
+from .tool.func import *
+
+def list_image_file(arg_num = 1, do_type = 0):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        sql_num = (arg_num * 50 - 50) if arg_num * 50 > 0 else 0
+
+        list_data = ''
+        if do_type == 0:
+            list_data += '<a href="/list/image">(' + get_lang(conn, 'image') + ')</a>'
+        else:
+            list_data += '<a href="/list/file">(' + get_lang(conn, 'normal') + ')</a>'
+        
+        list_data += '<hr class="main_hr">'
+
+        if do_type == 1:
+            render_data = ''
+            sub_data = ''
+            count = 0
+
+            curs.execute(db_change("select title from data where title like 'file:%' limit ?, 50"), [sql_num])
+            data_list = curs.fetchall()
+            for data in data_list:
+                if count != 0 and count % 4 == 0:
+                    render_data += '||\n'
+                    render_data += sub_data + '||\n'
+                    
+                    sub_data = ''
+
+                render_data += '|| [[' + data[0] + ']] '
+                sub_data += '|| [[:' + data[0] + ']] '
+                count += 1
+
+            if render_data != '':
+                render_data += '||\n'
+                render_data += sub_data + '||'
+
+            end_data = render_set(conn, 
+                doc_name = '',
+                doc_data = render_data,
+                data_type = 'view',
+                markup = 'namumark'
+            )
+            list_data += end_data
+        else:
+            list_data += '<ul>'
+
+            curs.execute(db_change("select title from data where title like 'file:%' limit ?, 50"), [sql_num])
+            data_list = curs.fetchall()
+            for data in data_list:
+                list_data += '<li><a href="/w/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a></li>'
+
+            list_data += '</ul>'
+
+        if do_type == 0:
+            list_data += next_fix(conn, '/list/file/', arg_num, data_list)
+        else:
+            list_data += next_fix(conn, '/list/image/', arg_num, data_list)
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'image_file_list'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = list_data,
+            menu = [['other', get_lang(conn, 'return')]]
+        ))

+ 32 - 0
route/list_long_page.py

@@ -0,0 +1,32 @@
+from .tool.func import *
+
+def list_long_page(tool = 'long_page', arg_num = 1):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        sql_num = (arg_num * 50 - 50) if arg_num * 50 > 0 else 0
+
+        div = '<ul>'
+        select_data = 'desc' if tool == 'long_page' else 'asc'
+        title = 'long_page' if tool == 'long_page' else 'short_page'
+
+        curs.execute(db_change("select doc_name, set_data from data_set where set_name = 'length' and doc_rev = '' order by set_data + 0 " + select_data + " limit ?, 50"), [sql_num])
+        n_list = curs.fetchall()
+        for data in n_list:
+            div += '<li>'
+            div += data[1] + ' | <a href="/w/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a>'
+            
+            curs.execute(db_change("select set_data from data_set where doc_name = ? and set_name = 'doc_type'"), [data[0]])
+            db_data = curs.fetchall()
+            if db_data and db_data[0][0] != '':
+                div += ' | ' + db_data[0][0]
+
+            div += '</li>'
+
+        div += '</ul>' + next_fix(conn, '/list/document/' + ('long' if title == 'long_page' else 'short') + '/', arg_num, n_list)
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, title), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = div,
+            menu = [['other', get_lang(conn, 'return')]]
+        ))

+ 30 - 0
route/list_no_link.py

@@ -0,0 +1,30 @@
+from .tool.func import *
+
+def list_no_link(num = 1):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+        
+        sql_num = (num * 50 - 50) if num * 50 > 0 else 0
+        
+        div = '<ul>'
+        
+        curs.execute(db_change("select doc_name, set_data from data_set where set_name = 'link_count' and doc_rev = '' and set_data = '0' limit ?, 50"), [sql_num])
+        n_list = curs.fetchall()
+        for data in n_list:
+            div += '<li>'
+            div += data[1] + ' | <a href="/w/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a>'
+            
+            curs.execute(db_change("select set_data from data_set where doc_name = ? and set_name = 'doc_type'"), [data[0]])
+            db_data = curs.fetchall()
+            if db_data and db_data[0][0] != '':
+                div += ' | ' + db_data[0][0]
+
+            div += '</li>'
+        
+        div += '</ul>' + next_fix(conn, '/list/document/no_link/', num, n_list)
+        
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'no_link_document_list'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = div,
+            menu = [['other', get_lang(conn, 'return')]]
+        ))

+ 26 - 0
route/list_please.py

@@ -0,0 +1,26 @@
+from .tool.func import *
+
+def list_please(arg_num = 1):
+    with get_db_connect() as conn:
+        curs = conn.cursor()
+
+        sql_num = (arg_num * 50 - 50) if arg_num * 50 > 0 else 0
+
+        div = '<ul>'
+
+        curs.execute(db_change("select distinct title from back where type = 'no' limit ?, 50"), [sql_num])
+        data_list = curs.fetchall()
+        for data in data_list:
+            div += '' + \
+                '<li>' + \
+                    '<a class="opennamu_not_exist_link" href="/w/' + url_pas(data[0]) + '">' + html.escape(data[0]) + '</a> ' + \
+                '</li>' + \
+            ''
+
+        div += '</ul>' + next_fix(conn, '/list/document/need/', arg_num, data_list)
+
+        return easy_minify(conn, flask.render_template(skin_check(conn),
+            imp = [get_lang(conn, 'need_document'), wiki_set(conn), wiki_custom(conn), wiki_css([0, 0])],
+            data = div,
+            menu = [['other', get_lang(conn, 'return')]]
+        ))

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä