commit 19806f04c153d2ce10b0a6d6a0069dec1b11b7a0 Author: lan-air Date: Fri Dec 9 19:06:47 2022 +0800 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..782c686 --- /dev/null +++ b/.gitignore @@ -0,0 +1,156 @@ +media/ +logs/ +.idea +static/ +__pycache__/ +*.py[cod] +*$py.class +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files + +# C extensions +*.so +*.env +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test.pdf / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# Project +.vscode +.DS_Store +for_test.py +.html +/evaluate/temp.py +/evaluation/back.json +.env* +.backup/ +/cloc-1.64.exe +*.db \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..11a06e3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.9.5 +LABEL author="Lan" +LABEL email="vast@tom.com" +LABEL version="1.0" + + +COPY . /app +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +RUN echo 'Asia/Shanghai' >/etc/timezone +WORKDIR /app +RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ +EXPOSE 123456 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "12345"] \ No newline at end of file diff --git a/database.db b/database.db new file mode 100644 index 0000000..5911bdc Binary files /dev/null and b/database.db differ diff --git a/database.py b/database.py new file mode 100644 index 0000000..cf6a6c5 --- /dev/null +++ b/database.py @@ -0,0 +1,23 @@ +import datetime + +from sqlalchemy import create_engine, DateTime +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Boolean, Column, Integer, String + +engine = create_engine('sqlite:///database.db', connect_args={"check_same_thread": False}) +Base = declarative_base() +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + +class Codes(Base): + __tablename__ = 'codes' + id = Column(Integer, primary_key=True, index=True) + code = Column(String(10), unique=True, index=True) + key = Column(String(30), unique=True, index=True) + name = Column(String(500)) + size = Column(Integer) + type = Column(String(20)) + text = Column(String(500)) + used = Column(Boolean, default=False) + use_time = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now) diff --git a/main.py b/main.py new file mode 100644 index 0000000..94f98ca --- /dev/null +++ b/main.py @@ -0,0 +1,124 @@ +import datetime +import os +import uuid + +from fastapi import FastAPI, Depends, UploadFile, Form, File +from sqlalchemy.orm import Session +from starlette.requests import Request +from starlette.responses import HTMLResponse +import random + +from starlette.staticfiles import StaticFiles + +import database +from database import engine, SessionLocal, Base + +Base.metadata.create_all(bind=engine) +app = FastAPI() +if not os.path.exists('./static'): + os.makedirs('./static') +app.mount("/static", StaticFiles(directory="static"), name="static") +# 过期时间 +exp_hour = 24 +# 允许错误次数 +error_count = 5 +# 禁止分钟数 +error_minute = 60 + +error_ip_count = {} + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def get_code(db: Session = Depends(get_db)): + code = random.randint(10000, 99999) + while db.query(database.Codes).filter(database.Codes.code == code).first(): + code = random.randint(10000, 99999) + return str(code) + + +def get_file_name(key, ext, file): + now = datetime.datetime.now() + path = f'./static/upload/{now.year}/{now.month}/{now.day}/' + name = f'{key}.{ext}' + if not os.path.exists(path): + os.makedirs(path) + file = file.file.read() + with open(f'{os.path.join(path, name)}', 'wb') as f: + f.write(file) + return key, len(file), path[1:] + name + + +@app.get('/') +async def index(): + with open('templates/index.html', 'r') as f: + return HTMLResponse(f.read()) + + +@app.post('/') +async def index(request: Request, code: str, db: Session = Depends(get_db)): + info = db.query(database.Codes).filter(database.Codes.code == code).first() + error = error_ip_count.get(request.client.host, {'count': 0, 'time': datetime.datetime.now()}) + if error['count'] > error_count: + if datetime.datetime.now() - error['time'] < datetime.timedelta(minutes=error_minute): + return {'code': 404, 'msg': '请求过于频繁,请稍后再试'} + else: + error['count'] = 0 + else: + if not info: + error['count'] += 1 + error_ip_count[request.client.host] = error + return {'code': 404, 'msg': f'取件码错误,错误5次将被禁止10分钟'} + else: + return {'code': 200, 'msg': '取件成功,请点击库查看', 'data': info} + + +@app.get('/share') +async def share(): + with open('templates/upload.html', 'r') as f: + return HTMLResponse(f.read()) + + +@app.post('/share') +async def share(text: str = Form(default=None), file: UploadFile = File(default=None), db: Session = Depends(get_db)): + cutoff_time = datetime.datetime.now() - datetime.timedelta(hours=exp_hour) + db.query(database.Codes).filter(database.Codes.use_time < cutoff_time).delete() + db.commit() + code = get_code(db) + if text: + info = database.Codes( + code=code, + text=text, + type='text/plain', + key=uuid.uuid4().hex, + size=len(text), + used=True, + name='分享文本' + ) + db.add(info) + db.commit() + return {'code': 200, 'msg': '上传成功,请点击文件库查看', + 'data': {'code': code, 'name': '分享文本', 'text': text}} + elif file: + key, size, full_path = get_file_name(uuid.uuid4().hex, file.filename.split('.')[-1], file) + info = database.Codes( + code=code, + text=full_path, + type=file.content_type, + key=key, + size=size, + used=True, + name=file.filename + ) + db.add(info) + db.commit() + return {'code': 200, 'msg': '上传成功,请点击文件库查看', + 'data': {'code': code, 'name': file.filename, 'text': full_path}} + else: + return {'code': 422, 'msg': '参数错误', 'data': []} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3ae1a7e --- /dev/null +++ b/readme.md @@ -0,0 +1,34 @@ +# 口令传送箱 + +## 主要特色 + +- [x] 拖拽,复制粘贴上传 +- [x] 文件口令传输 +- [x] 分享文件:多种上传方式供你选择 +- [x] 分享文本:直接复制粘贴直接上传 +- [x] 防爆破:错误五次拉黑十分钟 +- [x] 完全匿名:不记录任何信息 +- [x] 无需注册:无需注册,无需登录 +- [x] Sqlite3数据库:无需安装数据库 + +## 部署方式 + +### 服务端部署 + +1. 安装Python3 +2. 拉取代码,解压缩 +3. 安装依赖包:`pip install -r requirements.txt` +4. 运行` uvicorn main:app --host 0.0.0.0 --port 12345` +5. 然后你自己看怎么进程守护吧 + +### 宝塔部署 + +1. 安装宝塔Python Manager +2. 然后你自己看着填吧 + +### Docker部署 + +```bash +docker build --file Dockerfile --tag filecodebox . +docker run -d -p 12345:12345 --name filecodebox filecodebox +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e5e6704 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,19 @@ +anyio==3.6.2 +click==8.1.3 +fastapi==0.88.0 +h11==0.14.0 +httptools==0.5.0 +idna==3.4 +pydantic==1.10.2 +python-dotenv==0.21.0 +python-multipart==0.0.5 +PyYAML==6.0 +six==1.16.0 +sniffio==1.3.0 +SQLAlchemy==1.4.44 +starlette==0.22.0 +typing_extensions==4.4.0 +uvicorn==0.20.0 +uvloop==0.17.0 +watchfiles==0.18.1 +websockets==10.4 diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d2af90c --- /dev/null +++ b/templates/index.html @@ -0,0 +1,257 @@ + + + + + + + + + + 取件箱-口令传送箱 + + + +
+ + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + + 7 + + + 8 + + + 9 + + + + + + 0 + + + + + + + + + + + +
将文件拖、粘贴到此处,或点击上传,24h后删除
+
+ 去取件 + 文件箱 +
+
+
+
+
+
+ By: Lan +
+ + +
+
取件码:${ file.code }
+
文件名:${ file.name }
+
+ 内   容:${ file.text } +
+
+ 链   接: + 点击下载 +
+
+
+ +
+ + + + + + + ${ file.name } + + + 取件码:

${ file.code }

+
+
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 0000000..90cad7e --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,121 @@ + + + + + + + + + + 寄件箱-口令传送箱 + + + +
+ + + + + +
将文件拖、粘贴到此处,或点击上传,24h后删除
+
+ + 去取件 + + 文件箱 + +
+
+
+
+
+ + + + + + + ${ file.name } + + + 取件码:

${ file.code }

+
+
+
+
+
+
+ + + + + + + + \ No newline at end of file