bcmd 0.6.6__py3-none-any.whl → 0.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bcmd might be problematic. Click here for more details.
- bcmd/common/__init__.py +0 -0
- bcmd/common/func.py +19 -0
- bcmd/common/secret.py +51 -0
- bcmd/resources/project/main.py +1 -0
- bcmd/tasks/__init__.py +16 -0
- bcmd/tasks/bin.py +103 -0
- bcmd/tasks/code.py +132 -0
- bcmd/tasks/crypto.py +105 -0
- bcmd/tasks/debian.py +78 -0
- bcmd/tasks/download.py +74 -0
- bcmd/tasks/image.py +376 -0
- bcmd/tasks/json.py +25 -0
- bcmd/tasks/lib.py +94 -0
- bcmd/tasks/math.py +97 -0
- bcmd/tasks/mirror.py +46 -0
- bcmd/tasks/pdf.py +43 -0
- bcmd/tasks/proxy.py +56 -0
- bcmd/tasks/time.py +81 -0
- bcmd/tasks/upgrade.py +22 -0
- bcmd/tasks/wasabi.py +94 -0
- bcmd/utils/__init__.py +0 -0
- bcmd/utils/tkUtil.py +243 -0
- {bcmd-0.6.6.dist-info → bcmd-0.6.8.dist-info}/METADATA +1 -1
- bcmd-0.6.8.dist-info/RECORD +33 -0
- {bcmd-0.6.6.dist-info → bcmd-0.6.8.dist-info}/top_level.txt +1 -0
- test/__init__.py +0 -0
- test/conftest.py +17 -0
- test/test_pdf.py +47 -0
- test/test_proxy.py +8 -0
- test/test_wasabi.py +28 -0
- bcmd-0.6.6.dist-info/RECORD +0 -6
- {bcmd-0.6.6.dist-info → bcmd-0.6.8.dist-info}/WHEEL +0 -0
- {bcmd-0.6.6.dist-info → bcmd-0.6.8.dist-info}/entry_points.txt +0 -0
bcmd/common/__init__.py
ADDED
|
File without changes
|
bcmd/common/func.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import importlib.resources
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from beni import btask
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def checkFileOrNotExists(file: Path):
|
|
9
|
+
btask.assertTrue(file.is_file() or not file.exists(), f'必须是文件 {file}')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def checkPathOrNotExists(folder: Path):
|
|
13
|
+
btask.assertTrue(folder.is_dir() or not folder.exists(), f'必须是目录 {folder}')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@contextmanager
|
|
17
|
+
def useResources(name: str):
|
|
18
|
+
with importlib.resources.path('bcmdx.resources', name) as target:
|
|
19
|
+
yield target
|
bcmd/common/secret.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
from typing import Any, TypedDict, cast
|
|
3
|
+
|
|
4
|
+
from async_lru import alru_cache
|
|
5
|
+
from beni import bcrypto, btask
|
|
6
|
+
from beni.bcolor import printRed
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PypiSecret(TypedDict):
|
|
10
|
+
username: str
|
|
11
|
+
password: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@alru_cache
|
|
15
|
+
async def getPypi(value: str = '') -> PypiSecret:
|
|
16
|
+
return cast(
|
|
17
|
+
Any,
|
|
18
|
+
_getData(value or 'QbuF2mV/lqovtF5dskZGD7qHknYbNuF2QseWRtWxLZTPrC/jL1tcxV8JEKaRjLsu46PxJZ7zepJwggnUTIWnEAoV5VtgP2/hbuzxxHha8817kR5c65H9fXm8eOal7DYXsUoGPQMnm59UWNXUKjmIaP4sn9nySFlRYqa8sEZSbYQ4N0NL35Dpj1e3wyQxJ+7h2jwKAz50Hh8G4yAM3/js9+NUe4ymts+UXcwsP3ADIBMkzjnFc0lEYg2d+fw0A74XWCvoZPoGqHZR/THUOVNAYxoGgDzP4SPIk1XsmtpxvfO/DpJd/Cg/0fB3MYagGKI1+m6Bxqhvd1I/lf0YbM5y4E4='),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class QiniuSecret(TypedDict):
|
|
23
|
+
bucket: str
|
|
24
|
+
baseUrl: str
|
|
25
|
+
ak: str
|
|
26
|
+
sk: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@alru_cache
|
|
30
|
+
async def getQiniu(value: str = '') -> QiniuSecret:
|
|
31
|
+
return cast(
|
|
32
|
+
Any,
|
|
33
|
+
_getData(value or 'vNroFKeKklrdcJ89suFm+iyuJsq/cyUB5+QWoeeiMc/J0oSLF9cg5rqbK1IRxF0cCQ8KmkQQhdVa+PI6kuTBhoSH6IviVTylzAOrJywEccz9jWkJkW28Y9Vo4ePZmfWf/j7wdxNB144z234KD8IxJn4lR2A0L9JN5kk1o1/hpcydXL74FNtt03lYL/E3WVcvpUfw37mri2HMYOfUw81dRwW35/hMuQjtq1BBrKrIsSKTHH44tROMcgyvt+Qy292AtDBcsYiZxBKhQtBFPMq/vUs='),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _getData(content: str) -> dict[str, Any]:
|
|
38
|
+
index = content.find(' ')
|
|
39
|
+
if index > -1:
|
|
40
|
+
tips = f'请输入密码({content[:index]}):'
|
|
41
|
+
else:
|
|
42
|
+
tips = '请输入密码:'
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
pwd = getpass.getpass(tips)
|
|
46
|
+
return bcrypto.decryptJson(content, pwd)
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
print('')
|
|
49
|
+
btask.abort('用户操作取消')
|
|
50
|
+
except BaseException:
|
|
51
|
+
printRed('密码错误')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
print('OK')
|
bcmd/tasks/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
from . import bin
|
|
3
|
+
from . import code
|
|
4
|
+
from . import crypto
|
|
5
|
+
from . import debian
|
|
6
|
+
from . import download
|
|
7
|
+
from . import image
|
|
8
|
+
from . import json
|
|
9
|
+
from . import lib
|
|
10
|
+
from . import math
|
|
11
|
+
from . import mirror
|
|
12
|
+
from . import pdf
|
|
13
|
+
from . import proxy
|
|
14
|
+
from . import time
|
|
15
|
+
from . import upgrade
|
|
16
|
+
from . import wasabi
|
bcmd/tasks/bin.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from beni import bcolor, bfile, bpath, btask, bzip
|
|
7
|
+
from beni.bfunc import syncCall, textToAry
|
|
8
|
+
from beni.bqiniu import QiniuBucket
|
|
9
|
+
from beni.btype import Null
|
|
10
|
+
from prettytable import PrettyTable
|
|
11
|
+
|
|
12
|
+
from ..common import secret
|
|
13
|
+
|
|
14
|
+
app: Final = btask.newSubApp('bin 工具')
|
|
15
|
+
|
|
16
|
+
_PREFIX = 'bin/'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
@syncCall
|
|
21
|
+
async def download(
|
|
22
|
+
names: list[str] = typer.Argument(None, help="支持多个"),
|
|
23
|
+
file: Path = typer.Option(None, '--file', '-f', help="文件形式指定参数,行为单位"),
|
|
24
|
+
output: Path = typer.Option(Path.cwd(), '--output', '-o', help="本地保存路径"),
|
|
25
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
26
|
+
):
|
|
27
|
+
'从七牛云下载执行文件'
|
|
28
|
+
bucket: QiniuBucket = Null
|
|
29
|
+
if file:
|
|
30
|
+
content = await bfile.readText(Path(file))
|
|
31
|
+
names.extend(
|
|
32
|
+
textToAry(content)
|
|
33
|
+
)
|
|
34
|
+
for target in names:
|
|
35
|
+
binFile = output / target
|
|
36
|
+
if binFile.exists():
|
|
37
|
+
bcolor.printYellow(f'已存在 {binFile}')
|
|
38
|
+
else:
|
|
39
|
+
key = f'bin/{target}.zip'
|
|
40
|
+
bucket = bucket or await _getBucket(secretValue)
|
|
41
|
+
await bucket.downloadPrivateFileUnzip(key, output)
|
|
42
|
+
bcolor.printGreen(f'added {binFile}')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command('list')
|
|
46
|
+
@syncCall
|
|
47
|
+
async def getList(
|
|
48
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
49
|
+
):
|
|
50
|
+
'列出可下载的文件'
|
|
51
|
+
bucket = await _getBucket(secretValue)
|
|
52
|
+
datas = (await bucket.getFileList(_PREFIX, limit=1000))[0]
|
|
53
|
+
datas = [x for x in datas if x.key != _PREFIX and x.key.endswith('.zip')]
|
|
54
|
+
datas.sort(key=lambda x: x.time, reverse=True)
|
|
55
|
+
table = PrettyTable()
|
|
56
|
+
table.add_column(
|
|
57
|
+
bcolor.yellow('文件名称'),
|
|
58
|
+
[x.key[len(_PREFIX):-len('.zip')] for x in datas],
|
|
59
|
+
'l',
|
|
60
|
+
)
|
|
61
|
+
table.add_column(
|
|
62
|
+
bcolor.yellow('上传时间'),
|
|
63
|
+
[datetime.fromtimestamp(x.time / 10000000).strftime('%Y-%m-%d %H:%M:%S') for x in datas],
|
|
64
|
+
)
|
|
65
|
+
print()
|
|
66
|
+
print(table.get_string())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command()
|
|
70
|
+
@syncCall
|
|
71
|
+
async def upload(
|
|
72
|
+
file: Path = typer.Argument(..., help="本地文件路径"),
|
|
73
|
+
force: bool = typer.Option(False, '--force', '-f', help="强制覆盖"),
|
|
74
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
75
|
+
):
|
|
76
|
+
'上传'
|
|
77
|
+
bucket = await _getBucket(secretValue)
|
|
78
|
+
key = f'{_PREFIX}{file.name}.zip'
|
|
79
|
+
if not force:
|
|
80
|
+
if await bucket.getFileStatus(key):
|
|
81
|
+
btask.abort('云端文件已存在,可以使用 --force 强制覆盖')
|
|
82
|
+
with bpath.useTempFile() as f:
|
|
83
|
+
bzip.zipFile(f, file)
|
|
84
|
+
await bucket.uploadFile(key, f)
|
|
85
|
+
bcolor.printGreen('OK')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command()
|
|
89
|
+
@syncCall
|
|
90
|
+
async def remove(
|
|
91
|
+
key: str = typer.Argument(..., help="云端文件key"),
|
|
92
|
+
secretValue: str = typer.Option('', '--secret', '-s', help='密钥信息'),
|
|
93
|
+
):
|
|
94
|
+
bucket = await _getBucket(secretValue)
|
|
95
|
+
await bucket.deleteFiles(f'{_PREFIX}{key}.zip')
|
|
96
|
+
bcolor.printGreen('OK')
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ------------------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def _getBucket(secretValue: str):
|
|
103
|
+
return QiniuBucket(**await secret.getQiniu())
|
bcmd/tasks/code.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from beni import bfile, bpath, btask
|
|
8
|
+
from beni.bcolor import printGreen, printMagenta, printYellow
|
|
9
|
+
from beni.bfunc import syncCall
|
|
10
|
+
|
|
11
|
+
app: Final = btask.newSubApp('code 工具')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command()
|
|
15
|
+
@syncCall
|
|
16
|
+
async def tidy_tasks(
|
|
17
|
+
tasks_path: Path = typer.Argument(Path.cwd(), help="tasks 路径"),
|
|
18
|
+
):
|
|
19
|
+
'整理 task 项目中的 tasks/__init__.py'
|
|
20
|
+
|
|
21
|
+
initFile = tasks_path / '__init__.py'
|
|
22
|
+
btask.assertTrue(initFile.is_file(), '文件不存在', initFile)
|
|
23
|
+
files = bpath.listFile(tasks_path)
|
|
24
|
+
files = [x for x in files if not x.name.startswith('_')]
|
|
25
|
+
contents = [f'from . import {x.stem}' for x in files]
|
|
26
|
+
contents.insert(0, '# type: ignore')
|
|
27
|
+
contents.append('')
|
|
28
|
+
content = '\n'.join(contents)
|
|
29
|
+
oldContent = await bfile.readText(initFile)
|
|
30
|
+
if oldContent != content:
|
|
31
|
+
await bfile.writeText(
|
|
32
|
+
initFile,
|
|
33
|
+
content,
|
|
34
|
+
)
|
|
35
|
+
printYellow(initFile)
|
|
36
|
+
printMagenta(content)
|
|
37
|
+
printGreen('OK')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command()
|
|
41
|
+
@syncCall
|
|
42
|
+
async def tidy_modules(
|
|
43
|
+
modules_path: Path = typer.Argument(Path.cwd(), help="modules_path 路径"),
|
|
44
|
+
):
|
|
45
|
+
'整理 fastapi 项目中的 modules/__init__.py'
|
|
46
|
+
|
|
47
|
+
importContents: list[str] = []
|
|
48
|
+
managerContents: list[str] = []
|
|
49
|
+
|
|
50
|
+
xxdict: dict[str, set[Path]] = {}
|
|
51
|
+
for file in sorted(modules_path.glob('**/*Manager.py')):
|
|
52
|
+
if file.parent == modules_path:
|
|
53
|
+
subName = '.'
|
|
54
|
+
elif file.parent.parent == modules_path:
|
|
55
|
+
subName = f'.{file.parent.stem}'
|
|
56
|
+
else:
|
|
57
|
+
continue
|
|
58
|
+
xxdict.setdefault(subName, set()).add(file)
|
|
59
|
+
for subName in sorted(xxdict.keys()):
|
|
60
|
+
files = sorted(xxdict[subName])
|
|
61
|
+
importContents.append(f'from {subName} import {", ".join([x.stem for x in files])}')
|
|
62
|
+
managerContents.extend([f' {x.stem},' for x in sorted([y for x in xxdict.values() for y in x])])
|
|
63
|
+
|
|
64
|
+
managerContents = [x for x in managerContents if x]
|
|
65
|
+
contents = [
|
|
66
|
+
'\n'.join(importContents),
|
|
67
|
+
'managers = [\n' + '\n'.join(managerContents) + '\n]',
|
|
68
|
+
]
|
|
69
|
+
content = '\n\n'.join(contents) + '\n'
|
|
70
|
+
file = modules_path / '__init__.py'
|
|
71
|
+
printYellow(str(file))
|
|
72
|
+
printMagenta(content)
|
|
73
|
+
await bfile.writeText(file, content)
|
|
74
|
+
printGreen('OK')
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.command()
|
|
78
|
+
@syncCall
|
|
79
|
+
async def gen_init_py(
|
|
80
|
+
workspace_path: Path = typer.Argument(Path.cwd(), help='workspace 路径'),
|
|
81
|
+
):
|
|
82
|
+
'递归生成 __init__.py 文件'
|
|
83
|
+
|
|
84
|
+
ignoreSubDirs = [
|
|
85
|
+
'.git',
|
|
86
|
+
'venv',
|
|
87
|
+
'node_modules',
|
|
88
|
+
'.pytest_cache',
|
|
89
|
+
'__pycache__',
|
|
90
|
+
'.vscode',
|
|
91
|
+
]
|
|
92
|
+
folderList = bpath.listDir(workspace_path, True)
|
|
93
|
+
# 剔除子目录是这些的文件 .git venv ...
|
|
94
|
+
folderList = [x for x in folderList if not any([y in x.parts for y in ignoreSubDirs])]
|
|
95
|
+
for folder in folderList:
|
|
96
|
+
pyInitFile = folder / '__init__.py'
|
|
97
|
+
if not pyInitFile.exists():
|
|
98
|
+
printYellow(pyInitFile)
|
|
99
|
+
await bfile.writeText(pyInitFile, '')
|
|
100
|
+
printGreen('OK')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command()
|
|
104
|
+
@syncCall
|
|
105
|
+
async def to_lf(
|
|
106
|
+
path: Path = typer.Option(None, '--path', help='指定目录或具体图片文件,默认当前目录'),
|
|
107
|
+
):
|
|
108
|
+
'将所有文件转换为 LF 格式'
|
|
109
|
+
ignoreSubDirs = [
|
|
110
|
+
'.git',
|
|
111
|
+
'venv',
|
|
112
|
+
'node_modules',
|
|
113
|
+
'.pytest_cache',
|
|
114
|
+
'__pycache__',
|
|
115
|
+
]
|
|
116
|
+
path = path or Path(os.getcwd())
|
|
117
|
+
files = bpath.listFile(path, True)
|
|
118
|
+
# 剔除子目录是这些的文件 .git venv ...
|
|
119
|
+
files = [x for x in files if not any([y in x.parts for y in ignoreSubDirs])]
|
|
120
|
+
|
|
121
|
+
async def convertFile(file: Path):
|
|
122
|
+
try:
|
|
123
|
+
content = await bfile.readText(file)
|
|
124
|
+
if '\r\n' in content:
|
|
125
|
+
content = content.replace('\r\n', '\n')
|
|
126
|
+
await bfile.writeText(file, content)
|
|
127
|
+
printYellow(file)
|
|
128
|
+
except:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
await asyncio.gather(*[convertFile(file) for file in files])
|
|
132
|
+
printGreen('OK')
|
bcmd/tasks/crypto.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
import pyperclip
|
|
7
|
+
import typer
|
|
8
|
+
from beni import bcolor, bcrypto, btask
|
|
9
|
+
from beni.bfunc import syncCall
|
|
10
|
+
from beni.binput import genPassword
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
app: Final = btask.newSubApp('加密(v2)')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
@syncCall
|
|
18
|
+
async def encrypt_text():
|
|
19
|
+
'加密文本(使用剪贴板内容)'
|
|
20
|
+
content = pyperclip.paste()
|
|
21
|
+
assert content, '剪贴板内容不能为空'
|
|
22
|
+
bcolor.printGreen(content)
|
|
23
|
+
password = genPassword()
|
|
24
|
+
result = bcrypto.encryptText(content, password)
|
|
25
|
+
pyperclip.copy(result)
|
|
26
|
+
print('密文已复制到剪贴板')
|
|
27
|
+
bcolor.printYellow(result)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command()
|
|
31
|
+
@syncCall
|
|
32
|
+
async def decrypt_text():
|
|
33
|
+
'解密文本(使用剪贴板内容)'
|
|
34
|
+
content = pyperclip.paste().strip()
|
|
35
|
+
bcolor.printYellow(content)
|
|
36
|
+
while True:
|
|
37
|
+
try:
|
|
38
|
+
password = getpass.getpass('输入密码:')
|
|
39
|
+
result = bcrypto.decryptText(content, password)
|
|
40
|
+
print('解密成功')
|
|
41
|
+
bcolor.printGreen(result)
|
|
42
|
+
return
|
|
43
|
+
except KeyboardInterrupt:
|
|
44
|
+
break
|
|
45
|
+
except BaseException:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.command()
|
|
50
|
+
@syncCall
|
|
51
|
+
async def encrypt_json():
|
|
52
|
+
'生成JSON密文(使用剪贴板内容)'
|
|
53
|
+
content = pyperclip.paste()
|
|
54
|
+
try:
|
|
55
|
+
data = json.loads(content)
|
|
56
|
+
except:
|
|
57
|
+
return btask.abort('错误:剪贴板内容必须是JSON格式', content)
|
|
58
|
+
Console().print_json(data=data, indent=4, ensure_ascii=False, sort_keys=True)
|
|
59
|
+
password = genPassword()
|
|
60
|
+
result = bcrypto.encryptJson(data, password)
|
|
61
|
+
pyperclip.copy(result)
|
|
62
|
+
print('密文已复制到剪贴板')
|
|
63
|
+
bcolor.printYellow(result)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command()
|
|
67
|
+
@syncCall
|
|
68
|
+
async def decrypt_json():
|
|
69
|
+
'还原JSON密文内容(使用剪贴板内容)'
|
|
70
|
+
content = pyperclip.paste().strip()
|
|
71
|
+
bcolor.printYellow(content)
|
|
72
|
+
while True:
|
|
73
|
+
try:
|
|
74
|
+
password = getpass.getpass('输入密码:')
|
|
75
|
+
data = bcrypto.decryptJson(content, password)
|
|
76
|
+
Console().print_json(data=data, indent=4, ensure_ascii=False, sort_keys=True)
|
|
77
|
+
return
|
|
78
|
+
except KeyboardInterrupt:
|
|
79
|
+
break
|
|
80
|
+
except BaseException:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.command()
|
|
85
|
+
@syncCall
|
|
86
|
+
async def encrypt_file(
|
|
87
|
+
file: Path = typer.Argument(..., help='指定需要加密的文件')
|
|
88
|
+
):
|
|
89
|
+
'加密文件(文件路径使用剪贴板内容)'
|
|
90
|
+
assert file.is_file(), '文件不存在'
|
|
91
|
+
password = genPassword()
|
|
92
|
+
await bcrypto.encryptFile(file, password)
|
|
93
|
+
bcolor.printGreen('OK')
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
@syncCall
|
|
98
|
+
async def decrypt_file(
|
|
99
|
+
file: Path = typer.Argument(..., help='指定需要解密的文件')
|
|
100
|
+
):
|
|
101
|
+
'解密文件(文件路径使用剪贴板内容)'
|
|
102
|
+
assert file.is_file(), '文件不存在'
|
|
103
|
+
password = getpass.getpass('输入密码:')
|
|
104
|
+
await bcrypto.decryptFile(file, password)
|
|
105
|
+
bcolor.printGreen('OK')
|
bcmd/tasks/debian.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Final
|
|
3
|
+
|
|
4
|
+
from beni import bcolor, bfile, bpath, btask, binput
|
|
5
|
+
from beni.bfunc import syncCall
|
|
6
|
+
|
|
7
|
+
app: Final = btask.newSubApp('Debian 系统管理')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command()
|
|
11
|
+
@syncCall
|
|
12
|
+
async def install_python():
|
|
13
|
+
'安装python脚本'
|
|
14
|
+
pythonZipFile = ''
|
|
15
|
+
pythonName = ''
|
|
16
|
+
pythonVersion = ''
|
|
17
|
+
bcolor.printYellow(f'官网地址:https://www.python.org/downloads/source/')
|
|
18
|
+
bcolor.printYellow(f'先从官网上下载 python 源码压缩包(.tar.xz)')
|
|
19
|
+
while True:
|
|
20
|
+
pythonZipFile = input('输入python压缩文件名(如:Python-3.12.1.tar.xz):').strip()
|
|
21
|
+
if pythonZipFile.endswith('.tar.xz'):
|
|
22
|
+
pythonName = pythonZipFile.replace('.tar.xz', '')
|
|
23
|
+
pythonVersion = '.'.join(pythonName.split('-')[-1].split('.')[:-1])
|
|
24
|
+
break
|
|
25
|
+
else:
|
|
26
|
+
print('输入有误,请重新输入')
|
|
27
|
+
cmdList: list[str] = [
|
|
28
|
+
f'apt update',
|
|
29
|
+
f'apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev libbz2-dev -y',
|
|
30
|
+
f'tar -xf {pythonZipFile}',
|
|
31
|
+
f'cd {pythonName}',
|
|
32
|
+
f'./configure --enable-optimizations', # 配置编译选项
|
|
33
|
+
f'make -j `nproc`', # 编译源代码,`nproc` 会利用所有可用的 CPU 核心加快编译速度
|
|
34
|
+
f'make altinstall', # 安装 Python,altinstall 避免替换默认的 python 命令
|
|
35
|
+
f'cd ..',
|
|
36
|
+
f'rm -rf {pythonName}',
|
|
37
|
+
f'rm -rf /usr/local/bin/python',
|
|
38
|
+
f'ln -s /usr/local/bin/python{pythonVersion} /usr/local/bin/python',
|
|
39
|
+
f'rm -rf /usr/local/bin/pip',
|
|
40
|
+
f'ln -s /usr/local/bin/pip{pythonVersion} /usr/local/bin/pip',
|
|
41
|
+
f'python --version',
|
|
42
|
+
f'pip --version',
|
|
43
|
+
f'pip install pipx',
|
|
44
|
+
f'pipx install bcmd',
|
|
45
|
+
]
|
|
46
|
+
shFile = bpath.desktop(f'install-python-{pythonName}.sh')
|
|
47
|
+
await bfile.writeText(shFile, '\n'.join(cmdList))
|
|
48
|
+
bcolor.printGreen(f'将以下两个文件拷贝到服务器上,然后执行 sh {shFile.name} 即可安装 python')
|
|
49
|
+
print(pythonZipFile)
|
|
50
|
+
print(shFile)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command()
|
|
54
|
+
@syncCall
|
|
55
|
+
async def install_software():
|
|
56
|
+
'安装常用软件'
|
|
57
|
+
await binput.confirm('即将安装这些软件(7z nginx mariadb),是否确认?')
|
|
58
|
+
cmdList: list[str] = [
|
|
59
|
+
f'echo 更新软件包列表',
|
|
60
|
+
f'apt update',
|
|
61
|
+
f'echo 安装 7z',
|
|
62
|
+
f'apt install p7zip-full -y',
|
|
63
|
+
f'echo 安装 nginx',
|
|
64
|
+
f'apt install nginx -y',
|
|
65
|
+
f'systemctl start nginx',
|
|
66
|
+
f'systemctl enable nginx',
|
|
67
|
+
f'systemctl status nginx',
|
|
68
|
+
f'echo 安装 MariaDB',
|
|
69
|
+
f'apt install mariadb-server -y',
|
|
70
|
+
f'systemctl start mariadb',
|
|
71
|
+
f'systemctl enable mariadb',
|
|
72
|
+
f"sed -i 's/bind-address/# bind-address/' /etc/mysql/mariadb.conf.d/50-server.cnf"
|
|
73
|
+
f'systemctl restart mariadb',
|
|
74
|
+
f'systemctl status mariadb',
|
|
75
|
+
|
|
76
|
+
]
|
|
77
|
+
for cmd in cmdList:
|
|
78
|
+
os.system(cmd)
|
bcmd/tasks/download.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Final
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
import pyperclip
|
|
9
|
+
import typer
|
|
10
|
+
from beni import bcolor, bfile, bhttp, binput, bpath, btask
|
|
11
|
+
from beni.bfunc import syncCall, textToAry
|
|
12
|
+
|
|
13
|
+
app: Final = btask.app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
@syncCall
|
|
18
|
+
async def download(
|
|
19
|
+
url_file: Path = typer.Option(None, '--file', '-f', help='需要下载的url文件路径,默认使用剪贴板内容'),
|
|
20
|
+
save_path: Path = typer.Option(None, '--path', '-p', help='下载存放目录,默认当前目录'),
|
|
21
|
+
keep_directory: bool = typer.Option(False, '--keep', '-k', help='保持原始目录结构,默认不保持'),
|
|
22
|
+
):
|
|
23
|
+
'下载资源资源文件'
|
|
24
|
+
save_path = save_path or Path(os.getcwd())
|
|
25
|
+
|
|
26
|
+
if url_file:
|
|
27
|
+
if not url_file.exists():
|
|
28
|
+
btask.abort('指定文件不存在', url_file)
|
|
29
|
+
content = await bfile.readText(url_file)
|
|
30
|
+
else:
|
|
31
|
+
content = pyperclip.paste()
|
|
32
|
+
urlSet = set(textToAry(content))
|
|
33
|
+
|
|
34
|
+
for i, url in enumerate(urlSet):
|
|
35
|
+
print(f'{i + 1}. {url}')
|
|
36
|
+
print(f'输出目录:{save_path}')
|
|
37
|
+
await binput.confirm('是否确认?')
|
|
38
|
+
|
|
39
|
+
fileSet: set[Path] = set()
|
|
40
|
+
retryUrlSet: set[str] = set()
|
|
41
|
+
|
|
42
|
+
async def download(url: str):
|
|
43
|
+
urlPath = urlparse(url).path
|
|
44
|
+
if keep_directory:
|
|
45
|
+
file = bpath.get(save_path, '/'.join([x for x in urlPath.split('/') if x]))
|
|
46
|
+
else:
|
|
47
|
+
file = save_path / Path(urlPath).name
|
|
48
|
+
if file in fileSet:
|
|
49
|
+
file = file.with_stem(f'{file.stem}--{uuid4()}')
|
|
50
|
+
fileSet.add(file)
|
|
51
|
+
try:
|
|
52
|
+
bcolor.printGreen(url)
|
|
53
|
+
await bhttp.download(url, file)
|
|
54
|
+
except:
|
|
55
|
+
retryUrlSet.add(url)
|
|
56
|
+
bcolor.printRed(url)
|
|
57
|
+
|
|
58
|
+
await asyncio.gather(*[download(x) for x in urlSet])
|
|
59
|
+
|
|
60
|
+
for i in range(4):
|
|
61
|
+
if i > 0:
|
|
62
|
+
print(f'等待重试第 {i} 次')
|
|
63
|
+
await asyncio.sleep(3)
|
|
64
|
+
await asyncio.gather(*[download(x) for x in urlSet])
|
|
65
|
+
if not retryUrlSet:
|
|
66
|
+
break
|
|
67
|
+
urlSet = set(retryUrlSet)
|
|
68
|
+
retryUrlSet.clear()
|
|
69
|
+
|
|
70
|
+
if retryUrlSet:
|
|
71
|
+
pyperclip.copy('\n'.join(retryUrlSet))
|
|
72
|
+
bcolor.printYellow('部分下载失败,失败部分已复制到剪贴板')
|
|
73
|
+
else:
|
|
74
|
+
bcolor.printGreen('OK')
|