├── .gitignore
├── 0x01-监控反代pixiv链接
├── 0x01监听剪贴板自动反代.py
├── Readme.md
└── img
│ └── 1.png
├── 0x02-花火学园邀请码脚本
├── Readme.md
├── huahuo.py
├── img
│ ├── 1.png
│ └── 2.png
└── requirement.txt
├── 0x03-iwara下载脚本
├── Readme.md
├── img
│ ├── 1.png
│ ├── 2.png
│ └── 3.png
├── iwara.py
├── requirement.txt
└── thread_pool.py
├── 0x04-mzitu爬虫
├── Readme.md
├── img
│ ├── 1.png
│ └── 2.png
├── mzitu.py
└── requirement.txt
├── 0x05-绅士小站
├── Readme.md
├── gca.py
├── img
│ ├── 1.jpg
│ ├── 2.png
│ └── 3.png
└── requirement.txt
├── 0x06-pixiv动图合成
├── Readme.md
├── img
│ ├── 1.png
│ └── 2.png
├── pix.py
└── requirement.txt
├── 0x07-绅士仓库
├── .gitignore
├── Config
│ ├── __init__.py
│ ├── cangku.sql
│ ├── gca_tw.sql
│ └── openssl.cnf
├── Kit
│ ├── __init__.py
│ ├── chrome.py
│ └── kit.py
├── Pipfile
├── Pipfile.lock
├── login.py
└── main.py
├── 0x08-绅士小站2
├── .gitignore
├── Config
│ ├── __init__.py
│ └── openssl.cnf
├── Kit
│ ├── __init__.py
│ ├── chrome.py
│ └── kit.py
├── Pipfile
├── Pipfile.lock
└── main.py
├── 0x09-Pixiv插画tag数据导入Eagle
├── Home.library
│ ├── images
│ │ ├── ffe126de-87b1-915b-a0c3-6032b9343bbb.info
│ │ │ ├── 86352163-1.jpg
│ │ │ ├── 86352163-1_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── ffea5831-3817-f97f-c375-b5c38ebaa890.info
│ │ │ ├── 76315323.jpg
│ │ │ ├── 76315323_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info
│ │ │ ├── 89193684.jpg
│ │ │ ├── 89193684_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info
│ │ │ ├── 84372116.jpg
│ │ │ ├── 84372116_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info
│ │ │ ├── 70899400.png
│ │ │ ├── 70899400_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info
│ │ │ ├── 92016561-0.jpg
│ │ │ ├── 92016561-0_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info
│ │ │ ├── 73190808-0.jpg
│ │ │ ├── 73190808-0_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info
│ │ │ ├── 83619652-11.jpg
│ │ │ ├── 83619652-11_thumbnail.png
│ │ │ └── metadata.json
│ │ ├── fff5e73d-460c-fbad-e90a-3080c6a7a923.info
│ │ │ ├── 88199924.jpg
│ │ │ ├── 88199924_thumbnail.png
│ │ │ └── metadata.json
│ │ └── fff75130-4feb-2d23-24bc-1b098faab96e.info
│ │ │ ├── 82329785.png
│ │ │ ├── 82329785_thumbnail.png
│ │ │ └── metadata.json
│ ├── metadata.json
│ └── tags.json
├── Readme.md
├── img
│ ├── 0.png
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ └── 4.png
├── pixiv2eagle.py
├── requirement.txt
└── thread_pool.py
├── 0x0a-碧蓝航线-静态立绘解密加密工具
├── Readme.md
├── Test
│ ├── Mesh
│ │ └── beierfasite_4-mesh.obj
│ ├── Picture
│ │ └── beierfasite_4.png
│ └── Texture2D
│ │ └── beierfasite_4.png
├── blhx_img_tool.py
├── img
│ ├── 0.png
│ ├── 1.png
│ ├── 2.png
│ └── 3.png
├── requirement.txt
└── 解封包_0补偿与0.5补偿对比测试结果
│ ├── test_result.txt
│ ├── 封包_+0.5_×_明显白线区域_zhenhai_2.png
│ ├── 封包_+0.5_√_与游戏内拆包文件 一致_beierfasite_4.png
│ ├── 封包_+0_√_zhenhai_2.png
│ ├── 封包_+0_√_与游戏内的拆包出的文件对比,有些微白线_beierfasite_4.png
│ ├── 测试图片
│ ├── Picture
│ │ ├── beierfasite_4.png
│ │ └── zhenhai_2.png
│ └── Texture2D
│ │ ├── beierfasite_4.png
│ │ └── zhenhai_2.png
│ ├── 解包_+0.5_√_beierfasite_4.png
│ ├── 解包_+0.5_√_zhenhai_2.png
│ ├── 解包_+0_×_左下角与右下角有白色横线_zhenhai_2.png
│ └── 解包_+0_×_明显白线区域_beierfasite_4.png
├── 0x0b-批量拖拽-文件后缀名重命名工具
├── Readme.md
├── exe
│ └── FileRenamer v1.1.exe
├── filerenamer.py
└── img
│ └── 0.png
├── LICENSE
└── Readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ##### Tools project .gitignore Start #####
2 | # log
3 | log/*
4 |
5 | ##### Tools project .gitignore End #####
6 |
7 |
8 | ##### Github Python.gitignore Start #####
9 | # Byte-compiled / optimized / DLL files
10 | __pycache__/
11 | *.py[cod]
12 | *$py.class
13 |
14 | # C extensions
15 | *.so
16 |
17 | # Distribution / packaging
18 | .Python
19 | build/
20 | develop-eggs/
21 | dist/
22 | downloads/
23 | eggs/
24 | .eggs/
25 | lib/
26 | lib64/
27 | parts/
28 | sdist/
29 | var/
30 | wheels/
31 | share/python-wheels/
32 | *.egg-info/
33 | .installed.cfg
34 | *.egg
35 | MANIFEST
36 |
37 | # PyInstaller
38 | # Usually these files are written by a python script from a template
39 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
40 | *.manifest
41 | *.spec
42 |
43 | # Installer logs
44 | pip-log.txt
45 | pip-delete-this-directory.txt
46 |
47 | # Unit test / coverage reports
48 | htmlcov/
49 | .tox/
50 | .nox/
51 | .coverage
52 | .coverage.*
53 | .cache
54 | nosetests.xml
55 | coverage.xml
56 | *.cover
57 | *.py,cover
58 | .hypothesis/
59 | .pytest_cache/
60 | cover/
61 |
62 | # Translations
63 | *.mo
64 | *.pot
65 |
66 | # Django stuff:
67 | *.log
68 | local_settings.py
69 | db.sqlite3
70 | db.sqlite3-journal
71 |
72 | # Flask stuff:
73 | instance/
74 | .webassets-cache
75 |
76 | # Scrapy stuff:
77 | .scrapy
78 |
79 | # Sphinx documentation
80 | docs/_build/
81 |
82 | # PyBuilder
83 | .pybuilder/
84 | target/
85 |
86 | # Jupyter Notebook
87 | .ipynb_checkpoints
88 |
89 | # IPython
90 | profile_default/
91 | ipython_config.py
92 |
93 | # pyenv
94 | # For a library or package, you might want to ignore these files since the code is
95 | # intended to run in multiple environments; otherwise, check them in:
96 | # .python-version
97 |
98 | # pipenv
99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
102 | # install all needed dependencies.
103 | #Pipfile.lock
104 |
105 | # poetry
106 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107 | # This is especially recommended for binary packages to ensure reproducibility, and is more
108 | # commonly ignored for libraries.
109 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110 | #poetry.lock
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
162 | ##### Github Python.gitignore End #####
--------------------------------------------------------------------------------
/0x01-监控反代pixiv链接/0x01监听剪贴板自动反代.py:
--------------------------------------------------------------------------------
1 | import win32clipboard as wc
2 | import time
3 | import re
4 |
5 | """
6 | __author__:Coder-Sakura
7 | __time__:2020/07/06 00:44:53
8 | 监听所复制的pixiv插画地址,并将其反代成无需梯子即可访问的反代链接.
9 | 比如:
10 | 在pixiv复制了https://i.pximg.net/img-master/img/2020/07/01/21/08/21/82688845_p0_master1200.jpg
11 | 脚本会转换为https://i.pixiv.cat/img-master/img/2020/07/01/21/08/21/82688845_p0_master1200.jpg
12 | 会自动拷贝到剪贴板.
13 | """
14 |
15 | #获取粘贴板里的内容
16 | def getCopyTxet():
17 | # 异常则关闭,防止因为复制文件/文件夹/图片/视频等非文本类型文件而崩溃
18 | wc.OpenClipboard()
19 | try:
20 | copytxet = wc.GetClipboardData()
21 | except:
22 | wc.CloseClipboard()
23 | else:
24 | wc.CloseClipboard()
25 | return copytxet
26 |
27 | def setText(text):
28 | wc.OpenClipboard()
29 | wc.EmptyClipboard()
30 | wc.SetClipboardText(text)
31 | wc.CloseClipboard()
32 |
33 | def r_sub(text):
34 | return re.sub(r"pximg.net","pixiv.cat",text)
35 |
36 | if __name__ == '__main__':
37 | # 存储上次剪切板内容
38 | last_data = None
39 | while True:
40 | # 轮询
41 | time.sleep(1)
42 | data = getCopyTxet()
43 | if data == None:
44 | continue
45 |
46 | if data != last_data:
47 | # 如果内容和上次的不同
48 | print("剪贴板内容为:\n" + data,'\n')
49 |
50 | if "https://i.pximg.net" in data:
51 | data = r_sub(data)
52 | setText(data)
53 | print("反代链接替换成功:",data,'\n')
54 |
55 | last_data = data
--------------------------------------------------------------------------------
/0x01-监控反代pixiv链接/Readme.md:
--------------------------------------------------------------------------------
1 | **自动替换pixiv链接为反代链接**
2 |
3 | ---
4 |
5 | 监听文本复制并替换成可直接访问的反代链接
6 |
7 | ---
8 |
9 | 使用方法
10 |
11 | ```
12 | python 0x01监听剪贴板自动反代.py
13 | ```
14 |
15 | 复制:https://i.pximg.net/c/360x360_70/img-master/img/2021/02/14/22/20/18/87780332_p0_square1200.jpg
16 |
17 | 自动替换为:https://i.pixiv.cat/c/360x360_70/img-master/img/2021/02/14/22/20/18/87780332_p0_square1200.jpg
18 |
19 |
20 |
21 | 运行截图
22 |
23 | 
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/0x01-监控反代pixiv链接/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x01-监控反代pixiv链接/img/1.png
--------------------------------------------------------------------------------
/0x02-花火学园邀请码脚本/Readme.md:
--------------------------------------------------------------------------------
1 | ## 花火学园注册邀请码脚本
2 |
3 | ---
4 |
5 | 使用方法:
6 |
7 | 
8 |
9 |
10 |
11 | 如果题目不在题库,则会这样显示:
12 |
13 | 
14 |
15 |
--------------------------------------------------------------------------------
/0x02-花火学园邀请码脚本/huahuo.py:
--------------------------------------------------------------------------------
1 | import requests,json
2 | import random
3 |
4 | url ="https://www.say-huahuo.com/qa.php"
5 |
6 | headers = {
7 | "content-type":"application/json;charset=UTF-8",
8 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400",
9 | }
10 |
11 | se = requests.session()
12 |
13 | doc = [
14 | {"title":"下列哪个数据类型占用内存空间最少","answer":"Byte"},
15 | {"title":"以下哪个不是世萌萌王获得者","answer":"木之本樱"},
16 | {"title":"この世の果てで恋を唄う少女YU-NO里的主角有马拓也被称为?","answer":"行走的性欲"},
17 | {"title":"key社的春夏冬秋四季分别是,CLANNAD、AIR、Kanon和____。 ","answer":"ONE~辉之季节~"},
18 | {"title":"《北斗神拳》中不属于健次郎的奥义的是","answer":"无明封杀阵"},
19 | {"title":"动画《干物妹!小埋》中,小埋的游戏名称是什么","answer":"UMR"},
20 | {"title":"《尘骸魔京》中追风者寻找的夫婿就是","answer":"九门克绮"},
21 | {"title":"《黑执事》中,伊丽莎白是","answer":"剑术天才"},
22 | {"title":"《黑执事》中称呼女装的夏尔为美丽小巧的知更鸟的是","answer":"阿雷斯特·钱帕"},
23 | {"title":"《近月少女的礼仪》中人物的名字与以下哪个相关","answer":"银行"},
24 | {"title":"《名侦探柯南》《少年侦探团》《文豪野犬》这三部动画都与以下那位作家有关联","answer":"太宰治"},
25 | {"title":"《天书奇谭》中刚出生的蛋生一开始吃了什么","answer":"大饼"},
26 | {"title":"ChuSingura46+1中谁拯救了陷入绝望的直刃","answer":"主税"},
27 | {"title":"CLANNAD有多少集","answer":"49"},
28 | {"title":"在第三部jojo中DIO的the World最终暂停了几秒?","answer":"九秒"},
29 | {"title":"以下哪个动画不是游戏改编的?","answer":"2008年P.A.WORKS的真实之泪"},
30 | {"title":"中二社因为那部作品转变为巨乳社的?","answer":"sppl"},
31 | {"title":"下列哪一位是G吧大吧主?","answer":"ACG之神"},
32 | {"title":"请问论坛的通用解压密码是?","answer":"Say花火"},
33 | {"title":"下面哪个 jQuery 方法用于隐藏被选元素?","answer":"display(none)"},
34 | {"title":"传颂之物 二人的白皇中旧人类怎么样了?","answer":"没救了,都变树了"},
35 | {"title":"死に逝く君、館に芽吹く憎悪中,上位种族的弱点是什么?","answer":"血液"},
36 | {"title":"老虚是____。","answer":"爱的战士"},
37 | {"title":"动画《干物妹!小埋》中,被网友调侃为GBA的是在哪个场景?","answer":"开车去海边的时候"},
38 | {"title":"一休哥的法名是?","answer":"一休宗纯"},
39 | {"title":"《らぶらぶシスターズ ~花嫁&姉妹達とのドキドキハーレム生活》中H了就会变瘦的妹子是?","answer":"模特妹妹"},
40 | {"title":"这幅画的作者是谁?","answer":"梵高"},
41 | {"title":"蹭的累是什么的谐音","answer":"腹黑"},
42 | {"title":"财团b指的是?","answer":"万代"},
43 | {"title":"正田崇是什么系的剧本家?","answer":"燃系"},
44 | {"title":"被誉为为神仙水的是?","answer":"SK-II"},
45 | {"title":"奇迹的世代不包括下面的谁?","answer":"火神大我"},
46 | {"title":"駄作中四个女主角中,哪一位是渴求别人的爱的怪物?","answer":"菜々ヶ木アリス"},
47 | {"title":"在Linux中,哪一个标记可以把该命令(组)作为子进程","answer":"&"},
48 | {"title":"下面哪个声优的给初音未来的歌曲提供了舞蹈动作?","answer":"小仓唯"},
49 | {"title":"以下哪个作品不含ntr?","answer":"纯白交响曲"},
50 | {"title":"哆啦A梦原来是啥颜色?","answer":"黄色"},
51 | {"title":"以下哪个角色出自约会大作战?","answer":"时崎狂三"},
52 | {"title":"请问论坛的学园长是?","answer":"Say花火"},
53 | {"title":"百合同人作品花吻在上的20部正作中,哪部于2010年8月被改编成OVA动画?","answer":"3"},
54 | {"title":"蜥蜴の尻尾切り中男主为什么痴迷于人体再生?","answer":"女朋友得癌症了"},
55 | {"title":"游戏Myself ; Yourself中若月修辅是____之一。","answer":"男主"},
56 | {"title":"在 PHP 中,所有的变量以哪个符号开头?","answer":"$"},
57 | {"title":"以下不是国产gal的是?","answer":"格林花园的少女"},
58 | {"title":"自称superai的人工智能叫什么名字?","answer":"绊爱"},
59 | {"title":"战极姬系列里哪部有NTR的感觉?","answer":"战极姬6"},
60 | {"title":"爱衣酱大胜利出自哪部番?","answer":"我的女友和青梅竹马的惨烈修罗场"},
61 | {"title":"g弦上的魔王被称为?","answer":"赌上性命的——纯爱"},
62 | {"title":"幸福噩梦中主人公前期靠什么识别梦和现实?","answer":"眼镜"},
63 | {"title":"gore screaming show里ユカ的真名是?","answer":"音无紫"},
64 | {"title":"以下不属于米泽円配过的音是","answer":"樱木由加利"},
65 | {"title":"鬼哭街中男主的绰号是?","answer":"紫电掌"},
66 | {"title":"下列每条选项的声优中,所属事务所完全不同的是","answer":"佐仓绫音 东山央奈 种田梨沙"},
67 | {"title":"清水爱不光是一位声优也是一名职业运动员,那么这门项目是什么?","answer":"体操"},
68 | {"title":"多啦A梦的体重是多少?","answer":"129.3斤"},
69 | {"title":"动画《山田君与7人魔女》里白石丽的能力是?","answer":"交换身体"},
70 | {"title":"在Fate HF线中,土狼说他喜欢谁?","answer":"樱"},
71 | {"title":"国内玩家对于《夏娃年代纪》里大公主的争议主要原因为?","answer":"ntr"},
72 | {"title":"贾宝玉身上佩戴的玉叫啥?","answer":"通灵宝玉"},
73 | {"title":"下面哪一部没有和泉万夜参与脚本?","answer":"maggot biats"},
74 | {"title":"下面哪一部不是钟表社的作品?","answer":"maggot biats"},
75 | {"title":"传颂之物中,谁和谁去开店了?","answer":"藤香和卡米拉"},
76 | {"title":"观沧海的作者是?","answer":"曹操"},
77 | {"title":"装甲恶鬼村正英雄篇中最后村正最后成为了谁的剑?","answer":"一条"},
78 | {"title":"哪个作品与田中罗密欧,龙骑士07,都乃河勇人三人都相关?","answer":"rewrite"},
79 | {"title":"日本同人漫画家二阶堂みつき和哪位漫画家关系最近?","answer":"大岛永远"},
80 | {"title":"苍翼默示录动漫是游戏里哪一部的主线剧情?","answer":"厄运扳机"},
81 | {"title":"以下那部漫画不是BL向作品?","answer":"累"},
82 | {"title":"下面那个不是新十年的假面骑士?","answer":"假面骑士Decade"},
83 | {"title":"渡りの诗是一首什么语言的歌?","answer":"包含多种"},
84 | {"title":"纸上魔法使中哪一个是?","answer":"游行寺夜子"},
85 | {"title":"恋樱中带来幸福的死神是?","answer":"缇娜"},
86 | {"title":"游戏《秘密の花园》里,女主是?","answer":"母亲和父亲生的"},
87 | {"title":"以下哪个加藤不存在于三次元?","answer":"加藤断"},
88 | {"title":"以下哪个人物和佐伯克哉出现在同一款游戏里?","answer":"御堂孝典"},
89 | {"title":"以下哪个是Cross Days里的可攻略对象?","answer":"伊藤诚"},
90 | {"title":"以下哪个不是百合作品?","answer":"処女はお姉さまに恋してる"},
91 | {"title":"以下哪个不是elf会社的作品?","answer":"トラク=ナクア."},
92 | {"title":"以下四个角色哪个喜欢女性?","answer":"佐藤圣"},
93 | {"title":"多啦A梦2012年剧场版中出现的稀有独角仙叫啥?","answer":"黄金赫拉克勒斯"},
94 | {"title":"关于特摄假面骑士中骑士的安全区是下列那个地点?","answer":"水域"},
95 | {"title":"以下哪位导演没有参与异形系列?","answer":"昆汀·塔伦蒂诺"},
96 | {"title":"被钉X型十字架殉道的是?","answer":"圣安德烈"},
97 | {"title":"该场景出现时真白说的第一句话是什么?","answer":"呐,你想成为什么颜色?"},
98 | {"title":"提起法国剧作家罗斯丹的代表作《西哈诺》,你会想到以下哪部galgame?","answer":"美好的每一天"},
99 | {"title":"下列歌手不是隶属于Sony音乐旗下的是","answer":"春奈露娜"},
100 | {"title":"2017年7月20日发售的扶她百合游戏名为?","answer":"戦国の黒百合~ふたなり姫忍ぶ少女達~"},
101 | {"title":"被称为西域战神的是?","answer":"阿古柏"},
102 | {"title":"下面哪条 SQL 语句用于在数据库中插入新的数据?","answer":"INSERT NEW"},
103 | {"title":"星之卡比中,卡比吞下小怪之后会发生什么","answer":"变身"},
104 | {"title":"超炮中被称为海澜之家和上升气流lv6的是?","answer":"佐天泪子"},
105 | {"title":"“所谓的人类,是连短短的十分钟也等不起的!”出自?","answer":"AngleBeats!"},
106 | {"title":"生天目仁美曾为四大百合女王中的哪个配音?","answer":"花园静马"},
107 | {"title":"前前前世出自哪部电影?","answer":"你的名字"},
108 | {"title":"世界上最ng恋爱是哪位脚本作家的作品?","answer":"丸户史明"},
109 | {"title":"Teaching Feeling中最有名的动作是","answer":"摸头"},
110 | {"title":"与今野绪雪所写的女校故事《玛利亚的凝望》所对应的男校故事名为?","answer":"释迦摩尼也凝望"},
111 | {"title":"胃药魔女冈田磨里的成名作是什么?","answer":"真实之泪"},
112 | {"title":"动画《奇幻贵公子》的主角涉谷一也,被女主称为Naru的原因为。","answer":"百合熊岚"},
113 | {"title":"以下那部不是15年的动漫四大名著?","answer":"自恋"},
114 | {"title":"游戏《白色相簿》中,绪方理奈的职业是?","answer":"人气偶像"},
115 | {"title":"欧派星人是哪位画师?","answer":"西又葵"},
116 | {"title":"C3-魔方少女中的女主角是谁?","answer":"菲娅"},
117 | {"title":"妖精森林的小不点第八集中,什么使两派人和好?","answer":"蜂蜜酒"},
118 | {"title":"动画《绝园的暴风雨》里,不破爱花经常爱引用____的句子。","answer":"哈姆雷特"},
119 | {"title":"下面哪个与游戏主题无关?","answer":"ReLIFE"},
120 | {"title":"Gamers!第八集中,出现的key社作品为?","answer":"CLANNAD"},
121 | {"title":"“エル・プサイ・コングルゥ!”出自?","answer":"Steins;Gate"},
122 | {"title":"以下那作不是KEY社作品?","answer":"《Clover Day’s》"},
123 | {"title":"《近月少女的礼仪》中人物的名字与以下哪个有关?","answer":"寺庙"},
124 | {"title":"下面的四位中的那位是其中最漂亮的女主?","answer":"立华奏"},
125 | {"title":"斯特拉的魔法中,SNS部之前的作品叫什么?","answer":"TEARMENT TEARSTAR"},
126 | {"title":"小邪神飞踢!第二季中,出现的新人物名字叫什么?","answer":"僵僵"},
127 | {"title":"学园长花火喜欢____。","answer":"巨乳"},
128 | {"title":"在小说マリア様がみてる中出现的,小笠原祥子的胸围是?","answer":"65D"},
129 | {"title":"约会大作战小说第21卷结束时中谁已经死了?","answer":"冰芽川四糸乃"},
130 | ]
131 |
132 | resp = se.get(url).text
133 | print(resp)
134 | j = json.loads(resp)
135 |
136 | data = []
137 | for k,i in enumerate(j):
138 | d = {}
139 | # print(i["code"])
140 | # print(i["title"])
141 | # print(i["options"])
142 | for qa in doc:
143 | # print(i["title"],len(i["title"]))
144 | # print(qa["title"],len(qa["title"]))
145 | question = i["title"]
146 | question = i["title"].replace("?","")
147 | if question in qa["title"]:
148 | d["answer"] = qa["answer"]
149 | # print(d["answer"])
150 | break
151 | else:
152 | print(question)
153 | print("\n当前第{}题不在题库中...".format(k+1))
154 | print("题目为: ",i["title"])
155 | print("选项为: ",i["options"])
156 | print("第一项输入1,第二项输入2,第三项输入3,第四项输入4")
157 | num = input("选择答案项: ")
158 | if num == "":
159 | d["answer"] = random.choice(i["options"])
160 | else:
161 | n = int(num)-1
162 | d["answer"] = i["options"][n]
163 | d["answer"] = i["options"][n]
164 |
165 | d["code"] = i["code"]
166 | data.append(d)
167 | # break
168 |
169 | p_resp = se.post(url,headers=headers,json=data).text
170 | res = json.loads(p_resp)
171 | print("考试分数为: ",res["score"])
172 | print("获得邀请码: ",res["invitecode"])
173 | # print(p_resp.text)
--------------------------------------------------------------------------------
/0x02-花火学园邀请码脚本/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x02-花火学园邀请码脚本/img/1.png
--------------------------------------------------------------------------------
/0x02-花火学园邀请码脚本/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x02-花火学园邀请码脚本/img/2.png
--------------------------------------------------------------------------------
/0x02-花火学园邀请码脚本/requirement.txt:
--------------------------------------------------------------------------------
1 | requests
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/Readme.md:
--------------------------------------------------------------------------------
1 | ## Iwara下载脚本 V1.0.5
2 |
3 | ---
4 |
5 | ## 功能
6 |
7 | - [x] 支持下载`iwara`用户公开投稿的所有视频,视频文件下载存放在`./down/用户名`或`ROOT_DIR/用户名`文件夹下
8 | - [x] 支持自定义下载路径 (`ROOT_DIR=your path`)
9 | - [x] 支持单个`iwara`视频链接下载,视频文件下载存放在`./down`或`ROOT_DIR`文件夹下
10 | - [x] **添加视频完整性校验机制** (校验失败会进行重试、会删除校验失败的视频)——22.3.11
11 | - [x] **添加重试机制** (默认3次,`RETRY_COUNT=3`),支持重试网络错误或校验失败的视频——22.3.11
12 | - [x] 支持添加`cookie`下载`private`视频
13 | - [x] 多线程下载(默认8线程,`THREAD_NUM=8`)
14 | - [x] 日志记录功能,支持`debug`模式打印更多`log` (`DEBUG=True`)
15 |
16 |
17 |
18 | ## 使用方法
19 |
20 | ### 1、ROOT_DIR
21 |
22 | ```python
23 | # 填写下载目录
24 | ROOT_DIR = r"your root dir"
25 |
26 | # 若需要下载private视频,可自行填入账号cookie
27 | # 实验性
28 | cookie = "your cookie"
29 | ```
30 |
31 |
32 |
33 | ### 2、下载依赖并启动
34 |
35 | ```
36 | pip install -r requirement.txt
37 | python iwara.py
38 | ```
39 |
40 |
41 |
42 | ## 运行截图
43 |
44 | 
45 |
46 | 
47 |
48 | 
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x03-iwara下载脚本/img/1.png
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x03-iwara下载脚本/img/2.png
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x03-iwara下载脚本/img/3.png
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/iwara.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import sys
4 | import json
5 | import time
6 | import glob
7 | import requests
8 | from lxml import etree
9 | from loguru import logger
10 | from urllib.parse import unquote
11 | # 强制取消警告
12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
13 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
14 |
15 |
16 | from thread_pool import *
17 |
18 |
19 | # ======================= CONFIG =================== #
20 | # Version
21 | VERSION = "V1.0.8"
22 | # 如果需要下载private视频,则将自己的cookie填入此处
23 | cookie = ""
24 | # 设置为True 开启DEBUG模式
25 | DEBUG = False
26 | # 自定义下载路径
27 | ROOT_DIR = r""
28 | # thread num
29 | THREAD_NUM = 8
30 |
31 | # log config
32 | if DEBUG:
33 | level = "DEBUG"
34 | else:
35 | level = "INFO"
36 |
37 | # remove default handler
38 | logger.remove()
39 | # 控制台输出
40 | logger.add(
41 | sys.stderr,
42 | level=level
43 | )
44 | log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log")
45 | # 日志写入
46 | logger.add(
47 | os.path.join(log_path, "{time}.log"),
48 | encoding="utf-8",
49 | rotation="00:00",
50 | enqueue=True,
51 | level=level
52 | )
53 | logger.add(
54 | os.path.join(log_path, "[ERROR video] {time}.log"),
55 | encoding="utf-8",
56 | rotation="00:00",
57 | enqueue=True,
58 | level="ERROR"
59 | )
60 | # ======================= CONFIG =================== #
61 |
62 |
63 | # 重试视频列表 - 视频完整性校验失败
64 | # [{"args":args, "count":count},{}...]
65 | RETRY_COUNT = 3
66 | ERR_TASK_LIST = []
67 |
68 |
69 | def check_video(resp, path)->bool:
70 | """
71 | :params resp: 响应体
72 | :params path: 本地视频
73 | """
74 | local_size = int(os.path.getsize(path))
75 | resp_size = int(resp.headers["content-length"])
76 | logger.debug(f"{local_size} {resp_size}")
77 | if local_size != resp_size:
78 | return False
79 | else:
80 | return True
81 |
82 | def byte2size(value):
83 | value = int(value)
84 | units = ["B", "KB", "MB", "GB", "TB", "PB"]
85 | size = 1024.0
86 | for i in range(len(units)):
87 | if (value/size) < 1:
88 | return "%.2f%s" % (value, units[i])
89 | value = value/size
90 |
91 | def dealwith_title(title):
92 | title = re.sub('[\/:*?"<>|]', '_', title)
93 | title = title.replace("&", "&").replace("\u3000", "").\
94 | replace(".", "_")
95 | return title
96 |
97 | def check_errTask_exists(data, err_task_list):
98 | for _ in err_task_list:
99 | if data == _["args"]:
100 | return True
101 | return False
102 |
103 |
104 | class IwaraDownloader:
105 | """
106 | 目前支持
107 | 1、iwara用户的所有视频下载,存储在单个的<用户名称>文件夹中
108 | 2、单个iwara视频下载,存储在down文件夹下
109 |
110 | iwara地址:https://ecchi.iwara.tv/videos/v2km9s5npeterzlad
111 | 对应api接口为:https://ecchi.iwara.tv/api/video/v2km9s5npeterzlad
112 | """
113 | def __init__(self,links):
114 | self.iwara_host = "https://ecchi.iwara.tv"
115 |
116 | # 拼接videos页面
117 | # https://ecchi.iwara.tv/users/Forget%20Skyrim./videos?page=3
118 | self.host = "https://ecchi.iwara.tv{}?language=zh&page={}"
119 |
120 | # 视频信息接口
121 | self.api_url = "https://ecchi.iwara.tv/api/video/{}"
122 | # conver_link:用来判断跳出的页码
123 | self.page_num = 0
124 |
125 | # 页面请求头
126 | self.page_headers = {
127 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400",
128 | "referer":"https://ecchi.iwara.tv/",
129 | "accept-language": "zh-CN,zh;q=0.9"
130 | }
131 | # get_data请求头
132 | self.headers = {
133 | "host":"ecchi.iwara.tv",
134 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400",
135 | }
136 | # 添加用户自定义cookie,主要用于private视频获取
137 | if cookie:
138 | self.headers["cookie"] = cookie
139 |
140 | # 需要下载的iwara链接列表
141 | if links == []:
142 | logger.error("Iwara链接列表为空!")
143 | exit()
144 | else:
145 | # 去重
146 | self.original_links = list(set(links))
147 | logger.info(f"输入链接数量 - {len(self.original_links)}")
148 | logger.debug(f"输入链接 - {self.original_links}")
149 |
150 | # 初始化下载主目录
151 | self.root_path = os.path.abspath(os.path.dirname(__file__)) if not ROOT_DIR else ROOT_DIR
152 | if not os.path.exists(self.root_path):os.mkdir(self.root_path)
153 |
154 | self.error_code_list = {
155 | "ErrorVideoUrl": "无效的视频链接",
156 | "ErrorUserUrl": "错误的用户主页链接",
157 | "EmptyVideoLinks": "该用户无视频",
158 | "PrivateVideoUrl": "该视频为private视频 或 视频无法播放下载",
159 | "JsonDecodeError": "解析json数据出错",
160 | "UnknownError": "不明原因导致的失败",
161 | "NetWorkTimeoutError": "网络或代理超时"
162 | }
163 |
164 | def get_resp(self,url,
165 | params=None,
166 | headers=None,
167 | retry_num=3,
168 | stream=False,
169 | ):
170 | """
171 | 一个简单的网络请求函数
172 | """
173 | if headers == None:
174 | headers = self.page_headers
175 | try:
176 | resp = requests.get(url,params=params,
177 | headers=headers,
178 | timeout=20,
179 | stream=stream,
180 | verify=False
181 | )
182 | except Exception as e:
183 | if retry_num >= 3:
184 | logger.info(f"网络错误,正在重试 - {url}")
185 | return self.get_resp(url,params=params,headers=headers,retry_num=retry_num-1,stream=stream)
186 | else:
187 | logger.warning(f"超出重试次数 - {url}")
188 | logger.warning(f"Exception - {e}")
189 | return None
190 | else:
191 | time.sleep(1)
192 | return resp
193 |
194 | def get_title(self,link):
195 | """提供iwara视频链接,获取标题"""
196 | html = self.get_resp(link)
197 | if type(html) == type(None):
198 | return "NetWorkTimeoutError"
199 | response = html.text.replace("\u3000","")
200 | r = re.findall("
(.*?)", response)[0].split("| Iwara")[0]
201 | title = dealwith_title(r)
202 | return title
203 |
204 | def get_data(self,link):
205 | """
206 | 获取视频接口信息,返回原始视频链接和动态host
207 | :params link: iwara video url, https://ecchi.iwara.tv/videos/9jqbjse8qhzmo8ja
208 | :return: [source_url,host] or err str
209 | """
210 | # url - https://ecchi.iwara.tv/api/video/4kbwafpkz0tyv3k4m
211 | url = self.api_url.format(link.split("/")[-1])
212 | logger.debug(f" - {url}")
213 | resp = self.get_resp(url,headers=self.headers)
214 |
215 | if resp.text == '[]':
216 | # private视频
217 | return "PrivateVideoUrl"
218 | else:
219 | try:
220 | json_data = json.loads(resp.text)
221 | except json.decoder.JSONDecodeError as e:
222 | logger.warning(f"JsonDecodeError Exception - {e}")
223 | return "JsonDecodeError"
224 | except Exception as e:
225 | logger.warning(f"UnknownError Exception - {e}")
226 | return "UnknownError"
227 | else:
228 | source_url = f'https:{[r["uri"] for r in json_data if r["resolution"] == "Source"][0]}'
229 | host = source_url.split("file.php")[0]
230 | return [source_url,host]
231 |
232 | def get_links(self,link,pageNum):
233 | """
234 | https://ecchi.iwara.tv/users/%E5%92%95%E5%98%BF%E5%98%BF/videos?page=1
235 | https://ecchi.iwara.tv/users/forget-skyrim
236 | 获取当前页面的iwara链接和页数
237 | 视频不超过16个的用户,无videos页面
238 | """
239 | href_list,title_list = [],[]
240 | resp = self.get_resp(link,headers=self.page_headers).text
241 | home_obj = etree.HTML(resp)
242 | home_expression = '//div[@id="block-views-videos-block-2"]//div[@class="more-link"]//a/@href'
243 |
244 | # 无效作者主页/无效视频
245 | if "ユーザーリスト | Iwara" in resp:
246 | return ["ErrorUserUrl"],0
247 |
248 | # 无videos页面 --> 获取用户主页视频
249 | if home_obj.xpath(home_expression) == []:
250 | # /videos/83wvbuz4yfl7bn3l
251 | href_expression = '//div[@id="block-views-videos-block-2"]//div[contains(@class,"views-responsive-grid")]//h3/a/@href'
252 | title_expression = '//div[@id="block-views-videos-block-2"]//div[contains(@class,"views-responsive-grid")]//h3/a/text()'
253 | if home_obj.xpath(href_expression) != []:
254 | href_list = [f"{self.iwara_host}{r}" for r in home_obj.xpath(href_expression)]
255 | logger.info(f"User Have {len(href_list)} Videos. Not Video Page")
256 | # 转义html字符
257 | title_list = [self.get_title(href) for href in href_list]
258 |
259 | # 补充无Video Page的业务跳出条件
260 | total_pageNum = -1
261 | # 用户无视频
262 | else:
263 | logger.info("User Not Have Video")
264 | return [],0
265 | # 有videos页面 --> 获取用户videos页面链接
266 | else:
267 | useVideoUrl = home_obj.xpath(home_expression)[0].split("?")[0]
268 | page_link = self.host.format(useVideoUrl,pageNum)
269 |
270 | obj = etree.HTML(self.get_resp(page_link,headers=self.page_headers).text)
271 |
272 | href_list = ["{}{}".format(self.iwara_host,r) for r in obj.xpath("//h3[@class='title']/a/@href")]
273 | # logger.info(f"Page Have {len(href_list)} Videos")
274 | # 转义html字符
275 | title_list = [dealwith_title(r) for r in obj.xpath(""".//div[@class='field-item even']/a/img/@title""")]
276 | # 页数
277 | try:
278 | total_pageNum = int(obj.xpath("//li[@class='pager-last last']//@href")[0].split("page=")[-1])
279 | except:
280 | # logger.warning(f"User Have Video Page. LastOne - {page_link}")
281 | logger.info(f": {int(self.page_num)+1}/{int(self.page_num)+1} : {len(href_list)}")
282 | # 最后一页
283 | total_pageNum = -1
284 | else:
285 | logger.info(f": {int(pageNum)+1}/{int(total_pageNum)+1} : {len(href_list)}")
286 |
287 |
288 |
289 | res = []
290 | for h,t in zip(href_list,title_list):
291 | res.append({"link":h,"title":f"{t}--{h.split('videos/')[-1].split('?')[0]}"})
292 |
293 | logger.debug("Exit Function")
294 | return res,total_pageNum
295 |
296 | def check_folder(self,link):
297 | """
298 | https://ecchi.iwara.tv/users/%E8%B4%BE%E5%94%AF%E2%84%A1
299 | :params link: 原始链接
300 | :return :创建作者目录并返回目录
301 | """
302 | user = link.split("users/")[-1]
303 | user = unquote(user,'utf-8')
304 | user_path = os.path.join(self.root_path,user)
305 | if not os.path.exists(user_path):
306 | os.mkdir(user_path)
307 | return user_path
308 |
309 | def conver_link(self,link):
310 | """
311 | :params link: 作者主页链接 / 视频链接
312 | :return :原始链接 or 包含作者视频链接的列表
313 |
314 | 链接类型
315 | 1、作者主页 https://ecchi.iwara.tv/users/xxxx
316 |
317 | 更改获取videos页面的逻辑,改为从作者主页获取
318 | + 作者主页: https://ecchi.iwara.tv/users/forget-skyrim
319 | + videos页面会发生变化: https://ecchi.iwara.tv/users/Forget%20Skyrim./videos
320 |
321 | 2、单个视频链接 https://ecchi.iwara.tv/videos/xxxx
322 | """
323 | user_video_links = []
324 | if "/users" in link:
325 | logger.debug(f":{link} type:User")
326 | pageNum = 0
327 | while True:
328 | page_video_links,total_pageNum = self.get_links(link,pageNum)
329 | user_video_links.extend(page_video_links)
330 | pageNum += 1
331 | self.page_num = total_pageNum
332 | # 业务跳出条件
333 | if self.page_num == -1:
334 | break
335 |
336 | # 无效用户/视频链接 用户无视频
337 | if user_video_links == [] and \
338 | page_video_links == [] and \
339 | total_pageNum == 0:
340 | break
341 |
342 | # 用户无视频
343 | if user_video_links == []:
344 | return "EmptyVideoLinks"
345 |
346 | # 无效作者主页/视频链接
347 | if user_video_links[0] == "ErrorUserUrl":
348 | return "ErrorUserUrl"
349 |
350 | # 创建作者目录
351 | user_path = self.check_folder(link)
352 | for video in user_video_links:
353 | video["path"] = user_path
354 | # [{link, title, path}, {...}, ...]
355 | return user_video_links
356 |
357 | logger.debug(f":{link} type:Video")
358 | # 视频链接
359 | title = self.get_title(link)
360 |
361 | # 无效的视频链接
362 | if title == "Iwara":
363 | return "ErrorVideoUrl"
364 | # err code
365 | elif title in self.error_code_list.keys():
366 | return title
367 |
368 | return [{"link":link, "title":f"{title}--{link.split('videos/')[-1].split('?')[0]}", "path":self.root_path}]
369 |
370 | def download(self,args,source_url,file_path):
371 | """
372 | 下载原始视频
373 | """
374 | response = self.get_resp(source_url,headers=self.page_headers,stream=True)
375 | logger.info(f"视频标题: {args[0]['title']} - 大小: {byte2size(response.headers['content-length'])} 正在下载")
376 |
377 | # content_size = int(response.headers['content-length']) # 获得文件大小(字节)
378 | # data_count = 0 # 计数
379 | chunk_size = 1024 # 每次最大请求字节
380 | with open(file_path, "wb") as file:
381 | for data in response.iter_content(chunk_size=chunk_size):
382 | file.write(data)
383 | return response
384 |
385 | @logger.catch
386 | def iwara_process(self, *args):
387 | """
388 | 子线程任务函数
389 | """
390 | link, title, path = args[0]["link"], args[0]["title"], args[0]["path"]
391 | file_path = f"{os.path.join(path, title)}.mp4"
392 | logger.debug(f" - {file_path}")
393 | logger.debug(f" - {link}")
394 |
395 | # 检测本地文件
396 | isExists = False
397 | video_id = title.split("--")[-1]
398 | path_expression = os.path.join(path, f"**--{video_id}.mp4")
399 |
400 | for fn in glob.glob(path_expression):
401 | isExists = True
402 | break
403 |
404 | if isExists and os.path.getsize(glob.glob(path_expression)[0]) > 100000:
405 | logger.success(f"视频标题: {title} 已存在")
406 | return
407 |
408 | try:
409 | data = self.get_data(link)
410 | # err code
411 | if type(data) == type("") and data in self.error_code_list.keys():
412 | err_code = self.error_code_list.get(data,'NotFoundErrorCode')
413 | logger.info(f"视频标题: {title} | err_code: {err_code}")
414 | return
415 | else:
416 | source_url,host = data[0],data[1]
417 | except Exception as e:
418 | logger.warning(f"Exception - {e}")
419 | return
420 | else:
421 | try:
422 | response = self.download(args,source_url,file_path)
423 | except Exception as e:
424 | logger.warning(f"视频标题: {title} 视频下载失败,将在其他任务完成后进行重试.")
425 | logger.error(f"""错误视频链接: {link} - 视频标题: {title} - """\
426 | f"""服务器:{byte2size(response.headers['content-length'])} - 本地:{byte2size(os.path.getsize(file_path))}""")
427 | if not check_errTask_exists(args, ERR_TASK_LIST):
428 | ERR_TASK_LIST.append({"args": args, "count": RETRY_COUNT})
429 | # os.remove(file_path)
430 | else:
431 | # 3.9 视频完整性校验
432 | if not check_video(response, file_path):
433 | logger.warning(f"视频标题: {title} 视频完整性校验失败,将在其他任务完成后进行重试.")
434 | logger.error(f"""错误视频链接: {link} - 视频标题: {title} - """\
435 | f"""服务器:{byte2size(response.headers['content-length'])} - 本地:{byte2size(os.path.getsize(file_path))}""")
436 | if not check_errTask_exists(args, ERR_TASK_LIST):
437 | ERR_TASK_LIST.append({"args": args, "count": RETRY_COUNT})
438 | os.remove(file_path)
439 | else:
440 | logger.success(f"视频标题: {title} - {byte2size(response.headers['content-length'])}下载成功 让我歇歇,冲不动了~")
441 |
442 | # 判断/移除重试任务
443 | logger.debug(f"ERR_TASK_LIST 错误重试任务剩余: {len(ERR_TASK_LIST)}个")
444 | logger.debug(ERR_TASK_LIST)
445 | for _ in ERR_TASK_LIST[::]:
446 | if args == _["args"]:
447 | if _["count"] == 0:
448 | ERR_TASK_LIST.remove(_)
449 | logger.warning(f"视频标题: {title} 下载失败,无重试次数. 请参考log文件夹下的ERROR日志~")
450 | else:
451 | logger.warning(f"视频标题: {title} 剩余重试次数: {_['count']}次")
452 | _["count"] -= 1
453 | return self.iwara_process(*args)
454 | break
455 |
456 | time.sleep(0.5)
457 |
458 | def main(self):
459 | """
460 | 一、单个iwara视频
461 | 1.无限制 https://ecchi.iwara.tv/videos/4kbwafpkz0tyv3k4m
462 | 2.private https://ecchi.iwara.tv/videos/wdz0ofvm9buqwapjb
463 | 3.无效视频 https://ecchi.iwara.tv/videos/xxxxxxx
464 |
465 | 二、作者主页
466 | 1.无private
467 | 1.小于等于16个视频 https://ecchi.iwara.tv/users/shikou
468 | 2.大于16个视频 1页无页码 https://ecchi.iwara.tv/users/forget-skyrim
469 | 2页以上 https://ecchi.iwara.tv/users/shirakamisan
470 | # 存在无法播放的视频,按private视频处理
471 | 2.有private
472 | 1.小于等于16个视频 暂时没找到,按1.1处理
473 | 2.大于16个视频 https://ecchi.iwara.tv/users/贾唯℡
474 |
475 | 3.无视频 https://ecchi.iwara.tv/users/lilan0538
476 | 4.无效作者主页 https://ecchi.iwara.tv/users/测试
477 | """
478 | pool = ThreadPool(THREAD_NUM)
479 | # 下载
480 | try:
481 | for link in self.original_links:
482 | # i站相关链接下载
483 | if link.startswith("https://ecchi.iwara.tv/"):
484 | logger.debug(f" - {link}")
485 | link_list = self.conver_link(link)
486 |
487 | # err code
488 | if type(link_list) == type("link_list") and link_list in self.error_code_list.keys():
489 | err_code = self.error_code_list.get(link_list,"NotFoundErrorCode")
490 | logger.info(f"【{link}】 {err_code}")
491 | continue
492 |
493 | # thread working
494 | if link_list != []:
495 | logger.info(f" - {len(link_list)}")
496 | logger.debug(f"link_list - {link_list}")
497 |
498 | for l in link_list:
499 | pool.put(self.iwara_process, (l, ), callback)
500 | except Exception as e:
501 | logger.warning(f"Exception - {e}")
502 | # 错误重试
503 | else:
504 | logger.debug(ERR_TASK_LIST)
505 | for l in ERR_TASK_LIST:
506 | pool.put(self.iwara_process, (l, ), callback)
507 | finally:
508 | pool.close()
509 |
510 |
511 | if __name__ == '__main__':
512 | logger.success(f"=== 欢迎使用< iwara下载脚本 {VERSION} > ===")
513 | logger.success(f"Github地址: https://github.com/WriteCode-ChangeWorld/Tools 欢迎Star~")
514 |
515 | logger.info("下载作者视频则输入作者主页链接...")
516 | logger.info("如: https://ecchi.iwara.tv/users/qishi\n")
517 | logger.info("下载单个视频则输入视频链接...")
518 | logger.info("如: https://ecchi.iwara.tv/videos/4kbwafpkz0tyv3k4m\n")
519 | logger.info("输入完成后,请输入'go'以开始下载...")
520 | # https://ecchi.iwara.tv/users/%E8%B4%BE%E5%94%AF%E2%84%A1 含private视频
521 | # https://ecchi.iwara.tv/users/qishi
522 |
523 | input_links = []
524 | while True:
525 | input_iwara_link = input("现在输入Iwara链接: ")
526 | if input_iwara_link == "":
527 | pass
528 | elif input_iwara_link == "go":
529 | logger.info("开冲!~ (*^▽^*)")
530 | break
531 | else:
532 | input_links.append(input_iwara_link)
533 |
534 | logger.info("现在开始获取链接,请稍后...")
535 | IDLoader = IwaraDownloader(input_links)
536 | IDLoader.main()
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/requirement.txt:
--------------------------------------------------------------------------------
1 | requests
2 | lxml
3 | loguru
--------------------------------------------------------------------------------
/0x03-iwara下载脚本/thread_pool.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 | """
4 | 一个基于thread和queue的线程池,
5 | 任务为队列元素,动态创建线程并重复利用.
6 | 通过close和terminate关闭线程池.
7 | """
8 |
9 | import queue
10 | import threading
11 | import contextlib
12 | import time
13 |
14 | # 创建空对象,用于停止线程
15 | StopEvent = object()
16 |
17 |
18 | def callback(status, result):
19 | """
20 | 根据需要进行的回调函数,默认不执行。
21 | :param status: action函数的执行状态
22 | :param result: action函数的返回值
23 | :return:
24 | """
25 | pass
26 |
27 |
28 | def action(thread_name, arg):
29 | """
30 | 真实的任务定义在这个函数里
31 | :param thread_name: 执行该方法的线程名
32 | :param arg: 该函数需要的参数
33 | :return:
34 | """
35 | # 模拟执行
36 | time.sleep(0.1)
37 | print("第%s个任务调用了线程 %s,并打印了这条信息!" % (arg+1, thread_name))
38 |
39 |
40 | class ThreadPool:
41 |
42 | def __init__(self, max_num, max_task_num=None):
43 | """
44 | 初始化线程池
45 | :param max_num: 线程池最大线程数量
46 | :param max_task_num: 任务队列长度
47 | """
48 | # 如果提供了最大任务数的参数,则将队列的最大元素个数设置为这个值。
49 | max_task_num = 20
50 | if max_task_num:
51 | self.q = queue.Queue(max_task_num)
52 | # 默认队列可接受无限多个的任务
53 | else:
54 | self.q = queue.Queue()
55 | # print("创建好队列了")
56 | # 设置线程池最多可实例化的线程数
57 | self.max_num = max_num
58 | # 任务取消标识
59 | self.cancel = False
60 | # 任务中断标识
61 | self.terminal = False
62 | # 已实例化的线程列表
63 | self.generate_list = []
64 | # 处于空闲状态的线程列表
65 | self.free_list = []
66 |
67 | def put(self, func, args, callback=None):
68 | """
69 | 往任务队列里放入一个任务
70 | :param func: 任务函数
71 | :param args: 任务函数所需参数
72 | :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数
73 | 1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
74 | :return: 如果线程池已经终止,则返回True否则None
75 | """
76 | # 先判断标识,看看任务是否取消了
77 | if self.cancel:
78 | return
79 | # 如果没有空闲的线程,并且已创建的线程的数量小于预定义的最大线程数,则创建新线程。
80 | if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
81 | self.generate_thread()
82 | # 构造任务参数元组,分别是调用的函数,该函数的参数,回调函数。
83 | w = (func, args, callback,)
84 | # 将任务放入队列
85 | self.q.put(w)
86 |
87 | def generate_thread(self):
88 | """
89 | 创建一个线程
90 | """
91 | # 每个线程都执行call方法
92 | t = threading.Thread(target=self.call)
93 | t.start()
94 |
95 | def call(self):
96 | """
97 | 循环去获取任务函数并执行任务函数。在正常情况下,每个线程都保存生存状态,直到获取线程终止的flag。
98 | """
99 | # 获取当前线程的名字
100 | current_thread = threading.currentThread().getName()
101 | # 将当前线程的名字加入已实例化的线程列表中
102 | self.generate_list.append(current_thread)
103 | # 从任务队列中获取一个任务
104 | event = self.q.get()
105 | # 让获取的任务不是终止线程的标识对象时
106 | while event != StopEvent:
107 | # 解析任务中封装的三个参数
108 | func, arguments, callback = event
109 | # 抓取异常,防止线程因为异常退出
110 | try:
111 | # 正常执行任务函数
112 | result = func(*arguments)
113 | # result = func(current_thread, *arguments)
114 | success = True
115 | except Exception as e:
116 | # 当任务执行过程中弹出异常
117 | result = None
118 | success = False
119 | # 如果有指定的回调函数
120 | if callback is not None:
121 | # 执行回调函数,并抓取异常
122 | try:
123 | callback(success, result)
124 | except Exception as e:
125 | pass
126 | # 当某个线程正常执行完一个任务时,先执行worker_state方法
127 | with self.worker_state(self.free_list, current_thread):
128 | # 如果强制关闭线程的flag开启,则传入一个StopEvent元素
129 | if self.terminal:
130 | event = StopEvent
131 | # 否则获取一个正常的任务,并回调worker_state方法的yield语句
132 | else:
133 | # 从这里开始又是一个正常的任务循环
134 | event = self.q.get()
135 | else:
136 | # 一旦发现任务是个终止线程的标识元素,将线程从已创建线程列表中删除
137 | self.generate_list.remove(current_thread)
138 |
139 | def close(self):
140 | """
141 | 执行完所有的任务后,让所有线程都停止的方法
142 | """
143 | # 设置flag
144 | self.cancel = True
145 | # 计算已创建线程列表中线程的个数,
146 | # 然后往任务队列里推送相同数量的终止线程的标识元素
147 | full_size = len(self.generate_list)
148 | while full_size:
149 | self.q.put(StopEvent)
150 | full_size -= 1
151 |
152 | def terminate(self):
153 | """
154 | 在任务执行过程中,终止线程,提前退出。
155 | """
156 | self.terminal = True
157 | # 强制性的停止线程
158 | while self.generate_list:
159 | self.q.put(StopEvent)
160 |
161 | # 该装饰器用于上下文管理
162 | @contextlib.contextmanager
163 | def worker_state(self, state_list, worker_thread):
164 | """
165 | 用于记录空闲的线程,或从空闲列表中取出线程处理任务
166 | """
167 | # 将当前线程,添加到空闲线程列表中
168 | state_list.append(worker_thread)
169 | # 捕获异常
170 | try:
171 | # 在此等待
172 | yield
173 | finally:
174 | # 将线程从空闲列表中移除
175 | state_list.remove(worker_thread)
176 |
177 | """
178 |
179 | # 调用方式
180 | if __name__ == '__main__':
181 | # 创建一个最多包含5个线程的线程池
182 | # pool = ThreadPool(5)
183 |
184 | # 创建100个任务,让线程池进行处理
185 | # for i in range(100):
186 | # pool.put(action, (i,), callback)
187 |
188 | # 等待一定时间,让线程执行任务
189 | # time.sleep(3)
190 |
191 | print("-" * 50)
192 | while True:
193 | print("\033[32;0m任务停止之前线程池中有%s个线程,空闲的线程有%s个!\033[0m"
194 | % (len(pool.generate_list), len(pool.free_list)))
195 |
196 | if len(pool.free_list) == pool.max_num and len(pool.generate_list) == 5:
197 | # 正常关闭线程池
198 | pool.close()
199 | print("任务执行完毕,正常退出!")
200 | break
201 |
202 | time.sleep(1)
203 | # 强制关闭线程池
204 | # pool.terminate()
205 | # print("强制停止任务!")
206 |
207 | """
--------------------------------------------------------------------------------
/0x04-mzitu爬虫/Readme.md:
--------------------------------------------------------------------------------
1 | ## **mzitu下载脚本**
2 |
3 | ---
4 |
5 | 这几天偶然看了下妹子图站点,看到`all`和`old`页面已经404了(大多数都是基于这两个页面进行爬取)
6 |
7 | 故改动一下之前的版本,便有了**mzitu下载脚本**
8 |
9 | ---
10 |
11 | 功能:
12 |
13 | - [x] 下载妹子图全站图片
14 | - [x] 每次请求会延时0.1秒,以确保站点正常业务运行
15 | - [x] 已下载目录跳过机制
16 |
17 |
18 |
19 | 使用方法:
20 |
21 | 1、安装依赖
22 |
23 | ```
24 | pip install -r requirement.txt
25 | ```
26 |
27 | 2、修改`self.path`为你的下载目录
28 |
29 | ```
30 | # 代码第20行
31 | # 将此处修改为你的存储地址
32 | self.path = r'D:\Tools\mzitu'
33 | ```
34 |
35 | 3、运行
36 |
37 | ```
38 | python mzitu.py
39 | ```
40 |
41 | 中途停止可用`CTRL`+`C`大法进行中断,下次再运行继续下载即可
42 |
43 |
44 |
45 | 运行截图
46 |
47 | 
48 |
49 | 
50 |
51 |
--------------------------------------------------------------------------------
/0x04-mzitu爬虫/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x04-mzitu爬虫/img/1.png
--------------------------------------------------------------------------------
/0x04-mzitu爬虫/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x04-mzitu爬虫/img/2.png
--------------------------------------------------------------------------------
/0x04-mzitu爬虫/mzitu.py:
--------------------------------------------------------------------------------
1 | import re
2 | import os
3 | import time
4 | import random
5 | import requests
6 | from lxml import etree
7 |
8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning #强制取消警告
9 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
10 |
11 | class mzitu():
12 | def __init__(self):
13 | self.headers = {
14 | 'User-Agent': "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
15 | 'referer':'https://www.mzitu.com'
16 | }
17 | self.host = "https://www.mzitu.com"
18 | self.template = "https://www.mzitu.com/page/{}"
19 | # 修改self.path为你的存储地址
20 | self.path = r'D:\Tools\mzitu'
21 | self.folder_path = ''
22 |
23 | def max_num(self)->int:
24 | resp = self.request(self.host)
25 | resp_obj = etree.HTML(resp.text)
26 | resp_expression = """//div[@class='nav-links']//a/text()"""
27 | resp_result = resp_obj.xpath(resp_expression)
28 | if resp_result == []:
29 | return 0
30 | else:
31 | num = int(resp_result[-2])
32 | return num
33 |
34 | def main(self):
35 | """
36 | 主流程
37 | """
38 | num = self.max_num()
39 | print("一共有{}页".format(num))
40 | if num == 0:
41 | exit()
42 |
43 | for i in range(1,num+1):
44 | page_url = self.template.format(i)
45 | print(page_url)
46 |
47 | page_result = self.get_page_html(page_url)
48 | if page_result == []:
49 | continue
50 |
51 | for page_data in page_result:
52 | print("正在下载【{}】--{}".format(page_data["name"],page_data["href"]))
53 | # 创建套图文件夹
54 | self.folder_path = self.create_folder(page_data["name"])
55 | # 获取套图的所有图片链接
56 | self.get_img_href(page_data["href"])
57 | # break
58 |
59 | # break
60 |
61 | def get_page_html(self,page_url:str)->dict:
62 | """
63 | 获取当前page页面,各个套图的名称及链接
64 | :param page_url: 页面url:https://www.mzitu.com/page/1
65 | :return: 当前页面的套图名称及链接
66 | """
67 | html = self.request(page_url)
68 | html_obj = etree.HTML(html.text)
69 | name_expression = """//ul[@id='pins']//li/span/a/text()"""
70 | href_expression = """//ul[@id='pins']//li/span/a/@href"""
71 |
72 | name_list = html_obj.xpath(name_expression)
73 | href_list = html_obj.xpath(href_expression)
74 |
75 | result = []
76 | for name,href in zip(name_list,href_list):
77 | result.append({"name":name,"href":href})
78 | return result
79 |
80 | def get_img_href(self,url:str)->None:
81 | """
82 | 图片页面获取多个图片地址,并下载原图
83 | :param url: 套图链接,https://www.mzitu.com/227785
84 | :return: 套图链接中所有图片的原图链接
85 | """
86 | html = self.request(url)
87 | obj = etree.HTML(html.text)
88 | img_expression = """//div[@class="main-image"]//img/@src"""
89 | num_expression = """//div[@class='pagenavi']//a/span/text()"""
90 |
91 | num = int(obj.xpath(num_expression)[-2])
92 |
93 | # 套图目录下有对应num张的图片则跳过该套图下载
94 | if len(os.listdir(self.folder_path)) == num:
95 | # 休眠
96 | time.sleep(0.1)
97 | return
98 |
99 | for i in range(1,num+1):
100 | # https://www.mzitu.com/227785/61
101 | img_url = "{}/{}".format(url,i)
102 | print(img_url)
103 | img_resp = self.request(img_url)
104 | img_obj = etree.HTML(img_resp.text)
105 |
106 | try:
107 | img_href = img_obj.xpath(img_expression)[0]
108 | except:
109 | img_href = ""
110 | img_name = img_href.rsplit("/",1)[-1]
111 |
112 | img_path = os.path.join(self.folder_path,img_name)
113 | # 不存在则下载
114 | if os.path.exists(img_path) == False:
115 | self.save_img(img_href,img_path)
116 |
117 | # 存在但字节数少于1000
118 | if os.path.exists(img_path) == True and os.path.getsize(img_path) < 1000:
119 | self.save_img(img_href,img_path)
120 |
121 | # 休眠
122 | time.sleep(0.1)
123 |
124 | def save_img(self,img_url:str,path:str)->None:
125 | """
126 | 写入图片二进制数据
127 |
128 | :param img_url: 图片链接
129 | :param path: 图片存储路径
130 | :return: None
131 | """
132 | img = self.request(img_url)
133 | with open(path, 'ab') as f:
134 | f.write(img.content)
135 |
136 | def create_folder(self,title:str)->str:
137 | """
138 | 创建文件夹
139 | :param title: 文件夹名称:
140 | :return: 创建的文件夹路径
141 | """
142 | title = re.sub(r'[\/:*?"<>|]','_',title)
143 | folder_path = os.path.join(self.path,title)
144 | isExists = os.path.exists(folder_path)
145 | if not isExists:
146 | print("已创建【{}】该文件夹".format(title))
147 | os.makedirs(folder_path)
148 | return folder_path
149 | print("已存在【{}】该文件夹".format(title))
150 | return folder_path
151 |
152 | def request(self,url:str,retry_num=5):
153 | try:
154 | response = requests.get(url, headers=self.headers,verify=False, timeout=10)
155 | response.encoding = "utf8"
156 | return response
157 | except Exception as e:
158 | if retry_num > 0:
159 | return self.request(url,retry_num-1)
160 | else:
161 | print('网络错误:{} {}'.format(url,e))
162 | return None
163 |
164 | Mzitu = mzitu()
165 | Mzitu.main()
--------------------------------------------------------------------------------
/0x04-mzitu爬虫/requirement.txt:
--------------------------------------------------------------------------------
1 | requests
2 | lxml
--------------------------------------------------------------------------------
/0x05-绅士小站/Readme.md:
--------------------------------------------------------------------------------
1 | ## **绅士小站下载爬虫** v1.0.2——2021年3月2日
2 |
3 | ---
4 |
5 | 绅士仓库看到的一个[站点](https://ca.gca.tw),上面都是仓库内某个同学收集的里番。
6 |
7 | 点开就心动了,花了两个小时写完这个脚本就传上来了。
8 |
9 | ---
10 |
11 | 功能:
12 |
13 | - [x] 下载视频和视频封面(目前存在可能获取不到视频/视频封面的Bug)——**已解决**
14 | - [x] 动态下载进度条
15 | - [x] 网络请求自动重试
16 | - [x] 已下载视频/视频封面自动跳过(字节数大于1000)
17 |
18 |
19 |
20 | 使用方法:
21 |
22 | 1、安装依赖
23 |
24 | ```
25 | pip install -r requirement.txt
26 | ```
27 |
28 | 2、运行
29 |
30 | ```
31 | python gca.py
32 | ```
33 |
34 | 备注:如果有漏下载或当前文章无视频及视频封面(实际上有)的情况,再运行一次脚本补全即可。
35 |
36 |
37 |
38 | 运行截图
39 |
40 | 
41 |
42 | 
43 |
44 | 
45 |
46 |
--------------------------------------------------------------------------------
/0x05-绅士小站/gca.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import time
4 | import requests
5 | from lxml import etree
6 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
7 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
8 |
9 | default_headers = {
10 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
11 | "referer":"https://ca.gca.tw",
12 | }
13 |
14 | def log_str(*args,end=None):
15 | for i in args:
16 | print('[{}] {}'.format(time.strftime("%Y-%m-%d %H:%M:%S"),i),end=end)
17 |
18 | def net_requests(options,method="GET",data=None,headers=default_headers,stream=False,retry_num=5):
19 | try:
20 | # se = requests.session()
21 | response = requests.request(
22 | method,
23 | options["url"],
24 | data = data,
25 | headers = headers,
26 | stream = stream,
27 | verify = False,
28 | timeout = 10,
29 | )
30 | response.encoding = "utf8"
31 | return response
32 | except Exception as e:
33 | if retry_num > 0:
34 | return net_requests(options,method,data,headers,stream,retry_num-1)
35 | else:
36 | log_str("网络请求出错 url:{}".format(options["url"]))
37 | return None
38 |
39 | def stream_process(word,url,path):
40 | # 每次最大请求字节
41 | chunk_size = 1024
42 | # 网络请求
43 | response = net_requests(options={"url":url},stream=True)
44 | if response == None:
45 | return "NetWorkError"
46 | # 获得本次请求的字节
47 | content_size = int(response.headers['content-length'])
48 | data_count = 0
49 |
50 | with open(path, "wb") as file:
51 | for data in response.iter_content(chunk_size=chunk_size):
52 | file.write(data)
53 | data_count = data_count + len(data)
54 | now = (data_count / content_size) * 100
55 | print("\r%s下载进度:%d%%" % (word,now), end=" ")
56 | print("—— {}下载完成".format(word))
57 | return response
58 |
59 |
60 | class GCA:
61 | """绅士小站"""
62 | def __init__(self):
63 | self.HOST = "https://ca.gca.tw"
64 | self.host = "https://ca.gca.tw/page/{}"
65 | self.root_path = os.getcwd()
66 |
67 | # cover封面图 article_url文章链接 title文章标题
68 | self.expression = """//div[@class='simple-grid-posts simple-grid-posts-grid']/div"""
69 | # self.cover_expression = """.//img//@src"""
70 | self.article_url_expression = """.//div//a//@href"""
71 | self.title_expression = """.//h3//a/text()"""
72 | # video封面&video链接
73 | self.video_cover_expression = """//span[@class='arve-embed']/meta[@itemprop="thumbnailUrl"]/@content"""
74 | self.video_cover_expression_second = """//figure[@class="wp-block-video"]/video/@poster"""
75 |
76 | self.video_url_expression = """//span[@class='arve-embed']/meta[@itemprop="contentURL"]/@content"""
77 | self.video_url_expression_second = """//figure[@class="wp-block-video"]/video/@src"""
78 |
79 | def download(self,path,content):
80 | with open(path,"wb") as f:
81 | f.write(content)
82 |
83 | def folder(self,title):
84 | title = re.sub(r'[\/:*?"<>|]','',title)
85 | title = title.replace(".","")
86 | target = os.path.join(self.root_path,title)
87 | isExists = os.path.exists(target)
88 | if not isExists:
89 | os.mkdir(target)
90 | return target
91 |
92 | def get_html(self,article_url,title):
93 | """获取输入密码后的html"""
94 | action_url = "https://ca.gca.tw/wp-login.php?action=postpass"
95 | headers = {
96 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",
97 | "referer":article_url,
98 | }
99 | data = {
100 | "post_password":title, # 密码
101 | }
102 |
103 | resp = net_requests(options={"url":action_url},method="POST",data=data,headers=headers)
104 | return resp
105 |
106 | def main(self):
107 | """主流程"""
108 | offset = 1
109 | all_info_list = []
110 |
111 | while True:
112 | page_url = self.host.format(offset)
113 | obj = etree.HTML(net_requests(options={"url":page_url}).text)
114 | article_list = obj.xpath(self.expression)
115 | log_str("当前第{}页,共{}篇文章".format(offset,len(article_list)))
116 |
117 | for article in article_list:
118 | # article_url
119 | try:
120 | article_url = article.xpath(self.article_url_expression)[0]
121 | except Exception as e:
122 | article_url = ""
123 | # title
124 | try:
125 | title = article.xpath(self.title_expression)[0]
126 | except Exception as e:
127 | title = ""
128 | info = {"article_url":article_url,"title":title}
129 | all_info_list.append(info)
130 |
131 | # 最后一页小于60页,则跳出
132 | if len(article_list) < 60:
133 | log_str("全站文章获取完毕...共{}页{}个".format(offset,len(all_info_list)))
134 | break
135 |
136 | offset += 1
137 | # break # 测试
138 | time.sleep(1)
139 |
140 | if all_info_list == []:
141 | log_str("获取到的文章为空,可能是网站结构发现变化或其他原因")
142 | exit()
143 |
144 | # ============# start ============ #
145 | """
146 | 文件夹 --> title(文件夹名称)
147 | 视频 --> article_url(文章链接),title(密码) --> 后面获取的视频链接
148 | """
149 | for index,info in enumerate(all_info_list):
150 | log_str("({}/{}){} {} ".format(index+1,len(all_info_list),info["title"],info["article_url"]))
151 | if info["article_url"] == "" and info["title"] == "":
152 | log_str("当前文章标题及链接获取有误,将跳过")
153 | continue
154 |
155 | # 创建文章文件夹,title过滤一些字符
156 | article_path = self.folder(info["title"])
157 | # 获取页面解密后的html,原始title用于解密
158 | resp = self.get_html(info["article_url"],info["title"])
159 | obj = etree.HTML(resp.text)
160 |
161 | # video_cover视频封面
162 | try:
163 | video_cover = obj.xpath(self.video_cover_expression)
164 | if video_cover == []:
165 | video_cover = obj.xpath(self.video_cover_expression_second)
166 | except Exception as e:
167 | video_cover = []
168 | # video_url
169 | try:
170 | video_url = obj.xpath(self.video_url_expression)
171 | if video_url == []:
172 | video_url = obj.xpath(self.video_url_expression_second)
173 | except Exception as e:
174 | video_url = []
175 |
176 | if video_cover == [] and video_url == []:
177 | log_str("当前文章无视频及视频封面,将跳过")
178 | continue
179 | # log_str("video_cover: {} video_url: {}".format(video_cover,video_url))
180 |
181 | # 下载视频封面video_cover
182 | cover_path = os.path.join(article_path,"{}.jpg".format(info["title"]))
183 | if os.path.exists(cover_path) == False or os.path.getsize(cover_path) < 1000:
184 | try:
185 | video_cover = video_cover[0]
186 | video_cover = self.HOST + video_cover
187 | response = stream_process("视频封面",video_cover,cover_path)
188 | except Exception as e:
189 | log_str("video_cover: {}".format(video_cover))
190 | log_str(e)
191 | else:
192 | pass
193 | # log_str("视频封面已存在")
194 |
195 | # 下载视频
196 | video_path = os.path.join(article_path,"{}.mp4".format(info["title"]))
197 | if os.path.exists(video_path) == False or os.path.getsize(video_path) < 1000:
198 | try:
199 | video_url = video_url[0]
200 | response = stream_process("视频",video_url,video_path)
201 | if response == "NetWorkError":
202 | continue
203 | time.sleep(3)
204 | except Exception as e:
205 | log_str("video_url: {}".format(video_url))
206 | log_str(e,response)
207 | else:
208 | pass
209 | # log_str("视频已存在")
210 |
211 | # break # 测试
212 | # =============# end ============= #
213 |
214 |
215 | gca = GCA()
216 | gca.main()
--------------------------------------------------------------------------------
/0x05-绅士小站/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x05-绅士小站/img/1.jpg
--------------------------------------------------------------------------------
/0x05-绅士小站/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x05-绅士小站/img/2.png
--------------------------------------------------------------------------------
/0x05-绅士小站/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x05-绅士小站/img/3.png
--------------------------------------------------------------------------------
/0x05-绅士小站/requirement.txt:
--------------------------------------------------------------------------------
1 | requests
2 | lxml
--------------------------------------------------------------------------------
/0x06-pixiv动图合成/Readme.md:
--------------------------------------------------------------------------------
1 | ## Pixiv动图合成脚本 v1.0.1——2021年4月20日
2 |
3 | ---
4 |
5 | Pixiv动图是所谓的多图轮播,只是每一张图的停留时间较长
6 |
7 | ---
8 |
9 | 功能:
10 |
11 | - [x] 合成Pixiv动图
12 | - [x] 自动清理临时文件
13 | - [x] 网络请求自动重试
14 |
15 |
16 |
17 | 使用方法:
18 |
19 | 1、安装依赖
20 |
21 | ```
22 | pip install -r requirement.txt
23 | ```
24 |
25 | 2、运行
26 |
27 | ```
28 | python pix.py
29 | ```
30 |
31 |
32 |
33 |
34 |
35 | 运行截图
36 |
37 | 
38 |
39 |
40 |
41 | 
--------------------------------------------------------------------------------
/0x06-pixiv动图合成/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x06-pixiv动图合成/img/1.png
--------------------------------------------------------------------------------
/0x06-pixiv动图合成/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x06-pixiv动图合成/img/2.png
--------------------------------------------------------------------------------
/0x06-pixiv动图合成/pix.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import zipfile
4 | import imageio
5 | import requests
6 |
7 | class Pixiv_Gif:
8 | def __init__(self):
9 | self.headers = {
10 | # "cookie":"irst_visit_datetime_pc=2021-01-18+23%3A46%3A22; p_ab_id=4; p_ab_id_2=1; p_ab_d_id=1174719623; yuid_b=JVZjaQc; c_type=21; a_type=0; b_type=0; __utma=235335808.1683020896.1611026902.1611026902.1611026902.1; __utmz=235335808.1611026902.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmv=235335808.|3=plan=normal=1^6=user_id=27858363=1^11=lang=zh=1; login_ever=yes; PHPSESSID=27858363_yDl8WSYNlr2COVTjHHENyDFS9vYBmf3E; privacy_policy_agreement=2; __cfduid=d788199f9cb94e1c9fe4771d24633d2f31613573687; p_b_type=1; __cf_bm=570419761d3f9b0aecc8f92429e5c40fe20c142d-1614658458-1800-AYaL7o9FG66P0Z2Zv/IpHa8yZzNWKXRxzJLrZgSwnfV5BPsgmrFF9Yod5ZkXVBsfW/j6aBM9P38rwLbLNRwqTkNalbMMBLglxJq14e9kn0iu; tag_view_ranking=0xsDLqCEW6~_EOd7bsGyl~Lt-oEicbBr~tgP8r-gOe_~KN7uxuR89w~uusOs0ipBx~jhuUT0OJva~yTFlPOVybE~eVxus64GZU~RcahSSzeRf~BSlt10mdnm~Ie2c51_4Sp~RokSaRBUGr~RTJMXD26Ak~KOnmT1ndWG~5oPIfUbtd6~ziiAzr_h04~yroC1pdUO-~O2wfZxfonb~faHcYIP1U0~7Y-OaPrqAv~-TeGk6mN86~jk9IzfjZ6n~PHQDP-ccQD~zZZn32I7eS~aPdvNeJ_XM~txZ9z5ByU7~dK-VDbfFxD~zIv0cf5VVk~zyKU3Q5L4C~r01unnQL0a~qNQ253s6b0~Bd2L9ZBE8q~HY55MqmzzQ~AI_aJCDFn0~WVrsHleeCL~m3EJRa33xU~y8GNntYHsi~52-15K-YXl~WcTW9TCOx9~UX647z2Emo~skx_-I2o4Y~2EpPrOnc5S~M_kcfifITK~QaiOjmwQnI~CrFcrMFJzz~_vCZ2RLsY2~ouiK2OKQ-A~tL0dW9hy1Z~v3nOtgG77A~3cT9FM3R6t~q303ip6Ui5~m47KFuLUuf~sOBG5_rfE2~BU9SQkS-zU~FqVQndhufZ~3gc3uGrU1V~cofmB2mV_d~AYsIPsa0jE~PvCsalAgmW~rNs-bh_gk3~MM6RXH_rlN~4QveACRzn3~ePN3h1AXKX~0NVHtCi70U~leIwAgTj8E~TqiZfKmSCg~lz1iXPdNUF~F-9IXA9R-9~Qg95CBAuxh~ZQorENB8GQ~8EI9ovnGxw~-0JKThjl-M~tTvZK72fmv~D0nMcn6oGk~EZQqoW9r8g~QniSV1XRdS~5BhtaeNUYC~_3oeEue7S7~vzTU7cI86f~KhVXu5CuKx~rOnsP2Q5UN~-StjcwdYwv~cbmDKjZf9z~azESOjmQSV~GFExx0uFgX~u8McsBs7WV~04qbH3MKfd~d2ro84lQRz~96TwI9y7eq~6qxeznuWaX~L7-FiupSjg~y3NlVImyly~FPCeANM2Bm~X_1kwTzaXt~-MuiEJf_Sr~0F4QqxTayD~mzJgaDwBF5~YHRjLHL-7q~g5izzko3j2",
11 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400",
12 | "referer":"https://www.pixiv.net"
13 | }
14 | self.zip_url = "https://www.pixiv.net/ajax/illust/{}/ugoira_meta"
15 |
16 | for _ in ["gif","zip"]:
17 | _path = os.path.join(os.getcwd(),_)
18 | if not os.path.exists(_path):
19 | os.mkdir(_path)
20 |
21 | self.gif_path = os.path.join(os.getcwd(),"GIF")
22 | self.zip_path = os.path.join(os.getcwd(),"ZIP")
23 |
24 | def get_url_data(self,url,retry_num=3):
25 | try:
26 | resp = requests.get(url,headers=self.headers,timeout=15)
27 | except Exception as e:
28 | if retry_num > 0:
29 | return self.get_url_data(url,headers,retry_num=retry_num-1)
30 | else:
31 | print("网络出错,超过重试次数")
32 | exit()
33 | else:
34 | return resp
35 |
36 | def main(self,pid):
37 | resp = self.get_url_data(self.zip_url.format(pid))
38 | zip_name = "{}.zip".format(pid)
39 | zipfile_path = os.path.join(self.zip_path,zip_name)
40 | gif_name = '{}.gif'.format(pid)
41 | giffile_path = os.path.join(self.gif_path,gif_name)
42 |
43 | # delay
44 | gif_info = json.loads(resp.text)
45 | delay = [item["delay"]/1000 for item in gif_info["body"]["frames"]]
46 | print("delay list:\n",delay)
47 |
48 | # download zip
49 | zip_originalSrc = gif_info["body"]["src"]
50 | zip_resp = self.get_url_data(zip_originalSrc)
51 | with open(zipfile_path,'ab') as f:
52 | f.write(zip_resp.content)
53 |
54 | # extract zip & delete zip
55 | with zipfile.ZipFile(zipfile_path,'r') as f:
56 | for file in f.namelist():
57 | f.extract(file,self.zip_path)
58 | os.remove(zipfile_path)
59 |
60 | # composite gif
61 | images = []
62 | files = os.listdir(self.zip_path)
63 | print("extract list:\n",files)
64 | for file in files:
65 | file_path = os.path.join(self.zip_path,file)
66 | images.append(imageio.imread(file_path))
67 | imageio.mimsave(giffile_path,images,duration=delay)
68 |
69 | # delete extract file
70 | for file in files:
71 | os.remove(os.path.join(self.zip_path,file))
72 |
73 | # success
74 | print("Download Success\n{}".format(giffile_path))
75 |
76 | PG = Pixiv_Gif()
77 |
78 | if __name__ == '__main__':
79 | print("输入q以退出")
80 | print("接下来粘贴你要下载的动图pid吧!")
81 | while True:
82 | pid = input("\n现在输入:")
83 | if pid == "":
84 | print("pid not null")
85 | exit()
86 | elif pid == "q":
87 | print("退出成功")
88 | exit()
89 | else:
90 | PG.main(pid=pid)
--------------------------------------------------------------------------------
/0x06-pixiv动图合成/requirement.txt:
--------------------------------------------------------------------------------
1 | requests
2 | imageio
--------------------------------------------------------------------------------
/0x07-绅士仓库/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | cache
3 | __pycache__
4 | Config/*.ini
5 | !example.ini
6 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Config/__init__.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import logging
4 | import configparser
5 |
6 |
7 | def get_config():
8 | # 读取配置文件
9 | run_env = 'production'
10 | if 'SERVICE_ENV' in os.environ:
11 | run_env = os.environ['SERVICE_ENV']
12 | print("Load config [%s]" % run_env)
13 | config_path = '{}/{}.ini'.format(os.path.split(os.path.abspath(__file__))[0], run_env)
14 | if os.path.isfile(config_path):
15 | config = configparser.ConfigParser()
16 | config.read(config_path, encoding='utf-8')
17 |
18 | app_config = dict()
19 | for section in config.sections():
20 | app_config[section] = dict(config.items(section))
21 |
22 | app_config["RUN_ENV"] = run_env
23 | return app_config
24 | else:
25 | logging.error("Config not exist")
26 | exit()
27 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Config/cangku.sql:
--------------------------------------------------------------------------------
1 | SET NAMES utf8mb4;
2 | SET FOREIGN_KEY_CHECKS = 0;
3 |
4 | -- ----------------------------
5 | -- Table structure for cangku
6 | -- ----------------------------
7 | DROP TABLE IF EXISTS `cangku`;
8 | CREATE TABLE `cangku` (
9 | `id` int(11) NOT NULL,
10 | `url` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
11 | `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
12 | `user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
13 | `title` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
14 | `cover` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
15 | `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
16 | `raw_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
17 | PRIMARY KEY (`id`) USING BTREE,
18 | UNIQUE INDEX `index`(`id`) USING BTREE,
19 | INDEX `tag_index`(`tag`) USING BTREE
20 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
21 |
22 | SET FOREIGN_KEY_CHECKS = 1;
23 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Config/gca_tw.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Navicat Premium Data Transfer
3 |
4 | Source Server : ASUS MySQL
5 | Source Server Type : MySQL
6 | Source Server Version : 50734
7 | Source Host : asus.host.wiolfi.cn:12806
8 | Source Schema : videwolf
9 |
10 | Target Server Type : MySQL
11 | Target Server Version : 50734
12 | File Encoding : 65001
13 |
14 | Date: 02/07/2021 00:11:31
15 | */
16 |
17 | SET NAMES utf8mb4;
18 | SET FOREIGN_KEY_CHECKS = 0;
19 |
20 | -- ----------------------------
21 | -- Table structure for gca_tw
22 | -- ----------------------------
23 | DROP TABLE IF EXISTS `gca_tw`;
24 | CREATE TABLE `gca_tw` (
25 | `id` int(11) NOT NULL,
26 | `url` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
27 | `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
28 | `title` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
29 | `cover` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
30 | `video` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
31 | `download` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
32 | `raw_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
33 | PRIMARY KEY (`id`) USING BTREE,
34 | UNIQUE INDEX `index`(`id`) USING BTREE,
35 | INDEX `tag_index`(`tag`) USING BTREE
36 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
37 |
38 | SET FOREIGN_KEY_CHECKS = 1;
39 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Config/openssl.cnf:
--------------------------------------------------------------------------------
1 | #
2 | # OpenSSL example configuration file.
3 | # This is mostly being used for generation of certificate requests.
4 | #
5 |
6 | # Note that you can include other files from the main configuration
7 | # file using the .include directive.
8 | #.include filename
9 |
10 | # This definition stops the following lines choking if HOME isn't
11 | # defined.
12 | HOME = .
13 |
14 | # Extra OBJECT IDENTIFIER info:
15 | #oid_file = $ENV::HOME/.oid
16 | oid_section = new_oids
17 |
18 | # System default
19 | openssl_conf = default_conf
20 |
21 | # To use this configuration file with the "-extfile" option of the
22 | # "openssl x509" utility, name here the section containing the
23 | # X.509v3 extensions to use:
24 | # extensions =
25 | # (Alternatively, use a configuration file that has only
26 | # X.509v3 extensions in its main [= default] section.)
27 |
28 | [ new_oids ]
29 |
30 | # We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
31 | # Add a simple OID like this:
32 | # testoid1=1.2.3.4
33 | # Or use config file substitution like this:
34 | # testoid2=${testoid1}.5.6
35 |
36 | # Policies used by the TSA examples.
37 | tsa_policy1 = 1.2.3.4.1
38 | tsa_policy2 = 1.2.3.4.5.6
39 | tsa_policy3 = 1.2.3.4.5.7
40 |
41 | ####################################################################
42 | [ ca ]
43 | default_ca = CA_default # The default ca section
44 |
45 | ####################################################################
46 | [ CA_default ]
47 |
48 | dir = ./demoCA # Where everything is kept
49 | certs = $dir/certs # Where the issued certs are kept
50 | crl_dir = $dir/crl # Where the issued crl are kept
51 | database = $dir/index.txt # database index file.
52 | #unique_subject = no # Set to 'no' to allow creation of
53 | # several certs with same subject.
54 | new_certs_dir = $dir/newcerts # default place for new certs.
55 |
56 | certificate = $dir/cacert.pem # The CA certificate
57 | serial = $dir/serial # The current serial number
58 | crlnumber = $dir/crlnumber # the current crl number
59 | # must be commented out to leave a V1 CRL
60 | crl = $dir/crl.pem # The current CRL
61 | private_key = $dir/private/cakey.pem# The private key
62 |
63 | x509_extensions = usr_cert # The extensions to add to the cert
64 |
65 | # Comment out the following two lines for the "traditional"
66 | # (and highly broken) format.
67 | name_opt = ca_default # Subject Name options
68 | cert_opt = ca_default # Certificate field options
69 |
70 | # Extension copying option: use with caution.
71 | # copy_extensions = copy
72 |
73 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
74 | # so this is commented out by default to leave a V1 CRL.
75 | # crlnumber must also be commented out to leave a V1 CRL.
76 | # crl_extensions = crl_ext
77 |
78 | default_days = 365 # how long to certify for
79 | default_crl_days= 30 # how long before next CRL
80 | default_md = default # use public key default MD
81 | preserve = no # keep passed DN ordering
82 |
83 | # A few difference way of specifying how similar the request should look
84 | # For type CA, the listed attributes must be the same, and the optional
85 | # and supplied fields are just that :-)
86 | policy = policy_match
87 |
88 | # For the CA policy
89 | [ policy_match ]
90 | countryName = match
91 | stateOrProvinceName = match
92 | organizationName = match
93 | organizationalUnitName = optional
94 | commonName = supplied
95 | emailAddress = optional
96 |
97 | # For the 'anything' policy
98 | # At this point in time, you must list all acceptable 'object'
99 | # types.
100 | [ policy_anything ]
101 | countryName = optional
102 | stateOrProvinceName = optional
103 | localityName = optional
104 | organizationName = optional
105 | organizationalUnitName = optional
106 | commonName = supplied
107 | emailAddress = optional
108 |
109 | ####################################################################
110 | [ req ]
111 | default_bits = 2048
112 | default_keyfile = privkey.pem
113 | distinguished_name = req_distinguished_name
114 | attributes = req_attributes
115 | x509_extensions = v3_ca # The extensions to add to the self signed cert
116 |
117 | # Passwords for private keys if not present they will be prompted for
118 | # input_password = secret
119 | # output_password = secret
120 |
121 | # This sets a mask for permitted string types. There are several options.
122 | # default: PrintableString, T61String, BMPString.
123 | # pkix : PrintableString, BMPString (PKIX recommendation before 2004)
124 | # utf8only: only UTF8Strings (PKIX recommendation after 2004).
125 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
126 | # MASK:XXXX a literal mask value.
127 | # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
128 | string_mask = utf8only
129 |
130 | # req_extensions = v3_req # The extensions to add to a certificate request
131 |
132 | [ req_distinguished_name ]
133 | countryName = Country Name (2 letter code)
134 | countryName_default = AU
135 | countryName_min = 2
136 | countryName_max = 2
137 |
138 | stateOrProvinceName = State or Province Name (full name)
139 | stateOrProvinceName_default = Some-State
140 |
141 | localityName = Locality Name (eg, city)
142 |
143 | 0.organizationName = Organization Name (eg, company)
144 | 0.organizationName_default = Internet Widgits Pty Ltd
145 |
146 | # we can do this but it is not needed normally :-)
147 | #1.organizationName = Second Organization Name (eg, company)
148 | #1.organizationName_default = World Wide Web Pty Ltd
149 |
150 | organizationalUnitName = Organizational Unit Name (eg, section)
151 | #organizationalUnitName_default =
152 |
153 | commonName = Common Name (e.g. server FQDN or YOUR name)
154 | commonName_max = 64
155 |
156 | emailAddress = Email Address
157 | emailAddress_max = 64
158 |
159 | # SET-ex3 = SET extension number 3
160 |
161 | [ req_attributes ]
162 | challengePassword = A challenge password
163 | challengePassword_min = 4
164 | challengePassword_max = 20
165 |
166 | unstructuredName = An optional company name
167 |
168 | [ usr_cert ]
169 |
170 | # These extensions are added when 'ca' signs a request.
171 |
172 | # This goes against PKIX guidelines but some CAs do it and some software
173 | # requires this to avoid interpreting an end user certificate as a CA.
174 |
175 | basicConstraints=CA:FALSE
176 |
177 | # Here are some examples of the usage of nsCertType. If it is omitted
178 | # the certificate can be used for anything *except* object signing.
179 |
180 | # This is OK for an SSL server.
181 | # nsCertType = server
182 |
183 | # For an object signing certificate this would be used.
184 | # nsCertType = objsign
185 |
186 | # For normal client use this is typical
187 | # nsCertType = client, email
188 |
189 | # and for everything including object signing:
190 | # nsCertType = client, email, objsign
191 |
192 | # This is typical in keyUsage for a client certificate.
193 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment
194 |
195 | # This will be displayed in Netscape's comment listbox.
196 | nsComment = "OpenSSL Generated Certificate"
197 |
198 | # PKIX recommendations harmless if included in all certificates.
199 | subjectKeyIdentifier=hash
200 | authorityKeyIdentifier=keyid,issuer
201 |
202 | # This stuff is for subjectAltName and issuerAltname.
203 | # Import the email address.
204 | # subjectAltName=email:copy
205 | # An alternative to produce certificates that aren't
206 | # deprecated according to PKIX.
207 | # subjectAltName=email:move
208 |
209 | # Copy subject details
210 | # issuerAltName=issuer:copy
211 |
212 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
213 | #nsBaseUrl
214 | #nsRevocationUrl
215 | #nsRenewalUrl
216 | #nsCaPolicyUrl
217 | #nsSslServerName
218 |
219 | # This is required for TSA certificates.
220 | # extendedKeyUsage = critical,timeStamping
221 |
222 | [ v3_req ]
223 |
224 | # Extensions to add to a certificate request
225 |
226 | basicConstraints = CA:FALSE
227 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment
228 |
229 | [ v3_ca ]
230 |
231 |
232 | # Extensions for a typical CA
233 |
234 |
235 | # PKIX recommendation.
236 |
237 | subjectKeyIdentifier=hash
238 |
239 | authorityKeyIdentifier=keyid:always,issuer
240 |
241 | basicConstraints = critical,CA:true
242 |
243 | # Key usage: this is typical for a CA certificate. However since it will
244 | # prevent it being used as an test self-signed certificate it is best
245 | # left out by default.
246 | # keyUsage = cRLSign, keyCertSign
247 |
248 | # Some might want this also
249 | # nsCertType = sslCA, emailCA
250 |
251 | # Include email address in subject alt name: another PKIX recommendation
252 | # subjectAltName=email:copy
253 | # Copy issuer details
254 | # issuerAltName=issuer:copy
255 |
256 | # DER hex encoding of an extension: beware experts only!
257 | # obj=DER:02:03
258 | # Where 'obj' is a standard or added object
259 | # You can even override a supported extension:
260 | # basicConstraints= critical, DER:30:03:01:01:FF
261 |
262 | [ crl_ext ]
263 |
264 | # CRL extensions.
265 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
266 |
267 | # issuerAltName=issuer:copy
268 | authorityKeyIdentifier=keyid:always
269 |
270 | [ proxy_cert_ext ]
271 | # These extensions should be added when creating a proxy certificate
272 |
273 | # This goes against PKIX guidelines but some CAs do it and some software
274 | # requires this to avoid interpreting an end user certificate as a CA.
275 |
276 | basicConstraints=CA:FALSE
277 |
278 | # Here are some examples of the usage of nsCertType. If it is omitted
279 | # the certificate can be used for anything *except* object signing.
280 |
281 | # This is OK for an SSL server.
282 | # nsCertType = server
283 |
284 | # For an object signing certificate this would be used.
285 | # nsCertType = objsign
286 |
287 | # For normal client use this is typical
288 | # nsCertType = client, email
289 |
290 | # and for everything including object signing:
291 | # nsCertType = client, email, objsign
292 |
293 | # This is typical in keyUsage for a client certificate.
294 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment
295 |
296 | # This will be displayed in Netscape's comment listbox.
297 | nsComment = "OpenSSL Generated Certificate"
298 |
299 | # PKIX recommendations harmless if included in all certificates.
300 | subjectKeyIdentifier=hash
301 | authorityKeyIdentifier=keyid,issuer
302 |
303 | # This stuff is for subjectAltName and issuerAltname.
304 | # Import the email address.
305 | # subjectAltName=email:copy
306 | # An alternative to produce certificates that aren't
307 | # deprecated according to PKIX.
308 | # subjectAltName=email:move
309 |
310 | # Copy subject details
311 | # issuerAltName=issuer:copy
312 |
313 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
314 | #nsBaseUrl
315 | #nsRevocationUrl
316 | #nsRenewalUrl
317 | #nsCaPolicyUrl
318 | #nsSslServerName
319 |
320 | # This really needs to be in place for it to be a proxy certificate.
321 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
322 |
323 | ####################################################################
324 | [ tsa ]
325 |
326 | default_tsa = tsa_config1 # the default TSA section
327 |
328 | [ tsa_config1 ]
329 |
330 | # These are used by the TSA reply generation only.
331 | dir = ./demoCA # TSA root directory
332 | serial = $dir/tsaserial # The current serial number (mandatory)
333 | crypto_device = builtin # OpenSSL engine to use for signing
334 | signer_cert = $dir/tsacert.pem # The TSA signing certificate
335 | # (optional)
336 | certs = $dir/cacert.pem # Certificate chain to include in reply
337 | # (optional)
338 | signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
339 | signer_digest = sha256 # Signing digest to use. (Optional)
340 | default_policy = tsa_policy1 # Policy if request did not specify it
341 | # (optional)
342 | other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
343 | digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory)
344 | accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
345 | clock_precision_digits = 0 # number of digits after dot. (optional)
346 | ordering = yes # Is ordering defined for timestamps?
347 | # (optional, default: no)
348 | tsa_name = yes # Must the TSA name be included in the reply?
349 | # (optional, default: no)
350 | ess_cert_id_chain = no # Must the ESS cert id chain be included?
351 | # (optional, default: no)
352 | ess_cert_id_alg = sha1 # algorithm to compute certificate
353 | # identifier (optional, default: sha1)
354 | [default_conf]
355 | ssl_conf = ssl_sect
356 |
357 | [ssl_sect]
358 | system_default = system_default_sect
359 |
360 | [system_default_sect]
361 | MinProtocol = TLSv1.0
362 | CipherString = DEFAULT@SECLEVEL=1
363 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Kit/__init__.py:
--------------------------------------------------------------------------------
1 | from .kit import *
2 | from .chrome import env_check
3 | from .chrome import run_browser
4 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Kit/chrome.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import re
4 | import Kit
5 | import stat
6 | import zipfile
7 | import requests
8 | import subprocess
9 | from selenium import webdriver
10 |
11 | # 引入Windows特定库
12 | if Kit.run_platform() == "windows":
13 | import winreg
14 |
15 | # 全局变量
16 | version_re = re.compile(r'^[1-9]\d*\.\d*.\d*')
17 |
18 |
19 | def run_browser(driver_path, headless="Yes"):
20 | driver_path = os.path.abspath(driver_path) + "/chromedriver"
21 | option = webdriver.ChromeOptions()
22 | if headless == "Yes":
23 | option.add_argument('--headless')
24 | option.add_argument('--no-sandbox')
25 | option.add_argument('--disable-gpu')
26 | browser = webdriver.Chrome(driver_path, options=option)
27 | return browser
28 |
29 |
30 | def env_check(cache_path):
31 | print("[INFO]", "Check chrome driver env...")
32 | cache_path = os.path.abspath(cache_path)
33 |
34 | chrome_version = get_chrome_version()
35 | driver_version = get_driver_version(cache_path)
36 |
37 | if chrome_version == "0":
38 | print("[INFO]", "Please install chrome browser")
39 | return False
40 |
41 | if driver_version == "0":
42 | # Download driver
43 | print("[INFO]", "Download chrome driver")
44 | return download_driver(chrome_version, cache_path)
45 |
46 | if version_re.findall(chrome_version)[0] != version_re.findall(driver_version)[0]:
47 | # Update driver
48 | print("[INFO]", "Update chrome driver")
49 | return download_driver(chrome_version, cache_path)
50 |
51 | print("[INFO]", "Chrome driver check pass")
52 | return True # Check success
53 |
54 |
55 | def get_chrome_version():
56 | print("[INFO]", "Check chrome version...")
57 | if Kit.run_platform() == "windows":
58 | try:
59 | # 从注册表中获得版本号
60 | key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon")
61 | version, _ = winreg.QueryValueEx(key, "version")
62 |
63 | print("[INFO]", "Current chrome Version: {}".format(version)) # 这步打印会在命令行窗口显示
64 | return version
65 | except WindowsError as e:
66 | print("[INFO]", "Check chrome failed:{}".format(e))
67 | return "0"
68 | else:
69 | try:
70 | cmd = r"google-chrome-stable --version"
71 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
72 | out = out.decode("utf-8")
73 | version = out.split(" ")[2] # 拆分回显字符串,获取版本号
74 | print("[INFO]", "Current chrome Version:{}".format(version))
75 | return version
76 | except WindowsError as e:
77 | print("[INFO]", "Check chrome failed:{}".format(e))
78 | return "0"
79 |
80 |
81 | def get_driver_version(driver_path):
82 | print("[INFO]", "Check driver version...")
83 | if Kit.run_platform() == "windows":
84 | try:
85 | # 执行cmd命令并接收命令回显
86 | cmd = r'"{}/chromedriver" --version'.format(driver_path)
87 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
88 | out = out.decode("utf-8")
89 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号
90 | print("[INFO]", "Current driver Version:{}".format(version))
91 | return version
92 | except IndexError as e:
93 | print("[INFO]", "Check driver failed:{}".format(e))
94 | return "0"
95 | else:
96 | try:
97 | # 执行cmd命令并接收命令回显
98 | cmd = r"{}/chromedriver --version".format(driver_path)
99 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
100 | out = out.decode("utf-8")
101 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号
102 | print("[INFO]", "Current driver Version:{}".format(version))
103 | return version
104 | except IndexError as e:
105 | print("[INFO]", "Check driver failed:{}".format(e))
106 | return "0"
107 |
108 |
109 | def download_driver(version, driver_path):
110 | print("[INFO]", "Download driver on", Kit.run_platform())
111 | driver_path = os.path.abspath(driver_path)
112 |
113 | # 获取淘宝镜像列表
114 | http_res = requests.get("http://npm.taobao.org/mirrors/chromedriver")
115 | mirror_list = re.findall(r'(.*)', http_res.text)
116 |
117 | # 检索驱动版本列表
118 | url_path = ""
119 | version_prefix = version_re.findall(version)[0]
120 | for mirrors in mirror_list:
121 | if mirrors[1].startswith(version_prefix):
122 | url_path = mirrors[0]
123 |
124 | print("[INFO]", "Download driver version:", url_path.split("/")[-1])
125 | if Kit.run_platform() == "windows":
126 | file_url = "http://npm.taobao.org/{}/chromedriver_win32.zip".format(url_path)
127 | elif Kit.run_platform() == "linux":
128 | file_url = "http://npm.taobao.org/{}/chromedriver_linux64.zip".format(url_path)
129 | elif Kit.run_platform() == "darwin":
130 | file_url = "http://npm.taobao.org/{}/chromedriver_mac64.zip".format(url_path)
131 | else:
132 | print("[ERR]", "Not found chrome driver")
133 | return False
134 |
135 | print("[INFO]", "Download driver url:", file_url)
136 | driver_res = requests.get(file_url)
137 |
138 | # 写入压缩文件
139 | zip_path = driver_path + "/chromedriver.zip"
140 | zip_file = open(zip_path, "wb")
141 | zip_file.write(driver_res.content)
142 | zip_file.close()
143 |
144 | # 下载完成后解压
145 | zip_file = zipfile.ZipFile(zip_path, "r")
146 | for fileM in zip_file.namelist():
147 | zip_file.extract(fileM, os.path.dirname(zip_path))
148 | zip_file.close()
149 |
150 | # 删除残留文件
151 | os.remove(zip_path)
152 |
153 | # 授予执行权限
154 | if Kit.run_platform() == "linux":
155 | os.chmod(driver_path + "/chromedriver", stat.S_IXGRP)
156 |
157 | print("[INFO]", "Download chrome driver finish")
158 | return True
159 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Kit/kit.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import re
4 | import sys
5 | import uuid
6 | import time
7 | import random
8 | import string
9 | import inspect
10 | import hashlib
11 | import platform
12 | import pymysql
13 |
14 |
15 | # from bs4 import BeautifulSoup
16 |
17 | # MySQL
18 | def mysql_conn(config, db_key):
19 | config[db_key]['port'] = int(config[db_key]['port'])
20 | conn = pymysql.connect(**config[db_key])
21 | return conn
22 |
23 |
24 | # Print tools
25 | def _print(message, code=None, tag=None, end=None):
26 | if tag is None:
27 | message = '[{}] {}'.format(tag, message)
28 | if code is not None:
29 | message = '\033[0;{}m{}\033[0m'.format(code, message)
30 | print(message, end=end)
31 |
32 |
33 | def print_red(message, tag="ERROR", end=None):
34 | _print(message, 31, tag, end) # 红色
35 |
36 |
37 | def print_green(message, tag="DONE", end='\n'):
38 | _print(message, 32, tag, end) # 绿色
39 |
40 |
41 | def print_yellow(message, tag="WARNING", end='\n'):
42 | _print(message, 33, tag, end) # 黄色
43 |
44 |
45 | def print_blue(message, tag="BEG", end='\n'):
46 | _print(message, 34, tag, end) # 深蓝色
47 |
48 |
49 | def print_purple(message, tag="MID", end='\n'):
50 | _print(message, 35, tag, end) # 紫色
51 |
52 |
53 | def print_azure(message, tag="END", end='\n'):
54 | _print(message, 36, tag, end) # 浅蓝色
55 |
56 |
57 | def print_white(message, tag="INFO", end='\n'):
58 | _print(message, 37, tag, end) # 白色
59 |
60 |
61 | def print_none(message, tag="DEBUG", end='\n'):
62 | _print(message, None, tag, end) # 默认
63 |
64 |
65 | def process_bar(now, total, attach=''):
66 | # 在窗口底部动态显示进度条
67 | rate = now / total
68 | rate_num = int(rate * 100)
69 | bar_length = int(rate_num / 2)
70 | if rate_num == 100:
71 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " "))
72 | bar = '\r' + bar[0:40]
73 | bar += '%s>%d%%\n' % ('=' * bar_length, rate_num)
74 | else:
75 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " "))
76 | bar = '\r' + bar[0:40]
77 | bar += '%s>%d%%' % ('=' * bar_length, rate_num)
78 | sys.stdout.write(bar)
79 | sys.stdout.flush()
80 |
81 |
82 | # Time tools
83 |
84 | def unix_time(unit=1):
85 | return int(time.time() * unit)
86 |
87 |
88 | def str_time(pattern='%Y-%m-%d %H:%M:%S', timing=None):
89 | if timing is None:
90 | timing = time.time()
91 | return time.strftime(pattern, time.localtime(timing))
92 |
93 |
94 | def format_time(time_obj, pattern='%Y-%m-%d %H:%M:%S'):
95 | return time.strftime(pattern, time_obj)
96 |
97 |
98 | def datetime2unix(timing):
99 | return int(time.mktime(timing.timetuple()))
100 |
101 |
102 | def timestamp2unix(time_string, pattern='%Y-%m-%d %H:%M:%S'):
103 | time_array = time.strptime(time_string, pattern)
104 | return int(time.mktime(time_array))
105 |
106 |
107 | def unix2timestamp(u_time, pattern='%Y-%m-%d %H:%M:%S'):
108 | return time.strftime(pattern, time.localtime(u_time))
109 |
110 |
111 | # Calc tools
112 |
113 | def func_name(fa=1):
114 | return inspect.stack()[fa][3]
115 |
116 |
117 | def random_code():
118 | return str(uuid.uuid1()).split('-')[0]
119 |
120 |
121 | def random_string(length, chars=string.digits + string.ascii_letters):
122 | return ''.join(random.choice(chars) for _ in range(length))
123 |
124 |
125 | def calc_sha1(seed):
126 | seed = str(seed).encode('utf-8')
127 | sha1 = hashlib.sha1()
128 | sha1.update(seed)
129 | return sha1.hexdigest()
130 |
131 |
132 | def calc_md5(seed):
133 | seed = str(seed).encode('utf-8')
134 | md5 = hashlib.md5()
135 | md5.update(seed)
136 | return md5.hexdigest()
137 |
138 |
139 | # def parse_xml(data):
140 | # xml = re.sub(r'', lambda m: m.group(1), data)
141 | # xml = BeautifulSoup(xml, 'lxml')
142 | # xml = xml.html.body.xml
143 | # return xml
144 |
145 |
146 | def cpu_core():
147 | if run_platform() == "windows":
148 | return int(os.popen("echo %NUMBER_OF_PROCESSORS%").read())
149 | elif run_platform() == "linux":
150 | return int(os.popen(r"cat /proc/cpuinfo | grep 'cpu cores' | uniq | awk '{print $4}'").read())
151 | else:
152 | return 0
153 |
154 |
155 | def run_platform():
156 | # windows / linux / darwin
157 | return platform.system().lower()
158 |
159 |
160 | # File tools
161 |
162 |
163 | def code_dir():
164 | file = os.path.abspath(__file__)
165 | return os.path.dirname(file)
166 |
167 |
168 | def code_path():
169 | return os.path.abspath(__file__)
170 |
171 |
172 | def legalize_name(name):
173 | legal_name = re.sub(r"[\/\\\:\*\?\"\<\>\|\s']", '_', name)
174 | legal_name = re.sub(r'[‘’]', '_', legal_name)
175 | if len(legal_name) == 0:
176 | return 'null'
177 | return legal_name
178 |
179 |
180 | def delete_old_file(dir_path, expire_time):
181 | time_now = unix_time()
182 | dir_path = os.path.abspath(dir_path)
183 | for file in os.listdir(dir_path):
184 | file_path = '{}/{}'.format(dir_path, file)
185 | creat_time = os.path.getctime(file_path)
186 | if time_now > creat_time + expire_time:
187 | os.remove(file_path)
188 |
189 |
190 | # Network tools
191 |
192 | def parse_cookie(cookies):
193 | if cookies == "":
194 | return {}
195 | cookie_dict = {}
196 | for line in cookies.split(';'):
197 | name, value = line.strip().split('=', 1)
198 | cookie_dict[name] = value
199 | return cookie_dict
200 |
201 |
202 | def random_ip(model="all"):
203 | if model == "A":
204 | return "%d.%d.%d.%d" % (random.randint(1, 126), random.randint(1, 254),
205 | random.randint(1, 254), random.randint(1, 254))
206 | elif model == "B":
207 | return "%d.%d.%d.%d" % (random.randint(128, 191), random.randint(1, 254),
208 | random.randint(1, 254), random.randint(1, 254))
209 | elif model == "C":
210 | return "%d.%d.%d.%d" % (random.randint(192, 223), random.randint(1, 254),
211 | random.randint(1, 254), random.randint(1, 254))
212 | else:
213 | return "%d.%d.%d.%d" % (random.randint(1, 254), random.randint(1, 254),
214 | random.randint(1, 254), random.randint(1, 254))
215 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | selenium = "*"
8 | requests = "*"
9 | pymysql = "*"
10 |
11 | [dev-packages]
12 |
13 | [requires]
14 | python_version = "3.8"
15 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "1a52d96a1ef1b2de27506c7953d1206472f1a90be26e37fcc2bc3acf0adbe8f4"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "certifi": {
20 | "hashes": [
21 | "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
22 | "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
23 | ],
24 | "index": "pypi",
25 | "version": "==2022.12.7"
26 | },
27 | "chardet": {
28 | "hashes": [
29 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
30 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
31 | ],
32 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
33 | "version": "==4.0.0"
34 | },
35 | "idna": {
36 | "hashes": [
37 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
38 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
39 | ],
40 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
41 | "version": "==2.10"
42 | },
43 | "pymysql": {
44 | "hashes": [
45 | "sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641",
46 | "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"
47 | ],
48 | "index": "pypi",
49 | "version": "==1.0.2"
50 | },
51 | "requests": {
52 | "hashes": [
53 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
54 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
55 | ],
56 | "index": "pypi",
57 | "version": "==2.25.1"
58 | },
59 | "selenium": {
60 | "hashes": [
61 | "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
62 | "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
63 | ],
64 | "index": "pypi",
65 | "version": "==3.141.0"
66 | },
67 | "urllib3": {
68 | "hashes": [
69 | "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
70 | "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"
71 | ],
72 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
73 | "version": "==1.26.13"
74 | }
75 | },
76 | "develop": {}
77 | }
78 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/login.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 |
3 |
4 |
--------------------------------------------------------------------------------
/0x07-绅士仓库/main.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import re
4 | import Kit
5 | import json
6 | import time
7 | import Config
8 | import urllib3
9 | import pymysql
10 | import requests
11 | from requests import utils
12 | from selenium.webdriver.common.by import By
13 | from selenium.webdriver.support.ui import WebDriverWait
14 | from selenium.webdriver.support import expected_conditions as EC
15 |
16 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
17 |
18 |
19 | def main():
20 | config = Config.get_config()
21 | print("[INFO]", "Start at", Kit.str_time())
22 |
23 | # Check environment
24 | cache_path = config['BASE']['cache_path']
25 | os.makedirs(cache_path, exist_ok=True)
26 | if Kit.env_check(cache_path) is False:
27 | print("[ERR]", "Chrome driver run environment not found")
28 | return exit(1)
29 |
30 | # User login
31 | cookies = login(config)
32 |
33 | # Fetch archives
34 | session = requests.Session()
35 | cookies_jar = requests.utils.cookiejar_from_dict(cookies)
36 | session.cookies = cookies_jar
37 | session.headers = {"User-Agent": config['BASE']['user_agent']}
38 |
39 | conn = Kit.mysql_conn(config, "MYSQL")
40 | cursor = conn.cursor()
41 |
42 | for pid in range(189900, 1, -1):
43 | sql = "SELECT COUNT(*) FROM `cangku` WHERE `id`=%s"
44 | cursor.execute(sql, pid)
45 | count = int(cursor.fetchone()[0])
46 | if count == 1:
47 | continue
48 |
49 | for i in range(3):
50 | try:
51 | fetch_info(config, conn, session, pid)
52 | break
53 | except Exception as e:
54 | error_record(conn, pid, 000)
55 | Kit.print_red("RE{}".format(i))
56 | print(e)
57 |
58 |
59 | def login(config):
60 | print("[INFO]", "Opening website")
61 | cache_path = config['BASE']['cache_path']
62 | browser = Kit.run_browser(cache_path, config['BASE']['headless'])
63 | browser.implicitly_wait(10)
64 | browser.get("https://cangku.io/login")
65 | form_group = browser.find_elements_by_class_name("form-group")
66 | form_group[0].find_element_by_tag_name("input").send_keys(config['USER']['username'])
67 | form_group[1].find_element_by_tag_name("input").send_keys(config['USER']['password'])
68 | browser.find_element_by_class_name("btn-danger").click()
69 | WebDriverWait(browser, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, "global-announce")))
70 | cookies = {}
71 | for item in browser.get_cookies():
72 | cookies[item["name"]] = item["value"]
73 |
74 | browser.quit()
75 | return cookies
76 |
77 |
78 | def fetch_info(config, conn, session, pid):
79 | # Fetch info data
80 | print("[INFO]", "Fetch page {}...".format(pid), end="")
81 | params = {"include": "user,tags,categories,favorited,upvoted"}
82 | res = session.get("https://cangku.io/api/v1/post/info?id={}".format(pid), params=params,
83 | proxies=config["PROXY"], verify=False)
84 | if res.status_code == 403:
85 | error_record(conn, pid, 403)
86 | Kit.print_red("BAN 403")
87 | return False
88 |
89 | # Parser json
90 | page_data = json.loads(res.text)
91 | if page_data["status_code"] == 404:
92 | error_record(conn, pid, 404)
93 | Kit.print_red("ERR 404")
94 | return False
95 | elif page_data["status_code"] == 200:
96 | tag_name = page_data["data"]["title"].split("]")[0].split("[")[-1]
97 | if tag_name == page_data["data"]["title"]:
98 | tag_name = page_data["data"]["categories"][0]["name"]
99 | sql_data = {
100 | "pid": page_data["data"]["id"],
101 | "url": "https://cangku.io/archives/{}".format(pid),
102 | "tag": tag_name,
103 | "user": page_data["data"]["user_id"],
104 | "title": page_data["data"]["title"],
105 | "cover": page_data["data"]["thumb"],
106 | "content": page_data["data"]["content"],
107 | "raw_data": res.text
108 | }
109 | sql = "REPLACE INTO `cangku`(id,url,tag,user,title,cover,content,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)"
110 | cursor = conn.cursor()
111 | cursor.execute(sql, args=list(sql_data.values()))
112 | conn.commit()
113 |
114 | Kit.print_green("OK")
115 | print("Info: {} // {} // {}".format(pid, sql_data["tag"], sql_data["title"]))
116 | return True
117 | else:
118 | error_record(conn, pid, page_data["status_code"])
119 | Kit.print_red("ERR ???")
120 | print("Error status", pid, page_data["status_code"], page_data["message"])
121 | return False
122 |
123 |
124 | def error_record(conn, pid, status):
125 | cursor = conn.cursor()
126 | sql = "REPLACE INTO `cangku`(id,url,tag,user,title,cover,content,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)"
127 | cursor.execute(sql, args=[pid, "https://cangku.io/archives/{}".format(pid), status, "无法访问", "", "", "", ""])
128 | conn.commit()
129 |
130 |
131 | if __name__ == '__main__':
132 | main()
133 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | cache
3 | __pycache__
4 | Config/*.ini
5 | !example.ini
6 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Config/__init__.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import logging
4 | import configparser
5 |
6 |
7 | def get_config():
8 | # 读取配置文件
9 | run_env = 'production'
10 | if 'SERVICE_ENV' in os.environ:
11 | run_env = os.environ['SERVICE_ENV']
12 | print("Load config [%s]" % run_env)
13 | config_path = '{}/{}.ini'.format(os.path.split(os.path.abspath(__file__))[0], run_env)
14 | if os.path.isfile(config_path):
15 | config = configparser.ConfigParser()
16 | config.read(config_path, encoding='utf-8')
17 |
18 | app_config = dict()
19 | for section in config.sections():
20 | app_config[section] = dict(config.items(section))
21 |
22 | app_config["RUN_ENV"] = run_env
23 | return app_config
24 | else:
25 | logging.error("Config not exist")
26 | exit()
27 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Config/openssl.cnf:
--------------------------------------------------------------------------------
1 | #
2 | # OpenSSL example configuration file.
3 | # This is mostly being used for generation of certificate requests.
4 | #
5 |
6 | # Note that you can include other files from the main configuration
7 | # file using the .include directive.
8 | #.include filename
9 |
10 | # This definition stops the following lines choking if HOME isn't
11 | # defined.
12 | HOME = .
13 |
14 | # Extra OBJECT IDENTIFIER info:
15 | #oid_file = $ENV::HOME/.oid
16 | oid_section = new_oids
17 |
18 | # System default
19 | openssl_conf = default_conf
20 |
21 | # To use this configuration file with the "-extfile" option of the
22 | # "openssl x509" utility, name here the section containing the
23 | # X.509v3 extensions to use:
24 | # extensions =
25 | # (Alternatively, use a configuration file that has only
26 | # X.509v3 extensions in its main [= default] section.)
27 |
28 | [ new_oids ]
29 |
30 | # We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
31 | # Add a simple OID like this:
32 | # testoid1=1.2.3.4
33 | # Or use config file substitution like this:
34 | # testoid2=${testoid1}.5.6
35 |
36 | # Policies used by the TSA examples.
37 | tsa_policy1 = 1.2.3.4.1
38 | tsa_policy2 = 1.2.3.4.5.6
39 | tsa_policy3 = 1.2.3.4.5.7
40 |
41 | ####################################################################
42 | [ ca ]
43 | default_ca = CA_default # The default ca section
44 |
45 | ####################################################################
46 | [ CA_default ]
47 |
48 | dir = ./demoCA # Where everything is kept
49 | certs = $dir/certs # Where the issued certs are kept
50 | crl_dir = $dir/crl # Where the issued crl are kept
51 | database = $dir/index.txt # database index file.
52 | #unique_subject = no # Set to 'no' to allow creation of
53 | # several certs with same subject.
54 | new_certs_dir = $dir/newcerts # default place for new certs.
55 |
56 | certificate = $dir/cacert.pem # The CA certificate
57 | serial = $dir/serial # The current serial number
58 | crlnumber = $dir/crlnumber # the current crl number
59 | # must be commented out to leave a V1 CRL
60 | crl = $dir/crl.pem # The current CRL
61 | private_key = $dir/private/cakey.pem# The private key
62 |
63 | x509_extensions = usr_cert # The extensions to add to the cert
64 |
65 | # Comment out the following two lines for the "traditional"
66 | # (and highly broken) format.
67 | name_opt = ca_default # Subject Name options
68 | cert_opt = ca_default # Certificate field options
69 |
70 | # Extension copying option: use with caution.
71 | # copy_extensions = copy
72 |
73 | # Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
74 | # so this is commented out by default to leave a V1 CRL.
75 | # crlnumber must also be commented out to leave a V1 CRL.
76 | # crl_extensions = crl_ext
77 |
78 | default_days = 365 # how long to certify for
79 | default_crl_days= 30 # how long before next CRL
80 | default_md = default # use public key default MD
81 | preserve = no # keep passed DN ordering
82 |
83 | # A few difference way of specifying how similar the request should look
84 | # For type CA, the listed attributes must be the same, and the optional
85 | # and supplied fields are just that :-)
86 | policy = policy_match
87 |
88 | # For the CA policy
89 | [ policy_match ]
90 | countryName = match
91 | stateOrProvinceName = match
92 | organizationName = match
93 | organizationalUnitName = optional
94 | commonName = supplied
95 | emailAddress = optional
96 |
97 | # For the 'anything' policy
98 | # At this point in time, you must list all acceptable 'object'
99 | # types.
100 | [ policy_anything ]
101 | countryName = optional
102 | stateOrProvinceName = optional
103 | localityName = optional
104 | organizationName = optional
105 | organizationalUnitName = optional
106 | commonName = supplied
107 | emailAddress = optional
108 |
109 | ####################################################################
110 | [ req ]
111 | default_bits = 2048
112 | default_keyfile = privkey.pem
113 | distinguished_name = req_distinguished_name
114 | attributes = req_attributes
115 | x509_extensions = v3_ca # The extensions to add to the self signed cert
116 |
117 | # Passwords for private keys if not present they will be prompted for
118 | # input_password = secret
119 | # output_password = secret
120 |
121 | # This sets a mask for permitted string types. There are several options.
122 | # default: PrintableString, T61String, BMPString.
123 | # pkix : PrintableString, BMPString (PKIX recommendation before 2004)
124 | # utf8only: only UTF8Strings (PKIX recommendation after 2004).
125 | # nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
126 | # MASK:XXXX a literal mask value.
127 | # WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
128 | string_mask = utf8only
129 |
130 | # req_extensions = v3_req # The extensions to add to a certificate request
131 |
132 | [ req_distinguished_name ]
133 | countryName = Country Name (2 letter code)
134 | countryName_default = AU
135 | countryName_min = 2
136 | countryName_max = 2
137 |
138 | stateOrProvinceName = State or Province Name (full name)
139 | stateOrProvinceName_default = Some-State
140 |
141 | localityName = Locality Name (eg, city)
142 |
143 | 0.organizationName = Organization Name (eg, company)
144 | 0.organizationName_default = Internet Widgits Pty Ltd
145 |
146 | # we can do this but it is not needed normally :-)
147 | #1.organizationName = Second Organization Name (eg, company)
148 | #1.organizationName_default = World Wide Web Pty Ltd
149 |
150 | organizationalUnitName = Organizational Unit Name (eg, section)
151 | #organizationalUnitName_default =
152 |
153 | commonName = Common Name (e.g. server FQDN or YOUR name)
154 | commonName_max = 64
155 |
156 | emailAddress = Email Address
157 | emailAddress_max = 64
158 |
159 | # SET-ex3 = SET extension number 3
160 |
161 | [ req_attributes ]
162 | challengePassword = A challenge password
163 | challengePassword_min = 4
164 | challengePassword_max = 20
165 |
166 | unstructuredName = An optional company name
167 |
168 | [ usr_cert ]
169 |
170 | # These extensions are added when 'ca' signs a request.
171 |
172 | # This goes against PKIX guidelines but some CAs do it and some software
173 | # requires this to avoid interpreting an end user certificate as a CA.
174 |
175 | basicConstraints=CA:FALSE
176 |
177 | # Here are some examples of the usage of nsCertType. If it is omitted
178 | # the certificate can be used for anything *except* object signing.
179 |
180 | # This is OK for an SSL server.
181 | # nsCertType = server
182 |
183 | # For an object signing certificate this would be used.
184 | # nsCertType = objsign
185 |
186 | # For normal client use this is typical
187 | # nsCertType = client, email
188 |
189 | # and for everything including object signing:
190 | # nsCertType = client, email, objsign
191 |
192 | # This is typical in keyUsage for a client certificate.
193 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment
194 |
195 | # This will be displayed in Netscape's comment listbox.
196 | nsComment = "OpenSSL Generated Certificate"
197 |
198 | # PKIX recommendations harmless if included in all certificates.
199 | subjectKeyIdentifier=hash
200 | authorityKeyIdentifier=keyid,issuer
201 |
202 | # This stuff is for subjectAltName and issuerAltname.
203 | # Import the email address.
204 | # subjectAltName=email:copy
205 | # An alternative to produce certificates that aren't
206 | # deprecated according to PKIX.
207 | # subjectAltName=email:move
208 |
209 | # Copy subject details
210 | # issuerAltName=issuer:copy
211 |
212 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
213 | #nsBaseUrl
214 | #nsRevocationUrl
215 | #nsRenewalUrl
216 | #nsCaPolicyUrl
217 | #nsSslServerName
218 |
219 | # This is required for TSA certificates.
220 | # extendedKeyUsage = critical,timeStamping
221 |
222 | [ v3_req ]
223 |
224 | # Extensions to add to a certificate request
225 |
226 | basicConstraints = CA:FALSE
227 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment
228 |
229 | [ v3_ca ]
230 |
231 |
232 | # Extensions for a typical CA
233 |
234 |
235 | # PKIX recommendation.
236 |
237 | subjectKeyIdentifier=hash
238 |
239 | authorityKeyIdentifier=keyid:always,issuer
240 |
241 | basicConstraints = critical,CA:true
242 |
243 | # Key usage: this is typical for a CA certificate. However since it will
244 | # prevent it being used as an test self-signed certificate it is best
245 | # left out by default.
246 | # keyUsage = cRLSign, keyCertSign
247 |
248 | # Some might want this also
249 | # nsCertType = sslCA, emailCA
250 |
251 | # Include email address in subject alt name: another PKIX recommendation
252 | # subjectAltName=email:copy
253 | # Copy issuer details
254 | # issuerAltName=issuer:copy
255 |
256 | # DER hex encoding of an extension: beware experts only!
257 | # obj=DER:02:03
258 | # Where 'obj' is a standard or added object
259 | # You can even override a supported extension:
260 | # basicConstraints= critical, DER:30:03:01:01:FF
261 |
262 | [ crl_ext ]
263 |
264 | # CRL extensions.
265 | # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
266 |
267 | # issuerAltName=issuer:copy
268 | authorityKeyIdentifier=keyid:always
269 |
270 | [ proxy_cert_ext ]
271 | # These extensions should be added when creating a proxy certificate
272 |
273 | # This goes against PKIX guidelines but some CAs do it and some software
274 | # requires this to avoid interpreting an end user certificate as a CA.
275 |
276 | basicConstraints=CA:FALSE
277 |
278 | # Here are some examples of the usage of nsCertType. If it is omitted
279 | # the certificate can be used for anything *except* object signing.
280 |
281 | # This is OK for an SSL server.
282 | # nsCertType = server
283 |
284 | # For an object signing certificate this would be used.
285 | # nsCertType = objsign
286 |
287 | # For normal client use this is typical
288 | # nsCertType = client, email
289 |
290 | # and for everything including object signing:
291 | # nsCertType = client, email, objsign
292 |
293 | # This is typical in keyUsage for a client certificate.
294 | # keyUsage = nonRepudiation, digitalSignature, keyEncipherment
295 |
296 | # This will be displayed in Netscape's comment listbox.
297 | nsComment = "OpenSSL Generated Certificate"
298 |
299 | # PKIX recommendations harmless if included in all certificates.
300 | subjectKeyIdentifier=hash
301 | authorityKeyIdentifier=keyid,issuer
302 |
303 | # This stuff is for subjectAltName and issuerAltname.
304 | # Import the email address.
305 | # subjectAltName=email:copy
306 | # An alternative to produce certificates that aren't
307 | # deprecated according to PKIX.
308 | # subjectAltName=email:move
309 |
310 | # Copy subject details
311 | # issuerAltName=issuer:copy
312 |
313 | #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
314 | #nsBaseUrl
315 | #nsRevocationUrl
316 | #nsRenewalUrl
317 | #nsCaPolicyUrl
318 | #nsSslServerName
319 |
320 | # This really needs to be in place for it to be a proxy certificate.
321 | proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
322 |
323 | ####################################################################
324 | [ tsa ]
325 |
326 | default_tsa = tsa_config1 # the default TSA section
327 |
328 | [ tsa_config1 ]
329 |
330 | # These are used by the TSA reply generation only.
331 | dir = ./demoCA # TSA root directory
332 | serial = $dir/tsaserial # The current serial number (mandatory)
333 | crypto_device = builtin # OpenSSL engine to use for signing
334 | signer_cert = $dir/tsacert.pem # The TSA signing certificate
335 | # (optional)
336 | certs = $dir/cacert.pem # Certificate chain to include in reply
337 | # (optional)
338 | signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
339 | signer_digest = sha256 # Signing digest to use. (Optional)
340 | default_policy = tsa_policy1 # Policy if request did not specify it
341 | # (optional)
342 | other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
343 | digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory)
344 | accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
345 | clock_precision_digits = 0 # number of digits after dot. (optional)
346 | ordering = yes # Is ordering defined for timestamps?
347 | # (optional, default: no)
348 | tsa_name = yes # Must the TSA name be included in the reply?
349 | # (optional, default: no)
350 | ess_cert_id_chain = no # Must the ESS cert id chain be included?
351 | # (optional, default: no)
352 | ess_cert_id_alg = sha1 # algorithm to compute certificate
353 | # identifier (optional, default: sha1)
354 | [default_conf]
355 | ssl_conf = ssl_sect
356 |
357 | [ssl_sect]
358 | system_default = system_default_sect
359 |
360 | [system_default_sect]
361 | MinProtocol = TLSv1.0
362 | CipherString = DEFAULT@SECLEVEL=1
363 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Kit/__init__.py:
--------------------------------------------------------------------------------
1 | from .kit import *
2 | from .chrome import env_check
3 | from .chrome import run_browser
4 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Kit/chrome.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import re
4 | import Kit
5 | import stat
6 | import zipfile
7 | import requests
8 | import subprocess
9 | from selenium import webdriver
10 |
11 | # 引入Windows特定库
12 | if Kit.run_platform() == "windows":
13 | import winreg
14 |
15 | # 全局变量
16 | version_re = re.compile(r'^[1-9]\d*\.\d*.\d*')
17 |
18 |
19 | def run_browser(driver_path, headless="Yes"):
20 | driver_path = os.path.abspath(driver_path) + "/chromedriver"
21 | option = webdriver.ChromeOptions()
22 | if headless == "Yes":
23 | option.add_argument('--headless')
24 | option.add_argument('--no-sandbox')
25 | option.add_argument('--disable-gpu')
26 | browser = webdriver.Chrome(driver_path, options=option)
27 | return browser
28 |
29 |
30 | def env_check(cache_path):
31 | print("[INFO]", "Check chrome driver env...")
32 | cache_path = os.path.abspath(cache_path)
33 |
34 | chrome_version = get_chrome_version()
35 | driver_version = get_driver_version(cache_path)
36 |
37 | if chrome_version == "0":
38 | print("[INFO]", "Please install chrome browser")
39 | return False
40 |
41 | if driver_version == "0":
42 | # Download driver
43 | print("[INFO]", "Download chrome driver")
44 | return download_driver(chrome_version, cache_path)
45 |
46 | if version_re.findall(chrome_version)[0] != version_re.findall(driver_version)[0]:
47 | # Update driver
48 | print("[INFO]", "Update chrome driver")
49 | return download_driver(chrome_version, cache_path)
50 |
51 | print("[INFO]", "Chrome driver check pass")
52 | return True # Check success
53 |
54 |
55 | def get_chrome_version():
56 | print("[INFO]", "Check chrome version...")
57 | if Kit.run_platform() == "windows":
58 | try:
59 | # 从注册表中获得版本号
60 | key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon")
61 | version, _ = winreg.QueryValueEx(key, "version")
62 |
63 | print("[INFO]", "Current chrome Version: {}".format(version)) # 这步打印会在命令行窗口显示
64 | return version
65 | except WindowsError as e:
66 | print("[INFO]", "Check chrome failed:{}".format(e))
67 | return "0"
68 | else:
69 | try:
70 | cmd = r"google-chrome-stable --version"
71 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
72 | out = out.decode("utf-8")
73 | version = out.split(" ")[2] # 拆分回显字符串,获取版本号
74 | print("[INFO]", "Current chrome Version:{}".format(version))
75 | return version
76 | except WindowsError as e:
77 | print("[INFO]", "Check chrome failed:{}".format(e))
78 | return "0"
79 |
80 |
81 | def get_driver_version(driver_path):
82 | print("[INFO]", "Check driver version...")
83 | if Kit.run_platform() == "windows":
84 | try:
85 | # 执行cmd命令并接收命令回显
86 | cmd = r'"{}/chromedriver" --version'.format(driver_path)
87 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
88 | out = out.decode("utf-8")
89 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号
90 | print("[INFO]", "Current driver Version:{}".format(version))
91 | return version
92 | except IndexError as e:
93 | print("[INFO]", "Check driver failed:{}".format(e))
94 | return "0"
95 | else:
96 | try:
97 | # 执行cmd命令并接收命令回显
98 | cmd = r"{}/chromedriver --version".format(driver_path)
99 | out, err = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
100 | out = out.decode("utf-8")
101 | version = out.split(" ")[1] # 拆分回显字符串,获取版本号
102 | print("[INFO]", "Current driver Version:{}".format(version))
103 | return version
104 | except IndexError as e:
105 | print("[INFO]", "Check driver failed:{}".format(e))
106 | return "0"
107 |
108 |
109 | def download_driver(version, driver_path):
110 | print("[INFO]", "Download driver on", Kit.run_platform())
111 | driver_path = os.path.abspath(driver_path)
112 |
113 | # 获取淘宝镜像列表
114 | http_res = requests.get("http://npm.taobao.org/mirrors/chromedriver")
115 | mirror_list = re.findall(r'(.*)', http_res.text)
116 |
117 | # 检索驱动版本列表
118 | url_path = ""
119 | version_prefix = version_re.findall(version)[0]
120 | for mirrors in mirror_list:
121 | if mirrors[1].startswith(version_prefix):
122 | url_path = mirrors[0]
123 |
124 | print("[INFO]", "Download driver version:", url_path.split("/")[-1])
125 | if Kit.run_platform() == "windows":
126 | file_url = "http://npm.taobao.org/{}/chromedriver_win32.zip".format(url_path)
127 | elif Kit.run_platform() == "linux":
128 | file_url = "http://npm.taobao.org/{}/chromedriver_linux64.zip".format(url_path)
129 | elif Kit.run_platform() == "darwin":
130 | file_url = "http://npm.taobao.org/{}/chromedriver_mac64.zip".format(url_path)
131 | else:
132 | print("[ERR]", "Not found chrome driver")
133 | return False
134 |
135 | print("[INFO]", "Download driver url:", file_url)
136 | driver_res = requests.get(file_url)
137 |
138 | # 写入压缩文件
139 | zip_path = driver_path + "/chromedriver.zip"
140 | zip_file = open(zip_path, "wb")
141 | zip_file.write(driver_res.content)
142 | zip_file.close()
143 |
144 | # 下载完成后解压
145 | zip_file = zipfile.ZipFile(zip_path, "r")
146 | for fileM in zip_file.namelist():
147 | zip_file.extract(fileM, os.path.dirname(zip_path))
148 | zip_file.close()
149 |
150 | # 删除残留文件
151 | os.remove(zip_path)
152 |
153 | # 授予执行权限
154 | if Kit.run_platform() == "linux":
155 | os.chmod(driver_path + "/chromedriver", stat.S_IXGRP)
156 |
157 | print("[INFO]", "Download chrome driver finish")
158 | return True
159 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Kit/kit.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import re
4 | import sys
5 | import uuid
6 | import time
7 | import random
8 | import string
9 | import inspect
10 | import hashlib
11 | import platform
12 | import pymysql
13 |
14 |
15 | # from bs4 import BeautifulSoup
16 |
17 | # MySQL
18 | def mysql_conn(config, db_key):
19 | config[db_key]['port'] = int(config[db_key]['port'])
20 | conn = pymysql.connect(**config[db_key])
21 | return conn
22 |
23 |
24 | # Print tools
25 | def _print(message, code=None, tag=None, end=None):
26 | if tag is None:
27 | message = '[{}] {}'.format(tag, message)
28 | if code is not None:
29 | message = '\033[0;{}m{}\033[0m'.format(code, message)
30 | print(message, end=end)
31 |
32 |
33 | def print_red(message, tag="ERROR", end=None):
34 | _print(message, 31, tag, end) # 红色
35 |
36 |
37 | def print_green(message, tag="DONE", end='\n'):
38 | _print(message, 32, tag, end) # 绿色
39 |
40 |
41 | def print_yellow(message, tag="WARNING", end='\n'):
42 | _print(message, 33, tag, end) # 黄色
43 |
44 |
45 | def print_blue(message, tag="BEG", end='\n'):
46 | _print(message, 34, tag, end) # 深蓝色
47 |
48 |
49 | def print_purple(message, tag="MID", end='\n'):
50 | _print(message, 35, tag, end) # 紫色
51 |
52 |
53 | def print_azure(message, tag="END", end='\n'):
54 | _print(message, 36, tag, end) # 浅蓝色
55 |
56 |
57 | def print_white(message, tag="INFO", end='\n'):
58 | _print(message, 37, tag, end) # 白色
59 |
60 |
61 | def print_none(message, tag="DEBUG", end='\n'):
62 | _print(message, None, tag, end) # 默认
63 |
64 |
65 | def process_bar(now, total, attach=''):
66 | # 在窗口底部动态显示进度条
67 | rate = now / total
68 | rate_num = int(rate * 100)
69 | bar_length = int(rate_num / 2)
70 | if rate_num == 100:
71 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " "))
72 | bar = '\r' + bar[0:40]
73 | bar += '%s>%d%%\n' % ('=' * bar_length, rate_num)
74 | else:
75 | bar = 'Pid:[%5d]: %s' % (os.getpid(), attach.center(10, " "))
76 | bar = '\r' + bar[0:40]
77 | bar += '%s>%d%%' % ('=' * bar_length, rate_num)
78 | sys.stdout.write(bar)
79 | sys.stdout.flush()
80 |
81 |
82 | # Time tools
83 |
84 | def unix_time(unit=1):
85 | return int(time.time() * unit)
86 |
87 |
88 | def str_time(pattern='%Y-%m-%d %H:%M:%S', timing=None):
89 | if timing is None:
90 | timing = time.time()
91 | return time.strftime(pattern, time.localtime(timing))
92 |
93 |
94 | def format_time(time_obj, pattern='%Y-%m-%d %H:%M:%S'):
95 | return time.strftime(pattern, time_obj)
96 |
97 |
98 | def datetime2unix(timing):
99 | return int(time.mktime(timing.timetuple()))
100 |
101 |
102 | def timestamp2unix(time_string, pattern='%Y-%m-%d %H:%M:%S'):
103 | time_array = time.strptime(time_string, pattern)
104 | return int(time.mktime(time_array))
105 |
106 |
107 | def unix2timestamp(u_time, pattern='%Y-%m-%d %H:%M:%S'):
108 | return time.strftime(pattern, time.localtime(u_time))
109 |
110 |
111 | # Calc tools
112 |
113 | def func_name(fa=1):
114 | return inspect.stack()[fa][3]
115 |
116 |
117 | def random_code():
118 | return str(uuid.uuid1()).split('-')[0]
119 |
120 |
121 | def random_string(length, chars=string.digits + string.ascii_letters):
122 | return ''.join(random.choice(chars) for _ in range(length))
123 |
124 |
125 | def calc_sha1(seed):
126 | seed = str(seed).encode('utf-8')
127 | sha1 = hashlib.sha1()
128 | sha1.update(seed)
129 | return sha1.hexdigest()
130 |
131 |
132 | def calc_md5(seed):
133 | seed = str(seed).encode('utf-8')
134 | md5 = hashlib.md5()
135 | md5.update(seed)
136 | return md5.hexdigest()
137 |
138 |
139 | # def parse_xml(data):
140 | # xml = re.sub(r'', lambda m: m.group(1), data)
141 | # xml = BeautifulSoup(xml, 'lxml')
142 | # xml = xml.html.body.xml
143 | # return xml
144 |
145 |
146 | def cpu_core():
147 | if run_platform() == "windows":
148 | return int(os.popen("echo %NUMBER_OF_PROCESSORS%").read())
149 | elif run_platform() == "linux":
150 | return int(os.popen(r"cat /proc/cpuinfo | grep 'cpu cores' | uniq | awk '{print $4}'").read())
151 | else:
152 | return 0
153 |
154 |
155 | def run_platform():
156 | # windows / linux / darwin
157 | return platform.system().lower()
158 |
159 |
160 | # File tools
161 |
162 |
163 | def code_dir():
164 | file = os.path.abspath(__file__)
165 | return os.path.dirname(file)
166 |
167 |
168 | def code_path():
169 | return os.path.abspath(__file__)
170 |
171 |
172 | def legalize_name(name):
173 | legal_name = re.sub(r"[\/\\\:\*\?\"\<\>\|\s']", '_', name)
174 | legal_name = re.sub(r'[‘’]', '_', legal_name)
175 | if len(legal_name) == 0:
176 | return 'null'
177 | return legal_name
178 |
179 |
180 | def delete_old_file(dir_path, expire_time):
181 | time_now = unix_time()
182 | dir_path = os.path.abspath(dir_path)
183 | for file in os.listdir(dir_path):
184 | file_path = '{}/{}'.format(dir_path, file)
185 | creat_time = os.path.getctime(file_path)
186 | if time_now > creat_time + expire_time:
187 | os.remove(file_path)
188 |
189 |
190 | # Network tools
191 |
192 | def parse_cookie(cookies):
193 | if cookies == "":
194 | return {}
195 | cookie_dict = {}
196 | for line in cookies.split(';'):
197 | name, value = line.strip().split('=', 1)
198 | cookie_dict[name] = value
199 | return cookie_dict
200 |
201 |
202 | def random_ip(model="all"):
203 | if model == "A":
204 | return "%d.%d.%d.%d" % (random.randint(1, 126), random.randint(1, 254),
205 | random.randint(1, 254), random.randint(1, 254))
206 | elif model == "B":
207 | return "%d.%d.%d.%d" % (random.randint(128, 191), random.randint(1, 254),
208 | random.randint(1, 254), random.randint(1, 254))
209 | elif model == "C":
210 | return "%d.%d.%d.%d" % (random.randint(192, 223), random.randint(1, 254),
211 | random.randint(1, 254), random.randint(1, 254))
212 | else:
213 | return "%d.%d.%d.%d" % (random.randint(1, 254), random.randint(1, 254),
214 | random.randint(1, 254), random.randint(1, 254))
215 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | selenium = "*"
8 | requests = "*"
9 | pymysql = "*"
10 |
11 | [dev-packages]
12 |
13 | [requires]
14 | python_version = "3.8"
15 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "1a52d96a1ef1b2de27506c7953d1206472f1a90be26e37fcc2bc3acf0adbe8f4"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "certifi": {
20 | "hashes": [
21 | "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
22 | "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
23 | ],
24 | "version": "==2021.5.30"
25 | },
26 | "chardet": {
27 | "hashes": [
28 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
29 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
30 | ],
31 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
32 | "version": "==4.0.0"
33 | },
34 | "idna": {
35 | "hashes": [
36 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
37 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
38 | ],
39 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
40 | "version": "==2.10"
41 | },
42 | "pymysql": {
43 | "hashes": [
44 | "sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641",
45 | "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"
46 | ],
47 | "index": "pypi",
48 | "version": "==1.0.2"
49 | },
50 | "requests": {
51 | "hashes": [
52 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
53 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
54 | ],
55 | "index": "pypi",
56 | "version": "==2.25.1"
57 | },
58 | "selenium": {
59 | "hashes": [
60 | "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
61 | "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
62 | ],
63 | "index": "pypi",
64 | "version": "==3.141.0"
65 | },
66 | "urllib3": {
67 | "hashes": [
68 | "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
69 | "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
70 | ],
71 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
72 | "version": "==1.26.5"
73 | }
74 | },
75 | "develop": {}
76 | }
77 |
--------------------------------------------------------------------------------
/0x08-绅士小站2/main.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | import os
3 | import re
4 | import Kit
5 | import uuid
6 | import Config
7 | import urllib3
8 | import pymysql
9 | import requests
10 | import subprocess
11 | import urllib.error
12 | import urllib.request
13 | from bs4 import BeautifulSoup
14 | from urllib.parse import quote
15 |
16 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
17 |
18 |
19 | def main():
20 | config = Config.get_config()
21 | print("[INFO]", "Start at", Kit.str_time())
22 |
23 | # Fetch archives
24 | session = requests.Session()
25 | session.headers = {
26 | "User-Agent": config['BASE']['user_agent'],
27 | "Connection": "keep-alive",
28 | "Accept": "*/*",
29 | "Accept-Encoding": "gzip, deflate",
30 | "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
31 | }
32 | session.cookies.update({"age_gate": "18"})
33 |
34 | conn = Kit.mysql_conn(config, "MYSQL")
35 | cursor = conn.cursor(pymysql.cursors.DictCursor)
36 |
37 | for pid in range(1, 2760):
38 | sql = "SELECT * FROM `gca_tw` WHERE `id`=%s"
39 | cursor.execute(sql, pid)
40 | record = cursor.fetchone()
41 | if record is not None and record["tag"] in ["202", "404"]:
42 | continue
43 |
44 | if record is not None and record["download"] == "Yes":
45 | continue
46 |
47 | try:
48 | fetch_page(config, conn, session, pid)
49 | except Exception as e:
50 | Kit.print_red("Runtime Error: {}".format(e))
51 | raise e
52 |
53 |
54 | def fetch_page(config, conn, session, pid):
55 | # Fetch info data
56 | page_url = "https://ca.gca.tw/{}.html".format(pid)
57 | print("[INFO]", "Fetch page {} ... ".format(page_url), end="")
58 |
59 | # Get page data
60 | res = session.get(page_url)
61 |
62 | if res.status_code == 404:
63 | Kit.print_red("ERR-404")
64 | error_record(conn, pid, 404)
65 | return False
66 | Kit.print_green("SUCCESS")
67 |
68 | soup = BeautifulSoup(res.text, "lxml")
69 | title = soup.find(rel="bookmark")
70 | if title is None:
71 | title = "None-{}".format(uuid.uuid1())
72 | else:
73 | title = title.string
74 | print("Page title:", title)
75 |
76 | # Submit password
77 | payload = {
78 | "post_password": title,
79 | "Submit": "提交"
80 | }
81 | session.post("https://ca.gca.tw/wp-login.php?action=postpass", data=payload)
82 |
83 | # Re-get page content
84 | res = session.get(page_url)
85 |
86 | soup = BeautifulSoup(res.text, "lxml")
87 | content = soup.find(class_="entry-content")
88 |
89 | sql_data = {
90 | "pid": pid,
91 | "url": page_url,
92 | "tag": "video",
93 | "title": title,
94 | "cover": "",
95 | "video": "",
96 | "download": "No",
97 | "raw_data": res.text
98 | }
99 | sql = "REPLACE INTO `gca_tw`(id,url,tag,title,cover,video,download,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)"
100 | cursor = conn.cursor()
101 | cursor.execute(sql, args=list(sql_data.values()))
102 | conn.commit()
103 |
104 | # Download image
105 | image_list = content.find_all("img")
106 | if image_list is not None:
107 | for index, image in enumerate(image_list):
108 | image_url = "https://ca.gca.tw/{}".format(image["src"])
109 | image_path = "{}/Image/{}-{}.jpg".format(config["BASE"]["download_path"], legalize_name(title), index)
110 | try:
111 | urllib.request.urlretrieve(quote(image_url, safe='/:?='), filename=image_path) # Images
112 | except urllib.error.HTTPError:
113 | continue
114 |
115 | # Download video
116 | video_box = content.find("video")
117 | if video_box is not None:
118 |
119 | if video_box["poster"].find("ca.gca.tw") != -1:
120 | cover_url = video_box["poster"]
121 | else:
122 | cover_url = "https://ca.gca.tw/{}".format(video_box["poster"])
123 |
124 | if "src" in video_box.attrs.keys():
125 | video_url = video_box["src"]
126 | else:
127 | video_url = video_box.find("source")["src"]
128 |
129 | video_path = os.path.abspath("{}/{}".format(config["BASE"]["download_path"], legalize_name(title)))
130 | if os.path.exists(video_path) is False:
131 | os.mkdir(video_path)
132 |
133 | urllib.request.urlretrieve(quote(cover_url, safe='/:?='), filename="{}/cover.jpg".format(video_path)) # Image
134 | aria2c_pull(config, page_url, video_path, "{}.mp4".format(legalize_name(title)), [video_url], True) # Video
135 |
136 | sql = "UPDATE `gca_tw` SET `cover`=%s,`video`=%s WHERE `id`=%s"
137 | cursor.execute(sql, args=[cover_url, video_url, pid])
138 |
139 | sql = "UPDATE `gca_tw` SET `download`='Yes' WHERE `id`=%s"
140 | cursor.execute(sql, args=[pid])
141 | conn.commit()
142 |
143 |
144 | def error_record(conn, pid, status):
145 | cursor = conn.cursor()
146 | sql = "REPLACE INTO `gca_tw`(id,url,tag,title,cover,video,download,raw_data)VALUES(%s,%s,%s,%s,%s,%s,%s,%s)"
147 | cursor.execute(sql, args=[pid, "https://ca.gca.tw/{}.html".format(pid), status, "无法访问", "", "", "", ""])
148 | conn.commit()
149 |
150 |
151 | def aria2c_pull(config, page, path, name, url_list, show_process=False):
152 | # 设置输出信息
153 | if show_process:
154 | out_pipe = None
155 | else:
156 | out_pipe = subprocess.PIPE
157 |
158 | # 读取代理信息
159 | proxies = ''
160 | proxies += ' --http-proxy="{}"'.format(config["PROXY"]["http"]) if config["PROXY"]["http"] is not None else ""
161 | proxies += ' --https-proxy="{}"'.format(config["PROXY"]["https"]) if config["PROXY"]["https"] is not None else ""
162 |
163 | url = '"{}"'.format('" "'.join(url_list))
164 | shell = 'aria2c -c -k 1M -x {} -d "{}" -o "{}" --referer="{}" {} {}'
165 | shell = shell.format(len(url_list) * 4, path, name, page, proxies, url)
166 | process = subprocess.Popen(shell, stdout=out_pipe, stderr=out_pipe, shell=True)
167 | process.wait()
168 |
169 |
170 | def legalize_name(name):
171 | legal_name = re.sub(r"[\/\\\:\*\?\"\<\>\|\s']", '_', name)
172 | legal_name = re.sub(r'[‘’]', '_', legal_name)
173 | if len(legal_name) == 0:
174 | return 'null'
175 | return legal_name
176 |
177 |
178 | if __name__ == '__main__':
179 | main()
180 |
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/86352163-1_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffe126de-87b1-915b-a0c3-6032b9343bbb.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"ffe126de-87b1-915b-a0c3-6032b9343bbb","name":"86352163-1","size":58711,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191425037,"height":420,"width":560,"palettes":[{"color":[214,172,147],"ratio":61.65093537414966},{"color":[61,49,53],"ratio":18.721513605442176},{"color":[250,245,237],"ratio":14.74702380952381},{"color":[179,69,80],"ratio":4.719812925170068},{"color":[215,101,65],"ratio":0.1607142857142857}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/76315323_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffea5831-3817-f97f-c375-b5c38ebaa890.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"ffea5831-3817-f97f-c375-b5c38ebaa890","name":"76315323","size":1383919,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191428551,"height":1174,"width":1660,"palettes":[{"color":[236,216,208],"ratio":79.83029801324504},{"color":[76,75,76],"ratio":4.908940397350993},{"color":[208,183,123],"ratio":12.304773730684326},{"color":[155,77,113],"ratio":1.584575055187638},{"color":[190,161,82],"ratio":1.3714128035320088}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/89193684_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeaeaaa-e475-8741-663e-c4a3308c6e3b.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"ffeaeaaa-e475-8741-663e-c4a3308c6e3b","name":"89193684","size":2191039,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191423869,"height":3508,"width":2000,"palettes":[{"color":[240,227,225],"ratio":72.95608860195904},{"color":[66,62,76],"ratio":14.129424532502227},{"color":[168,141,153],"ratio":10.545970614425645},{"color":[134,100,107],"ratio":2.1948742208370438},{"color":[195,138,98],"ratio":0.1736420302760463}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/84372116_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffeb50a5-5447-a4fc-cc95-a009fc385fbd.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"ffeb50a5-5447-a4fc-cc95-a009fc385fbd","name":"84372116","size":1467404,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191426487,"height":1400,"width":1900,"palettes":[{"color":[198,194,194],"ratio":70.76635328389831},{"color":[52,51,55],"ratio":11.096398305084746},{"color":[108,70,78],"ratio":7.77443061440678},{"color":[115,166,201],"ratio":9.830177436440678},{"color":[129,109,88],"ratio":0.5326403601694916}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/70899400_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbbbf-0a8f-732d-b196-e36fa2a12b9e.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"ffebbbbf-0a8f-732d-b196-e36fa2a12b9e","name":"70899400","size":1384499,"ext":"png","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191429063,"height":1754,"width":1240,"palettes":[{"color":[249,209,179],"ratio":54.89813535911602},{"color":[67,43,47],"ratio":25.211671270718234},{"color":[212,149,101],"ratio":17.17092541436464},{"color":[182,108,72],"ratio":2.2821132596685083},{"color":[122,114,98],"ratio":0.4371546961325967}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/92016561-0_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/ffebbe70-01e0-4ef0-9060-c9380f4aabc4.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"ffebbe70-01e0-4ef0-9060-c9380f4aabc4","name":"92016561-0","size":1170919,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191423103,"height":1200,"width":1600,"palettes":[{"color":[231,218,218],"ratio":71.7060546875},{"color":[72,76,90],"ratio":15.440104166666666},{"color":[141,142,157],"ratio":11.693684895833334},{"color":[128,101,109],"ratio":1.0696614583333333},{"color":[161,125,99],"ratio":0.09049479166666666}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/73190808-0_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"fff07694-c82e-e1b8-c7b3-9c7e4cb5d2a3","name":"73190808-0","size":347574,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191428865,"height":1200,"width":882,"palettes":[{"color":[242,217,212],"ratio":85.34138203214697},{"color":[92,100,124],"ratio":1.551377726750861},{"color":[211,142,161],"ratio":10.058481630309988},{"color":[169,78,106],"ratio":1.8477324913892077},{"color":[139,160,98],"ratio":1.201026119402985}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/83619652-11_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff4c7d0-7060-e34d-1dc5-cdb85cb058c9.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"fff4c7d0-7060-e34d-1dc5-cdb85cb058c9","name":"83619652-11","size":920930,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191426753,"height":1912,"width":1080,"palettes":[{"color":[232,216,214],"ratio":67.99205648720212},{"color":[49,51,67],"ratio":9.545316637246248},{"color":[182,107,86],"ratio":7.982265004413063},{"color":[164,127,135],"ratio":11.561258826125332},{"color":[151,82,66],"ratio":2.919103045013239}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924.jpg
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/88199924_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff5e73d-460c-fbad-e90a-3080c6a7a923.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"fff5e73d-460c-fbad-e90a-3080c6a7a923","name":"88199924","size":2718489,"ext":"jpg","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191424174,"height":3743,"width":2637,"palettes":[{"color":[91,87,97],"ratio":28.84567731277533},{"color":[245,243,243],"ratio":47.4671324339207},{"color":[42,41,52],"ratio":18.91416574889868},{"color":[130,136,149],"ratio":3.848430616740088},{"color":[187,132,109],"ratio":0.9245938876651982}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/82329785_thumbnail.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/images/fff75130-4feb-2d23-24bc-1b098faab96e.info/metadata.json:
--------------------------------------------------------------------------------
1 | {"id":"fff75130-4feb-2d23-24bc-1b098faab96e","name":"82329785","size":1688685,"ext":"png","tags":[],"folders":[],"isDeleted":false,"url":"","annotation":"","modificationTime":1636191427179,"height":2000,"width":1500,"palettes":[{"color":[183,136,117],"ratio":50.92944021101993},{"color":[31,24,41],"ratio":22.69306858147714},{"color":[250,231,216],"ratio":23.539346424384526},{"color":[107,63,54],"ratio":1.936730656506448},{"color":[145,80,59],"ratio":0.9014141266119577}]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/metadata.json:
--------------------------------------------------------------------------------
1 | {"folders":[{"children":[],"iconColor":"yellow","id":"e02bd9a5-e2c8-7f80-5c88-967c49387065","modificationTime":1623600319529,"name":"pixiv","tags":[]},{"children":[],"iconColor":"green","id":"e8a01709-1e28-c7ae-24ed-87a84b69fa9d","modificationTime":1634125750902,"name":"检验集","tags":[]}],"smartFolders":[],"quickAccess":[],"tagsGroups":[],"modificationTime":1634126063780,"applicationVersion":"1.6.2"}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Home.library/tags.json:
--------------------------------------------------------------------------------
1 | {"historyTags":["巨乳","新泽西"],"starredTags":[]}
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/Readme.md:
--------------------------------------------------------------------------------
1 | ## Pixiv插画tag导入Eagle
2 |
3 | ---
4 |
5 | + 偶然间被安利了`Eagle`这样一款图片管理软件,初次使用的时候看到这个tag就觉得大有用处。
6 | + 现在可以使用`pixiv2eagle`为`Eagle`内的`pixiv`插画添加`tag`,从而更好的建造、管理、筛选自己的`pixiv`插画数据库(涩图库)
7 | + 需要魔法使用
8 |
9 | ↓ 如图,筛选一下<柴郡>,全是可爱的猫猫:happy:
10 | (会遗漏部分不是从`pixiv`上下载的插画,因为没有`tag`可筛选)
11 |
12 | 
13 |
14 | + 这里给下数据参考。此处为使用`PixiC`下载`pixiv`个人收藏共`4.6w`张图片(`88G`),导入Eagle后为`98G` (`Eagle`生成缩略图占用了`10G`)
15 | + 脚本默认使用8线程,处理以上数据花费时间 : `52min`(`13:05:34.040~13:57:52.723`)
16 |
17 |
18 |
19 | **功能**
20 |
21 | - [x] 从[PIXIC API](https://github.com/Coder-Sakura/PixiC/wiki)或`pixiv`获取对应插画的tag数据
22 | - [x] 将插画`tag`数据及画师名称导入`Eagle metadata.json`
23 | - [x] 线程池处理任务(默认8线程)
24 | - [x] 执行过程及多线程日志记录
25 |
26 |
27 |
28 | **支持识别的`pixiv`插画文件名称**
29 |
30 | + `86352163.jpg` / `86352163.png`
31 | + `86352163-1.jpg` / `86352163-2.png`
32 | + `86352163_p0.jpg` / `86352163_p1.png`
33 |
34 |
35 |
36 | **一些话**
37 |
38 | 1. 由`PixiC`下载得到的`pixiv`插画,格式为`86352163-1.jpg`或`86352163.jpg`的形式(截图中使用的)
39 | 2. 直接从`pixiv`处下载的插画格式为`86352163_p0.jpg`,其他工具下载的文件请自行命令或在`Pixiv2Eagle.get_pid`处添加识别逻辑
40 |
41 |
42 |
43 | **使用方法**
44 |
45 | 1. 添加插画到`Eagle`
46 |
47 | 选中要加入的图片文件,拖拽到`Eagle`上即可;或从左上角选择路径载入
48 |
49 | 
50 |
51 |
52 |
53 | 2. 复制`Eagle`资源库地址
54 |
55 | > 这个地址就是上面创建的;比如`G:\EagleHome\个人收藏.library`
56 |
57 | 填入`pixiv2eagle.py`中的`EAGLE_HOME_PATH`,如:
58 |
59 | ```python
60 | EAGLE_HOME_PATH = r"your EAGLE_HOME_PATH"
61 | ```
62 |
63 |
64 |
65 | > 这里提供了一个TEST DIR供测试使用,即`Home.library`目录
66 |
67 |
68 |
69 | 3. 选择获取pixiv插画数据的方式
70 |
71 | **从`PIXIC_API`获取**
72 |
73 | 将你的`PIXIC_API`地址填入到`pixiv2eagle.py`中的`PIXIC_API`,如:
74 |
75 | ```python
76 | # 使用的是get-info API
77 | # 默认操作的是Bookmark表
78 | PIXIC_API = "http://xxx.xx.xxx.xx:1526/api/v2/get-info"
79 | ```
80 |
81 |
82 |
83 | **从`pixiv`获取**
84 |
85 | 将`PIXIC_API`设置为空即可
86 |
87 | ```python
88 | PIXIC_API = ""
89 | ```
90 |
91 |
92 |
93 | 4. 开始使用
94 |
95 | ```bash
96 | # 安装依赖
97 | pip install -r requirement.txt
98 | # 运行脚本
99 | python pixiv2eagle.py
100 | ```
101 |
102 |
103 |
104 | **运行及效果截图**
105 |
106 | 脚本写入tag
107 |
108 | 
109 |
110 | 写入tag后在Eagle中的使用
111 |
112 | 柴郡
113 |
114 | 
115 |
116 | 大凤
117 |
118 | 
119 |
120 | 支持联想tag
121 |
122 | 
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/img/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/0.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/1.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/2.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/3.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/img/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x09-Pixiv插画tag数据导入Eagle/img/4.png
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/pixiv2eagle.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | '''
3 | @File : pixiv2eagle.py
4 | @Time : 2021/11/04 15:25:48
5 | @Author : Coder-Sakura
6 | @Version : 1.0
7 | @Desc : 将pixiv插画的tag信息写入到Eagle的metadata.json中\
8 | 使得在Eagle中也能通过标签查找自己喜欢的作品
9 | '''
10 |
11 | # here put the import lib
12 | import os
13 | import json
14 | import time
15 | import requests
16 | from loguru import logger
17 | # 强制取消警告
18 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
19 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
20 |
21 | from thread_pool import ThreadPool,callback
22 |
23 | """
24 | =========================================================
25 | 1. 插画图片名称
26 | 可识别的pixiv插画名称包括:
27 | # 图片从PIXIC下载
28 | .jpg
29 | .png
30 | -1.jpg
31 | -2.png
32 | # 直接从PIXIV下载
33 | _p0.jpg
34 | _p1.png
35 |
36 | =========================================================
37 | 2. tag数据来源
38 | pixiv2eagle.py 提供两种形式以获取 pixiv 插画的 tag 信息
39 |
40 | # 通过PixiC API的形式, PixiC API的部署形式可以参考:
41 | https://github.com/Coder-Sakura/PixiC/wiki
42 |
43 | 部署好之后,先地址填入 PIXIC_API_HOST.
44 | 如: PIXIC_API = http://xxx.xx.xxx.xx:1526/api/v2/get-info
45 |
46 | # 觉得麻烦的朋友直接将 PIXIC_API 留空. 如: PIXIC_API = ""
47 |
48 | =========================================================
49 | 3. 填入Eagle Home Path
50 |
51 | 使用Eagle创建一个新的资源库时,在你指定的文件夹下会生成
52 | 形如 Home.library的文件夹, 将这个地址填入到EAGLE_HOME_PATH即可
53 | 如: EAGLE_HOME_PATH = r"G:\EagleHome\Home.library"
54 |
55 | """
56 |
57 |
58 | PIXIC_API = ""
59 | # EAGLE_HOME_PATH = r"G:\EagleHome\个人收藏.library"
60 | # TEST DIR -> Home.library
61 | EAGLE_HOME_PATH = r""
62 |
63 | log_path = os.path.split(os.path.abspath(__file__))[0]
64 | # 日志写入
65 | logger.add(
66 | os.path.join(log_path, "{time}.log"),
67 | encoding="utf-8",
68 | enqueue=True,
69 | )
70 | TOOL = ThreadPool(8)
71 |
72 | class Pixiv2Eagle:
73 | # 计数
74 | count = 0
75 | # 识别成功
76 | _count = 0
77 | # 无法识别
78 | un_count = 0
79 | # 写入成功
80 | succees_count = 0
81 | # 已写入
82 | pass_count = 0
83 |
84 | def __init__(self):
85 | # EAGLE_HOME_PATH
86 | if not EAGLE_HOME_PATH:
87 | logger.warning(" 没有正确配置. 请填写")
88 | exit()
89 | else:
90 | self.eagle_home_path = EAGLE_HOME_PATH
91 |
92 | # PIXIC_API
93 | # FLAG - False - TEMP URL
94 | # FLAG - True - PIXIC API
95 | if not PIXIC_API:
96 | self.pixic_api = ""
97 | self.flag = False
98 | else:
99 | self.pixic_api = PIXIC_API
100 | self.flag = True
101 | logger.info(f" {self.pixic_api}")
102 |
103 | # AJAX URL
104 | self.temp_url = "https://www.pixiv.net/ajax/illust/"
105 |
106 | # HEADERS
107 | self.headers = {
108 | "Host": "www.pixiv.net",
109 | "referer": "https://www.pixiv.net/",
110 | "origin": "https://accounts.pixiv.net",
111 | "accept-language": "zh-CN,zh;q=0.9",
112 | "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64) '
113 | 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
114 | }
115 |
116 | def baseRequest(self, options, method="GET", data=None, params=None, retry_num=5):
117 | """
118 | 一个小型的健壮的网络请求函数
119 | :params options: 包括url,自定义headers,超时时间,cookie等
120 | :params method: 请求类型
121 | :params data: POST传参
122 | :params params: GET传参
123 | :params retry_num: 重试次数
124 |
125 | demo如下:
126 | demo_headers = headers.copy()
127 | demo_headers['referer'] = 'www.example.com'
128 | options ={
129 | "url": origin_url,
130 | "headers": demo_headers
131 | }
132 | baseRequest(options = options)
133 | """
134 | try:
135 | response = requests.request(
136 | method,
137 | options["url"],
138 | data = data,
139 | params = params,
140 | cookies = options.get("cookies",""),
141 | headers = self.headers,
142 | verify = False,
143 | timeout = options.get("timeout",5),
144 | )
145 | response.encoding = "utf8"
146 | return response
147 | except Exception as e:
148 | if retry_num > 0:
149 | time.sleep(0.5)
150 | return self.baseRequest(options,data,params,retry_num=retry_num-1)
151 | else:
152 | logger.info("网络请求出错 url:{}".format(options["url"]))
153 | return
154 |
155 | def get_images(self, path=None):
156 | if not path:path = self.eagle_home_path
157 | return os.listdir(path)
158 |
159 | def get_pid(self, name):
160 | """
161 | 识别和切分pid
162 | :params name: 文件名称
163 | :return: pid or ""
164 | """
165 | pid = ""
166 | if "-" in name:
167 | pid = name.split("-")[0]
168 | elif "_" in name:
169 | pid = name.split("_")[0]
170 | else:
171 | pid = name.split(".")[0]
172 |
173 | try:
174 | int(pid)
175 | except Exception as e:
176 | return ""
177 | else:
178 | return pid
179 |
180 | # PIXIC API
181 | def get_tags_pixic(self,pid):
182 | """
183 | 从PIXIC API获取pid tag(get-info API)
184 | 查询不到结果则使用pixiv api进行复查
185 |
186 | :params pid: pixiv插画id
187 | :return: [tag1,tag2...] or []
188 | """
189 | data = {"pid": pid}
190 | resp = self.baseRequest(
191 | options = {"url":self.pixic_api},
192 | method = "POST",
193 | data = data
194 | )
195 | if not resp:
196 | return []
197 |
198 | json_data = json.loads(resp.text)
199 |
200 | if json_data["result"][0].get("message","") != "":
201 | # PIXIC API内包含画师名称
202 | tags = json_data["result"][0]["tag"]
203 | tag_list = []
204 |
205 | _ = tags.split("、")
206 | for i in _:
207 | temp = i.split("/")
208 | for t in temp:tag_list.append(t)
209 | return list(set(tag_list))
210 | else:
211 | logger.info(json_data["result"][0]["message"])
212 | return []
213 |
214 | # AJAX URL
215 | def get_tags(self, pid):
216 | """
217 | 从pixiv api获取pid tag
218 | :params pid: pixiv插画id
219 | :return: [tag1,tag2...] or []
220 | """
221 | resp = self.baseRequest(
222 | options = {"url":f"{self.temp_url}{pid}"}
223 | )
224 | if not resp:
225 | return []
226 |
227 | json_data = json.loads(resp.text)
228 |
229 | if json_data["error"] == False:
230 | tags = json_data["body"]["tags"]["tags"]
231 | tag_list = []
232 | # 加入画师名称
233 | tag_list.append(json_data["body"]["userName"])
234 | for i in tags:
235 | if "translation" in i.keys():
236 | tag_list.append(i["translation"]["en"])
237 | tag_list.append(i["tag"])
238 | return list(set(tag_list))
239 | else:
240 | logger.info(json_data["message"])
241 | return []
242 |
243 | @logger.catch
244 | def thread_task(self, _, num):
245 | """
246 | 线程任务函数
247 | :params _: 待处理文件路径
248 | :params num: 当前序号
249 | """
250 | metajson_path = os.path.join(self.images_path,_,"metadata.json")
251 | # logger.info(f"<{num}/{self.len_images_folders}> {metajson_path}")
252 | with open(metajson_path,encoding="utf8") as f:
253 | # 记载metadata
254 | metadata = json.load(f)
255 | # 获取metafilename
256 | name = metadata["name"]
257 | logger.info(f"<{num}/{self.len_images_folders}> {name} {metajson_path}")
258 | pid = self.get_pid(name)
259 | # 成功识别
260 | if pid:
261 | self._count += 1
262 | # 无法识别
263 | else:
264 | self.count += 1
265 | self.un_count += 1
266 | return
267 |
268 | # TAG
269 | # tags已有内容跳过
270 | if metadata["tags"]:
271 | self.count += 1
272 | self.pass_count += 1
273 | return
274 | else:
275 | tag_list = []
276 | # 有定义PIXIC API则使用
277 | if self.flag:
278 | tag_list = self.get_tags_pixic(pid)
279 |
280 | # PIXIC获取失败或未定义PIXIC API
281 | if not tag_list:
282 | tag_list = self.get_tags(pid)
283 |
284 | # tag_list获取为空则跳过
285 | if tag_list == []:
286 | self.count += 1
287 | self.pass_count += 1
288 | return
289 | else:
290 | metadata["tags"] = tag_list
291 |
292 | # 写入
293 | if metadata:
294 | with open(metajson_path,"w",encoding="utf8") as f:
295 | json.dump(metadata,f,ensure_ascii=False)
296 | self.succees_count += 1
297 | time.sleep(0.1)
298 | self.count += 1
299 |
300 | def main(self):
301 | self.images_path = os.path.join(self.eagle_home_path,"images")
302 | images_folders = self.get_images(self.images_path)
303 | self.len_images_folders = len(images_folders)
304 |
305 | try:
306 | for _ in range(0,len(images_folders)):
307 | TOOL.put(self.thread_task, (images_folders[_], _+1, ), callback)
308 | # break
309 | except Exception as e:
310 | logger.info("Exception:{}".format(e))
311 | TOOL.close()
312 | finally:
313 | TOOL.close()
314 |
315 | while True:
316 | logger.info(f" {TOOL.free_list} {TOOL.max_num} {TOOL.generate_list}")
317 | # 正常关闭线程池
318 | if TOOL.free_list == [] and TOOL.generate_list == []:
319 | TOOL.close()
320 | logger.info(f"<当前总数> {self.count}")
321 | logger.info(f"<成功识别数> {self._count}")
322 | logger.info(f"<无法识别数> {self.un_count}")
323 | logger.info(f"<写入成功数> {self.succees_count}")
324 | logger.info(f"<跳过数> {self.pass_count}")
325 | break
326 | time.sleep(3)
327 |
328 | if __name__ == "__main__":
329 | pixiv2eagle = Pixiv2Eagle()
330 | pixiv2eagle.main()
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/requirement.txt:
--------------------------------------------------------------------------------
1 | requests
2 | loguru
--------------------------------------------------------------------------------
/0x09-Pixiv插画tag数据导入Eagle/thread_pool.py:
--------------------------------------------------------------------------------
1 | # coding=utf8
2 |
3 | """
4 | 一个基于thread和queue的线程池,
5 | 任务为队列元素,动态创建线程并重复利用.
6 | 通过close和terminate关闭线程池.
7 | """
8 |
9 | import queue
10 | import threading
11 | import contextlib
12 | import time
13 |
14 | # 创建空对象,用于停止线程
15 | StopEvent = object()
16 |
17 |
18 | def callback(status, result):
19 | """
20 | 根据需要进行的回调函数,默认不执行。
21 | :param status: action函数的执行状态
22 | :param result: action函数的返回值
23 | :return:
24 | """
25 | pass
26 |
27 |
28 | def action(thread_name, arg):
29 | """
30 | 真实的任务定义在这个函数里
31 | :param thread_name: 执行该方法的线程名
32 | :param arg: 该函数需要的参数
33 | :return:
34 | """
35 | # 模拟执行
36 | time.sleep(0.1)
37 | print("第%s个任务调用了线程 %s,并打印了这条信息!" % (arg+1, thread_name))
38 |
39 |
40 | class ThreadPool:
41 |
42 | def __init__(self, max_num, max_task_num=None):
43 | """
44 | 初始化线程池
45 | :param max_num: 线程池最大线程数量
46 | :param max_task_num: 任务队列长度
47 | """
48 | # 如果提供了最大任务数的参数,则将队列的最大元素个数设置为这个值。
49 | max_task_num = 20
50 | if max_task_num:
51 | self.q = queue.Queue(max_task_num)
52 | # 默认队列可接受无限多个的任务
53 | else:
54 | self.q = queue.Queue()
55 | # print("创建好队列了")
56 | # 设置线程池最多可实例化的线程数
57 | self.max_num = max_num
58 | # 任务取消标识
59 | self.cancel = False
60 | # 任务中断标识
61 | self.terminal = False
62 | # 已实例化的线程列表
63 | self.generate_list = []
64 | # 处于空闲状态的线程列表
65 | self.free_list = []
66 |
67 | def put(self, func, args, callback=None):
68 | """
69 | 往任务队列里放入一个任务
70 | :param func: 任务函数
71 | :param args: 任务函数所需参数
72 | :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数
73 | 1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
74 | :return: 如果线程池已经终止,则返回True否则None
75 | """
76 | # 先判断标识,看看任务是否取消了
77 | if self.cancel:
78 | return
79 | # 如果没有空闲的线程,并且已创建的线程的数量小于预定义的最大线程数,则创建新线程。
80 | if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
81 | self.generate_thread()
82 | # 构造任务参数元组,分别是调用的函数,该函数的参数,回调函数。
83 | w = (func, args, callback,)
84 | # 将任务放入队列
85 | self.q.put(w)
86 |
87 | def generate_thread(self):
88 | """
89 | 创建一个线程
90 | """
91 | # 每个线程都执行call方法
92 | t = threading.Thread(target=self.call)
93 | t.start()
94 |
95 | def call(self):
96 | """
97 | 循环去获取任务函数并执行任务函数。在正常情况下,每个线程都保存生存状态,直到获取线程终止的flag。
98 | """
99 | # 获取当前线程的名字
100 | current_thread = threading.currentThread().getName()
101 | # 将当前线程的名字加入已实例化的线程列表中
102 | self.generate_list.append(current_thread)
103 | # 从任务队列中获取一个任务
104 | event = self.q.get()
105 | # 让获取的任务不是终止线程的标识对象时
106 | while event != StopEvent:
107 | # 解析任务中封装的三个参数
108 | func, arguments, callback = event
109 | # 抓取异常,防止线程因为异常退出
110 | try:
111 | # 正常执行任务函数
112 | result = func(*arguments)
113 | # result = func(current_thread, *arguments)
114 | success = True
115 | except Exception as e:
116 | # 当任务执行过程中弹出异常
117 | result = None
118 | success = False
119 | # 如果有指定的回调函数
120 | if callback is not None:
121 | # 执行回调函数,并抓取异常
122 | try:
123 | callback(success, result)
124 | except Exception as e:
125 | pass
126 | # 当某个线程正常执行完一个任务时,先执行worker_state方法
127 | with self.worker_state(self.free_list, current_thread):
128 | # 如果强制关闭线程的flag开启,则传入一个StopEvent元素
129 | if self.terminal:
130 | event = StopEvent
131 | # 否则获取一个正常的任务,并回调worker_state方法的yield语句
132 | else:
133 | # 从这里开始又是一个正常的任务循环
134 | event = self.q.get()
135 | else:
136 | # 一旦发现任务是个终止线程的标识元素,将线程从已创建线程列表中删除
137 | self.generate_list.remove(current_thread)
138 |
139 | def close(self):
140 | """
141 | 执行完所有的任务后,让所有线程都停止的方法
142 | """
143 | # 设置flag
144 | self.cancel = True
145 | # 计算已创建线程列表中线程的个数,
146 | # 然后往任务队列里推送相同数量的终止线程的标识元素
147 | full_size = len(self.generate_list)
148 | while full_size:
149 | self.q.put(StopEvent)
150 | full_size -= 1
151 |
152 | def terminate(self):
153 | """
154 | 在任务执行过程中,终止线程,提前退出。
155 | """
156 | self.terminal = True
157 | # 强制性的停止线程
158 | while self.generate_list:
159 | self.q.put(StopEvent)
160 |
161 | # 该装饰器用于上下文管理
162 | @contextlib.contextmanager
163 | def worker_state(self, state_list, worker_thread):
164 | """
165 | 用于记录空闲的线程,或从空闲列表中取出线程处理任务
166 | """
167 | # 将当前线程,添加到空闲线程列表中
168 | state_list.append(worker_thread)
169 | # 捕获异常
170 | try:
171 | # 在此等待
172 | yield
173 | finally:
174 | # 将线程从空闲列表中移除
175 | state_list.remove(worker_thread)
176 |
177 | """
178 |
179 | # 调用方式
180 | if __name__ == '__main__':
181 | # 创建一个最多包含5个线程的线程池
182 | # pool = ThreadPool(5)
183 |
184 | # 创建100个任务,让线程池进行处理
185 | # for i in range(100):
186 | # pool.put(action, (i,), callback)
187 |
188 | # 等待一定时间,让线程执行任务
189 | # time.sleep(3)
190 |
191 | print("-" * 50)
192 | while True:
193 | print("\033[32;0m任务停止之前线程池中有%s个线程,空闲的线程有%s个!\033[0m"
194 | % (len(pool.generate_list), len(pool.free_list)))
195 |
196 | if len(pool.free_list) == pool.max_num and len(pool.generate_list) == 5:
197 | # 正常关闭线程池
198 | pool.close()
199 | print("任务执行完毕,正常退出!")
200 | break
201 |
202 | time.sleep(1)
203 | # 强制关闭线程池
204 | # pool.terminate()
205 | # print("强制停止任务!")
206 |
207 | """
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/Readme.md:
--------------------------------------------------------------------------------
1 | ## 碧蓝航线静态立绘解密&加密工具 v1.1.1
2 |
3 | ---
4 |
5 | + 这是一个用于**解密**和**重新加密**碧蓝航线立绘的脚本——2022.1.4
6 | + 解密算法是贴吧某篇帖子提供的(已经不记得是哪位用户了,有知道的可以提个issue)
7 | + 加密算法是自己根据解密算法逆推并一步步调试出来的
8 | + 主要用途
9 |
10 | 1、**解密**加密图像从而得到游戏内的立绘文件
11 |
12 | 
13 |
14 | 2、将经过修改的图片**重新加密**
15 |
16 | 
17 |
18 |
19 |
20 | ### 功能
21 |
22 | - [x] 解密碧蓝航线加密立绘
23 | - [x] 将修改过的立绘**重新加密**回去
24 | - [x] 提供0补偿与0.5补偿两个模式,具体可查看[url](./解封包_0补偿与0.5补偿对比测试结果)
25 | - [x] 提供较详细的说明(程序启动时)
26 | - [x] 提供debug模式和日志记录
27 | - [x] 提供批量解密/加密
28 |
29 |
30 |
31 | ### 目录结构
32 |
33 | ```bash
34 | .
35 | |-- Mesh # mesh纹理文件
36 | |-- Picture # 解密立绘文件夹
37 | |-- Readme.md
38 | |-- Test # 提供了部分测试文件
39 | |-- Texture2D # 加密立绘文件夹
40 | |-- Used # 由程序copy保留的加密立绘文件夹
41 | |-- blhx_img_tool.py
42 | |-- img
43 | `-- requirement.txt
44 | ```
45 |
46 |
47 |
48 | ### 解密立绘
49 |
50 | 需要:
51 |
52 | + 加密立绘 --> `Texture2D`目录
53 | + 加密立绘同名`mesh`文件 --> `Mesh`目录
54 |
55 | 得到:
56 |
57 | + 解密立绘 --> `Picture`目录
58 | + copy加密立绘 --> `Used`目录(用于在加密时使用,不需要动)
59 |
60 | 
61 |
62 | > 非`debug`模式下的`立绘解密还原log`
63 |
64 | ---
65 |
66 | ### 加密立绘
67 |
68 | 需要:
69 |
70 | + 解密立绘 --> `Picture`目录
71 | + 解密立绘同名`mesh`文件 --> `Mesh`目录
72 | + 之前的加密立绘 --> `Used`目录(通常由程序copy)
73 |
74 | 得到:
75 |
76 | + 加密立绘 --> `Texture2D`目录
77 |
78 | 
79 |
80 | > 非`debug`模式下的`立绘加密还原log`
81 |
82 | ---
83 |
84 | ### 使用方法
85 |
86 | ```bash
87 | # 安装依赖
88 | pip install -r requirement.txt
89 | # 运行脚本
90 | python blhx_img_tool.py
91 | ```
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Mesh/beierfasite_4-mesh.obj:
--------------------------------------------------------------------------------
1 | g beierfasite_4-mesh
2 | v -544 79 0
3 | v -544 463 0
4 | v -832 463 0
5 | v -832 79 0
6 | v -320 591 0
7 | v -320 1167 0
8 | v -480 1167 0
9 | v -480 591 0
10 | v -320 15 0
11 | v -320 591 0
12 | v -480 591 0
13 | v -480 15 0
14 | v -128 335 0
15 | v -128 591 0
16 | v -320 591 0
17 | v -320 335 0
18 | v -128 591 0
19 | v -128 847 0
20 | v -320 847 0
21 | v -320 591 0
22 | v -480 655 0
23 | v -480 879 0
24 | v -640 879 0
25 | v -640 655 0
26 | v -480 463 0
27 | v -480 655 0
28 | v -640 655 0
29 | v -640 463 0
30 | v -128 79 0
31 | v -128 175 0
32 | v -320 175 0
33 | v -320 79 0
34 | v -64 335 0
35 | v -64 591 0
36 | v -128 591 0
37 | v -128 335 0
38 | v -64 79 0
39 | v -64 335 0
40 | v -128 335 0
41 | v -128 79 0
42 | v -640 783 0
43 | v -640 879 0
44 | v -768 879 0
45 | v -768 783 0
46 | v -480 175 0
47 | v -480 367 0
48 | v -544 367 0
49 | v -544 175 0
50 | v -192 847 0
51 | v -192 943 0
52 | v -320 943 0
53 | v -320 847 0
54 | v -480 15 0
55 | v -480 175 0
56 | v -544 175 0
57 | v -544 15 0
58 | v -480 975 0
59 | v -480 1103 0
60 | v -544 1103 0
61 | v -544 975 0
62 | v -32 783 0
63 | v -32 879 0
64 | v -96 879 0
65 | v -96 783 0
66 | v -128 239 0
67 | v -128 335 0
68 | v -192 335 0
69 | v -192 239 0
70 | v -480 879 0
71 | v -480 975 0
72 | v -544 975 0
73 | v -544 879 0
74 | v -832 207 0
75 | v -832 367 0
76 | v -864 367 0
77 | v -864 207 0
78 | v -32 175 0
79 | v -32 335 0
80 | v -64 335 0
81 | v -64 175 0
82 | v -32 335 0
83 | v -32 495 0
84 | v -64 495 0
85 | v -64 335 0
86 | v -160 943 0
87 | v -160 1007 0
88 | v -224 1007 0
89 | v -224 943 0
90 | v -128 175 0
91 | v -128 239 0
92 | v -192 239 0
93 | v -192 175 0
94 | v -832 79 0
95 | v -832 207 0
96 | v -864 207 0
97 | v -864 79 0
98 | v 0 367 0
99 | v 0 463 0
100 | v -32 463 0
101 | v -32 367 0
102 | v -288 1007 0
103 | v -288 1103 0
104 | v -320 1103 0
105 | v -320 1007 0
106 | v -640 495 0
107 | v -640 559 0
108 | v -672 559 0
109 | v -672 495 0
110 | v -864 239 0
111 | v -864 303 0
112 | v -896 303 0
113 | v -896 239 0
114 | v -480 1103 0
115 | v -480 1167 0
116 | v -512 1167 0
117 | v -512 1103 0
118 | v -192 175 0
119 | v -192 239 0
120 | v -224 239 0
121 | v -224 175 0
122 | v -288 271 0
123 | v -288 335 0
124 | v -320 335 0
125 | v -320 271 0
126 | v -32 527 0
127 | v -32 591 0
128 | v -64 591 0
129 | v -64 527 0
130 | v -352 1167 0
131 | v -352 1199 0
132 | v -416 1199 0
133 | v -416 1167 0
134 | v -416 1167 0
135 | v -416 1199 0
136 | v -480 1199 0
137 | v -480 1167 0
138 | v -288 943 0
139 | v -288 1007 0
140 | v -320 1007 0
141 | v -320 943 0
142 | v 0 303 0
143 | v 0 367 0
144 | v -32 367 0
145 | v -32 303 0
146 | v -96 783 0
147 | v -96 847 0
148 | v -128 847 0
149 | v -128 783 0
150 | v -96 719 0
151 | v -96 783 0
152 | v -128 783 0
153 | v -128 719 0
154 | v -480 367 0
155 | v -480 399 0
156 | v -512 399 0
157 | v -512 367 0
158 | v -832 399 0
159 | v -832 431 0
160 | v -864 431 0
161 | v -864 399 0
162 | v -672 463 0
163 | v -672 495 0
164 | v -704 495 0
165 | v -704 463 0
166 | v -32 111 0
167 | v -32 143 0
168 | v -64 143 0
169 | v -64 111 0
170 | v -224 175 0
171 | v -224 207 0
172 | v -256 207 0
173 | v -256 175 0
174 | v -192 303 0
175 | v -192 335 0
176 | v -224 335 0
177 | v -224 303 0
178 | v -64 751 0
179 | v -64 783 0
180 | v -96 783 0
181 | v -96 751 0
182 | v -128 975 0
183 | v -128 1007 0
184 | v -160 1007 0
185 | v -160 975 0
186 | v -864 207 0
187 | v -864 239 0
188 | v -896 239 0
189 | v -896 207 0
190 | v -640 463 0
191 | v -640 495 0
192 | v -672 495 0
193 | v -672 463 0
194 | v -160 911 0
195 | v -160 943 0
196 | v -192 943 0
197 | v -192 911 0
198 | v -224 943 0
199 | v -224 975 0
200 | v -256 975 0
201 | v -256 943 0
202 | v -544 975 0
203 | v -544 1007 0
204 | v -576 1007 0
205 | v -576 975 0
206 | vt 0.0009765625 0.001351357
207 | vt 0.0009765625 0.5202702
208 | vt 0.2822266 0.5202702
209 | vt 0.2822266 0.001351357
210 | vt 0.2841797 0.001351357
211 | vt 0.2841797 0.7797297
212 | vt 0.4404297 0.7797297
213 | vt 0.4404297 0.001351357
214 | vt 0.4423828 0.001351357
215 | vt 0.4423828 0.7797297
216 | vt 0.5986328 0.7797297
217 | vt 0.5986328 0.001351357
218 | vt 0.6005859 0.001351357
219 | vt 0.6005859 0.3472973
220 | vt 0.7880859 0.3472973
221 | vt 0.7880859 0.001351357
222 | vt 0.7900391 0.001351357
223 | vt 0.7900391 0.3472973
224 | vt 0.9775391 0.3472973
225 | vt 0.9775391 0.001351357
226 | vt 0.6005859 0.35
227 | vt 0.6005859 0.6527027
228 | vt 0.7568359 0.6527027
229 | vt 0.7568359 0.35
230 | vt 0.7587891 0.35
231 | vt 0.7587891 0.6094595
232 | vt 0.9150391 0.6094595
233 | vt 0.9150391 0.35
234 | vt 0.0009765625 0.5229729
235 | vt 0.0009765625 0.6527027
236 | vt 0.1884766 0.6527027
237 | vt 0.1884766 0.5229729
238 | vt 0.9169922 0.35
239 | vt 0.9169922 0.695946
240 | vt 0.9794922 0.695946
241 | vt 0.9794922 0.35
242 | vt 0.1904297 0.5229729
243 | vt 0.1904297 0.8689189
244 | vt 0.2529297 0.8689189
245 | vt 0.2529297 0.5229729
246 | vt 0.7587891 0.6121622
247 | vt 0.7587891 0.7418919
248 | vt 0.8837891 0.7418919
249 | vt 0.8837891 0.6121622
250 | vt 0.0009765625 0.6554054
251 | vt 0.0009765625 0.9148649
252 | vt 0.06347656 0.9148649
253 | vt 0.06347656 0.6554054
254 | vt 0.6005859 0.6554054
255 | vt 0.6005859 0.7851351
256 | vt 0.7255859 0.7851351
257 | vt 0.7255859 0.6554054
258 | vt 0.06542969 0.6554054
259 | vt 0.06542969 0.8716216
260 | vt 0.1279297 0.8716216
261 | vt 0.1279297 0.6554054
262 | vt 0.9169922 0.6986487
263 | vt 0.9169922 0.8716216
264 | vt 0.9794922 0.8716216
265 | vt 0.9794922 0.6986487
266 | vt 0.7587891 0.7445946
267 | vt 0.7587891 0.8743243
268 | vt 0.8212891 0.8743243
269 | vt 0.8212891 0.7445946
270 | vt 0.8232422 0.7445946
271 | vt 0.8232422 0.8743243
272 | vt 0.8857422 0.8743243
273 | vt 0.8857422 0.7445946
274 | vt 0.2841797 0.7824324
275 | vt 0.2841797 0.9121622
276 | vt 0.3466797 0.9121622
277 | vt 0.3466797 0.7824324
278 | vt 0.1298828 0.6554054
279 | vt 0.1298828 0.8716216
280 | vt 0.1611328 0.8716216
281 | vt 0.1611328 0.6554054
282 | vt 0.3486328 0.7824324
283 | vt 0.3486328 0.9986486
284 | vt 0.3798828 0.9986486
285 | vt 0.3798828 0.7824324
286 | vt 0.3818359 0.7824324
287 | vt 0.3818359 0.9986486
288 | vt 0.4130859 0.9986486
289 | vt 0.4130859 0.7824324
290 | vt 0.4150391 0.7824324
291 | vt 0.4150391 0.8689189
292 | vt 0.4775391 0.8689189
293 | vt 0.4775391 0.7824324
294 | vt 0.4794922 0.7824324
295 | vt 0.4794922 0.8689189
296 | vt 0.5419922 0.8689189
297 | vt 0.5419922 0.7824324
298 | vt 0.5439453 0.7824324
299 | vt 0.5439453 0.9554054
300 | vt 0.5751953 0.9554054
301 | vt 0.5751953 0.7824324
302 | vt 0.6005859 0.7878379
303 | vt 0.6005859 0.9175676
304 | vt 0.6318359 0.9175676
305 | vt 0.6318359 0.7878379
306 | vt 0.6337891 0.7878379
307 | vt 0.6337891 0.9175676
308 | vt 0.6650391 0.9175676
309 | vt 0.6650391 0.7878379
310 | vt 0.6669922 0.7878379
311 | vt 0.6669922 0.8743243
312 | vt 0.6982422 0.8743243
313 | vt 0.6982422 0.7878379
314 | vt 0.7001953 0.7878379
315 | vt 0.7001953 0.8743243
316 | vt 0.7314453 0.8743243
317 | vt 0.7314453 0.7878379
318 | vt 0.1904297 0.8716216
319 | vt 0.1904297 0.9581081
320 | vt 0.2216797 0.9581081
321 | vt 0.2216797 0.8716216
322 | vt 0.2236328 0.8716216
323 | vt 0.2236328 0.9581081
324 | vt 0.2548828 0.9581081
325 | vt 0.2548828 0.8716216
326 | vt 0.4150391 0.8716216
327 | vt 0.4150391 0.9581081
328 | vt 0.4462891 0.9581081
329 | vt 0.4462891 0.8716216
330 | vt 0.4482422 0.8716216
331 | vt 0.4482422 0.9581081
332 | vt 0.4794922 0.9581081
333 | vt 0.4794922 0.8716216
334 | vt 0.06542969 0.8743243
335 | vt 0.06542969 0.9175676
336 | vt 0.1279297 0.9175676
337 | vt 0.1279297 0.8743243
338 | vt 0.9169922 0.8743243
339 | vt 0.9169922 0.9175676
340 | vt 0.9794922 0.9175676
341 | vt 0.9794922 0.8743243
342 | vt 0.4814453 0.8716216
343 | vt 0.4814453 0.9581081
344 | vt 0.5126953 0.9581081
345 | vt 0.5126953 0.8716216
346 | vt 0.1298828 0.8743243
347 | vt 0.1298828 0.9608108
348 | vt 0.1611328 0.9608108
349 | vt 0.1611328 0.8743243
350 | vt 0.6669922 0.877027
351 | vt 0.6669922 0.9635135
352 | vt 0.6982422 0.9635135
353 | vt 0.6982422 0.877027
354 | vt 0.7001953 0.877027
355 | vt 0.7001953 0.9635135
356 | vt 0.7314453 0.9635135
357 | vt 0.7314453 0.877027
358 | vt 0.7587891 0.877027
359 | vt 0.7587891 0.9202703
360 | vt 0.7900391 0.9202703
361 | vt 0.7900391 0.877027
362 | vt 0.7919922 0.877027
363 | vt 0.7919922 0.9202703
364 | vt 0.8232422 0.9202703
365 | vt 0.8232422 0.877027
366 | vt 0.8251953 0.877027
367 | vt 0.8251953 0.9202703
368 | vt 0.8564453 0.9202703
369 | vt 0.8564453 0.877027
370 | vt 0.8583984 0.877027
371 | vt 0.8583984 0.9202703
372 | vt 0.8896484 0.9202703
373 | vt 0.8896484 0.877027
374 | vt 0.2841797 0.9148649
375 | vt 0.2841797 0.9581081
376 | vt 0.3154297 0.9581081
377 | vt 0.3154297 0.9148649
378 | vt 0.0009765625 0.9175676
379 | vt 0.0009765625 0.9608108
380 | vt 0.03222656 0.9608108
381 | vt 0.03222656 0.9175676
382 | vt 0.06542969 0.9202703
383 | vt 0.06542969 0.9635135
384 | vt 0.09667969 0.9635135
385 | vt 0.09667969 0.9202703
386 | vt 0.6005859 0.9202703
387 | vt 0.6005859 0.9635135
388 | vt 0.6318359 0.9635135
389 | vt 0.6318359 0.9202703
390 | vt 0.6337891 0.9202703
391 | vt 0.6337891 0.9635135
392 | vt 0.6650391 0.9635135
393 | vt 0.6650391 0.9202703
394 | vt 0.9169922 0.9202703
395 | vt 0.9169922 0.9635135
396 | vt 0.9482422 0.9635135
397 | vt 0.9482422 0.9202703
398 | vt 0.9501953 0.9202703
399 | vt 0.9501953 0.9635135
400 | vt 0.9814453 0.9635135
401 | vt 0.9814453 0.9202703
402 | vt 0.7587891 0.922973
403 | vt 0.7587891 0.9662162
404 | vt 0.7900391 0.9662162
405 | vt 0.7900391 0.922973
406 | vt 0.7919922 0.922973
407 | vt 0.7919922 0.9662162
408 | vt 0.8232422 0.9662162
409 | vt 0.8232422 0.922973
410 | g beierfasite_4-mesh_0
411 | f 3/3/3 2/2/2 1/1/1
412 | f 1/1/1 4/4/4 3/3/3
413 | f 7/7/7 6/6/6 5/5/5
414 | f 5/5/5 8/8/8 7/7/7
415 | f 11/11/11 10/10/10 9/9/9
416 | f 9/9/9 12/12/12 11/11/11
417 | f 15/15/15 14/14/14 13/13/13
418 | f 13/13/13 16/16/16 15/15/15
419 | f 19/19/19 18/18/18 17/17/17
420 | f 17/17/17 20/20/20 19/19/19
421 | f 23/23/23 22/22/22 21/21/21
422 | f 21/21/21 24/24/24 23/23/23
423 | f 27/27/27 26/26/26 25/25/25
424 | f 25/25/25 28/28/28 27/27/27
425 | f 31/31/31 30/30/30 29/29/29
426 | f 29/29/29 32/32/32 31/31/31
427 | f 35/35/35 34/34/34 33/33/33
428 | f 33/33/33 36/36/36 35/35/35
429 | f 39/39/39 38/38/38 37/37/37
430 | f 37/37/37 40/40/40 39/39/39
431 | f 43/43/43 42/42/42 41/41/41
432 | f 41/41/41 44/44/44 43/43/43
433 | f 47/47/47 46/46/46 45/45/45
434 | f 45/45/45 48/48/48 47/47/47
435 | f 51/51/51 50/50/50 49/49/49
436 | f 49/49/49 52/52/52 51/51/51
437 | f 55/55/55 54/54/54 53/53/53
438 | f 53/53/53 56/56/56 55/55/55
439 | f 59/59/59 58/58/58 57/57/57
440 | f 57/57/57 60/60/60 59/59/59
441 | f 63/63/63 62/62/62 61/61/61
442 | f 61/61/61 64/64/64 63/63/63
443 | f 67/67/67 66/66/66 65/65/65
444 | f 65/65/65 68/68/68 67/67/67
445 | f 71/71/71 70/70/70 69/69/69
446 | f 69/69/69 72/72/72 71/71/71
447 | f 75/75/75 74/74/74 73/73/73
448 | f 73/73/73 76/76/76 75/75/75
449 | f 79/79/79 78/78/78 77/77/77
450 | f 77/77/77 80/80/80 79/79/79
451 | f 83/83/83 82/82/82 81/81/81
452 | f 81/81/81 84/84/84 83/83/83
453 | f 87/87/87 86/86/86 85/85/85
454 | f 85/85/85 88/88/88 87/87/87
455 | f 91/91/91 90/90/90 89/89/89
456 | f 89/89/89 92/92/92 91/91/91
457 | f 95/95/95 94/94/94 93/93/93
458 | f 93/93/93 96/96/96 95/95/95
459 | f 99/99/99 98/98/98 97/97/97
460 | f 97/97/97 100/100/100 99/99/99
461 | f 103/103/103 102/102/102 101/101/101
462 | f 101/101/101 104/104/104 103/103/103
463 | f 107/107/107 106/106/106 105/105/105
464 | f 105/105/105 108/108/108 107/107/107
465 | f 111/111/111 110/110/110 109/109/109
466 | f 109/109/109 112/112/112 111/111/111
467 | f 115/115/115 114/114/114 113/113/113
468 | f 113/113/113 116/116/116 115/115/115
469 | f 119/119/119 118/118/118 117/117/117
470 | f 117/117/117 120/120/120 119/119/119
471 | f 123/123/123 122/122/122 121/121/121
472 | f 121/121/121 124/124/124 123/123/123
473 | f 127/127/127 126/126/126 125/125/125
474 | f 125/125/125 128/128/128 127/127/127
475 | f 131/131/131 130/130/130 129/129/129
476 | f 129/129/129 132/132/132 131/131/131
477 | f 135/135/135 134/134/134 133/133/133
478 | f 133/133/133 136/136/136 135/135/135
479 | f 139/139/139 138/138/138 137/137/137
480 | f 137/137/137 140/140/140 139/139/139
481 | f 143/143/143 142/142/142 141/141/141
482 | f 141/141/141 144/144/144 143/143/143
483 | f 147/147/147 146/146/146 145/145/145
484 | f 145/145/145 148/148/148 147/147/147
485 | f 151/151/151 150/150/150 149/149/149
486 | f 149/149/149 152/152/152 151/151/151
487 | f 155/155/155 154/154/154 153/153/153
488 | f 153/153/153 156/156/156 155/155/155
489 | f 159/159/159 158/158/158 157/157/157
490 | f 157/157/157 160/160/160 159/159/159
491 | f 163/163/163 162/162/162 161/161/161
492 | f 161/161/161 164/164/164 163/163/163
493 | f 167/167/167 166/166/166 165/165/165
494 | f 165/165/165 168/168/168 167/167/167
495 | f 171/171/171 170/170/170 169/169/169
496 | f 169/169/169 172/172/172 171/171/171
497 | f 175/175/175 174/174/174 173/173/173
498 | f 173/173/173 176/176/176 175/175/175
499 | f 179/179/179 178/178/178 177/177/177
500 | f 177/177/177 180/180/180 179/179/179
501 | f 183/183/183 182/182/182 181/181/181
502 | f 181/181/181 184/184/184 183/183/183
503 | f 187/187/187 186/186/186 185/185/185
504 | f 185/185/185 188/188/188 187/187/187
505 | f 191/191/191 190/190/190 189/189/189
506 | f 189/189/189 192/192/192 191/191/191
507 | f 195/195/195 194/194/194 193/193/193
508 | f 193/193/193 196/196/196 195/195/195
509 | f 199/199/199 198/198/198 197/197/197
510 | f 197/197/197 200/200/200 199/199/199
511 | f 203/203/203 202/202/202 201/201/201
512 | f 201/201/201 204/204/204 203/203/203
513 |
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Picture/beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Picture/beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Texture2D/beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/Test/Texture2D/beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/blhx_img_tool.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | '''
3 | @File : blhx_img_tool.py
4 | @Time : 2021/12/24 17:07:46
5 | @Author : Coder-Sakura
6 | @Version : 1.1
7 | @Desc : 碧蓝航线静态立绘加解密工具
8 | '''
9 |
10 | # here put the import lib
11 | import os
12 | import cv2
13 | import sys
14 | import time
15 | import shutil
16 | import numpy as np
17 | from loguru import logger
18 |
19 |
20 | # DEBUG
21 | DEBUG = False
22 |
23 | # log config
24 | level = "DEBUG" if DEBUG else "INFO"
25 | logger.remove()
26 | logger.add(
27 | sys.stderr,
28 | level=level
29 | )
30 | log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log")
31 | # 日志写入
32 | logger.add(
33 | os.path.join(log_path, "{time}.log"),
34 | encoding="utf-8",
35 | rotation="00:00",
36 | enqueue=True,
37 | level="INFO"
38 | )
39 |
40 |
41 | class tool:
42 | """公共方法"""
43 | check_list = [
44 | "Mesh", # obj文件
45 | "Picture", # 解密立绘
46 | "Texture2D", # 加密立绘
47 | "Used" # 处理过的加密立绘
48 | ]
49 |
50 | def __init__(self):
51 | self.check_folders()
52 |
53 | def check_folders(self):
54 | """检查文件夹"""
55 | for i in tool.check_list:
56 | _ = os.path.join(os.getcwd(),i)
57 | if not os.path.exists(_):
58 | os.mkdir(_)
59 | logger.debug(f"Path Not Found: {_}. 请在脚本当前目录创建<{i}>文件夹")
60 |
61 | def get_material(self):
62 | """
63 | 获取文件夹文件
64 | :return:
65 | [
66 | {"Mesh": ["22-mesh.obj"]},
67 | {"Texture2D": ["22.png"]}
68 | ]
69 | """
70 | result = {}
71 | for i in tool.check_list:
72 | result[i] = os.listdir(os.path.join(os.getcwd(),i))
73 | return result
74 |
75 | def get_shape(self,img):
76 | """
77 | return Img height and weight
78 | """
79 | return img.shape[0], img.shape[1]
80 |
81 | def read_file(self,mesh_path):
82 | """
83 | 读取mesh文件,去除\n后以空格为间隔切分返回
84 | :params s: Mesh文件地址
85 | :return: [["v",1,1,0], ...]
86 | """
87 | List1 = []
88 | Filer = open(mesh_path,'r')
89 | List2 = Filer.readlines()
90 | Filer.close()
91 | for i0 in range(0, len(List2)):
92 | List2[i0] = List2[i0].replace("\n", "")
93 | List1.append(List2[i0].split())
94 | return List1
95 |
96 | def read_png(self,png_path):
97 | """
98 | 读取PNG图片 - 参考: https://blog.csdn.net/weixin_44015965/article/details/109547129
99 | cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道,可用1作为实参替代
100 | cv2.IMREAD_GRAYSCALE:读入灰度图片,可用0作为实参替代
101 | cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道,可用-1作为实参替代
102 | PS: alpha通道,又称A通道,是一个8位的灰度通道,该通道用256级灰度来记录图像中的透明度复信息,\
103 | 定义透明、不透明和半透明区域,其中黑表示全透明,白表示不透明,灰表示半透明
104 | :params png_path: PNG文件路径
105 | :returnL img obj
106 | """
107 | img = cv2.imread(png_path, cv2.IMREAD_UNCHANGED)
108 | return img
109 |
110 | def save_png(self, file_path, img):
111 | """
112 | 以PNG形式保存图像
113 | :params file_path: 保存路径
114 | :params img: img obj
115 | :return: None
116 | """
117 | cv2.imwrite(file_path, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
118 |
119 | def rotate(self, img, flipCode=-1):
120 | """
121 | 根据翻转代码来翻转图片
122 | cv2.flip(image, 1) # 水平翻转
123 | cv2.flip(image, 0) # 垂直翻转
124 | cv2.flip(image, -1) # 水平垂直翻转 180°垂直翻转一次+180°水平翻转一次
125 |
126 | :params img: img obj
127 | :params flipCode: 翻转代码
128 | :return: new_img obj
129 | """
130 | new_img = cv2.flip(img, flipCode)
131 | return new_img
132 |
133 |
134 | class encrypt_img:
135 | """封包 - 将修改好的立绘加密为碎片图"""
136 | def __init__(self):
137 | self.compensation = True
138 |
139 | def task(self, mesh_obj, pic):
140 | """
141 | :params mesh_obj: mesh文件名称
142 | :params pic: 加密或解密图片名称
143 | """
144 | logger.info(f" ===== 正在处理: <{pic}> ===== ")
145 |
146 | # mesh数据
147 | mesh = os.path.join(".", "Mesh", mesh_obj)
148 | # 解密后的正常立绘
149 | picture = os.path.join(".", "Picture", pic)
150 | # 游戏内拆包出来的加密图
151 | texture = os.path.join(".", "Used", pic)
152 | # 最后的结果图片路径
153 | final_png = os.path.join(".", "Texture2D", pic)
154 | logger.debug(f" - {mesh}")
155 | logger.debug(f" - {picture}")
156 | logger.debug(f" - {texture}")
157 | logger.debug(f" - {final_png}")
158 |
159 | mesh_data = Tool.read_file(mesh)
160 | img = Tool.read_png(picture)
161 | texture_img = Tool.read_png(texture)
162 |
163 | vjShu = firstx = endx = firsty = endy = 0
164 | # 解密图 - 高,宽
165 | # height,weight = img.shape[0],img.shape[1]
166 | # 根据之前的加密图创建还原画布
167 | try:
168 | max_height, max_weight = texture_img.shape[0],texture_img.shape[1]
169 | except Exception as e:
170 | logger.warning("无法获取原加密文件尺寸信息")
171 | logger.warning(f"请尝试将拆包出的<{pic}>加密文件放置于Used文件夹下")
172 | return
173 |
174 | # 水平垂直翻转
175 | img = Tool.rotate(img, -1)
176 | img = Tool.rotate(img,1)
177 |
178 | # 第一个vt坐标的序列
179 | vt_index = 0
180 | for i in range(1, len(mesh_data)):
181 | # 第一个v所在行 1
182 | if mesh_data[i][0] == 'v' and vjShu == 0:
183 | vjShu = i
184 |
185 | if mesh_data[i][0] != "vt" and mesh_data[i-1][0] == "vt":
186 | break
187 |
188 | if mesh_data[i][0] == "vt":
189 | if vt_index == 0:
190 | vt_index = i
191 |
192 | # 创建画布前打印信息
193 | logger.debug(f" - {max_height}")
194 | logger.debug(f" - {max_weight}")
195 | logger.debug(f" - {vjShu}")
196 | logger.debug("vjShu | firstx | firsty | endx | endy | x1 | y1")
197 |
198 | # 根据最大宽高创建画布
199 | Base_img = np.zeros([max_height, max_weight, 4], np.uint8)
200 | jShu = vjShu
201 | while vjShu < len(mesh_data):
202 | if mesh_data[vjShu][0] != 'v':
203 | break
204 |
205 | # 选择解密图中的图像 v第一组
206 | firsty = abs(int(mesh_data[vjShu][1]))
207 | firstx = abs(int(mesh_data[vjShu][2]))
208 | endy = abs(int(mesh_data[vjShu+2][1]))
209 | endx = abs(int(mesh_data[vjShu+2][2]))
210 | # 锁定粘贴的坐标 vt第一组
211 | if self.compensation:
212 | y1 = int(float(mesh_data[vjShu-jShu+vt_index][1]) * float(max_weight) + 0.5)
213 | x1 = int(float(mesh_data[vjShu-jShu+vt_index][2]) * float(max_height) + 0.5)
214 | else:
215 | y1 = int(float(mesh_data[vjShu-jShu+vt_index][1]) * float(max_weight))
216 | x1 = int(float(mesh_data[vjShu-jShu+vt_index][2]) * float(max_height))
217 |
218 | logger.debug(f"{vjShu},{firstx},{firsty},{endx},{endy},{x1},{y1}")
219 |
220 | for i in range(firstx-1, endx+1):
221 | for j in range(firsty-1, endy+1):
222 | Base_img[x1+i-firstx][y1+j-firsty] = img[i][j]
223 |
224 | # Tool.save_png(final_png, Base_img)
225 |
226 | vjShu += 4
227 |
228 | # 垂直翻转
229 | Base_img = Tool.rotate(Base_img, 0)
230 | Tool.save_png(final_png, Base_img)
231 | logger.info(f"处理完成: <{pic}> 路径: {final_png}")
232 |
233 | def main(self,compensation=True):
234 | # compensation 默认开启0.5补偿
235 | self.compensation = compensation
236 |
237 | material = Tool.get_material()
238 | Mesh_list = material.get("Mesh", [])
239 | Picture_list = material.get("Picture", [])
240 | Texture2D_list = material.get("Texture2D", [])
241 | Used_list = material.get("Used", [])
242 |
243 | logger.info(f"检测到{len(Picture_list)}组文件...")
244 | for i in range(0, len(Picture_list)):
245 | mesh_obj = Picture_list[i].replace(".png", "-mesh.obj")
246 | if mesh_obj not in Mesh_list:
247 | logger.warning(f"Can't Find {mesh_obj} in Mesh.")
248 | else:
249 | time_start = time.time()
250 | self.task(mesh_obj, Picture_list[i])
251 | time_end = time.time()
252 | logger.info(f"处理完毕. 耗时 - {round(time_end-time_start,2)}")
253 | logger.success("Tasks Over.")
254 | os.system("pause")
255 |
256 |
257 | class decrypt_img:
258 | """解包 - 将解包出来的加密立绘解密"""
259 | def task(self, mesh_obj, pic):
260 | """
261 | :params mesh_obj: mesh文件名称
262 | :params pic: 加密或解密图片名称
263 | """
264 | logger.info(f"\n ===== 正在处理: <{pic}> ===== ")
265 |
266 | # mesh数据
267 | mesh = os.path.join(".", "Mesh", mesh_obj)
268 | # 游戏内拆包出来的加密图
269 | texture = os.path.join(".", "Texture2D", pic)
270 | # 最后的结果图片路径
271 | final_png = os.path.join(".", "Picture", pic)
272 | logger.debug(f" - {mesh}")
273 | logger.debug(f" - {texture}")
274 | logger.debug(f" - {final_png}")
275 |
276 | mesh_data = Tool.read_file(mesh)
277 | img = Tool.read_png(texture)
278 |
279 | max_height = max_weight = vtjShu = firstx = endx = firsty = endy = 0
280 | # 加密图 宽,高
281 | height,weight = img.shape[0],img.shape[1]
282 |
283 | # 垂直翻转
284 | img = Tool.rotate(img,0)
285 |
286 | for i in range(1, len(mesh_data)):
287 | if mesh_data[i][0] != 'v':
288 | # beierfasite为例 - 205
289 | vtjShu = i
290 | break
291 | if abs(int(mesh_data[i][1])) > max_weight:
292 | max_weight = abs(int(mesh_data[i][1]))
293 | if abs(int(mesh_data[i][2])) > max_height:
294 | max_height = abs(int(mesh_data[i][2]))
295 |
296 | # 创建画布前打印信息
297 | logger.debug(f" - {max_height}")
298 | logger.debug(f" - {max_weight}")
299 | logger.debug(f" - {vtjShu}")
300 | logger.debug("vtjShu | firstx | firsty | endx | endy | x1 | y1")
301 |
302 | # 根据得出的最大宽高创建画布
303 | Base_img = np.zeros([max_height+1, max_weight+1, 4], np.uint8)
304 | jShu = vtjShu
305 | while vtjShu < len(mesh_data):
306 | if mesh_data[vtjShu][0] != 'vt':
307 | break
308 |
309 | # 选择加密图中的图像 vt第一组
310 | firsty = int(float(mesh_data[vtjShu][1]) * float(weight) + 0.5)
311 | firstx = int(float(mesh_data[vtjShu][2]) * float(height) + 0.5)
312 | endy = int(float(mesh_data[vtjShu+2][1]) * float(weight) + 0.5)
313 | endx = int(float(mesh_data[vtjShu+2][2]) * float(height) + 0.5)
314 | # 锁定粘贴的坐标 v第一组
315 | y1 = abs(int(mesh_data[vtjShu-jShu+1][1]))
316 | x1 = abs(int(mesh_data[vtjShu-jShu+1][2]))
317 |
318 | logger.debug(f"{vtjShu},{firstx},{firsty},{endx},{endy},{x1},{y1}")
319 |
320 | for i in range(firstx, endx):
321 | for j in range(firsty, endy):
322 | Base_img[x1+i-firstx][y1+j-firsty] = img[i][j]
323 |
324 | # Tool.save_png(final_png, Base_img)
325 |
326 | vtjShu += 4
327 |
328 | # 水平垂直翻转
329 | Base_img = Tool.rotate(Base_img,-1)
330 | Base_img = Tool.rotate(Base_img,1)
331 | Tool.save_png(final_png, Base_img)
332 | logger.info(f"处理完成: <{pic}> 路径: {final_png}")
333 |
334 | def main(self):
335 | material = Tool.get_material()
336 | Mesh_list = material.get("Mesh", [])
337 | Picture_list = material.get("Picture", [])
338 | Texture2D_list = material.get("Texture2D", [])
339 | Used_list = material.get("Used", [])
340 |
341 | logger.info(f"检测到{len(Texture2D_list)}组文件...")
342 | for i in range(0, len(Texture2D_list)):
343 | mesh_obj = Texture2D_list[i].replace(".png", "-mesh.obj")
344 | if mesh_obj not in Mesh_list:
345 | logger.warning(f"Can't Find {mesh_obj} in Mesh.")
346 | else:
347 | time_start = time.time()
348 | self.task(mesh_obj, Texture2D_list[i])
349 | time_end = time.time()
350 | logger.info(f"处理完毕. 耗时 - {round(time_end-time_start,2)}")
351 | shutil.copy("./Texture2D/" + Texture2D_list[i], "./Used/" + Texture2D_list[i])
352 | logger.success("Tasks Over.")
353 | os.system("pause")
354 |
355 |
356 | class manager:
357 | """处理流程"""
358 | help_info = """\n === 欢迎使用碧蓝航线立绘加解密工具 ===\n"""\
359 | """代码托管于: https://github.com/WriteCode-ChangeWorld/Tools 欢迎star~\n\n"""\
360 | """ ===== 说明 ===== \n1.立绘解密: 将mesh文件 (如22.mesh.obj) 放在文件夹, 将加密文件"""\
361 | """ (如:22.png) 放在文件夹. \n脚本执行完成后,解密文件在文件夹. """\
362 | """\n\n2.立绘加密: 将mesh文件 (如22.mesh.obj) 放在文件夹, 将魔改好或要替换立绘的解密文件"""\
363 | """ (如:22.png) 放在文件夹, 同时将同名加密立绘文件 (如:22.png) 放在文件夹. """\
364 | """\n脚本执行完成后,加密文件在文件夹. """\
365 | """\n\n>> 特别说明: 如果是先执行1再执行2, 通常程序会copy一份加密文件到文件夹. 此时不用再手动copy <<"""\
366 | """\n\n\n === 支持功能 === \n请将文件放置好后再运行本程序\n1.立绘解密还原\n2.立绘加密封包(+0.5补偿, 一般建议)"""\
367 | """\n3.立绘加密封包(无0.5补偿, 使用<2>封包的图片有细微白线,可尝试使用该选项)\n4.退出"""\
368 | """\n\n>> 特别说明: 针对<3>选项,请将封包后ok的图片移动到其他文件夹后再使用,以避免带来麻烦. <<"""
369 |
370 | def __init__(self):
371 | self.command_info = {
372 | "1": "Decrypt_Img.main()", # 解密还原
373 | "2": "Encrypt_Img.main()", # 加密封包
374 | "3": "Encrypt_Img.main(compensation=False)", # 加密封包-无0.5补偿
375 | "4": "exit()" # 退出
376 | }
377 |
378 | def main(self):
379 | """主函数"""
380 | print(manager.help_info)
381 | mode = input("回复数字以使用功能: ")
382 | if str(mode) not in list(self.command_info.keys()):
383 | logger.warning(f": {mode} 不在支持列表内,请重新输入或检查输入内容...")
384 | exit()
385 | else:
386 | eval(self.command_info[mode])
387 |
388 | """
389 | # Mesh文件
390 | Mesh_files = os.listdir("./Mesh")
391 | # 加密图像文件
392 | Texture2D_files = os.listdir("./Texture2D")
393 | # 解密图像/魔改图像文件
394 | Picture_files = os.listdir("./Picture")
395 | Encrypt_Img.main()
396 | """
397 |
398 |
399 | Tool = tool()
400 | Encrypt_Img = encrypt_img()
401 | Decrypt_Img = decrypt_img()
402 | Manager = manager()
403 | Manager.main()
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/img/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/0.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/1.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/img/3.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/requirement.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | loguru
3 | opencv-python
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/test_result.txt:
--------------------------------------------------------------------------------
1 | 1、对比测试——解包
2 | 名称 补偿 结果 备注
3 | 镇海 +0.5 √
4 | 镇海 +0 × 左下角与右下角有白色横线
5 | 贝尔法斯特 +0.5 √
6 | 贝尔法斯特 +0 ×明显白线区域
7 |
8 | 对比:0.5更优
9 |
10 | 2、对比测试——封包(使用补偿+0.5得出的图片进行测试)
11 | 名称 补偿 结果 备注
12 | 镇海 +0.5 × 明显白线区域
13 | 镇海 +0 √ 与游戏内的拆包出的文件对比,有些微白线
14 | 贝尔法斯特 +0.5 √ 与游戏内拆包文件 一致
15 | 贝尔法斯特 +0 √ 与游戏内的拆包出的文件对比,有些微白线
16 |
17 | 对比:
18 | 部分0.5优(22、贝尔法斯特)
19 | 部分0优(镇海)
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_×_明显白线区域_zhenhai_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_×_明显白线区域_zhenhai_2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_√_与游戏内拆包文件 一致_beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0.5_√_与游戏内拆包文件 一致_beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_zhenhai_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_zhenhai_2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_与游戏内的拆包出的文件对比,有些微白线_beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/封包_+0_√_与游戏内的拆包出的文件对比,有些微白线_beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/zhenhai_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Picture/zhenhai_2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/zhenhai_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/测试图片/Texture2D/zhenhai_2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_beierfasite_4.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_zhenhai_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0.5_√_zhenhai_2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_左下角与右下角有白色横线_zhenhai_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_左下角与右下角有白色横线_zhenhai_2.png
--------------------------------------------------------------------------------
/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_明显白线区域_beierfasite_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0a-碧蓝航线-静态立绘解密加密工具/解封包_0补偿与0.5补偿对比测试结果/解包_+0_×_明显白线区域_beierfasite_4.png
--------------------------------------------------------------------------------
/0x0b-批量拖拽-文件后缀名重命名工具/Readme.md:
--------------------------------------------------------------------------------
1 | ## 批量拖拽-文件后缀名重命名工具 v1.1
2 |
3 | ### 功能
4 |
5 | + 支持拖拽文件夹/单个文件/多个文件到GUI界面进行批量重命名
6 | + 后缀名检测
7 | + 全中文/检测名单后缀名 -> 替换为默认后缀名
8 | + 部分中文后缀名 -> 去除中文
9 | + 无后缀名 -> 添加默认后缀名
10 |
11 | + 涉及修改的文件、未知类型文件会高亮显示
12 | + 检测名单和跳过名单独立为配置文件,提供exe打包
13 | + 支持自定义跳过的后缀名
14 |
15 | 
16 |
17 |
18 |
19 | ### 后续可能的更新
20 |
21 |
22 | + 重命名并调用bandizip解压
23 |
24 |
25 |
26 | ### 其他
27 | pyinstaller打包命令
28 |
29 | ```bash
30 | pyinstaller --onefile --noconsole -n "FileRenamer v1.1.exe" filerenamer.py
31 | ```
32 |
33 |
--------------------------------------------------------------------------------
/0x0b-批量拖拽-文件后缀名重命名工具/exe/FileRenamer v1.1.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0b-批量拖拽-文件后缀名重命名工具/exe/FileRenamer v1.1.exe
--------------------------------------------------------------------------------
/0x0b-批量拖拽-文件后缀名重命名工具/filerenamer.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | '''
3 | @File : filerenamer.py
4 | @Time : 2024/09/06 21:29:04
5 | @Author : Coder-Sakura
6 | @Version : 1.1
7 | @Desc : None
8 | '''
9 |
10 | # here put the import lib
11 | import wx
12 | import os
13 | import re
14 | import json
15 | from datetime import datetime
16 | from loguru import logger
17 |
18 |
19 | # 配置loguru日志
20 | logger.add("{time:YYYY-MM}.log",encoding="utf-8",enqueue=True)
21 | # 跳过名单 , "."
22 | skip_formats = [".mp4", ".avi", ".mov", ".mkv", ".flv", ".zip", ".rar", ".7z", ".tar.gz", ".EXE", ".exe",\
23 | ".png", ".jpg", ".jpeg", ".gif", ".ico", ".psd", ".webp", ".mp3", ".wav", ".ass", ".lrc",\
24 | ".vtt", ".WAV", ".html"]
25 | # 检测名单 , ""
26 | target_formats = [".zi", ".rr", ".r", ".ra"]
27 | # 无后缀名时默认添加的后缀名
28 | default_suffix = ".zip"
29 | configName = "config.json"
30 | if not os.path.exists(configName):
31 | with open(configName, "w", encoding="utf-8") as f:
32 | json.dump({"skip_formats":skip_formats, "target_formats":target_formats, "default_suffix":default_suffix},\
33 | f, ensure_ascii=False, indent=4)
34 |
35 |
36 | class FileDrop(wx.FileDropTarget):
37 | def __init__(self, window):
38 | wx.FileDropTarget.__init__(self)
39 | # super(FileDropTarget, self).__init__()
40 | self.window = window
41 |
42 | def OnDropFiles(self, x, y, files):
43 | """处理拖放的文件"""
44 | self.window.WriteLog(f"============ 运行开始 ============\n")
45 | if files:
46 | for file in files:
47 | if os.path.isdir(file):
48 | self.window.WriteLog(f"识别到文件夹 - {file}\n")
49 | for r, ds, fs in os.walk(file):
50 | for f_ in fs:
51 | full_f_ = os.path.join(r,f_)
52 | self.window.WriteLog(f"识别到文件 - {full_f_}")
53 | self.window.RenameFile(full_f_)
54 | else:
55 | self.window.WriteLog(f"识别到文件 - {file}")
56 | self.window.RenameFile(file)
57 |
58 | # 返回True表示成功处理了拖放的文件
59 | self.window.WriteLog(f"============ 运行结束 ============\n")
60 | return True
61 |
62 | # 返回False表示没有文件被拖放
63 | self.window.WriteLog(f"============ 运行结束 ============\n")
64 | return False
65 |
66 |
67 | class DropFile(wx.Frame):
68 | # def __init__(self, parent, id, title):
69 | # wx.Frame.__init__(self, parent, id, title, size = (450, 400))
70 | def __init__(self, *args, **kw):
71 | wx.Frame.__init__(self, *args, **kw)
72 | self.InitUI()
73 | self.InitConfig()
74 |
75 | def InitUI(self):
76 | self.title = "文件重命名工具 By Coder-Sakura"
77 |
78 | # 启用拖放
79 | self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_RICH)
80 | self.text.SetBackgroundColour(wx.Colour(200, 200, 200))
81 | dt = FileDrop(self)
82 | self.text.SetDropTarget(dt)
83 |
84 | # 设置窗口大小和位置
85 | self.Centre()
86 | self.SetSize((800, 400))
87 | self.SetTitle(self.title)
88 | self.Show(True)
89 |
90 | def InitConfig(self):
91 | with open(configName, "r", encoding="utf-8") as f:
92 | self.configData = json.load(f)
93 | self.skip_formats = self.configData["skip_formats"]
94 | self.target_formats = self.configData["target_formats"]
95 | self.default_suffix = self.configData["default_suffix"]
96 |
97 | def WriteLog(self, text, level="info", color=""):
98 | now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
99 | start = self.text.GetInsertionPoint() # 获取当前文本位置
100 | attr = wx.TextAttr()
101 | attr.SetTextColour(wx.Colour(255, 0, 0)) # 红色
102 | attr.SetTextColour([wx.Colour(255, 0, 0) if color == "red" else wx.Colour(0, 0, 255) if color == "blue" else wx.Colour(0, 0, 0)][0]) # 红/蓝/黑
103 | self.text.WriteText(f"{now} {text}\n")
104 | self.text.SetStyle(start, start + len(now+text), attr) # 插入文本并应用样式
105 |
106 | log_level_method = getattr(logger, level, None)
107 | log_level_method(f"{now} {text}")
108 |
109 | def RenameFile(self, file_path):
110 | dir,fullfname = os.path.split(file_path)
111 | fname,ext = os.path.splitext(fullfname)
112 |
113 | # ================= 后缀名处理 ================= #
114 | # 跳过名单的后缀名 -> 跳过
115 | if ext in self.skip_formats:
116 | self.WriteLog(f"跳过文件 - {file_path}\n")
117 | return
118 |
119 | # 无后缀名 -> 添加默认后缀名
120 | if not ext and fullfname == fname:
121 | dst = os.path.join(dir, f"{fname}{self.default_suffix}")
122 | os.rename(file_path, dst)
123 | self.WriteLog(f"添加后缀名 - {dst}\n", level="success", color="blue")
124 | return
125 |
126 | # 全中文后缀名/检测名单的后缀名 -> 修改为默认后缀名
127 | if bool(re.match(r"^[\u4e00-\u9fa5]+$", ext[1:])) \
128 | or ext in self.target_formats:
129 | dst = os.path.join(dir, f"{fname}{self.default_suffix}")
130 | os.rename(file_path, dst)
131 | self.WriteLog(f"重命名文件 - {dst}\n", level="success", color="blue")
132 | return
133 |
134 | # 部分中文后缀名 -> 去除中文
135 | if bool(re.findall(r"[\u4e00-\u9fa5]", ext[1:])):
136 | new_ext = re.sub(r"[\u4e00-\u9fa5]", "", ext)
137 | dst = os.path.join(dir, f"{fname}{new_ext}")
138 | os.rename(file_path, dst)
139 | self.WriteLog(f"重命名文件 - {dst}\n", level="success", color="blue")
140 | return
141 |
142 | self.WriteLog(f"未知类型 - {file_path}\n", level="warning", color="red")
143 | return
144 |
145 |
146 | app = wx.App()
147 | DropFile(None, -1, 'filedrop.py')
148 | app.MainLoop()
--------------------------------------------------------------------------------
/0x0b-批量拖拽-文件后缀名重命名工具/img/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WriteCode-ChangeWorld/Tools/096dd8af8b9d1f501187ce62ab38e6be07f1518d/0x0b-批量拖拽-文件后缀名重命名工具/img/0.png
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | ### 概述
2 |
3 | 这是一个收集各种各样工具或脚本并分享的仓库。
4 |
5 |
6 |
7 | ### 目前收集的脚本
8 |
9 | | 名称 | 描述 | 更新时间 |
10 | | ---------------------------------- | ------------------------------------------------------------ | -------------------------------------- |
11 | | 0x01-监控反代pixiv链接 | 将pixiv图片链接替换为可直接访问的反代链接 | 2021/2/14 |
12 | | 0x02-花火学园邀请码脚本 | 根据现有题库,自动匹配答题得出邀请码 | 目前站点禁止新用户注册 |
13 | | 0x03-iwara下载脚本 | iwara用户投稿视频/指定视频下载器 | 2022/3/11 |
14 | | 0x04-mzitu爬虫 | 妹子图全站下载爬虫 | 2021/2/14 |
15 | | 0x05-绅士小站 | 绅士小站爬虫 | 站点无法访问/无限期维护
2021/8/10 |
16 | | 0x06-pixiv动图合成 | pixiv动图合成脚本 | 2021/4/20 |
17 | | 0x07-绅士仓库 | 绅士仓库投稿抓取(不含下载)By [WolfBolin](https://github.com/wolfbolin) | 2021/6/30 |
18 | | 0x08-绅士小站2 | 绅士小站爬虫(MySQL+Aria2)By [WolfBolin](https://github.com/wolfbolin) | 2021/7/2 |
19 | | 0x09-pixiv2eagle | Pixiv插画tag导入Eagle | 2021/11/7 |
20 | | 0x0a-碧蓝航线-静态立绘解密加密工具 | 用于碧蓝航线静态立绘加密与解密的脚本 | 2022/1/4 |
21 | | 0x0b-批量拖拽-文件后缀名重命名工具 | 可以拖拽进行批量文件名后缀命名的GUI | 2024/9/8 |
22 |
23 |
--------------------------------------------------------------------------------