commit b4958755a2abd848b60bd36a87fe8c1c05c54404 Author: MarkusJoe Date: Fri Sep 2 20:56:16 2022 +0800 init: upload diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ef5524e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2022] [RTAkland] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..669cfe5 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# py 版访问次数计数器 + +> 翻自 [journey-ad](https://github.com/journey-ad/Moe-counter) 的计数器 + +# 预览 + + +# 新特性 + +* 使用了异步Web框架`FastAPI`来提高效率 ***注:数据库部分使用了同步*** +* 可以自定义显示图片的数量(长度) +* 支持API接口以便自行开发 + +# 失去的特性 + +* 只支持`SQLite`数据库 + +# 安装&运行 +```bash +$ pip3 install -r requirements.txt +$ uvicorn src:create_app --factory or +$ python3 main.py # 两条命令等价 +``` + +# 单元测试 +* 没有单元测试(没写) + +# 开源 + +- 本项目以[Apache-2.0](./LICENSE)许可开源, 即: + - 你可以直接使用该项目提供的功能, 无需任何授权 + - 你可以在**注明来源版权信息**的情况下对源代码进行任意分发和修改以及衍生 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9c76af5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,73 @@ +# 这里是API接口的开发文档 + +> 所有接口前缀都为`/api/` +> 返回类型除了 `export` 都是JSON + +# 接口 + +## query + +* 参数: `name` + +```bash +$ curl -X GET 'http://127.0.0.1/api/query/test' +``` + +### 返回数据 + +```json +{ + "test": 0 +} +``` + +## query-all + +* 参数: 无 + +```bash +$ curl -X GET 'http://127.0.0.1/api/query-all/' +``` + +### 返回数据 + +```json +{ + "test": 114514, + "test1": 1919810 +} +``` + +## query-theme + +* 参数: `name` + +```bash +$ curl -X GET 'http://127.0.0.1/api/query-theme/lewd' +``` + +* ***注:返回的base64文本开头没有添加`data:image/gif;base64,`清手动添加*** + +### 返回数据 + +```json +[ + [ + "lewd/0", + "base64...", + 45, + 100 + ], + ... +] +``` + +### export + +* 参数: 无 + +```bash +$ curl -X GET 'http://127.0.0.1/api/export/' +``` + +> 此接口返回文件 diff --git a/main.py b/main.py new file mode 100644 index 0000000..34ecd3f --- /dev/null +++ b/main.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/9/2 +# @File Name: main.py + + +import uvicorn +from src import create_app + +app = create_app() + +if __name__ == '__main__': + uvicorn.run(app, host='0.0.0.0') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d70dda6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +jinja2 +aiofiles \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..dc49a84 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: __init__.py.py + + +from src.main import main +from src.api import api +from fastapi import FastAPI + + +def create_app(): + """ + Factory function + :return: + """ + app = FastAPI() + app.include_router(main) + app.include_router(api, prefix='/api') + return app diff --git a/src/api/__init__.py b/src/api/__init__.py new file mode 100644 index 0000000..ae07ca3 --- /dev/null +++ b/src/api/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/9/2 +# @File Name: __init__.py.py + +from fastapi import APIRouter + +api = APIRouter() + +from src.api import view diff --git a/src/api/view.py b/src/api/view.py new file mode 100644 index 0000000..2058f70 --- /dev/null +++ b/src/api/view.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/9/2 +# @File Name: view.py + + +from src.api import api +from src.db import SQLite +from fastapi.responses import FileResponse + + +@api.get('/query/{name}') +async def query(name: str): + times = SQLite().query(name)[1] + return {name: times} + + +@api.get('/query-all/') +async def query_all(limit: int = 30): + data = SQLite().query_all()[:limit] + result = {} + for i in data: + result[i[0]] = i[1] + return result + + +@api.get('/export/') +async def export(): + return FileResponse('./src/db/data.sqlite') + + +@api.get('/query-theme/{name}') +async def query_theme(name: str): + themes = [] + for i in range(10): + data = SQLite().query_image(name + '/' + str(i))[0] + themes.append(data) + return themes diff --git a/src/db/__init__.py b/src/db/__init__.py new file mode 100644 index 0000000..2244ed1 --- /dev/null +++ b/src/db/__init__.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: __init__.py.py + + +import os +import sqlite3 +from urllib.request import urlopen + + +class SQLite: + def __init__(self): + self.conn = sqlite3.connect('./src/db/data.sqlite') + # self.conn = sqlite3.connect('./data.sqlite') + self.cursor = self.conn.cursor() + + def __del__(self): + self.conn.commit() + self.cursor.close() + self.conn.close() + + def query(self, _id: str) -> tuple: + self.cursor.execute('select * from data where id="%s"' % _id) + result = self.cursor.fetchone() + if result is None: + self.insert(_id) + return tuple([_id, 0]) + self.update(_id, result[1]) + return result + + def insert(self, _id: str) -> bool: + self.cursor.execute('insert into data (id, times) values ("%s", 0)' % _id) + return True + + def update(self, _id: str, times: int) -> bool: + times += 1 + self.cursor.execute('update data set times=%s where id="%s"' % (times, _id)) + return True + + def query_all(self) -> list: + self.cursor.execute('select * from data') + result = self.cursor.fetchall() + return result + + def query_image(self, _id: str) -> list: + self.cursor.execute('select * from image where id="%s"' % _id) + result = self.cursor.fetchall() + return result + + +if __name__ != '__main__': + if not os.path.exists('./src/db/data.sqlite'): + print('database file not exists, start downloading...') + resp = urlopen('https://pac.rtst.tech/static_file_hosting/static/counter/data.sqlite').read() + with open('./src/db/data.sqlite', 'wb') as fp: + fp.write(resp) + print('Done.') diff --git a/src/main/__init__.py b/src/main/__init__.py new file mode 100644 index 0000000..6391e45 --- /dev/null +++ b/src/main/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: __init__.py.py + + +from fastapi import APIRouter + +main = APIRouter() + +from src.main import view diff --git a/src/main/view.py b/src/main/view.py new file mode 100644 index 0000000..2b06aed --- /dev/null +++ b/src/main/view.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: view.py + + +from src.main import main +from src.utils import resp +from fastapi import Request +from fastapi.templating import Jinja2Templates + +template = Jinja2Templates('./src/templates') + + +@main.get('/{name}') +async def index(req: Request, name: str, length: int = 7, theme: str = 'lewd'): + response = await resp(name, length, theme) + + return template.TemplateResponse('index.html', context={'request': req, + 'context': response['context'], + 'g_width': response['g_width'], + 'g_height': response['g_height']}, + headers=response['headers']) diff --git a/src/templates/index.html b/src/templates/index.html new file mode 100644 index 0000000..fae1b8e --- /dev/null +++ b/src/templates/index.html @@ -0,0 +1,10 @@ + + + + {% for value in context %} + + {% endfor %} + + \ No newline at end of file diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..c1c6e94 --- /dev/null +++ b/src/tests/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: __init__.py.py diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..54967fd --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: __init__.py.py + + +from src.utils.response import resp diff --git a/src/utils/convert.py b/src/utils/convert.py new file mode 100644 index 0000000..5ff67ef --- /dev/null +++ b/src/utils/convert.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: convert.py + + +import os +import base64 +import sqlite3 +import asyncio + + +class SQLite: + def __init__(self): + self.conn = sqlite3.connect('../db/data.sqlite') + self.cursor = self.conn.cursor() + + def __del__(self): + self.conn.commit() + self.cursor.close() + self.conn.close() + + async def insert(self, _id: str, b64: str, width: int, height: int) -> bool: + self.cursor.execute('insert into image values ("%s", "%s", %s, %s)' % (_id, b64, width, height)) + return True + + +async def convert(): + dirs = os.listdir('../static') + for i in dirs: + files = os.listdir(f'../static/{i}') + for b in files: + with open(f'../static/{i}/{b}', 'rb') as fp: + if i == 'moebooru': + width = 45 + height = 100 + elif i == 'lewd': + width = 45 + height = 100 + elif i == 'lisu': + width = 66 + height = 152 + else: + width = 68 + height = 150 + await SQLite().insert(f'{i}/{b}', base64.b64encode(fp.read()).decode('utf-8'), width, height) + + +asyncio.run(convert()) diff --git a/src/utils/response.py b/src/utils/response.py new file mode 100644 index 0000000..c37c765 --- /dev/null +++ b/src/utils/response.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -- coding:utf-8 -- +# @Author: markushammered@gmail.com +# @Development Tool: PyCharm +# @Create Time: 2022/8/30 +# @File Name: response.py + + +from src.db import SQLite + + +async def resp(_id: str, length: int = 7, theme: str = 'lewd') -> dict: + """ + return a response + :param _id: + :param length: + :param theme: + :return: + """ + times = SQLite().query(_id)[1] + str_number = str(times) # 将整形转换为字符串 + len_number = len(str_number) # 再获取字符串长度 + g_length = length * '0' # 根据输入的位数来自动生成0的数量 + show_number = str(g_length[:-len_number] + str_number) + context = [] + w_h = SQLite().query_image(theme + '/0')[0] + count = 0 # 计数器, 每次遍历一次则加一, 让图片x轴相乘 + for i in show_number: + data = SQLite().query_image(theme + '/' + i)[0] + context.append({'position': data[-2] * count, + 'width': data[-2], + 'height': data[-1], + 'base64': data[1]}) + count += 1 + headers = {'cache-control': 'max-age=0, no-cache, no-store, must-revalidate'} + + return {'context': context, + 'g_width': length * w_h[-2], + 'g_height': w_h[-1], + 'headers': headers}