From c44a20c64d0c2b2eef2706e07ece5fd9387396af Mon Sep 17 00:00:00 2001 From: Benny Date: Sun, 15 Aug 2021 12:28:19 +0800 Subject: [PATCH] Dev (#52) update bunch of features --- .dockerignore | 1 - .gitattributes | 15 +- .github/workflows/builder.yaml | 4 +- .gitignore | 8 + API.md | 516 ++- Dockerfile | 8 +- YYeTsFE | 2 +- conf/yyets.env | 6 + docker-compose.yml | 31 +- requirements.txt | 2 +- {hooks => scripts}/dev_robots.sh | 0 scripts/install.sh | 2 +- hooks/pre_build => scripts/pre_build.sh | 0 tests/.coveragerc | 5 - tests/data/yyets_search.html | 474 --- tests/data/zimuxia_result.html | 236 -- tests/data/zimuxia_search.html | 259 -- tests/mongo_test_data.json | 1053 ------ tests/test_bot.py | 36 - tests/test_fansub.py | 115 - yyetsweb/Makefile | 3 +- yyetsweb/Mongo.py | 436 ++- yyetsweb/README.md | 22 +- yyetsweb/craw_data/douban.py | 23 - yyetsweb/craw_data/douban_detail.html | 3151 ----------------- yyetsweb/craw_data/douban_search.html | 642 ---- yyetsweb/database.py | 57 +- yyetsweb/fonts/test.txt | 1 - yyetsweb/go.mod | 1 + yyetsweb/go.sum | 2 + yyetsweb/handler.py | 274 +- yyetsweb/server.py | 26 +- yyetsweb/{ => templates}/404.html | 0 yyetsweb/{ => templates}/css/3rd/animate.css | 0 yyetsweb/{ => templates}/css/3rd/icons.css | 0 yyetsweb/{ => templates}/css/3rd/widgets.css | 0 yyetsweb/{ => templates}/css/aYin.css | 0 .../{ => templates}/css/bootstrap.min.css | 0 yyetsweb/{ => templates}/css/data.json | 0 .../css/down-list-20180530.css | 0 .../{ => templates}/css/font-awesome.min.css | 0 yyetsweb/{ => templates}/css/index.json | 0 .../css/jquery.mCustomScrollbar.css | 0 .../{ => templates}/css/normalize.min.css | 0 yyetsweb/{ => templates}/css/noty.css | 0 yyetsweb/{ => templates}/favicon.ico | Bin .../fonts/fontawesome-webfont.woff2 | Bin yyetsweb/{ => templates}/help.html | 0 .../img/11bcd4d0f2daf8b02fecc72bc8ca38ab.png | Bin .../img/200-wrangler-ferris.gif | Bin .../img/404-wrangler-ferris.gif | Bin yyetsweb/{ => templates}/img/afdian.png | Bin .../{ => templates}/img/default-green.png | Bin yyetsweb/{ => templates}/img/grid16.png | Bin yyetsweb/{ => templates}/img/yyetsTrans.png | Bin yyetsweb/{ => templates}/index.html | 2 +- yyetsweb/{ => templates}/js/aYin.js | 0 yyetsweb/{ => templates}/js/axios.min.js | 0 yyetsweb/{ => templates}/js/bootstrap.min.js | 0 yyetsweb/{ => templates}/js/common.js | 0 .../js/jquery.mCustomScrollbar.min.js | 0 yyetsweb/{ => templates}/js/jquery.min.js | 0 .../js/jquery.mousewheel.min.js | 0 yyetsweb/{ => templates}/js/noty.min.js | 0 yyetsweb/{ => templates}/js/rshare.js | 0 yyetsweb/{ => templates}/js/sample.json | 0 yyetsweb/{ => templates}/js/vue.js | 0 yyetsweb/{ => templates}/resource.html | 0 yyetsweb/templates/robots.txt | 3 + yyetsweb/{ => templates}/search.html | 0 yyetsweb/utils.py | 36 + 71 files changed, 1282 insertions(+), 6170 deletions(-) create mode 100644 conf/yyets.env rename {hooks => scripts}/dev_robots.sh (100%) rename hooks/pre_build => scripts/pre_build.sh (100%) delete mode 100644 tests/.coveragerc delete mode 100644 tests/data/yyets_search.html delete mode 100644 tests/data/zimuxia_result.html delete mode 100644 tests/data/zimuxia_search.html delete mode 100644 tests/mongo_test_data.json delete mode 100644 tests/test_bot.py delete mode 100644 tests/test_fansub.py delete mode 100644 yyetsweb/craw_data/douban.py delete mode 100644 yyetsweb/craw_data/douban_detail.html delete mode 100644 yyetsweb/craw_data/douban_search.html delete mode 100644 yyetsweb/fonts/test.txt rename yyetsweb/{ => templates}/404.html (100%) rename yyetsweb/{ => templates}/css/3rd/animate.css (100%) rename yyetsweb/{ => templates}/css/3rd/icons.css (100%) rename yyetsweb/{ => templates}/css/3rd/widgets.css (100%) rename yyetsweb/{ => templates}/css/aYin.css (100%) rename yyetsweb/{ => templates}/css/bootstrap.min.css (100%) rename yyetsweb/{ => templates}/css/data.json (100%) rename yyetsweb/{ => templates}/css/down-list-20180530.css (100%) rename yyetsweb/{ => templates}/css/font-awesome.min.css (100%) rename yyetsweb/{ => templates}/css/index.json (100%) rename yyetsweb/{ => templates}/css/jquery.mCustomScrollbar.css (100%) rename yyetsweb/{ => templates}/css/normalize.min.css (100%) rename yyetsweb/{ => templates}/css/noty.css (100%) rename yyetsweb/{ => templates}/favicon.ico (100%) rename yyetsweb/{ => templates}/fonts/fontawesome-webfont.woff2 (100%) rename yyetsweb/{ => templates}/help.html (100%) rename yyetsweb/{ => templates}/img/11bcd4d0f2daf8b02fecc72bc8ca38ab.png (100%) rename yyetsweb/{ => templates}/img/200-wrangler-ferris.gif (100%) rename yyetsweb/{ => templates}/img/404-wrangler-ferris.gif (100%) rename yyetsweb/{ => templates}/img/afdian.png (100%) rename yyetsweb/{ => templates}/img/default-green.png (100%) rename yyetsweb/{ => templates}/img/grid16.png (100%) rename yyetsweb/{ => templates}/img/yyetsTrans.png (100%) rename yyetsweb/{ => templates}/index.html (99%) rename yyetsweb/{ => templates}/js/aYin.js (100%) rename yyetsweb/{ => templates}/js/axios.min.js (100%) rename yyetsweb/{ => templates}/js/bootstrap.min.js (100%) rename yyetsweb/{ => templates}/js/common.js (100%) rename yyetsweb/{ => templates}/js/jquery.mCustomScrollbar.min.js (100%) rename yyetsweb/{ => templates}/js/jquery.min.js (100%) rename yyetsweb/{ => templates}/js/jquery.mousewheel.min.js (100%) rename yyetsweb/{ => templates}/js/noty.min.js (100%) rename yyetsweb/{ => templates}/js/rshare.js (100%) rename yyetsweb/{ => templates}/js/sample.json (100%) rename yyetsweb/{ => templates}/js/vue.js (100%) rename yyetsweb/{ => templates}/resource.html (100%) create mode 100644 yyetsweb/templates/robots.txt rename yyetsweb/{ => templates}/search.html (100%) diff --git a/.dockerignore b/.dockerignore index 7348d97..6620795 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,7 +5,6 @@ logs/* YYeTsFE/node_modules/* .github/* assets/* -scripts/* conf/* tests/* yyetsweb/yyets.sqlite \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index baa0ca1..fd8df4a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,14 +6,13 @@ tools/worker/public/js/search.js -linguist-vendored tools/worker/public/404.html linguist-vendored tools/worker/public/resource.html linguist-vendored -yyetsweb/css/** linguist-vendored -yyetsweb/fonts/* linguist-vendored -yyetsweb/img/* linguist-vendored -yyetsweb/js/* linguist-vendored -yyetsweb/404.html linguist-vendored -yyetsweb/resource.html linguist-vendored +yyetsweb/templates/css/** linguist-vendored +yyetsweb/templates/fonts/* linguist-vendored +yyetsweb/templates/img/* linguist-vendored +yyetsweb/templates/js/* linguist-vendored +yyetsweb/templates/404.html linguist-vendored +yyetsweb/templates/resource.html linguist-vendored -yyetsweb/js/common.js -linguist-vendored +yyetsweb/templates/js/common.js -linguist-vendored tests/data/* linguist-vendored -yyetsweb/craw_data/*.html linguist-vendored \ No newline at end of file diff --git a/.github/workflows/builder.yaml b/.github/workflows/builder.yaml index 8c5bc7e..9669ace 100644 --- a/.github/workflows/builder.yaml +++ b/.github/workflows/builder.yaml @@ -42,7 +42,7 @@ jobs: REACT_APP_DOMAIN: ${{ secrets.REACT_APP_DOMAIN }} REACT_APP_GA: ${{ secrets.REACT_APP_GA }} GENERATE_SOURCEMAP: ${{ secrets.GENERATE_SOURCEMAP }} - run: bash hooks/pre_build + run: bash scripts/pre_build.sh - name: Lower case id: string @@ -54,7 +54,7 @@ jobs: uses: docker/build-push-action@v2 with: context: . - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64, linux/arm64 push: true tags: ${{ steps.string.outputs.lowercase }} cache-from: type=local,src=/tmp/.buildx-cache diff --git a/.gitignore b/.gitignore index 3b2a44c..164f82b 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,11 @@ logs/* /yyetsweb/yyets.sqlite /yyetsweb/yyetsweb /yyetsweb/assets.go +/yyetsweb/templates/static/* +/yyetsweb/templates/sponsor/* +/yyetsweb/templates/svg/* +/yyetsweb/templates/index.css +/yyetsweb/templates/logo* +/yyetsweb/templates/*.json + + diff --git a/API.md b/API.md index 3229b28..445c33a 100644 --- a/API.md +++ b/API.md @@ -7,16 +7,30 @@ - [x] 联合搜索,当本地数据库搜索不到数据时,会返回extra字段 - [x] 最新评论 - [x] 公告 -- [ ] 评论通知(浏览器通知) +- [x] 最新更新资源 +- [x] API变更:登录时需要验证码 +- [x] API变更:like API变更 PATCH `/api/user/` --> PATCH `/api/like/` +- [x] 删除评论(admin only) +- [ ] 添加下载地址到已有资源 +- [ ] 新增资源 +- [ ] 删除资源、删除已有资源的下载 +- [ ] 对评论的反应 +- [ ] 更改用户信息(添加邮箱) +- [ ] 分类 +- [ ] 评论通知(浏览器通知,暂时隐藏了) # BE - [x] 联合搜索:字幕侠、new字幕组、追新番 - [x] grafana面板 - [x] 豆瓣接口 -- [ ] 用户体系(添加邮箱,邮件支持,找回密码) -- [ ] 评论通知,需要新接口 -- [ ] 添加资源API +- [x] 评论通知:站内通知 +- [x] 添加邮箱 +- [x] 邮件通知 +- [x] 添加下载地址到已有资源 +- [x] 删除资源 +- [x] 新建资源 +- [ ] 找回密码 # 资源 @@ -25,6 +39,11 @@ * GET `/api/resource?id=10004` 数据结构参考 [sample.json](yyetsweb/js/sample.json) +**对于非官方、由用户提交的下载,与 `files` `dateline` 同级会有一个 `creator` 用于标明是谁创建的** +**对于非官方、由用户提交的资源,与 `cnname` `enname` 同级会有一个 `creator` 用于标明是谁创建的** + +如果没有,那么就是官方资源 + ## 搜索 * GET `/api/resource?keyword=逃避` @@ -115,6 +134,119 @@ } ``` +## 添加下载地址到已有资源 + +比如更新S01E05 + +* PATCH `http://127.0.0.1:8888/api/resource` + +```json +{ + "resource_id": 39894, + "season_num": "1,对于电影纪录片等,应该是0或者101", + "items": { + "MP4": [ + { + "episode": "12", + "name": "第五集.mp4", + "size": "9.43GB", + "dateline": "1628399290 单位秒", + "files": [ + { + "way": "1", + "way_cn": "电驴", + "address": "ed2k://|filszpwzec5|/", + "passwd": "" + }, + { + "way": "2", + "way_cn": "磁力", + "address": "magnet:37", + "passwd": "" + } + ] + } + ] + }, + "formats": [ + "MP4" + ] +} +``` + +返回201 + +## 创建新资源 + +仅登录用户可用,用于创建新的资源,不包括 `data.list`。 + +* POST `http://127.0.0.1:8888/api/resource` + +```json +{ + "status": 1, + "info": "OK", + "data": { + "info": { + "id": "设置成任意值即可", + "cnname": "中文名", + "enname": "英文名", + "aliasname": "别名", + "channel": "movie/tv", + "channel_cn": "电影/美剧", + "area": "法国", + "show_type": "", + "expire": "1610401225", + "views": 0 + }, + "list": [ + ] + } +} +``` + +返回 + +```json +{ + "status": true, + "message": "success", + "id": 50623 +} +``` + +## 删除资源 + +仅管理员可用,分成两种情况,一种是删除某个id全部资源,如39894;另外一种是删除这个资源下的某集下载 + +* POST `http://127.0.0.1:8888/api/resource` + +均返回 202 + +### 删除全部 + +```json +{ + "resource_id": 39894 +} +``` + +### 删除部分资源 + +会尽可能的匹配并删除对应的行 + +```json +{ + "resource_id": 39894, + "meta": { + "episode": "1", + "name": "超235678-213.mp4", + "size": "1.43GB", + "dateline": "1628399290" + } +} +``` + # Top 获取top信息,每类返回15条访问量最高的数据 @@ -213,7 +345,11 @@ ## 登录或注册 -* POST `/api/user`,提交json,字段 `username`, `password` +* POST `/api/user`,提交json,字段 `username`, `password`,`captcha_id` 和 `captcha` + +当验证码失效时,会返回303 See Other + +返回json ## 获取当前登录用户信息 @@ -235,12 +371,56 @@ "group": [ "admin" ], - "comments_like": [ - "60c46d6a6d7c5dd22d69fd3b" - ], - "comments_dislike": [ - "60c46d6a6d7c5dd22d69fd3b" - ] + "email": { + "verified": false, + "address": "123@qq.com" + } +} +``` + +## 更改用户信息 + +* PATCH `http://127.0.0.1:8888/api/user` + +* 目前只支持修改email字段,会发送验证邮件,1800秒之内只能验证一次,有效期24小时 + +暂不支持取消绑定 + +```json +{ + "email": "123@qq.com" +} +``` + +response + +```json +{ + "status_code": 429, + "status": false, + "message": "try again in 1797s" +} +``` + +## 验证邮件 + +* POST `http://127.0.0.1:8888/api/user/email` + +10次错误会被加到黑名单,账号注销 + +```json +{ + "code": "83216" +} +``` + +response + +```json +{ + "status": true, + "status_code": 201, + "message": "success" } ``` @@ -251,7 +431,7 @@ # 添加或删除收藏 -* PATCH `/api/user`,提交json,字段 `resource_id` +* PATCH `/api/like`,提交json,字段 `resource_id` # 评论 @@ -547,28 +727,50 @@ } ``` -## 点赞或踩评论 +## 对评论的反应 -* PATCH `/api/comment` +仅对登录用户可用 -verb 为`like` 或 `dislike` +* POST `/api/comment/reaction` 添加反应 201 +* DELETE `/api/comment/reaction` 删除反应 202 + +verb 为任意字符串,包括emoji ```json { - "comment_id": "60c46d6a6d7c5dd22d69fd3b", - "verb": "dislike/like" + "comment_id": "60c46d6a6d7c5dd22d69fd3b,父评论子评论均可", + "verb": "😍👍" } ``` 返回: -* 201 成功 +* 201 成功添加反应 +* 202 成功删除反应 * 404 评论没找到 -* 422 已经赞/踩过了 -* 400 请求参数错误 -用户曾经点赞的记录会在 `GET /api/user` 返回 +用户添加的反应,在返回评论时可以看到,会额外多一个字段 `reactions`,子评论同理 + +```json +{ + "reactions": [ + { + "verb": "😍", + "users": [ + "user2" + ] + }, + { + "verb": "🤔", + "users": [ + "user3", + "da" + ] + } + ] +} +``` # metrics @@ -757,4 +959,274 @@ verb 为`like` 或 `dislike` } ] } -``` \ No newline at end of file +``` + +# 通知 + +只有登录用户可以获取,只有楼主能够获取到通知,其他楼层的人获取不到。 + +## 获取通知 + +* GET `http://127.0.0.1:8888/api/notification` + +支持URL参数page和size,默认1和5,size是已读和未读共享的,优先返回未读数据 + +```json +{ + "username": "user1", + "unread_item": [ + { + "username": "user3", + "date": "2021-08-14 20:23:02", + "browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:86.1) Gecko/20100101 Firefox/86.1", + "content": "@user2u3 to u2", + "resource_id": 233, + "type": "child", + "id": "6117b5a6598f80ca3ebb13ed", + "reply_to_content": "@user1ajnkwa" + }, + { + "username": "user3", + "date": "2021-08-14 20:22:37", + "browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:86.1) Gecko/20100101 Firefox/86.1", + "content": "@user1u3", + "resource_id": 233, + "type": "child", + "id": "6117b58dce422260bcbb13ec", + "reply_to_content": "hello" + } + ], + "read_item": [ + { + "username": "user2", + "date": "2021-08-14 20:10:13", + "browser": "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:86.1) Gecko/20100101 Firefox/86.1", + "content": "@user1ajnkwa", + "resource_id": 233, + "type": "child", + "id": "6117b2a59195e6b1ab86eb30", + "reply_to_content": "hello" + } + ], + "unread_count": 2, + "read_count": 1 +} +``` + +# 已读、未读消息 + +* PATCH `http://127.0.0.1:8888/api/notification` + json body + +```json +{ + "comment_id": "61013c839633a80254ef2e38", + "verb": "unread" +} +``` + +verb只可以是 `read` 和 `unread` +comment_id 是评论的id + +# 分类 + +最灵活的API! 推荐组合方式: 国家、分类 + +* GET `/api/category` + +参数 `douban=True` 会返回豆瓣信息,默认不返回 支持如下参数,均为可选参数,可自由组合,URL参数可以URL编码,也可以不编码: + +## 分页参数 + +* page 默认1 +* size 默认15 + +## channel + +大分类,电影、电视剧、公开课,详细分类可以使用 `channel_cn` + +```python +[('movie', 12057), ('tv', 5428), ('openclass', 35), ('discuss', 1)] +``` + +## channel_cn + +推荐值: + +```python +[('电影', 12057), ('美剧', 2119), ('日剧', 1215), ('英剧', 641), ('纪录片', 314), ('韩剧', 212), ('动画', 126), ('泰剧', 112), + ('加剧', 82), ('西剧', 62), ('澳剧', 61)] + +``` + +全部可选值: + +```python +[('电影', 12057), ('美剧', 2119), ('日剧', 1215), ('英剧', 641), ('纪录片', 314), ('韩剧', 212), ('动画', 126), ('泰剧', 112), + ('加剧', 82), ('西剧', 62), ('澳剧', 61), ('真人秀', 51), ('法剧', 50), ('德剧', 41), ('公开课', 35), ('其剧', 34), ('越剧', 23), + ('巴剧', 21), ('俄剧', 21), ('意剧', 19), ('墨剧', 16), ('印剧', 16), ('土剧', 16), ('\x00剧', 12), ('电视电影', 12), ('脱口秀', 10), + ('挪威剧', 9), ('丹麦剧', 8), ('综艺', 7), ('葡萄牙剧', 6), ('颁奖礼', 5), ('以色列剧', 4), ('新剧', 4), ('菲律宾剧', 4), ('动漫', 4), ('瑞典剧', 4), + ('新西兰剧', 4), ('神剧', 3), ('短视频', 3), ('舞台剧', 3), ('MV', 3), ('演讲', 3), ('颁奖典礼', 3), ('比利时剧', 3), ('南非剧', 3), ('电视剧', 3), + ('晨间剧', 2), ('短片', 2), ('荷兰剧', 2), ('巴西电视剧', 2), ('爱尔兰剧', 2), ('汽车三贱客', 2), ('芬兰剧', 2), ('大剧', 2), ('美剧 律政', 1), + ('美剧/英剧', 1), ('小镇疑云(美版)', 1), ('动画片', 1), ('埃剧', 1), ('探案', 1), ('纪录', 1), ('演唱会', 1), ('冰岛剧', 1), ('深夜剧', 1), + ('萌剧', 1), ('律政/剧情', 1), ('2013年BBC历史记录片', 1), ('催眠剧', 1), ('波兰剧', 1), ('幼教', 1), ('约旦', 1), ('闹剧', 1), ('浪漫/喜剧', 1), + ('悬疑/罪案', 1), ('BBC世界杯专题纪录片', 1), ('克罗地亚剧', 1), ('台剧', 1), ('墨西哥剧', 1), ('惊悚', 1), ('阿拉伯剧', 1), ('委内瑞拉电视剧', 1), + ('音乐会', 1), ('巴西剧', 1), ('新闻', 1), ('土耳其剧', 1), ('约旦剧', 1), ('发布会', 1), ('丹麦瑞典合拍', 1), ('捷克剧', 1), ('越南剧', 1), + ('剧情', 1), ('墨西哥电视剧', 1), ('韩综', 1), ('花絮', 1), ('', 1)] + +``` + +## area + +**推荐使用** +推荐值 + +```python +[('美国', 9057), ('日本', 2233), ('英国', 1637), ('法国', 902), ('韩国', 763), ('其他', 535), ('德国', 402), ('加拿大', 313), + ('西班牙', 280), ('印度', 247), ('俄罗斯', 234), ('泰国', 191), ('澳大利亚', 182), ('意大利', 150), ('', 109), ('越南', 60), ('巴西', 54), + ('大陆', 52), ('墨西哥', 40), ('土耳其', 35), ('新加坡', 23), ('香港', 20), ('埃及', 1), ('台湾', 1)] + +``` + +## show_type + +不推荐使用!数据缺失非常严重 + +```python +[('', 16751), ('纪录片', 314), ('动画', 126), ('日剧', 110), ('真人秀', 51), ('电视电影', 12), ('脱口秀', 10), ('挪威剧', 9), ('美剧', 8), + ('丹麦剧', 8), ('综艺', 7), ('葡萄牙剧', 6), ('颁奖礼', 5), ('以色列剧', 4), ('菲律宾剧', 4), ('动漫', 4), ('瑞典剧', 4), ('新西兰剧', 4), + ('神剧', 3), ('英剧', 3), ('短视频', 3), ('舞台剧', 3), ('MV', 3), ('演讲', 3), ('颁奖典礼', 3), ('比利时剧', 3), ('南非剧', 3), ('晨间剧', 2), + ('短片', 2), ('荷兰剧', 2), ('新剧', 2), ('巴西电视剧', 2), ('爱尔兰剧', 2), ('汽车三贱客', 2), ('芬兰剧', 2), ('泰剧', 1), ('美剧 律政', 1), + ('美剧/英剧', 1), ('小镇疑云(美版)', 1), ('动画片', 1), ('探案', 1), ('纪录', 1), ('演唱会', 1), ('冰岛剧', 1), ('深夜剧', 1), ('萌剧', 1), + ('律政/剧情', 1), ('2013年BBC历史记录片', 1), ('催眠剧', 1), ('波兰剧', 1), ('幼教', 1), ('约旦', 1), ('闹剧', 1), ('浪漫/喜剧', 1), ('韩剧', 1), + ('悬疑/罪案', 1), ('西剧', 1), ('BBC世界杯专题纪录片', 1), ('克罗地亚剧', 1), ('墨西哥剧', 1), ('惊悚', 1), ('阿拉伯剧', 1), ('委内瑞拉电视剧', 1), + ('音乐会', 1), ('巴西剧', 1), ('新闻', 1), ('土耳其剧', 1), ('约旦剧', 1), ('发布会', 1), ('丹麦瑞典合拍', 1), ('捷克剧', 1), ('越南剧', 1), + ('剧情', 1), ('墨西哥电视剧', 1), ('韩综', 1), ('花絮', 1)] + +``` + +## 请求范例 + +全部使用 `size=1&douban=True`做为范例,响应结构如下 + +注意,由于并不是所有的资源都有豆瓣信息,因此有些可能douban字段为 `{}` + +```json +{ + "data": [ + { + "id": 30552, + "cnname": "极限战队", + "enname": "Ultraforce", + "aliasname": "极端力量", + "channel": "tv", + "channel_cn": "美剧", + "area": "美国", + "show_type": "", + "expire": "1610397126", + "views": 0, + "year": [ + 2013 + ], + "douban": { + "name": "极限战队", + "doubanId": 1295384, + "doubanLink": "https://movie.douban.com/subject/1295384/", + "posterLink": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2512733819.jpg", + "posterData": "base64编码的图片", + "resourceId": 30552, + "rating": "7.9", + "actors": [ + "卡斯帕·范·迪恩", + "迪娜·迈耶", + "丹妮丝·理查兹", + "杰克·布塞", + "尼尔·帕特里克·哈里斯", + "克兰西·布朗", + "塞斯·吉列姆", + "帕特里克·茂顿", + "迈克尔·艾恩塞德", + "露·麦克拉纳罕", + "马绍尔·贝尔", + "埃里克·布鲁斯科特尔", + "马特·莱文", + "布蕾克·林斯利", + "安东尼·瑞维瓦", + "布兰达·斯特朗", + "迪恩·诺里斯", + "克里斯托弗·柯里", + "莱诺尔·卡斯多夫", + "罗伯特·斯莫特", + "斯蒂芬·福特", + "罗伯特·大卫·豪尔", + "艾米·斯马特", + "蒂莫西·奥门德森", + "代尔·戴" + ], + "directors": [ + "保罗·范霍文" + ], + "genre": [ + "动作", + "科幻", + "惊悚", + "冒险" + ], + "releaseDate": "1997-11-07", + "episodeCount": "", + "episodeDuration": "129 分钟", + "writers": [ + "爱德华·诺麦尔", + "罗伯特·A·海因莱因" + ], + "year": "1997", + "introduction": "高中生瑞科(卡斯帕•凡•迪恩CasperVanDien饰)毕业后,在女友卡门(丹妮丝•理查兹DeniseRichards饰)的鼓动下,违背了父亲的意志,加入了机械化步兵学院,卡门亦加入了海军学院。在他们参加训练不久后,地球遭到了来自外星球的昆虫袭击。瑞科的亲人均惨遭杀害,卡门将拍摄到的影像传送给了瑞科。悲愤交加的瑞科率领部下投入到了对抗外星昆虫的战斗中。然而,军队低估了这些昆虫的实力。在一次遭遇战中,10万军队惨遭杀戮,只剩瑞科、卡门等几人侥幸逃生。瑞科亲眼目睹了恐怖的杀戮场面,意外获知了这些昆虫变得如此聪明、强大的秘密。瑞科意识到必须制造更先进的武器才能对付这些昆虫,人类的反击开始了!" + } + } + ], + "count": 9057 +} +``` + +* 日剧 `http://127.0.0.1:8888/api/category?channel_cn=日剧` +* 国家为"美国"的资源 `http://127.0.0.1:8888/api/category?area=美国` +* 美国的纪录片 `http://127.0.0.1:8888/api/category?&area=美国&channel_cn=纪录片` +* 日本的电影 `http://127.0.0.1:8888/api/category?size=1&area=日本&channel=movie` 或 `channel_cn=电影` +* 动漫 `http://127.0.0.1:8888/api/category?size=1&channel_cn=动漫` + +# 最新资源 + +* GET `/api/resource/latest` + +可选URL参数 size,最大100,超过100无效。如 `http://127.0.0.1:8888/api/resource/latest?size=5` 即为获取最新5条数据 + +```json +{ + "data": [ + { + "name": "速度与激情9-F9 (2021) (1080p) [BluRay] [HD FULL].avi 1.52 GB", + "timestamp": "1623415867", + "size": "1.52GB", + "resource_id": 39894, + "res_name": "速度与激情9", + "date": "2021-06-11 20:51:07" + }, + { + "name": "洛基-E01", + "timestamp": "1623415867", + "size": "788.53MB", + "resource_id": 41382, + "res_name": "洛基", + "date": "2021-06-11 20:51:07" + }, + { + "name": "致命女人-EP01", + "timestamp": "1623415867", + "size": "790MB", + "resource_id": 38413, + "res_name": "致命女人", + "date": "2021-06-11 20:51:07" + } + ] +} +``` diff --git a/Dockerfile b/Dockerfile index 962c916..81ea3ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,10 +20,8 @@ COPY YYeTsFE/package.json /YYeTsBot/YYeTsFE/ COPY YYeTsFE/yarn.lock /YYeTsBot/YYeTsFE/ RUN yarn --network-timeout 1000000 COPY YYeTsFE /YYeTsBot/YYeTsFE/ -COPY .git/modules /YYeTsBot/.git/modules/ -COPY hooks/dev_robots.sh /tmp/ -RUN echo "gitdir: ../.git/modules/YYeTsFE" > .git -RUN if [ "$env" = "dev" ]; then echo "dev build"; yarn build; sh /tmp/dev_robots.sh; rm /tmp/dev_robots.sh; else echo "prod build"; yarn run release; fi +COPY scripts/dev_robots.sh /tmp/ +RUN if [ "$env" = "dev" ]; then echo "dev build"; yarn build; sh /tmp/dev_robots.sh; else echo "prod build"; yarn run release; fi FROM runner @@ -32,7 +30,7 @@ COPY --from=pybuilder /root/.local /usr/local COPY --from=pybuilder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=pybuilder /usr/share/zoneinfo /usr/share/zoneinfo RUN true -COPY --from=nodebuilder /YYeTsBot/YYeTsFE/build /YYeTsBot/yyetsweb +COPY --from=nodebuilder /YYeTsBot/YYeTsFE/build /YYeTsBot/yyetsweb/templates/ ENV TZ=Asia/Shanghai WORKDIR /YYeTsBot/yyetsbot diff --git a/YYeTsFE b/YYeTsFE index 5c85689..ec00c3e 160000 --- a/YYeTsFE +++ b/YYeTsFE @@ -1 +1 @@ -Subproject commit 5c85689ad0060c7894e4229ad09fb9fb64ab72db +Subproject commit ec00c3ee0b6b3e8aeac66de6993bfe5b014a8e4e diff --git a/conf/yyets.env b/conf/yyets.env new file mode 100644 index 0000000..f82e9d1 --- /dev/null +++ b/conf/yyets.env @@ -0,0 +1,6 @@ +mongo=mongo +redis=redis +email_user=username +email_password=passord +email_host=mailhog +email_port=1025 diff --git a/docker-compose.yml b/docker-compose.yml index 7cd3c7c..46d85d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,19 @@ services: logging: driver: none + yyets-web: + image: bennythink/yyetsbot + restart: unless-stopped + environment: + - mongo=mongo + - redis=redis + working_dir: /YYeTsBot/yyetsweb/ + volumes: + - ./data:/YYeTsBot/yyetsweb/data/ + command: [ "python3","server.py","-h=0.0.0.0" ] + ports: + - "127.0.0.1:8888:8888" + socat: image: alpine/socat restart: always @@ -32,6 +45,12 @@ services: env_file: - env/yyets.env + mailhog: + image: cd2team/mailhog + restart: unless-stopped + ports: + - "8025:8025" + nginx: restart: always image: nginx:alpine @@ -45,15 +64,3 @@ services: - "443:443" environment: TZ: Asia/Shanghai - - yyets-web: - image: bennythink/yyetsbot - restart: unless-stopped - environment: - - mongo=mongo - working_dir: /YYeTsBot/yyetsweb/ - volumes: - - ./data:/YYeTsBot/yyetsweb/data/ - command: [ "python3","server.py","-h=0.0.0.0" ] - ports: - - "127.0.0.1:8888:8888" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 73593bc..d01da31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ tgbot-ping redis==3.5.3 apscheduler==3.6.3 pymongo==3.11.2 -tornado==6.0.4 +tornado==6.1 redis==3.5.3 captcha==0.3 passlib==1.7.4 diff --git a/hooks/dev_robots.sh b/scripts/dev_robots.sh similarity index 100% rename from hooks/dev_robots.sh rename to scripts/dev_robots.sh diff --git a/scripts/install.sh b/scripts/install.sh index 0c8f9db..39971cb 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -23,7 +23,7 @@ function prepare() { function prepare_compose() { echo "[2/5] 下载docker-compose.yaml" curl -o docker-compose.yaml https://raw.githubusercontent.com/tgbot-collection/YYeTsBot/master/docker-compose.yml - sed -i '18,47d' docker-compose.yaml + sed -i '31,67d' docker-compose.yaml sed -i 's/127.0.0.1/0.0.0.0/' docker-compose.yaml } diff --git a/hooks/pre_build b/scripts/pre_build.sh similarity index 100% rename from hooks/pre_build rename to scripts/pre_build.sh diff --git a/tests/.coveragerc b/tests/.coveragerc deleted file mode 100644 index 7483f5f..0000000 --- a/tests/.coveragerc +++ /dev/null @@ -1,5 +0,0 @@ -[run] -omit = - */site-packages/* - */distutils/* - tests/* \ No newline at end of file diff --git a/tests/data/yyets_search.html b/tests/data/yyets_search.html deleted file mode 100644 index 119e308..0000000 --- a/tests/data/yyets_search.html +++ /dev/null @@ -1,474 +0,0 @@ - - - - - - - - -abc,搜索结果页 - - - - - - - - - - -
-
-
-

- - 捐助我们 - 客户端下载 - -
-
- -
-
-

网站公告:

-
    -
    -
    - - - - - - - - - - - - - - -
    - -
    -
    -
    -
    -
    - 查看:全部(141)影视(4)字幕(5)资讯(132) -
    -
    - - - - -
    -
    -
    -
    -
    - - - - - - - - -
    -
    -
    -
    - -
    - -
    - - - -
    - - - - - - - - - - - - - - -
    -
    人人影视在线聊天室
    -
    -

    人在线
    一起来热聊

    -
    -
    - - - - \ No newline at end of file diff --git a/tests/data/zimuxia_result.html b/tests/data/zimuxia_result.html deleted file mode 100644 index b365878..0000000 --- a/tests/data/zimuxia_result.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - -天国与地狱 | 字幕组 FIX字幕侠 做国内最好的字幕组 天国与地狱FIX字幕侠百度网盘,FIX字幕侠天国与地狱,天国与地狱字幕下载 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - -
    - -
    -
    -
    -
    -

    天国与地狱

    -

    天国与地狱

    -
    天国与地狱
    -
    导演: 平川雄一朗 / 青山贵洋 / 松木彩
    -编剧: 森下佳子
    -主演: 绫濑遥 / 高桥一生 / 北村一辉 / 柄本佑 / 沟端淳平 / 更多…
    -类型: 悬疑 / 犯罪
    -官方网站: www.tbs.co.jp/tengokutojigoku_tbs/
    -制片国家/地区: 日本
    -语言: 日语
    -首播: 2021-01-17(日本)
    -单集片长: 54分钟
    -
    -
    【剧情简介】
    -
    绫濑遥将饰演本剧女主角、东京警视厅搜查一课的正义女警望月彩子,不幸与高桥一生饰演的高智商杀人犯日高互换灵魂,两个人从说话、生活到思维方式都发了逆转,日子变得一团糟,而柄本佑饰演的邻家自由业者渡边,和北村一辉饰演的彩子上司河原,也参与其中。本剧编剧为森下佳子(《乱世花道》《天皇的御厨》),导演为平川雄一朗(《天皇的御厨》)、松木彩(《半泽直树2》)和青山贵洋,每周日晚9点播出。
    -
    -
    -
    【资源下载】
    -
    -
    E01 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E02 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E03 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E04 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E05 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E06 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E07 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -
    E08 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -E09 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -
    E10 UC网盘 115网盘 百度网盘 磁力下载 电驴下载 提取码:FIXX
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - - - - diff --git a/tests/data/zimuxia_search.html b/tests/data/zimuxia_search.html deleted file mode 100644 index 877baae..0000000 --- a/tests/data/zimuxia_search.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - -You searched for 逃避 | 字幕组 FIX字幕侠 做国内最好的字幕组 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - -
    - -
    -
    -

    Search results for "逃避"

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    -逃避可耻但有用 -

    -
    -
    -
    -

    逃避可耻但有用 导演: 金子文紀 / 土井裕泰 / 石井康晴 编剧: 野木亜紀子 主演: 新垣结衣 / 星野源 […]

    - -Read More - - -
    -
    -
    -
    -
    -
    -
    -
    -

    -亢奋 -

    -
    -
    -
    -

    亢奋 导演: 奥古斯丁·弗里泽尔 编剧: 萨姆·李文森 主演: 赞达亚 / 茉德·阿帕图 / Brian Br […]

    - -Read More - - -
    -
    -
    -
    -
    -
    -
    -
    -

    -第二十二条军规 -

    -
    -
    -
    -

    第二十二条军规 导演: 乔治·克鲁尼 / 格兰特·赫斯洛夫 / 艾伦·库拉斯 编剧: 卢克·戴维斯 / 大卫· […]

    - -Read More - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - - - - - - - - \ No newline at end of file diff --git a/tests/mongo_test_data.json b/tests/mongo_test_data.json deleted file mode 100644 index bacd44f..0000000 --- a/tests/mongo_test_data.json +++ /dev/null @@ -1,1053 +0,0 @@ -[ - { - "status": 1, - "info": "OK", - "data": { - "info": { - "id": 34812, - "cnname": "逃避可耻却有用", - "enname": "NIGERUHA HAJIDAGA YAKUNITATSU", - "aliasname": "逃避虽可耻但有用 / 雇佣妻子(港) / 月薪娇妻(台) / 逃跑是可耻但是有用 / 逃避虽可耻但很有用 / 逃避可耻但有用", - "channel": "tv", - "channel_cn": "日剧", - "area": "日本", - "show_type": "", - "expire": "1610399344", - "views": 202 - }, - "list": [ - { - "season_num": "101", - "season_cn": "单剧", - "items": { - "APP": [ - { - "itemid": "554385", - "episode": "12", - "name": "yyets://N=逃避可耻却有用 人类加油!新春特别篇!!.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ganbare.Jinrui.Shinshun.Special.SP.Chi_Jap.HDTVrip.1280X720.mp4|S=1505489064|H=b7aae378e30689eab20125e3bff3bac1d16a043e|", - "size": "", - "yyets_trans": 0, - "dateline": "1609622857", - "files": [ - { - "way": "102", - "way_cn": "百度云", - "address": "https://pan.baidu.com/s/149tnE0DWW68jU5nMbdFH3w", - "passwd": "x28a" - }, - { - "way": "115", - "way_cn": "微云", - "address": "https://share.weiyun.com/Y2ooLgRe", - "passwd": "" - } - ] - }, - { - "itemid": "298235", - "episode": "11", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep11.Final.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=733784950|H=c464b9e32a999b417324b53cd92d2fbfa89b597a|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298234", - "episode": "10", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep10.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=733930391|H=3c572ede5b41a2368d0f64071ea733592876344a|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298233", - "episode": "9", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep09.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629132305|H=129e9e73d44ccf575afc70eecdd171732fc89329|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298232", - "episode": "8", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep08.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629207765|H=47e4573d0d72fa7d2394a377f467d0b352fce08f|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298231", - "episode": "7", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep07.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629119647|H=bb2ff32f008d3d2e58371f34b83292ddde7b51e8|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298230", - "episode": "6", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep06.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629239487|H=83f72df4cd440c22c8c5ab94b559773eb472c46d|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298229", - "episode": "5", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep05.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629262958|H=30bb5f65bff74ebe60d848edd4748b3c3e7e76c7|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298228", - "episode": "4", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep04.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629037391|H=3274e38c1ba20493a0bf62d4f7c65bec651dc3d2|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298227", - "episode": "3", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep03.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=628416378|H=9ac2a719d66b3626011c2d3366367a9d56c20e26|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298226", - "episode": "2", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep02.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|S=629086728|H=d21a081cc6a32daa85310ca6aad81e378f0b736e|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - }, - { - "itemid": "298225", - "episode": "1", - "name": "yyets://N=逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep01.Chi_Jap.HDTVrip.1280X720-ZhuixinFanV2.mp4|S=733967923|H=fa1a42ee8066da0aa2b66ca470814489160ad214|", - "size": "", - "yyets_trans": 0, - "dateline": "1491891741", - "files": null - } - ], - "HR-HDTV": [ - { - "itemid": "554384", - "episode": "12", - "name": "逃避可耻却有用 人类加油!新春特别篇!!.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ganbare.Jinrui.Shinshun.Special.SP.Chi_Jap.HDTVrip.1280X720.mp4", - "size": "1.4GB", - "yyets_trans": 0, - "dateline": "1609622857", - "files": [ - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:4a6be139e640db770dbe266471e5cc81357c205e&tr=http://tr.cili001.com:8070/announce&tr=udp://p4p.arenabg.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://open.demonii.com:1337", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "https://pan.baidu.com/s/149tnE0DWW68jU5nMbdFH3w", - "passwd": "x28a" - } - ] - }, - { - "itemid": "284973", - "episode": "11", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep11.Final.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "699.79MB", - "yyets_trans": 0, - "dateline": "1482282868", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep11.Final.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|733784950|51a18ead729a833f58264700b963113a|h=ncojnutlufhohfrl42xr7t2m6lqdwzjf|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:78930bae5efe391ee6bcc4c7dcfa237b05a493f0&tr=http://tracker.openbittorrent.com/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tr.cili001.com:6666/announce&tr=http://tracker.publicbt.com/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1jHAM8aq", - "passwd": "" - } - ] - }, - { - "itemid": "284430", - "episode": "10", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep10.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "699.93MB", - "yyets_trans": 0, - "dateline": "1481704074", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep10.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|733930391|15b6440bdd991d04a7ff6ba9d22e07d6|h=aodlfl4jgt7paj6v4vbolfjeho5d744t|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:1e50cd628d6829127534e5b9411d505efaea98f8&tr=http://tracker.openbittorrent.com/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tr.cili001.com:6666/announce&tr=http://tracker.publicbt.com/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1nvPP4db", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/DFw163629975", - "passwd": "" - } - ] - }, - { - "itemid": "283661", - "episode": "9", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep09.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "599.99MB", - "yyets_trans": 0, - "dateline": "1481098725", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep09.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629132305|9d5152f4f21f1a1bf053ce33abd41ad5|h=mwd7hctmq5ziym7ugc2vptn6ieazici3|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:7d047723d5697c68361b05eb20da1c50d224425e&tr=http://tracker.openbittorrent.com/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tr.cili001.com:6666/announce&tr=http://tracker.publicbt.com/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1nu9tJM5", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/9K9162939027", - "passwd": "" - } - ] - }, - { - "itemid": "282897", - "episode": "8", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep08.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "600.06MB", - "yyets_trans": 0, - "dateline": "1480497463", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep08.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629207765|1ba73a37879514eabd5ab2adf938be40|h=scdffp3xfc4e36fdhslh5p3sr76oh5a6|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:3ac5f75b4ad9e59f1a9752067eaa8ed9117a0931&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1kUGjaMb", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/Ula162377565", - "passwd": "" - } - ] - }, - { - "itemid": "282123", - "episode": "7", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep07.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "599.98MB", - "yyets_trans": 0, - "dateline": "1479886413", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep07.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629119647|1565719ed7d08c10a7bac9a740e3bdda|h=5pho2gfiivlv3rkhrh7q4dvsietozz5r|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:b8283af2cdca0b1b751f716423611e4795fbee77&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1qYfaU3E", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/AtK161811663", - "passwd": "" - } - ] - }, - { - "itemid": "281371", - "episode": "6", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep06.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "600.09MB", - "yyets_trans": 0, - "dateline": "1479281049", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep06.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629239487|9fc21b8ce21f0699f035404ad4baae6a|h=4yse343oq7vymc4qe6qtjyge2yfukf7q|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:0186d5447834e8051af6551faa4cfde44476aff5&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1slzXo3z", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/4ia161194698", - "passwd": "" - } - ] - }, - { - "itemid": "280656", - "episode": "5", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep05.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "600.11MB", - "yyets_trans": 0, - "dateline": "1478670362", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep05.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629262958|ba41b35dcfec2716f90ab664048f7e09|h=yrdanlqzxpqrsldgoi5aobjqhldxm6vq|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:f52c3ba04703cd4298e7a6a09ee87f520f700f7b&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1c1Rl1BU", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/fMW160612929", - "passwd": "" - } - ] - }, - { - "itemid": "280068", - "episode": "4", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep04.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "599.9MB", - "yyets_trans": 0, - "dateline": "1478065113", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep04.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629037391|07d320c3db4ad737e398cc7ba08c5216|h=yzfnqwlsvw2yloobhvkjf6yil6gc5kag|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:46ec914334a77f968d1c789b2f4a9fee8648435a&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1pKED1gv", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/cOt160028088", - "passwd": "" - } - ] - }, - { - "itemid": "279384", - "episode": "3", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep03.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "599.3MB", - "yyets_trans": 0, - "dateline": "1477483732", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep03.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|628416378|8fdc3bd1714d5dab282f5e0f7cdf25be|h=on5tdwesk4maldu4txop3eutgihi2yyu|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:157d2fc48b9efbe8f846643652a7c2f3d7f14989&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1ge3yfL5", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/Ov3159483996", - "passwd": "" - } - ] - }, - { - "itemid": "278649", - "episode": "2", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep02.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4", - "size": "599.94MB", - "yyets_trans": 0, - "dateline": "1476868310", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep02.Chi_Jap.HDTVrip.1280X720-ZhuixinFan.mp4|629086728|3812eb6a3ef37b3ead6ad9c3a26efa24|h=jogqyi4cauhueoppsgcgdnprpbu7om3g|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:1e5a9180c2be2aa6a1e932479dae91ce082ca22d&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1gfHRfnt", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/2Tb158731035", - "passwd": "" - } - ] - }, - { - "itemid": "277958", - "episode": "1", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep01.Chi_Jap.HDTVrip.1280X720-ZhuixinFanV2.mp4", - "size": "699.97MB", - "yyets_trans": 0, - "dateline": "1476238571", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep01.Chi_Jap.HDTVrip.1280X720-ZhuixinFanV2.mp4|733967923|7116a9dfb86fceed3f5b71604e48745f|h=ku7saiivihhc26hcbx4cjsplvkum62ho|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:2a331028531d0254a2b1f30e55edf99b6b716e49&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1hrOugEK", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/Dde158319090", - "passwd": "" - } - ] - } - ], - "MP4": [ - { - "itemid": "554387", - "episode": "12", - "name": "逃避可耻却有用 人类加油!新春特别篇!!.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ganbare.Jinrui.Shinshun.Special.SP.Chi_Jap.HDTVrip.1280X720.mp4", - "size": "1.4GB", - "yyets_trans": 0, - "dateline": "1609622857", - "files": [ - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:4a6be139e640db770dbe266471e5cc81357c205e&tr=http://tr.cili001.com:8070/announce&tr=udp://p4p.arenabg.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://open.demonii.com:1337", - "passwd": "" - } - ] - }, - { - "itemid": "284971", - "episode": "11", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep11.Final.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "273.97MB", - "yyets_trans": 0, - "dateline": "1482265302", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep11.Final.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|287277713|c5e3cda7312d5c959b054ce1fe2d9648|h=yons5tync72soz55fil76fhasuyef2p5|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:e643ee71dcfc70ef5c768f4801937bee3474ab7c&tr=http://tracker.openbittorrent.com/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tr.cili001.com:6666/announce&tr=http://tracker.publicbt.com/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1nu7sDDJ", - "passwd": "" - } - ] - }, - { - "itemid": "284424", - "episode": "10", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep10.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "250.46MB", - "yyets_trans": 0, - "dateline": "1481694187", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep10.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|262629609|5ddda8755130865fd0b506b897dd3933|h=56ncsvze3suc4pszccf3frhbw24u454u|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:64c03d502e04f592fa7dc86ea9b13ccce52c53e5&tr=http://tracker.openbittorrent.com/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tr.cili001.com:6666/announce&tr=http://tracker.publicbt.com/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1cvdJye", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/mcp163620606", - "passwd": "" - } - ] - }, - { - "itemid": "283576", - "episode": "9", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep09.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "197.99MB", - "yyets_trans": 0, - "dateline": "1481091708", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep09.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|207609828|c3bf94d4f2e63139d7196f0297aa3882|h=lixjdvvffgkozmcbujjkeusdmx5uz6h4|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:f331279acc58e85d679418c7a11ebeefb4b02b42&tr=http://tracker.openbittorrent.com/announce&tr=udp://tracker.openbittorrent.com:80/announce&tr=udp://tr.cili001.com:6666/announce&tr=http://tracker.publicbt.com/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1miyMEbm", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/pU0162934266", - "passwd": "" - } - ] - }, - { - "itemid": "282890", - "episode": "8", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep08.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "193.81MB", - "yyets_trans": 0, - "dateline": "1480490078", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep08.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|203226404|0b2c976116eead8cee3a086d112ce738|h=b56bqcmprjcnkhwez5siuz2dzzvfljo4|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:2fc115a1a26ed12f8d2f540e9f8699b989193e04&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1kUIkQ7X", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/NyN162369651", - "passwd": "" - } - ] - }, - { - "itemid": "282120", - "episode": "7", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep07.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "205.1MB", - "yyets_trans": 0, - "dateline": "1479881031", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep07.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|215062889|ca0ffa335b3a595e41f71d3c8f277040|h=a65hvn5lzc5c6loc746tf3yexszea4eh|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:2d47c371eba87c52e7618a782b627a95fffed7b4&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1bo9Dwv1", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/8Tk161811666", - "passwd": "" - } - ] - }, - { - "itemid": "281368", - "episode": "6", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep06.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "225.23MB", - "yyets_trans": 0, - "dateline": "1479275434", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep06.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|236167107|5d1ca5827f3cf88cb39bf55df64e017b|h=k2bzvh2zpyljsq6b2g5xbtci3s63q5xy|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:043c0fe675011d2d6a1fcba243e130864e5ab461&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1qYQxCBi", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/aXo161179959", - "passwd": "" - } - ] - }, - { - "itemid": "280651", - "episode": "5", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep05.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "214.19MB", - "yyets_trans": 0, - "dateline": "1478665773", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep05.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|224589979|fc95f51dd77b80abcfc8d2be7955a0fe|h=wgrml4u6cey6iiyxgtx4ml6yrzqdx7ap|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:fb7c0b0c634af47bd5249873eecef81edaefbb5e&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1miLLJBi", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/zRI160610232", - "passwd": "" - } - ] - }, - { - "itemid": "280038", - "episode": "4", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep04.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "191.02MB", - "yyets_trans": 0, - "dateline": "1478060356", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep04.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|200298651|b9e1eef644649f12ef4684f92fdb0379|h=5cvm2z6kxke24ti7gqpgcwesvsfejkvr|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:4edc1466dea534e178214ed701731f6a376996b1&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1hrZk4cc", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/DuN160026666", - "passwd": "" - } - ] - }, - { - "itemid": "279352", - "episode": "3", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep03.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "230.2MB", - "yyets_trans": 0, - "dateline": "1477463950", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep03.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|241387154|923cd649dd5d48840a7f1c42d8ba3700|h=czevlgexn5os63rxulctjubd2u65ukf6|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:0f71cd2401057ef47cdc6ede0dcb1ebaec2e1618&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1gfPtce3", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/Xsc159444636", - "passwd": "" - } - ] - }, - { - "itemid": "278647", - "episode": "2", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep02.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4", - "size": "200.22MB", - "yyets_trans": 0, - "dateline": "1476861702", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep02.Chi_Jap.HDTVrip.852X480-ZhuixinFan.mp4|209947733|ed7626c02262f287b92f2472c5ed1a29|h=f5dwdur3fhjifrztundnr7psdhzzqihv|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:f5e244402d31013208dafea5e0231084cd4b3ed3&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1o8Rgrm6", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/POv158726721", - "passwd": "" - } - ] - }, - { - "itemid": "277951", - "episode": "1", - "name": "逃避可耻却有用.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep01.Chi_Jap.HDTVrip.852X480-ZhuixinFanV2.mp4", - "size": "304.92MB", - "yyets_trans": 0, - "dateline": "1476216447", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E5%8F%AF%E8%80%BB%E5%8D%B4%E6%9C%89%E7%94%A8.NIGERUHA.HAJIDAGA.YAKUNITATSU.Ep01.Chi_Jap.HDTVrip.852X480-ZhuixinFanV2.mp4|319730163|56c0ff1e0c00f4ae8b2636d6fa0c20ee|h=teyixuxqajhhehxiupbozfst3r5b5ytk|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:e4dcb4f8fc0f99416107da0beb6697ce77b8b66b&tr.1=http://tracker.openbittorrent.com/announce&tr.2=udp://tracker.openbittorrent.com:80/announce&tr.3=udp://tr.cili001.com:6666/announce&tr.4=http://tracker.publicbt.com/announce&tr.5=udp://open.demonii.com:1337&tr.6=udp://tracker.opentrackr.org:1337/announce&tr.7=http://tr.cili001.com:6666/announce", - "passwd": "" - }, - { - "way": "9", - "way_cn": "网盘", - "address": "http://pan.baidu.com/s/1kUTD9cj", - "passwd": "" - }, - { - "way": "12", - "way_cn": "诚通网盘", - "address": "http://ZiMuZuUSTV.ctfile.com/fs/07i158318898", - "passwd": "" - } - ] - } - ] - }, - "formats": [ - "APP", - "HR-HDTV", - "MP4" - ] - } - ] - } - }, - { - "status": 1, - "info": "OK", - "data": { - "info": { - "id": 29540, - "cnname": "无法逃避", - "enname": "Inescapable", - "aliasname": "无法避免", - "channel": "movie", - "channel_cn": "电影", - "area": "加拿大", - "show_type": "", - "expire": "1610396227", - "views": 2 - }, - "list": [ - { - "season_num": "0", - "season_cn": "正片", - "items": { - "720P": [ - { - "itemid": "123134", - "episode": "0", - "name": "无法逃避.Inescapable.2012.720p.BluRay.DTS.x264-HDWing.mkv", - "size": "4.37GB", - "yyets_trans": 0, - "dateline": "1364314150", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|Inescapable.2012.720p.BluRay.DTS.x264-HDWing.mkv|4687769606|FEB821FA30195604DD120F1D9036F68C|h=U3MAAOU35DLMQ6IF3A55CT6X6ZNQGFXK|/", - "passwd": "" - } - ] - } - ] - }, - "formats": [ - "720P" - ] - } - ] - } - }, - { - "status": 1, - "info": "OK", - "data": { - "info": { - "id": 37089, - "cnname": "逃避者", - "enname": "Shirkers", - "aliasname": "", - "channel": "movie", - "channel_cn": "电影", - "area": "美国", - "show_type": "", - "expire": "1610400512", - "views": 2 - }, - "list": [ - { - "season_num": "0", - "season_cn": "正片", - "items": { - "APP": [ - { - "itemid": "396362", - "episode": "0", - "name": "yyets://N=逃避者.shirkers.2018.中文字幕.WEBrip.AAC.1080p.x264-VINEnc.mp4|S=2184526436|H=cf0fd5c26b3734518ee0528fc29f688f494afe94|", - "size": "", - "yyets_trans": 0, - "dateline": "1540784598", - "files": [ - { - "way": "115", - "way_cn": "微云", - "address": "https://share.weiyun.com/5mWnx0N", - "passwd": "" - } - ] - } - ], - "MP4": [ - { - "itemid": "396286", - "episode": "0", - "name": "逃避者.shirkers.2018.中文字幕.WEBrip.AAC.1080p.x264-VINEnc.mp4", - "size": "2.03GB", - "yyets_trans": 0, - "dateline": "1540724633", - "files": [ - { - "way": "1", - "way_cn": "电驴", - "address": "ed2k://|file|%E9%80%83%E9%81%BF%E8%80%85.shirkers.2018.%E4%B8%AD%E6%96%87%E5%AD%97%E5%B9%95.WEBrip.AAC.1080p.x264-VINEnc.mp4|2184526436|70674c6ceddff0f97b8d59a827d5fb0c|h=tkktn7gbrof5hp354xx6lw7g7ugkpotb|/", - "passwd": "" - }, - { - "way": "2", - "way_cn": "磁力", - "address": "magnet:?xt=urn:btih:cc51e109b0abbb35d5db230d2a8ef1b5a6350de6&tr=udp://9.rarbg.to:2710/announce&tr=udp://9.rarbg.me:2710/announce&tr=http://tr.cili001.com:8070/announce&tr=http://tracker.trackerfix.com:80/announce&tr=udp://open.demonii.com:1337&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://p4p.arenabg.com:1337&tr=wss://tracker.openwebtorrent.com&tr=wss://tracker.btorrent.xyz&tr=wss://tracker.fastcast.nz", - "passwd": "" - } - ] - } - ] - }, - "formats": [ - "APP", - "MP4" - ] - } - ] - } - } -] \ No newline at end of file diff --git a/tests/test_bot.py b/tests/test_bot.py deleted file mode 100644 index a79afc6..0000000 --- a/tests/test_bot.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/local/bin/python3 -# coding: utf-8 - -# YYeTsBot - test_bot.py -# 1/31/21 14:05 -# - -__author__ = "Benny " - -import unittest -import sys -from unittest import mock - -from telebot.types import Message - -sys.path.append("../yyetsbot") -import yyetsbot - - -@mock.patch("yyetsbot.bot") -class TestStartHandler(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - jsonstring = r'{"message_id":1,"from":{"id":108929734,"first_name":"Frank","last_name":"Wang","username":"eternnoir","is_bot":true},"chat":{"id":1734,"first_name":"F","type":"private","last_name":"Wa","username":"oir"},"date":1435296025,"text":"/start"}' - cls.message = Message.de_json(jsonstring) - - @classmethod - def tearDownClass(cls) -> None: - pass - - def test_start(self, b): - yyetsbot.send_welcome(self.message) - self.assertEqual(1, b.send_message.call_count) - self.assertEqual(1, b.send_chat_action.call_count) - self.assertEqual(self.message.chat.id, b.send_message.call_args.args[0]) - self.assertIn("欢迎使用", b.send_message.call_args.args[1]) diff --git a/tests/test_fansub.py b/tests/test_fansub.py deleted file mode 100644 index f9ff772..0000000 --- a/tests/test_fansub.py +++ /dev/null @@ -1,115 +0,0 @@ -# coding: utf-8 - -import unittest -import os -import sys - -import requests_mock -from unittest import mock - -sys.path.append("../yyetsbot") -import yyetsbot as _ - -from fansub import BaseFansub, YYeTsOnline, YYeTsOffline - - -class TestBaseFunsub(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.ins = BaseFansub() - cls.cookie_jar = dict(name="hello") - cls.ins.cookie_file = "test_cookies.dump" # generate on tests/test_cookies.dump - - @classmethod - def tearDownClass(cls) -> None: - cls().ins.redis.flushall() - os.unlink(cls().ins.cookie_file) - - def test_save_cookies(self): - self.ins.__save_cookies__(self.cookie_jar) - exists = os.path.exists(self.ins.cookie_file) - self.assertTrue(exists) - - def test_load_cookies(self): - self.test_save_cookies() - cookie = self.ins.__load_cookies__() - self.assertEqual(cookie, self.cookie_jar) - - def test_get_from_cache(self): - value = self.ins.__get_from_cache__("http://test.url", "__hash__") - self.assertEqual(value, self.ins.__hash__()) - - def test_save_to_cache(self): - # never expire - url = "http://test2.url" - self.ins.__save_to_cache__(url, self.cookie_jar) - cache_copy = self.ins.__get_from_cache__(url, "never mind method") - self.assertEqual(cache_copy, self.cookie_jar) - - -class TestYYeTsTestOnline(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.ins = YYeTsOnline() - cls.cookie_jar = dict(name="hello yyets") - cls.ins.cookie_file = "test_cookies.dump" # generate on tests/test_cookies.dump - cls.ins.url = "http://www.rrys2020.com/resource/1988" - with open("data/yyets_search.html") as f: - cls.search_html = f.read() - - @classmethod - def tearDownClass(cls) -> None: - cls().ins.redis.flushall() - # os.unlink(cls().ins.cookie_file) - - def test_get_id(self): - self.assertEqual(self.ins.id, "1988") - - @requests_mock.mock() - def test_get_search_html(self, m): - m.get('http://www.rrys2020.com/search?keyword=abc&type=resource', text=self.search_html) - response = self.ins.__get_search_html__("abc") - self.assertEqual(self.search_html, response) - - @requests_mock.mock() - def test_search_preview(self, m): - kw = "abc" - m.get(f'http://www.rrys2020.com/search?keyword={kw}&type=resource', text=self.search_html) - results = self.ins.search_preview(kw) - results.pop("source") - for name in results.values(): - self.assertIn(kw, name.lower()) - - # TODO.... - def test_search_result(self): - pass - - -class TestYYeTsTestOffline(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.ins = YYeTsOffline(db="test") - - @classmethod - def tearDownClass(cls) -> None: - cls().ins.mongo.close() - - def test_search_preview(self): - kw = "逃避" - results = self.ins.search_preview(kw) - self.assertEqual(results["source"], self.ins.label) - results.pop("source") - self.assertEqual(3, len(results)) - for name in results.values(): - self.assertIn(kw, name) - - def test_search_result(self): - url = "http://www.rrys2020.com/resource/34812" - results = self.ins.search_result(url) - self.assertIn(str(results['all']['data']['info']['id']), url) - self.assertIn("逃避可耻", results["cnname"]) - self.assertIn("34812", results["share"]) - - -if __name__ == '__main__': - unittest.main() diff --git a/yyetsweb/Makefile b/yyetsweb/Makefile index dc7bd79..9e01b63 100644 --- a/yyetsweb/Makefile +++ b/yyetsweb/Makefile @@ -8,7 +8,8 @@ default: asset: - go-bindata -o assets.go resource.html index.html search.html js/... css/... fonts/... img/... + cd templates; go-bindata -o assets.go resource.html index.html search.html js/... css/... fonts/... img/... + mv templates/assets.go ./ dev: go-bindata -o assets.go index.html diff --git a/yyetsweb/Mongo.py b/yyetsweb/Mongo.py index 4385657..6403f47 100644 --- a/yyetsweb/Mongo.py +++ b/yyetsweb/Mongo.py @@ -7,13 +7,17 @@ __author__ = "Benny " +import base64 import contextlib +import json import logging import os import pathlib +import random import re import sys import time +import uuid from datetime import date, timedelta from http import HTTPStatus from urllib.parse import unquote @@ -24,14 +28,17 @@ from bs4 import BeautifulSoup from bson.objectid import ObjectId from passlib.handlers.pbkdf2 import pbkdf2_sha256 from retry import retry +from tqdm import tqdm from database import (AnnouncementResource, BlacklistResource, CaptchaResource, - CommentChildResource, CommentNewestResource, + CategoryResource, CommentChildResource, + CommentNewestResource, CommentReactionResource, CommentResource, DoubanReportResource, DoubanResource, - GrafanaQueryResource, MetricsResource, NameResource, - OtherResource, Redis, ResourceResource, TopResource, - UserLikeResource, UserResource) -from utils import ts_date + GrafanaQueryResource, LikeResource, MetricsResource, + NameResource, NotificationResource, OtherResource, Redis, + ResourceLatestResource, ResourceResource, TopResource, + UserEmailResource, UserResource) +from utils import send_mail, ts_date lib_path = pathlib.Path(__file__).parent.parent.joinpath("yyetsbot").resolve().as_posix() sys.path.append(lib_path) @@ -147,6 +154,7 @@ class CommentMongoResource(CommentResource, Mongo): .sort("_id", pymongo.DESCENDING).limit(self.inner_size).skip((self.inner_page - 1) * self.inner_size) children_data = list(children_data) self.get_user_group(children_data) + self.add_reactions(children_data) item["children"] = [] if children_data: @@ -162,6 +170,16 @@ class CommentMongoResource(CommentResource, Mongo): group = user.get("group", ["user"]) comment["group"] = group + def add_reactions(self, data): + for comment in data: + cid = comment.get("id") or comment.get("_id") + cid = str(cid) + reactions = self.db["reactions"].find_one({"comment_id": cid}, + projection={"_id": False, "comment_id": False}) or {} + for verb, users in reactions.items(): + if users: + comment.setdefault("reactions", []).append({"verb": verb, "users": users}) + def get_comment(self, resource_id: int, page: int, size: int, **kwargs) -> dict: self.inner_page = kwargs.get("inner_page", 1) self.inner_size = kwargs.get("inner_size", 5) @@ -174,6 +192,8 @@ class CommentMongoResource(CommentResource, Mongo): self.find_children(data) self.convert_objectid(data) self.get_user_group(data) + self.add_reactions(data) + return { "data": data, "count": count, @@ -227,6 +247,30 @@ class CommentMongoResource(CommentResource, Mongo): ) returned["status_code"] = HTTPStatus.CREATED returned["message"] = "评论成功" + + # notification + if parent_comment_id: + # find username + + self.db["notification"].find_one_and_update( + {"username": exists["username"]}, + {"$push": {"unread": inserted_id}}, + upsert=True + ) + # send email + # TODO unsubscribe + parent_comment = self.db["comment"].find_one({"_id": ObjectId(parent_comment_id)}) + if resource_id == 233: + link = f"https://yyets.dmesg.app/discuss#{parent_comment_id}" + else: + link = f"https://yyets.dmesg.app/resource?id={resource_id}#{parent_comment_id}" + user_info = self.db["users"].find_one({"username": parent_comment["username"], "email.verified": True}) + if user_info: + subject = "[人人影视下载分享站] 你的评论有了新的回复" + pt_content = content.split("")[-1] + body = f"{username} 您好,
    你的评论 {parent_comment['content']} 有了新的回复:
    {pt_content}" \ + f"
    你可以点此链接查看

    请勿回复此邮件" + send_mail(user_info["email"]["address"], subject, body) return returned def delete_comment(self, comment_id): @@ -252,29 +296,36 @@ class CommentMongoResource(CommentResource, Mongo): return returned - def react_comment(self, username, comment_id, verb): - if verb not in ("like", "dislike"): - return {"status": False, - "message": "verb could only be like or dislike", - "status_code": HTTPStatus.BAD_REQUEST} - result = self.db["users"].find_one({"username": username, f"comments_{verb}": {"$in": [comment_id]}}) - if result: - return {"status": False, "message": "too many reactions", "status_code": HTTPStatus.UNPROCESSABLE_ENTITY} +class CommentReactionMongoResource(CommentReactionResource, Mongo): + def react_comment(self, username, data): + # {"comment_id":"da23","😊":["user1","user2"]} + comment_id = data["comment_id"] + verb = data["verb"] + method = data["method"] if not self.db["comment"].find_one({"_id": ObjectId(comment_id)}): return {"status": False, "message": "Where is your comments?", "status_code": HTTPStatus.NOT_FOUND} - self.db["users"].update_one({"username": username}, - {"$push": {f"comments_{verb}": comment_id}} - ) - - self.db["comment"].update_one({"_id": ObjectId(comment_id)}, - {"$inc": {verb: 1}} - ) - + if method == "POST": + self.db["reactions"].update_one({"comment_id": comment_id}, + { + "$addToSet": {verb: username} + }, + upsert=True + ) + code = HTTPStatus.CREATED + elif method == "DELETE": + self.db["reactions"].update_one({"comment_id": comment_id}, + { + "$pull": {verb: username} + } + ) + code = HTTPStatus.ACCEPTED + else: + code = HTTPStatus.BAD_REQUEST return {"status": True, "message": "success", - "status_code": HTTPStatus.CREATED} + "status_code": code} class CommentChildMongoResource(CommentChildResource, CommentMongoResource, Mongo): @@ -450,6 +501,69 @@ class ResourceMongoResource(ResourceResource, Mongo): return returned + def patch_resource(self, new_data: dict): + rid = new_data["resource_id"] + new_data.pop("resource_id") + old_data = self.db["yyets"].find_one( + {"data.info.id": rid}, + ) + new_data["season_cn"] = self.convert_season(new_data["season_num"]) + # 1. totally empty resource: + if len(old_data["data"]["list"]) == 0: + new_data["season_cn"] = self.convert_season(new_data["season_num"]) + old_data["data"]["list"].append(new_data) + else: + for season in old_data["data"]["list"]: + if new_data["season_num"] in [season["season_num"], int(season["season_num"])]: + user_format = new_data["formats"][0] + for u in new_data["items"][user_format]: + season["items"][user_format].append(u) + + self.db["yyets"].find_one_and_replace( + {"data.info.id": rid}, + old_data + ) + + def add_resource(self, new_data: dict): + rid = self.get_appropriate_id() + new_data["data"]["info"]["id"] = rid + self.db["yyets"].insert_one(new_data) + return {"status": True, "message": "success", "id": rid} + + def delete_resource(self, data: dict): + rid = data["resource_id"] + meta = data.get("meta") + if meta: + db_data = self.db["yyets"].find_one({"data.info.id": rid}) + for season in db_data["data"]["list"]: + for episode in season["items"].values(): + for v in episode: + if v["episode"] == meta["episode"] and v["name"] == meta["name"] and \ + v["size"] == meta["size"] and v["dateline"] == meta["dateline"]: + episode.remove(v) + # replace it + self.db["yyets"].find_one_and_replace({"data.info.id": rid}, db_data) + + else: + self.db["yyets"].delete_one({"data.info.id": rid}) + + def get_appropriate_id(self): + col = self.db["yyets"] + random_id = random.randint(50000, 80000) + data = col.find_one({"data.info.id": random_id}, projection={"_id": True}) + if data: + return self.get_appropriate_id() + else: + return random_id + + @staticmethod + def convert_season(number: [int, str]): + pass + if number in (0, "0"): + return "正片" + else: + return f"第{number}季" + class TopMongoResource(TopResource, Mongo): projection = {'_id': False, 'data.info': True} @@ -479,7 +593,7 @@ class TopMongoResource(TopResource, Mongo): return all_data -class UserLikeMongoResource(UserLikeResource, Mongo): +class LikeMongoResource(LikeResource, Mongo): projection = {'_id': False, 'data.info': True} def get_user_like(self, username: str) -> list: @@ -488,35 +602,6 @@ class UserLikeMongoResource(UserLikeResource, Mongo): .sort("data.info.views", pymongo.DESCENDING) return list(data) - -class UserMongoResource(UserResource, Mongo): - def login_user(self, username: str, password: str, ip: str, browser: str) -> dict: - data = self.db["users"].find_one({"username": username}) - returned_value = {"status_code": 0, "message": ""} - - if data: - # try to login - stored_password = data["password"] - if pbkdf2_sha256.verify(password, stored_password): - returned_value["status_code"] = HTTPStatus.OK - else: - returned_value["status_code"] = HTTPStatus.FORBIDDEN - returned_value["message"] = "用户名或密码错误" - - else: - hash_value = pbkdf2_sha256.hash(password) - try: - self.db["users"].insert_one(dict(username=username, password=hash_value, - date=ts_date(), ip=ip, browser=browser) - ) - returned_value["status_code"] = HTTPStatus.CREATED - - except Exception as e: - returned_value["status_code"] = HTTPStatus.INTERNAL_SERVER_ERROR - returned_value["message"] = str(e) - - return returned_value - def add_remove_fav(self, resource_id: int, username: str) -> dict: returned = {"status_code": 0, "message": ""} like_list: list = self.db["users"].find_one({"username": username}).get("like", []) @@ -533,9 +618,58 @@ class UserMongoResource(UserResource, Mongo): self.db["users"].update_one({"username": username}, {'$set': value}) return returned + +class UserMongoResource(UserResource, Mongo): + def login_user(self, username: str, password: str, captcha: str, captcha_id: str, ip: str, browser: str) -> dict: + # verify captcha in the first place. + redis = Redis().r + correct_captcha = redis.get(captcha_id) + if correct_captcha is None: + return {"status_code": HTTPStatus.BAD_REQUEST, "message": "验证码已过期", "status": False} + elif correct_captcha.lower() == captcha.lower(): + redis.expire(captcha_id, 0) + else: + return {"status_code": HTTPStatus.FORBIDDEN, "message": "验证码错误", "status": False} + # check user account is locked. + + data = self.db["users"].find_one({"username": username}) or {} + if data.get("status", {}).get("disable"): + return {"status_code": HTTPStatus.FORBIDDEN, + "status": False, + "message": data.get("status", {}).get("reason")} + + returned_value = {"status_code": 0, "message": ""} + + if data: + # try to login + stored_password = data["password"] + if pbkdf2_sha256.verify(password, stored_password): + returned_value["status_code"] = HTTPStatus.OK + else: + returned_value["status_code"] = HTTPStatus.FORBIDDEN + returned_value["message"] = "用户名或密码错误" + + else: + # register + hash_value = pbkdf2_sha256.hash(password) + try: + self.db["users"].insert_one(dict(username=username, password=hash_value, + date=ts_date(), ip=ip, browser=browser) + ) + returned_value["status_code"] = HTTPStatus.CREATED + + except Exception as e: + returned_value["status_code"] = HTTPStatus.INTERNAL_SERVER_ERROR + returned_value["message"] = str(e) + + returned_value["username"] = data.get("username") + returned_value["group"] = data.get("group", ["user"]) + return returned_value + def get_user_info(self, username: str) -> dict: projection = {"_id": False, "password": False} data = self.db["users"].find_one({"username": username}, projection) + data.update(group=data.get("group", ["user"])) return data def update_user_last(self, username: str, now_ip: str) -> None: @@ -543,6 +677,43 @@ class UserMongoResource(UserResource, Mongo): {"$set": {"lastDate": (ts_date()), "lastIP": now_ip}} ) + def update_user_info(self, username: str, data: dict) -> dict: + redis = Redis().r + valid_fields = ["email"] + valid_data = {} + for field in valid_fields: + if data.get(field): + valid_data[field] = data[field] + + if valid_data.get("email") and not re.findall(r"\S@\S", valid_data.get("email")): + return {"status_code": HTTPStatus.BAD_REQUEST, "status": False, "message": "email format error "} + elif valid_data.get("email"): + # rate limit + user_email = valid_data.get("email") + timeout_key = f"timeout-{user_email}" + if redis.get(timeout_key): + return {"status_code": HTTPStatus.TOO_MANY_REQUESTS, + "status": False, + "message": f"try again in {redis.ttl(timeout_key)}s"} + + verify_code = random.randint(10000, 99999) + valid_data["email"] = {"verified": False, "address": user_email} + # send email confirm + subject = "[人人影视下载分享站] 请验证你的邮箱" + body = f"{username} 您好,
    请输入如下验证码完成你的邮箱认证。验证码有效期为24小时。
    " \ + f"如果您未有此请求,请忽略此邮件。

    验证码: {verify_code}" + + redis.set(timeout_key, username, ex=1800) + redis.hset(user_email, mapping={"code": verify_code, "wrong": 0}) + redis.expire(user_email, 24 * 3600) + send_mail(user_email, subject, body) + + self.db["users"].update_one( + {"username": username}, + {"$set": valid_data} + ) + return {"status_code": HTTPStatus.CREATED, "status": True, "message": "success"} + class DoubanMongoResource(DoubanResource, Mongo): @@ -663,3 +834,164 @@ class DoubanReportMongoResource(DoubanReportResource, Mongo): {"resource_id": resource_id}, {"$push": {"content": content}}, upsert=True).matched_count return dict(count=count) + + +class NotificationMongoResource(NotificationResource, Mongo): + def get_notification(self, username, page, size): + # .sort("_id", pymongo.DESCENDING).limit(size).skip((page - 1) * size) + notify = self.db["notification"].find_one({"username": username}, projection={"_id": False}) + if not notify: + return {} + + # size is shared + unread = notify.get("unread", []) + id_list = [] + for item in unread[(page - 1) * size:size * page]: + id_list.append(item) + notify["unread_item"] = self.get_content(id_list) + + size = size - len(unread) + read = notify.get("read", []) + id_list = [] + for item in read[(page - 1) * size:size * page]: + id_list.append(item) + notify["read_item"] = self.get_content(id_list) + + notify.pop("unread", None) + notify.pop("read", None) + notify["unread_count"] = len(unread) + notify["read_count"] = len(read) + return notify + + def get_content(self, id_list): + comments = self.db["comment"].find({"_id": {"$in": id_list}}, + projection={"ip": False, "parent_id": False} + ).sort("_id", pymongo.DESCENDING) + comments = list(comments) + for comment in comments: + comment["id"] = str(comment["_id"]) + comment.pop("_id") + reply_to_id = re.findall(r'"(.*)"', comment["content"])[0] + rtc = self.db["comment"].find_one({"_id": ObjectId(reply_to_id)}, + projection={"content": True, "_id": False}) + comment["reply_to_content"] = rtc["content"] + + return comments + + def update_notification(self, username, verb, comment_id): + if verb == "read": + v1, v2 = "read", "unread" + else: + v1, v2 = "unread", "read" + self.db["notification"].find_one_and_update( + {"username": username}, + { + "$push": {v1: ObjectId(comment_id)}, + "$pull": {v2: ObjectId(comment_id)} + } + ) + + return {} + + +class UserEmailMongoResource(UserEmailResource, Mongo): + def verify_email(self, username, code): + r = Redis().r + email = self.db["users"].find_one({"username": username})["email"]["address"] + verify_data = r.hgetall(email) + wrong_count = int(verify_data["wrong"]) + MAX = 10 + if wrong_count >= MAX: + self.db["users"].update_one({"username": username}, + {"$set": {"status": {"disable": True, "reason": "verify email crack"}}} + ) + return {"status": False, "status_code": HTTPStatus.FORBIDDEN, "message": "Account locked. Please stay away"} + correct_code = verify_data["code"] + + if correct_code == code: + r.expire(email, 0) + r.expire(f"timeout-{email}", 0) + self.db["users"].update_one({"username": username}, + {"$set": {"email.verified": True}} + ) + return {"status": True, "status_code": HTTPStatus.CREATED, "message": "success"} + else: + r.hset(email, "wrong", wrong_count + 1) + return {"status": False, + "status_code": HTTPStatus.FORBIDDEN, + "message": f"verification code is incorrect. You have {MAX - wrong_count} attempts remaining"} + + +class CategoryMongoResource(CategoryResource, Mongo): + def get_category(self, query: dict): + page, size, douban = query["page"], query["size"], query["douban"] + query.pop("page") + query.pop("size") + query.pop("douban") + query_dict = {} + for key, value in query.items(): + query_dict[f"data.info.{key}"] = value + logging.info("Query dict %s", query_dict) + projection = {"_id": False, "data.list": False} + data = self.db["yyets"].find(query_dict, projection=projection).limit(size).skip((page - 1) * size) + count = self.db["yyets"].count_documents(query_dict) + f = [] + for item in data: + if douban: + douban_data = self.db["douban"].find_one({"resourceId": item["data"]["info"]["id"]}, + projection=projection) + if douban_data: + douban_data["posterData"] = base64.b64encode(douban_data["posterData"]).decode("u8") + item["data"]["info"]["douban"] = douban_data + else: + item["data"]["info"]["douban"] = {} + f.append(item["data"]["info"]) + return dict(data=f, count=count) + + +class ResourceLatestMongoResource(ResourceLatestResource, Mongo): + @staticmethod + def get_latest_resource() -> dict: + redis = Redis().r + key = "latest-resource" + latest = redis.get(key) + if latest: + logging.info("Cache hit for latest resource") + latest = json.loads(latest) + latest["data"] = latest["data"][:100] + else: + logging.warning("Cache miss for latest resource") + latest = ResourceLatestMongoResource().query_db() + redis.set(key, json.dumps(latest, ensure_ascii=False)) + return latest + + def query_db(self) -> dict: + col = self.db["yyets"] + projection = {"_id": False, "status": False, "info": False} + episode_data = {} + for res in tqdm(col.find(projection=projection), total=col.count()): + for season in res["data"]["list"]: + for item in season["items"].values(): + for single in item: + ts = single["dateline"] + res_name = res["data"]["info"]["cnname"] + name = "{}-{}".format(res_name, single["name"]) + size = single["size"] + episode_data[name] = {"timestamp": ts, "size": size, "resource_id": res["data"]["info"]["id"], + "res_name": res_name, "date": ts_date(int(ts))} + + sorted_res: list = sorted(episode_data.items(), key=lambda x: x[1]["timestamp"], reverse=True) + limited_res = dict(sorted_res[:100]) + ok = [] + for k, v in limited_res.items(): + t = {"name": k} + t.update(v) + ok.append(t) + return dict(data=ok) + + def refresh_latest_resource(self): + redis = Redis().r + logging.info("Getting new resources...") + latest = self.query_db() + redis.set("latest-resource", json.dumps(latest, ensure_ascii=False)) + logging.info("latest-resource data refreshed.") diff --git a/yyetsweb/README.md b/yyetsweb/README.md index bbc922b..ed4cdd7 100644 --- a/yyetsweb/README.md +++ b/yyetsweb/README.md @@ -30,10 +30,26 @@ mongorestore --gzip --archive=yyets_mongo.gz use zimuzu; db.getCollection('yyets').createIndex({"data.info.id": 1}); - db.getCollection('yyets').createIndex({"data.info.views" : -1}); - db.getCollection('yyets').createIndex({"data.info.area" : 1}); - db.getCollection('yyets').getIndexes(); + +db.getCollection('douban').createIndex({"resourceId" : 1}); +db.getCollection('douban').getIndexes(); + +db.getCollection('users').createIndex({"username" : 1}, { unique: true }); +db.getCollection('users').getIndexes(); + +db.getCollection('comment').createIndex({"resource_id" : 1}); +db.getCollection('comment').getIndexes(); + +db.getCollection('reactions').createIndex({"comment_id" : 1}); +db.getCollection('reactions').getIndexes(); + +db.getCollection('metrics').createIndex({"date" : 1}); +db.getCollection('metrics').getIndexes(); + +db.getCollection('notification').createIndex({"username" : 1}); +db.getCollection('notification').getIndexes(); + ``` diff --git a/yyetsweb/craw_data/douban.py b/yyetsweb/craw_data/douban.py deleted file mode 100644 index edac331..0000000 --- a/yyetsweb/craw_data/douban.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/local/bin/python3 -# coding: utf-8 - -# YYeTsBot - douban.py -# 7/10/21 22:59 -# - -__author__ = "Benny " - -import re - -from bs4 import BeautifulSoup - -from Mongo import DoubanMongoResource - -with open("douban_detail.html") as f: - detail_html = f.read() -soup = BeautifulSoup(detail_html, 'html.parser') - - -douban = DoubanMongoResource() -rid = 27238 -douban.find_douban(rid) diff --git a/yyetsweb/craw_data/douban_detail.html b/yyetsweb/craw_data/douban_detail.html deleted file mode 100644 index 1020d67..0000000 --- a/yyetsweb/craw_data/douban_detail.html +++ /dev/null @@ -1,3151 +0,0 @@ - - - - - - - - - - 遇见恶魔 (豆瓣) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - -
    -

    - 遇见恶魔 Meeting Evil - (2012) -

    - -
    - - - -
    - - - - - - - - - -
    -
    -
    - - - - - - - - - -
    - 导演: 克里斯·费舍
    - 编剧: 克里斯·费舍 / 托马斯·伯格
    - 主演: 塞缪尔·杰克逊 / 莱丝莉·比伯 / 卢克·威尔逊 / 佩顿·利斯特 / 翠茜·索姆斯 / 李·瑞恩
    - 类型: 剧情 / 悬疑 / 犯罪
    - - 制片国家/地区: 美国
    - 语言: 英语
    - - 片长: 89分钟
    - 又名: 见见恶魔 / 当魔鬼来敲门
    - IMDb: tt1810697
    - -
    - - - - -
    - - - -
    -
    -
    - - - - -
    - - - - -
    - 6.1 - - -
    -
    - -
    - - - 5星 - -
    - 8.8% -
    -
    -
    - - - 4星 - -
    - 23.8% -
    -
    -
    - - - 3星 - -
    - 38.5% -
    -
    -
    - - - 2星 - -
    - 19.4% -
    -
    -
    - - - 1星 - -
    - 9.5% -
    -
    -
    - -
    -
    - 好于 29% 悬疑片
    - 好于 18% 犯罪片
    -
    -
    - - - -
    - - - - - -
    - - - -
    - - - 评价: - - - - - - - - - - - -
    - - -
    - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - - - - - - - - - - -
    - - - - - - - - -
    - - - - - - - -
    - - - -
    - - - - - - - - - -
    - - - - - - - - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - -

    - 喜欢这部电影的人也喜欢 - · · · · · · -

    - - - -
    -
    -
    - - 黑暗之下 - -
    -
    - 黑暗之下 -
    -
    -
    -
    - - 赌徒杰克:坏账 - -
    -
    - 赌徒杰克:坏账 -
    -
    -
    -
    - - 足迹追踪 - -
    -
    - 足迹追踪 -
    -
    -
    -
    - - 迫害 - -
    -
    - 迫害 -
    -
    -
    -
    - - 合理怀疑 - -
    -
    - 合理怀疑 -
    -
    -
    -
    - - 分秒间离 - -
    -
    - 分秒间离 -
    -
    -
    -
    - - 致命选择 - -
    -
    - 致命选择 -
    -
    -
    -
    - - 冰冻 - -
    -
    - 冰冻 -
    -
    -
    -
    - - 白领流氓 - -
    -
    - 白领流氓 -
    -
    -
    -
    - - 召唤 - -
    -
    - 召唤 -
    -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - - -

    - 遇见恶魔的短评 - · · · · · · - - ( - 全部 251 条 - ) - -

    - - -
    -
    - - -
    - 热门 /  - 最新 /  - -
    - -
    -
    - - - -
    - - -
    -

    - - 1 - - - - - - - - 披着人皮的鬼 - 看过 - - - 2012-04-12 - - -

    -

    - - 呆着一切不合理都是合理的心态看这是一部很有诚意的片子 若干细节理解不能和渣字幕有关 -

    -
    - -
    - -
    - - -
    -

    - - 5 - - - - - - - - cherrie - 看过 - - - 2012-10-22 - - -

    -

    - - 坏进骨子里的 是那些道貌岸然的假圣人。 -

    -
    - -
    - -
    - - -
    -

    - - 0 - - - - - - - - Joey - 看过 - - - 2012-08-30 - - -

    -

    - - wtf -

    -
    - -
    - -
    - - -
    -

    - - 0 - - - - - - - - Avalon - 看过 - - - 2012-04-15 - - -

    -

    - - 人不错,故事没劲! -

    -
    - -
    - -
    - - -
    -

    - - 0 - - - - - - - - 杨子虚 - 看过 - - - 2013-12-29 - - -

    -

    - - 此片被严重低估……至少风格化的片子很我对胃口 -

    -
    - -
    - - - - - - - - - > - 更多短评 - 251条 - -
    -
    -
    -
    -
    - 为什么被折叠? - 有一些短评被折叠了 -
    - 评论被折叠,是因为发布这条评论的帐号行为异常。评论仍可以被展开阅读,对发布人的账号不造成其他影响。如果认为有问题,可以联系豆瓣电影。 -
    -
    -
    -
    - -
    -
    - - - - - -
    - 你关注的人还没写过短评 -
    - - - - -
    -
    - - - - -
    -
    - - - - - - - -
    -
    -

    - 遇见恶魔的话题 · · · · · · - ( 全部 ) -

    -
    - - - - - - -
    -
    - -
    -
    什么是话题
    -
    -
    无论是一部作品、一个人,还是一件事,都往往可以衍生出许多不同的话题。将这些话题细分出来,分别进行讨论,会有更多收获。
    -
    -
    - - -
    - -
    - - - - - - - -
    - - -
    -
    -
    - - - 我要写影评 - -

    - 遇见恶魔的影评 · · · · · · - ( 全部 14 条 ) -

    -
    - - - - - - - - -
    - 热门 / - 最新 / - 好友 -
    - - - - - - - -
    - - - - - -
    -
    - - - -
    - - - - -   - - - - 2014-01-26 12:59:01 - - -
    - - -
    - -

    转载别人的

    - -
    -
    - - 杀手是主角老婆雇佣的,而主角最后会杀了他的老婆。    不得不说导演的拍摄手法很隐晦,不是简单就能体会到的。影片开始主角就已经知道老婆和那个修游泳池的偷情,镜头里主角纠结的趴在铁锹上,看着那个并不是新挖的坑,给结尾做了很好的铺垫。    塞缪尔杰克逊饰演的杀... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 不笑生李奕 - - - - 2018-03-19 11:16:31 - - -
    - - -
    - -

    虽然杀人如麻,肆意妄为,但知道自己是好男人,至少曾经是!

    - -
    -
    -

    这篇影评可能有剧透

    - - Samuel L. Jackson是我个人认为最好的演员,因为他真的是演什么像什么,而且几乎每一部电影都是可圈可点的好作品,而且经常有非常经典的形象。被解放的姜戈里面狡猾卑微,充满奴性的老管家,低俗小说里大彻大悟的黑帮杀手,星战前传里正气凛然的长老,甚至可以说他就是昆丁的... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 迪斯维瑟 - - - - 2013-07-16 11:33:50 - - -
    - - -
    - -

    《你是恶魔,但你就是我》

    - -
    -
    - - 好片 故事递进的很有层次,一层层的铺垫,在结局打开所有谜团 牵着狗的小女孩是神来之笔 画龙点睛的恰到好处,各种暗示就像是在玩解谜游戏 塞缪尔·杰克逊扮演的杀手不断地告诉主角: 我和你很像 直到恶魔被杀,灯被关掉 男主角吹起了杀手的口哨............... 哇~~~~赞!!! - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 米修 - - - 2020-10-14 22:20:44 - - -
    - - -
    - -

    2008金融危机下,男主救家牺牲自己

    - -
    -
    - - 2008金融危机下,男主没有办法保住房产、工作,无奈之下想雇佣杀手杀死自己,用保险金帮助妻儿度过难关,保住住宅,大环境之下,妻子出轨、自己出轨都抵不过让儿女活下去欲望,凶手告诉男主是妻子雇凶的时候,说是妻子让别人联系杀手的,其实是男主自己,杀手误认为而已,因为... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - '﹎|零¨ヰ - - - - 2020-02-08 19:31:07 - - -
    - - -
    - -

    电影语录

    - -
    -
    - - "Be sober,be vigilant ;because your adversary the Devil walks abot like a roaring lion,seeking whom he may devour." 要清醒 要警觉 因为你们的仇敌魔鬼如同吼叫的狮子 到处游荡 寻找可下手的人——“彼得前书” 5:8 这种人本性上就是希望被愚弄 一旦发现这一点就能让他... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 尽在今夜 - - - - 2012-04-11 15:30:39 - - -
    - - -
    - -

    拍得很认真的超级烂片

    - -
    -
    - - 草泥马,这么认真地拍一部烂片。到昨晚为之IMDB上竟没有一个人愿意评分,还不允许给零分,老子被迫给了1分,都觉得给高了。看完刚好午夜宣布不厚落马,真是活见鬼了,恶魔加噩梦。 - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 糯隐糯现段朕朕 - - - - 2016-01-22 17:21:42 - - -
    - - -
    - -

    因为喜欢 ,所以反复斟酌,

    - -
    -
    - - 杀手跟主角有一样的遭遇,小女孩和狗是杀手的幻觉,这是杀手当杀手以前的生活, 狗就是他自己,也是他眼里的男一, 女孩就是他以前的老婆,也是男一的老婆, build a new life, 他帮主角build a new life, __________________________________________ 警察第一次到... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - X白 - - - - 2015-05-21 22:23:35 - - -
    - - -
    - -

    释放内心的恶魔

    - -
    -
    - - 开始觉得没什么意思,纯粹因为神盾局长才看的片子,快进看了一部分突然发现这片子很有趣又从头看了一遍。疯子杀手是碰见不爽的就干掉,包括奚落男主的人。颇有点水浒传里好汉们一言不合,拔刀相向的意思。男主从一个小受,不断受到刺激,最后骗走警察,关灯以后吹起杀手的口哨... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 毛毛熊 - - - - 2013-02-04 10:44:22 - - -
    - - -
    - -

    没有尿点的一部好片!

    - -
    -
    -

    这篇影评可能有剧透

    - - 个人与个人的理解能力都会不同,我谅解那些骂个没完还给一分的哥们姐们。如果你不是影迷,偶尔看一看欧美片,对这个片子看不懂给一分的没啥。老美的思维就是很跳跃的,在国际比赛里天朝孩子的想象力评分为垫底,相信大伙在网上看到过,这是教育的问题吧。这个片子开始让人很纳... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - -
    -
    - - - -
    - - - - - 那一年 - - - - 2017-01-19 20:30:38 - - -
    - - -
    - -

    这个评论一定颠覆本片的所有影评

    - -
    -
    -

    这篇影评可能有剧透

    - - 难道没人想到是男主买凶杀自己吗?男主对杀手说过,他爱自己妻子,谁伤害他妻子他会杀死谁。 因为清楚知道自己彻底破产,能够为家人摆脱困境的唯一办法就是自己的人寿保险。为了所爱的家人他愿意牺牲自己,为此,他为家人安排好了后路,这也是影片一开始他在面临各种财务绝境时... - -  (展开) -
    -
    - - - - -
    -
    -
    - - - - - - - - - - -
    - - - - - - - - -

    - > - - 更多影评 - 14篇 - -

    -
    - - -
    - -
    - -
    - - -

    - 讨论区 -   ·  ·  ·  ·  ·  · -

    - -
    - - -
    - -

    - - > 去这部影片的讨论区(全部0条) - -

    -
    - - - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    - -

    - 在哪儿看这部电影 -  · · · · · · -

    - - - -
    - - - -
    -
    - - - - - - - - - - - - - - - - - - - -
    - - -

    - 豆瓣成员常用的标签 - · · · · · · -

    - - -
    - - -
    -
    -
    - - - - - - - - - -
    - - -

    - 以下片单推荐 - · · · · · · - - ( - 全部 - ) - -

    - - - - - -
    - - - - - - - - - - -
    - - -

    - 谁在看这部电影 - · · · · · · -

    - - - - - -
    - - 1005人看过 -  /  - 255人想看 -
    - -
    - - - - - - -
    - - - - - -
    - - -

    订阅遇见恶魔的评论:
    - feed: rss 2.0

    - - -
    -
    - - - -
    - - - - -
    - - -
    -
    -
    - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/yyetsweb/craw_data/douban_search.html b/yyetsweb/craw_data/douban_search.html deleted file mode 100644 index 615ac4d..0000000 --- a/yyetsweb/craw_data/douban_search.html +++ /dev/null @@ -1,642 +0,0 @@ - - - - - - - - - - 搜索: 逃避可耻却有用 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - -

    搜索: 逃避可耻却有用

    - -
    - - -
    - - - - - - - -
    - -
    - - - - - -

    - 相关电影: - -

    - -
    - - - - -
    -
    - -
    -
    -
    -

    - [电影] -  逃避虽可耻但有用 - 可播放 -

    - -
    - - - 8.4 - (154381人评价) - - 原名:逃げるは恥だが役に立つ / 金子文纪 / 新垣结衣 / 2016 -
    - -
    -

    森山实栗(新垣结衣 饰)自研究生毕业之后就一直仕途不顺,最近更是惨遭解雇,处于“无业游民”的状态之下,日子过得十分凄惨。经由父亲的介绍,无处可去的实栗来到了名...

    -
    -
    - - - - - - - -
    -
    - -
    -
    -
    -

    - [电影] -  逃避虽可耻但有用 新春特别篇 -

    - -
    - - - 8.4 - (26905人评价) - - 原名:逃げるは恥だが役に立つ ガンバレ人類! 新春スペシャル!! / 金子文纪 / 新垣结衣 / 2021 -
    - -
    -

    森山实栗(新垣结衣 饰)和津崎平匡(星野源 饰)之间的事实婚姻愉快而又轻松的进行着,在日常生活的摩擦中,他们逐渐接受了彼此不同的生活步调,关系越来越亲密融洽。...

    -
    -
    - - - - -
    -
    - -
    -
    -
    -

    - [电影] -  逃避虽可耻但有用 爱的悸动!特别篇 -

    - -
    - - - 8.9 - (1179人评价) - - 原名:逃げるは恥だが役に立つ ムズキュン!特別編 / 金子文纪 / 新垣结衣 / 2020 -
    - -
    -

    本特别篇为2016年10月首播的《逃避虽可耻但有用》原剧的再剪辑版,在首播版的基础上加入了一些未公开镜头。

    -
    -
    - -
    - -
    - - - - - -
    -
    - - - - -
    - - - - -
    -
    - -

    - 豆瓣还没有这个电影,我来添加 -  · · · · · · -

    - -
    - -
    - - - - - - -
    -
    - -
    -
    -
    - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/yyetsweb/database.py b/yyetsweb/database.py index 68129a8..a1278b7 100644 --- a/yyetsweb/database.py +++ b/yyetsweb/database.py @@ -20,6 +20,7 @@ import fakeredis import redis from captcha.image import ImageCaptcha +captcha_ex = 60 * 10 predefined_str = re.sub(r"[1l0oOI]", "", string.ascii_letters + string.digits) @@ -114,10 +115,7 @@ class OtherResource(): class UserResource: - def login_user(self, username: str, password: str, ip: str, browser: str) -> dict: - pass - - def add_remove_fav(self, resource_id: int, username: str) -> str: + def login_user(self, username: str, password: str, captcha: str, captcha_id: str, ip: str, browser: str) -> dict: pass def get_user_info(self, username: str) -> dict: @@ -126,6 +124,9 @@ class UserResource: def update_user_last(self, username: str, now_ip: str) -> None: pass + def update_user_info(self, username: str, data: dict) -> dict: + pass + class TopResource: @@ -136,10 +137,13 @@ class TopResource: pass -class UserLikeResource: +class LikeResource: def get_user_like(self, username: str) -> list: pass + def add_remove_fav(self, resource_id: int, username: str) -> str: + pass + class NameResource: def get_names(self, is_readable: [str, bool]) -> dict: @@ -157,7 +161,10 @@ class CommentResource: def delete_comment(self, comment_id: str): pass - def react_comment(self, username, comment_id, verb): + +class CommentReactionResource: + + def react_comment(self, username, data): pass @@ -178,7 +185,7 @@ class CaptchaResource: chars = "".join([random.choice(predefined_str) for _ in range(4)]) image = ImageCaptcha() data = image.generate(chars) - self.redis.r.set(captcha_id, chars, ex=60 * 10) + self.redis.r.set(captcha_id, chars, ex=captcha_ex) return f"data:image/png;base64,{base64.b64encode(data.getvalue()).decode('ascii')}" def verify_code(self, user_input, captcha_id) -> dict: @@ -207,6 +214,15 @@ class ResourceResource: def search_resource(self, keyword: str) -> dict: pass + def patch_resource(self, data: dict): + pass + + def add_resource(self, data: dict): + pass + + def delete_resource(self, data: dict): + pass + class GrafanaQueryResource: def get_grafana_data(self, date_series) -> str: @@ -242,3 +258,30 @@ class DoubanReportResource: def get_error(self) -> dict: pass + + +class NotificationResource: + + def get_notification(self, username, page, size): + pass + + def update_notification(self, username, verb, comment_id): + pass + + +class UserEmailResource: + + def verify_email(self, username, code): + pass + + +class CategoryResource: + + def get_category(self, query: dict): + pass + + +class ResourceLatestResource: + @staticmethod + def get_latest_resource() -> dict: + pass diff --git a/yyetsweb/fonts/test.txt b/yyetsweb/fonts/test.txt deleted file mode 100644 index 274c005..0000000 --- a/yyetsweb/fonts/test.txt +++ /dev/null @@ -1 +0,0 @@ -1234 \ No newline at end of file diff --git a/yyetsweb/go.mod b/yyetsweb/go.mod index 370f8ca..fd83f3e 100644 --- a/yyetsweb/go.mod +++ b/yyetsweb/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/gin-gonic/gin v1.7.2 + github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect github.com/mattn/go-sqlite3 v1.14.8 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect ) diff --git a/yyetsweb/go.sum b/yyetsweb/go.sum index 1a3484d..9a4e0c6 100644 --- a/yyetsweb/go.sum +++ b/yyetsweb/go.sum @@ -5,6 +5,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= +github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= diff --git a/yyetsweb/handler.py b/yyetsweb/handler.py index 0d690d8..d0c6eb9 100644 --- a/yyetsweb/handler.py +++ b/yyetsweb/handler.py @@ -7,6 +7,7 @@ __author__ = "Benny " +import contextlib import importlib import json import logging @@ -14,6 +15,7 @@ import os import re import sys import time +import uuid from concurrent.futures import ThreadPoolExecutor from datetime import date, timedelta from hashlib import sha1 @@ -35,6 +37,9 @@ else: logging.info("%s Running with %s. %s", "#" * 10, adapter, "#" * 10) +static_path = os.path.join(os.path.dirname(__file__), 'templates') +index = os.path.join(static_path, "index.html") + class BaseHandler(web.RequestHandler): executor = ThreadPoolExecutor(200) @@ -43,6 +48,9 @@ class BaseHandler(web.RequestHandler): def __init__(self, application, request, **kwargs): super().__init__(application, request, **kwargs) + self.json = {} + with contextlib.suppress(ValueError): + self.json: dict = json.loads(self.request.body) self.instance = getattr(self.adapter_module, self.class_name)() def write_error(self, status_code, **kwargs): @@ -87,8 +95,6 @@ class IndexHandler(BaseHandler): @run_on_executor() def send_index(self): - root_path = os.path.dirname(__file__) - index = os.path.join(root_path, "index.html") with open(index, encoding="u8") as f: html = f.read() return html @@ -110,35 +116,27 @@ class UserHandler(BaseHandler): @run_on_executor() def login_user(self): - data = json.loads(self.request.body) + data = self.json username = data["username"] password = data["password"] + captcha = data.get("captcha") + captcha_id = data.get("captcha_id", "") ip = AntiCrawler(self).get_real_ip() browser = self.request.headers['user-agent'] - response = self.instance.login_user(username, password, ip, browser) + response = self.instance.login_user(username, password, captcha, captcha_id, ip, browser) if response["status_code"] in (HTTPStatus.CREATED, HTTPStatus.OK): self.set_login(username) - returned_value = "" else: - self.set_status(HTTPStatus.FORBIDDEN) - returned_value = response["message"] + self.set_status(response["status_code"]) - return returned_value + return response @run_on_executor() - def add_remove_fav(self): - data = json.loads(self.request.body) - resource_id = int(data["resource_id"]) - username = self.get_current_user() - if username: - response = self.instance.add_remove_fav(resource_id, username) - self.set_status(response["status_code"]) - else: - response = {"message": "请先登录"} - self.set_status(HTTPStatus.UNAUTHORIZED) - - return response["message"] + def update_info(self): + result = self.instance.update_user_info(self.current_user, self.json) + self.set_status(result.get("status_code", HTTPStatus.IM_A_TEAPOT)) + return result @run_on_executor() def get_user_info(self) -> dict: @@ -147,7 +145,7 @@ class UserHandler(BaseHandler): data = self.instance.get_user_info(username) else: self.set_status(HTTPStatus.UNAUTHORIZED) - data = {} + data = {"message": "Please try to login"} return data @gen.coroutine @@ -156,13 +154,6 @@ class UserHandler(BaseHandler): self.write(resp) @gen.coroutine - @web.authenticated - def patch(self): - resp = yield self.add_remove_fav() - self.write(resp) - - @gen.coroutine - @web.authenticated def get(self): resp = yield self.get_user_info() self.write(resp) @@ -173,6 +164,12 @@ class UserHandler(BaseHandler): now_ip = AntiCrawler(self).get_real_ip() self.instance.update_user_last(username, now_ip) + @gen.coroutine + @web.authenticated + def patch(self): + resp = yield self.update_info() + self.write(resp) + class ResourceHandler(BaseHandler): class_name = f"Resource{adapter}Resource" @@ -216,11 +213,80 @@ class ResourceHandler(BaseHandler): resp = "error" self.write(resp) + # patch and post are available to every login user + # these are rare operations, so no gen.coroutine and run_on_executor + @web.authenticated + def patch(self): + if self.instance.is_admin(self.get_current_user()): + # may consider add admin restrictions + pass + for item in self.json["items"].values(): + for i in item: + i["creator"] = self.get_current_user() + i["itemid"] = uuid.uuid4().hex + self.instance.patch_resource(self.json) + self.set_status(HTTPStatus.CREATED) + self.write({}) -class UserLikeHandler(BaseHandler): - class_name = f"UserLike{adapter}Resource" + @web.authenticated + def post(self): + self.json["data"]["list"] = [] + self.json["data"]["info"]["creator"] = self.get_current_user() + self.set_status(HTTPStatus.CREATED) + resp = self.instance.add_resource(self.json) + self.write(resp) - # from Mongo import UserLikeMongoResource + @web.authenticated + def delete(self): + if not self.instance.is_admin(self.get_current_user()): + self.set_status(HTTPStatus.FORBIDDEN) + self.write({"status": False, "message": "admin only"}) + return + self.instance.delete_resource(self.json) + self.set_status(HTTPStatus.ACCEPTED) + self.write({}) + + +class ResourceLatestHandler(BaseHandler): + class_name = f"ResourceLatest{adapter}Resource" + + # from Mongo import ResourceLatestMongoResource + # instance = ResourceLatestMongoResource() + @run_on_executor() + def get_latest(self): + size = int(self.get_query_argument("size", "100")) + result = self.instance.get_latest_resource() + result["data"] = result["data"][:size] + return result + + @gen.coroutine + def get(self): + resp = yield self.get_latest() + self.write(resp) + + +# +# class ResourceLatestHandler(BaseHandler): +# from concurrent.futures import ProcessPoolExecutor +# +# class_name = f"ResourceLatest{adapter}Resource" +# executor = ProcessPoolExecutor(200) +# +# # from Mongo import ResourceLatestMongoResource +# # instance = ResourceLatestMongoResource() +# +# @gen.coroutine +# def get(self): +# # This returns a concurrent.futures.Future +# fut = self.executor.submit(self.instance.get_latest_resource) +# ret = yield fut +# self.write(ret) + + +class LikeHandler(BaseHandler): + class_name = f"Like{adapter}Resource" + + # from Mongo import LikeMongoResource # instance = UserLikeMongoResource() @run_on_executor() @@ -234,6 +300,26 @@ class UserLikeHandler(BaseHandler): resp = yield self.like_data() self.write(resp) + @run_on_executor() + def add_remove_fav(self): + data = self.json + resource_id = int(data["resource_id"]) + username = self.get_current_user() + if username: + response = self.instance.add_remove_fav(resource_id, username) + self.set_status(response["status_code"]) + else: + response = {"message": "请先登录"} + self.set_status(HTTPStatus.UNAUTHORIZED) + + return response["message"] + + @gen.coroutine + @web.authenticated + def patch(self): + resp = yield self.add_remove_fav() + self.write(resp) + class NameHandler(BaseHandler): class_name = f"Name{adapter}Resource" @@ -281,7 +367,7 @@ class CommentHandler(BaseHandler): @run_on_executor() def add_comment(self): - payload = json.loads(self.request.body) + payload = self.json captcha = payload["captcha"] captcha_id = payload["id"] content = payload["content"] @@ -301,7 +387,7 @@ class CommentHandler(BaseHandler): def delete_comment(self): # need resource_id & id # payload = {"id": "obj_id"} - payload = json.loads(self.request.body) + payload = self.json username = self.get_current_user() comment_id = payload["comment_id"] @@ -313,16 +399,6 @@ class CommentHandler(BaseHandler): self.set_status(HTTPStatus.UNAUTHORIZED) return {"count": 0, "message": "You're unauthorized to delete comment."} - @run_on_executor() - def comment_reaction(self): - payload = json.loads(self.request.body) - username = self.get_current_user() - comment_id = payload["comment_id"] - verb = payload["verb"] - result = self.instance.react_comment(username, comment_id, verb) - self.set_status(result.get("status_code") or HTTPStatus.IM_A_TEAPOT) - return result - @gen.coroutine def get(self): resp = yield self.get_comment() @@ -340,9 +416,30 @@ class CommentHandler(BaseHandler): resp = yield self.delete_comment() self.write(resp) + +class CommentReactionHandler(BaseHandler): + class_name = f"CommentReaction{adapter}Resource" + + # from Mongo import CommentReactionMongoResource + # instance = CommentReactionMongoResource() + + @run_on_executor() + def comment_reaction(self): + self.json.update(method=self.request.method) + username = self.get_current_user() + result = self.instance.react_comment(username, self.json) + self.set_status(result.get("status_code")) + return result + @gen.coroutine @web.authenticated - def patch(self): + def post(self): + resp = yield self.comment_reaction() + self.write(resp) + + @gen.coroutine + @web.authenticated + def delete(self): resp = yield self.comment_reaction() self.write(resp) @@ -412,7 +509,7 @@ class AnnouncementHandler(BaseHandler): self.set_status(HTTPStatus.FORBIDDEN) return {"message": "只有管理员可以设置公告"} - payload = json.loads(self.request.body) + payload = self.json content = payload["content"] real_ip = AntiCrawler(self).get_real_ip() browser = self.request.headers['user-agent'] @@ -437,7 +534,7 @@ class CaptchaHandler(BaseHandler, CaptchaResource): @run_on_executor() def verify_captcha(self): - data = json.loads(self.request.body) + data = self.json captcha_id = data.get("id", None) userinput = data.get("captcha", None) if captcha_id is None or userinput is None: @@ -477,7 +574,7 @@ class MetricsHandler(BaseHandler): @run_on_executor() def set_metrics(self): - payload = json.loads(self.request.body) + payload = self.json metrics_type = payload["type"] self.instance.set_metrics(metrics_type) @@ -550,7 +647,7 @@ class GrafanaQueryHandler(BaseHandler): return time.mktime(time.strptime(text, "%Y-%m-%d")) def post(self): - payload = json.loads(self.request.body) + payload = self.json start = payload["range"]["from"].split("T")[0] end = payload["range"]["to"].split("T")[0] date_series = self.generate_date_series(start, end) @@ -589,7 +686,7 @@ class BlacklistHandler(BaseHandler): class NotFoundHandler(BaseHandler): def get(self): # for react app - self.render("index.html") + self.render(index) class DBDumpHandler(BaseHandler): @@ -694,7 +791,7 @@ class DoubanReportHandler(BaseHandler): @run_on_executor() def report_error(self): - data = json.loads(self.request.body) + data = self.json user_captcha = data["captcha_id"] captcha_id = data["id"] content = data["content"] @@ -713,3 +810,80 @@ class DoubanReportHandler(BaseHandler): def get(self): resp = yield self.get_error() self.write(resp) + + +class NotificationHandler(BaseHandler): + class_name = f"Notification{adapter}Resource" + + # from Mongo import NotificationResource + # instance = NotificationResource() + + @run_on_executor() + def get_notification(self): + username = self.get_current_user() + size = int(self.get_argument("size", "5")) + page = int(self.get_argument("page", "1")) + + return self.instance.get_notification(username, page, size) + + @run_on_executor() + def update_notification(self): + username = self.get_current_user() + verb = self.json["verb"] + comment_id = self.json["comment_id"] + if verb not in ["read", "unread"]: + self.set_status(HTTPStatus.BAD_REQUEST) + return {"status": False, "message": "verb: read or unread"} + self.set_status(HTTPStatus.CREATED) + return self.instance.update_notification(username, verb, comment_id) + + @gen.coroutine + @web.authenticated + def get(self): + resp = yield self.get_notification() + self.write(resp) + + @gen.coroutine + @web.authenticated + def patch(self): + resp = yield self.update_notification() + self.write(resp) + + +class UserEmailHandler(BaseHandler): + class_name = f"UserEmail{adapter}Resource" + + # from Mongo import UserEmailResource + # instance = UserEmailResource() + + @run_on_executor() + def verify_email(self): + result = self.instance.verify_email(self.get_current_user(), self.json["code"]) + self.set_status(result.get("status_code")) + return result + + @gen.coroutine + @web.authenticated + def post(self): + resp = yield self.verify_email() + self.write(resp) + + +class CategoryHandler(BaseHandler): + class_name = f"Category{adapter}Resource" + + from Mongo import CategoryResource + instance = CategoryResource() + + @run_on_executor() + def get_data(self): + self.json = {k: self.get_argument(k) for k in self.request.arguments} + self.json["size"] = int(self.json.get("size", 15)) + self.json["page"] = int(self.json.get("page", 1)) + self.json["douban"] = self.json.get("douban", False) + return self.instance.get_category(self.json) + + @gen.coroutine + def get(self): + resp = yield self.get_data() + self.write(resp) diff --git a/yyetsweb/server.py b/yyetsweb/server.py index 04fd516..1a07438 100644 --- a/yyetsweb/server.py +++ b/yyetsweb/server.py @@ -17,14 +17,17 @@ from tornado import httpserver, ioloop, options, web from tornado.log import enable_pretty_logging from handler import (AnnouncementHandler, BlacklistHandler, CaptchaHandler, - CommentChildHandler, CommentHandler, CommentNewestHandler, + CategoryHandler, CommentChildHandler, CommentHandler, + CommentNewestHandler, CommentReactionHandler, DBDumpHandler, DoubanHandler, DoubanReportHandler, GrafanaIndexHandler, GrafanaQueryHandler, - GrafanaSearchHandler, IndexHandler, MetricsHandler, - NameHandler, NotFoundHandler, ResourceHandler, TopHandler, - UserHandler, UserLikeHandler) + GrafanaSearchHandler, IndexHandler, LikeHandler, + MetricsHandler, NameHandler, NotFoundHandler, + NotificationHandler, ResourceHandler, + ResourceLatestHandler, TopHandler, UserEmailHandler, + UserHandler) from migration.douban_sync import sync_douban -from Mongo import OtherMongoResource +from Mongo import OtherMongoResource, ResourceLatestMongoResource enable_pretty_logging() @@ -34,14 +37,18 @@ if os.getenv("debug"): class RunServer: root_path = os.path.dirname(__file__) - static_path = os.path.join(root_path, '') + static_path = os.path.join(root_path, 'templates') handlers = [ + (r'/', IndexHandler), (r'/api/resource', ResourceHandler), + (r'/api/resource/latest', ResourceLatestHandler), (r'/api/top', TopHandler), - (r'/api/like', UserLikeHandler), + (r'/api/like', LikeHandler), (r'/api/user', UserHandler), + (r'/api/user/email', UserEmailHandler), (r'/api/name', NameHandler), (r'/api/comment', CommentHandler), + (r'/api/comment/reaction', CommentReactionHandler), (r'/api/comment/child', CommentChildHandler), (r'/api/comment/newest', CommentNewestHandler), (r'/api/captcha', CaptchaHandler), @@ -52,9 +59,11 @@ class RunServer: (r'/api/blacklist', BlacklistHandler), (r'/api/db_dump', DBDumpHandler), (r'/api/announcement', AnnouncementHandler), - (r'/', IndexHandler), (r'/api/douban', DoubanHandler), (r'/api/douban/report', DoubanReportHandler), + (r'/api/notification', NotificationHandler), + (r'/api/category', CategoryHandler), + (r'/(.*\.html|.*\.js|.*\.css|.*\.png|.*\.jpg|.*\.ico|.*\.gif|.*\.woff2|.*\.gz|.*\.zip|' r'.*\.svg|.*\.json|.*\.txt)', web.StaticFileHandler, @@ -89,6 +98,7 @@ if __name__ == "__main__": scheduler = BackgroundScheduler(timezone=timez) scheduler.add_job(OtherMongoResource().reset_top, 'cron', hour=0, minute=0, day=1) scheduler.add_job(sync_douban, 'cron', hour=0, minute=0, day=1) + scheduler.add_job(ResourceLatestMongoResource().refresh_latest_resource, 'cron', minute=0) scheduler.start() options.define("p", default=8888, help="running port", type=int) options.define("h", default='127.0.0.1', help="listen address", type=str) diff --git a/yyetsweb/404.html b/yyetsweb/templates/404.html similarity index 100% rename from yyetsweb/404.html rename to yyetsweb/templates/404.html diff --git a/yyetsweb/css/3rd/animate.css b/yyetsweb/templates/css/3rd/animate.css similarity index 100% rename from yyetsweb/css/3rd/animate.css rename to yyetsweb/templates/css/3rd/animate.css diff --git a/yyetsweb/css/3rd/icons.css b/yyetsweb/templates/css/3rd/icons.css similarity index 100% rename from yyetsweb/css/3rd/icons.css rename to yyetsweb/templates/css/3rd/icons.css diff --git a/yyetsweb/css/3rd/widgets.css b/yyetsweb/templates/css/3rd/widgets.css similarity index 100% rename from yyetsweb/css/3rd/widgets.css rename to yyetsweb/templates/css/3rd/widgets.css diff --git a/yyetsweb/css/aYin.css b/yyetsweb/templates/css/aYin.css similarity index 100% rename from yyetsweb/css/aYin.css rename to yyetsweb/templates/css/aYin.css diff --git a/yyetsweb/css/bootstrap.min.css b/yyetsweb/templates/css/bootstrap.min.css similarity index 100% rename from yyetsweb/css/bootstrap.min.css rename to yyetsweb/templates/css/bootstrap.min.css diff --git a/yyetsweb/css/data.json b/yyetsweb/templates/css/data.json similarity index 100% rename from yyetsweb/css/data.json rename to yyetsweb/templates/css/data.json diff --git a/yyetsweb/css/down-list-20180530.css b/yyetsweb/templates/css/down-list-20180530.css similarity index 100% rename from yyetsweb/css/down-list-20180530.css rename to yyetsweb/templates/css/down-list-20180530.css diff --git a/yyetsweb/css/font-awesome.min.css b/yyetsweb/templates/css/font-awesome.min.css similarity index 100% rename from yyetsweb/css/font-awesome.min.css rename to yyetsweb/templates/css/font-awesome.min.css diff --git a/yyetsweb/css/index.json b/yyetsweb/templates/css/index.json similarity index 100% rename from yyetsweb/css/index.json rename to yyetsweb/templates/css/index.json diff --git a/yyetsweb/css/jquery.mCustomScrollbar.css b/yyetsweb/templates/css/jquery.mCustomScrollbar.css similarity index 100% rename from yyetsweb/css/jquery.mCustomScrollbar.css rename to yyetsweb/templates/css/jquery.mCustomScrollbar.css diff --git a/yyetsweb/css/normalize.min.css b/yyetsweb/templates/css/normalize.min.css similarity index 100% rename from yyetsweb/css/normalize.min.css rename to yyetsweb/templates/css/normalize.min.css diff --git a/yyetsweb/css/noty.css b/yyetsweb/templates/css/noty.css similarity index 100% rename from yyetsweb/css/noty.css rename to yyetsweb/templates/css/noty.css diff --git a/yyetsweb/favicon.ico b/yyetsweb/templates/favicon.ico similarity index 100% rename from yyetsweb/favicon.ico rename to yyetsweb/templates/favicon.ico diff --git a/yyetsweb/fonts/fontawesome-webfont.woff2 b/yyetsweb/templates/fonts/fontawesome-webfont.woff2 similarity index 100% rename from yyetsweb/fonts/fontawesome-webfont.woff2 rename to yyetsweb/templates/fonts/fontawesome-webfont.woff2 diff --git a/yyetsweb/help.html b/yyetsweb/templates/help.html similarity index 100% rename from yyetsweb/help.html rename to yyetsweb/templates/help.html diff --git a/yyetsweb/img/11bcd4d0f2daf8b02fecc72bc8ca38ab.png b/yyetsweb/templates/img/11bcd4d0f2daf8b02fecc72bc8ca38ab.png similarity index 100% rename from yyetsweb/img/11bcd4d0f2daf8b02fecc72bc8ca38ab.png rename to yyetsweb/templates/img/11bcd4d0f2daf8b02fecc72bc8ca38ab.png diff --git a/yyetsweb/img/200-wrangler-ferris.gif b/yyetsweb/templates/img/200-wrangler-ferris.gif similarity index 100% rename from yyetsweb/img/200-wrangler-ferris.gif rename to yyetsweb/templates/img/200-wrangler-ferris.gif diff --git a/yyetsweb/img/404-wrangler-ferris.gif b/yyetsweb/templates/img/404-wrangler-ferris.gif similarity index 100% rename from yyetsweb/img/404-wrangler-ferris.gif rename to yyetsweb/templates/img/404-wrangler-ferris.gif diff --git a/yyetsweb/img/afdian.png b/yyetsweb/templates/img/afdian.png similarity index 100% rename from yyetsweb/img/afdian.png rename to yyetsweb/templates/img/afdian.png diff --git a/yyetsweb/img/default-green.png b/yyetsweb/templates/img/default-green.png similarity index 100% rename from yyetsweb/img/default-green.png rename to yyetsweb/templates/img/default-green.png diff --git a/yyetsweb/img/grid16.png b/yyetsweb/templates/img/grid16.png similarity index 100% rename from yyetsweb/img/grid16.png rename to yyetsweb/templates/img/grid16.png diff --git a/yyetsweb/img/yyetsTrans.png b/yyetsweb/templates/img/yyetsTrans.png similarity index 100% rename from yyetsweb/img/yyetsTrans.png rename to yyetsweb/templates/img/yyetsTrans.png diff --git a/yyetsweb/index.html b/yyetsweb/templates/index.html similarity index 99% rename from yyetsweb/index.html rename to yyetsweb/templates/index.html index a1aecd1..e19b300 100644 --- a/yyetsweb/index.html +++ b/yyetsweb/templates/index.html @@ -280,4 +280,4 @@ } - + \ No newline at end of file diff --git a/yyetsweb/js/aYin.js b/yyetsweb/templates/js/aYin.js similarity index 100% rename from yyetsweb/js/aYin.js rename to yyetsweb/templates/js/aYin.js diff --git a/yyetsweb/js/axios.min.js b/yyetsweb/templates/js/axios.min.js similarity index 100% rename from yyetsweb/js/axios.min.js rename to yyetsweb/templates/js/axios.min.js diff --git a/yyetsweb/js/bootstrap.min.js b/yyetsweb/templates/js/bootstrap.min.js similarity index 100% rename from yyetsweb/js/bootstrap.min.js rename to yyetsweb/templates/js/bootstrap.min.js diff --git a/yyetsweb/js/common.js b/yyetsweb/templates/js/common.js similarity index 100% rename from yyetsweb/js/common.js rename to yyetsweb/templates/js/common.js diff --git a/yyetsweb/js/jquery.mCustomScrollbar.min.js b/yyetsweb/templates/js/jquery.mCustomScrollbar.min.js similarity index 100% rename from yyetsweb/js/jquery.mCustomScrollbar.min.js rename to yyetsweb/templates/js/jquery.mCustomScrollbar.min.js diff --git a/yyetsweb/js/jquery.min.js b/yyetsweb/templates/js/jquery.min.js similarity index 100% rename from yyetsweb/js/jquery.min.js rename to yyetsweb/templates/js/jquery.min.js diff --git a/yyetsweb/js/jquery.mousewheel.min.js b/yyetsweb/templates/js/jquery.mousewheel.min.js similarity index 100% rename from yyetsweb/js/jquery.mousewheel.min.js rename to yyetsweb/templates/js/jquery.mousewheel.min.js diff --git a/yyetsweb/js/noty.min.js b/yyetsweb/templates/js/noty.min.js similarity index 100% rename from yyetsweb/js/noty.min.js rename to yyetsweb/templates/js/noty.min.js diff --git a/yyetsweb/js/rshare.js b/yyetsweb/templates/js/rshare.js similarity index 100% rename from yyetsweb/js/rshare.js rename to yyetsweb/templates/js/rshare.js diff --git a/yyetsweb/js/sample.json b/yyetsweb/templates/js/sample.json similarity index 100% rename from yyetsweb/js/sample.json rename to yyetsweb/templates/js/sample.json diff --git a/yyetsweb/js/vue.js b/yyetsweb/templates/js/vue.js similarity index 100% rename from yyetsweb/js/vue.js rename to yyetsweb/templates/js/vue.js diff --git a/yyetsweb/resource.html b/yyetsweb/templates/resource.html similarity index 100% rename from yyetsweb/resource.html rename to yyetsweb/templates/resource.html diff --git a/yyetsweb/templates/robots.txt b/yyetsweb/templates/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/yyetsweb/templates/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/yyetsweb/search.html b/yyetsweb/templates/search.html similarity index 100% rename from yyetsweb/search.html rename to yyetsweb/templates/search.html diff --git a/yyetsweb/utils.py b/yyetsweb/utils.py index 135e0c6..4bb9abe 100644 --- a/yyetsweb/utils.py +++ b/yyetsweb/utils.py @@ -7,8 +7,44 @@ __author__ = "Benny " +import os +import smtplib import time +from email.header import Header +from email.mime.text import MIMEText +from email.utils import formataddr, parseaddr def ts_date(ts=None): return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)) + + + +def _format_addr(s): + name, addr = parseaddr(s) + return formataddr((Header(name, 'utf-8').encode(), addr)) + + +def send_mail(to: str, subject: str, body: str): + user = os.getenv("email_user") + password = os.getenv("email_password") + host = os.getenv("email_host") or "localhost" + port = os.getenv("email_port") or "1025" # mailhog + from_addr = os.getenv("from_addr") or "yyets@dmesg.app" + + msg = MIMEText(body, 'html', 'utf-8') + msg['From'] = _format_addr('YYeTs <%s>' % from_addr) + msg['To'] = _format_addr(to) + msg['Subject'] = Header(subject, 'utf-8').encode() + + if port == "1025": + server = smtplib.SMTP(host, port) + else: + server = smtplib.SMTP_SSL(host, port) + server.login(user, password) + server.sendmail(from_addr, [to], msg.as_string()) + server.quit() + + +if __name__ == '__main__': + send_mail("benny.think@gmail.com", "subj", 'aaaa
    bbb')