Full Code of tgbot-collection/YYeTsBot for AI

master d4ae72f3dcad cached
93 files
1015.6 KB
308.3k tokens
829 symbols
1 requests
Download .txt
Showing preview only (1,055K chars total). Download the full file or copy to clipboard to get everything.
Repository: tgbot-collection/YYeTsBot
Branch: master
Commit: d4ae72f3dcad
Files: 93
Total size: 1015.6 KB

Directory structure:
gitextract_3u5rcyng/

├── .dockerignore
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       └── docker.yaml
├── .gitignore
├── API.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── conf/
│   ├── yyets.dmesg.app.conf
│   └── yyets.env
├── docker-compose.yml
├── requirements.txt
├── scripts/
│   ├── install.sh
│   └── migrate_sub.py
├── setup.py
├── tea.yaml
├── yyets/
│   ├── BagAndDrag/
│   │   ├── README.md
│   │   ├── bag.py
│   │   ├── cfkv.py
│   │   ├── convert_db.py
│   │   ├── create_db.py
│   │   ├── drag.py
│   │   ├── sample.json
│   │   └── zimuxia/
│   │       ├── convert_db.py
│   │       └── zimuxia.py
│   ├── __init__.py
│   ├── healthcheck/
│   │   ├── check.py
│   │   └── restart_service.py
│   ├── management/
│   │   ├── format.json
│   │   └── ui.py
│   └── worker/
│       ├── .cargo-ok
│       ├── README.md
│       ├── public/
│       │   ├── 404.html
│       │   ├── css/
│       │   │   ├── 3rd/
│       │   │   │   ├── animate.css
│       │   │   │   ├── icons.css
│       │   │   │   └── widgets.css
│       │   │   ├── aYin.css
│       │   │   ├── data.json
│       │   │   ├── down-list-20180530.css
│       │   │   ├── index.json
│       │   │   └── jquery.mCustomScrollbar.css
│       │   ├── fonts/
│       │   │   └── test.txt
│       │   ├── index.html
│       │   ├── js/
│       │   │   ├── aYin.js
│       │   │   ├── rshare.js
│       │   │   ├── search.js
│       │   │   └── vue.js
│       │   ├── resource.html
│       │   └── search.html
│       ├── workers-site/
│       │   ├── index.js
│       │   └── package.json
│       └── wrangler.toml
├── yyetsbot/
│   ├── config.py
│   ├── fansub.py
│   ├── utils.py
│   └── yyetsbot.py
└── yyetsweb/
    ├── README.md
    ├── YYeTs-grafana.json
    ├── commands/
    │   ├── common.py
    │   ├── douban_fix.py
    │   ├── grafana_test_data.py
    │   └── share_excel.py
    ├── common/
    │   ├── __init__.py
    │   ├── dump_db.py
    │   ├── sync.py
    │   └── utils.py
    ├── databases/
    │   ├── __init__.py
    │   ├── base.py
    │   ├── comment.py
    │   ├── douban.py
    │   ├── grafana.py
    │   ├── oauth.py
    │   ├── other.py
    │   ├── resources.py
    │   └── user.py
    ├── go.mod
    ├── go.sum
    ├── handlers/
    │   ├── __init__.py
    │   ├── base.py
    │   ├── comment.py
    │   ├── douban.py
    │   ├── grafana.py
    │   ├── oauth.py
    │   ├── other.py
    │   ├── resources.py
    │   └── user.py
    ├── server.go
    ├── server.py
    └── tests/
        └── router_test.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
mongo_data/*
certs/*
data/*
logs/*
YYeTsFE/node_modules/*
.github/*
assets/*
conf/*
tests/*
yyetsweb/yyets.sqlite
yyetsweb/subtitle_data

================================================
FILE: .gitattributes
================================================
yyets/worker/public/css/** linguist-vendored
yyets/worker/public/fonts/* linguist-vendored
yyets/worker/public/img/* linguist-vendored
yyets/worker/public/js/* linguist-vendored
yyets/worker/public/js/search.js -linguist-vendored
yyets/worker/public/404.html linguist-vendored
yyets/worker/public/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/templates/js/common.js -linguist-vendored

tests/data/* linguist-vendored


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: BennyThink
custom: https://buy.stripe.com/dR67vU4p13Ox73a6oq


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"
    groups:
      all-dependencies:
        patterns: ["*"]

================================================
FILE: .github/workflows/docker.yaml
================================================
name: Build and push docker image
on:
  push:
    branches:
      - 'master'
    paths-ignore:
      - '**.md'
      - 'LICENSE'

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          submodules: true

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Cache Docker layers
        uses: actions/cache@v3
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Lower case for Docker Hub
        id: dh_string
        uses: ASzc/change-string-case-action@v5
        with:
          string: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}

      - name: Lower case for ghcr
        id: ghcr_string
        uses: ASzc/change-string-case-action@v5
        with:
          string: ${{ github.event.repository.full_name }}

      - name: Build and push docker images
        uses: docker/build-push-action@v4
        with:
          context: .
          platforms: linux/amd64, linux/arm64
          push: true
          tags: |
            ${{ steps.dh_string.outputs.lowercase }}
            ghcr.io/${{ steps.ghcr_string.outputs.lowercase }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

      - name: Notification to Telegram
        env:
          TOKEN: ${{ secrets.BOT_TOKEN }}
        run: |
          curl "https://api.telegram.org/bot$TOKEN/sendMessage?chat_id=260260121&text=Normal%20Build%20complete!"
          echo "YYeTsBot Build complete!"


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
#*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot
*.dat
*.dir

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
/.idea/$CACHE_FILE$
/.idea/.gitignore
/.idea/misc.xml
/.idea/modules.xml
/.idea/vcs.xml
.idea/
/yyetsbot/data/cookies.dump
/.idea/inspectionProfiles/profiles_settings.xml
yyetsbot/data/
health_check/client.session

/tools/worker/.idea/
/tools/worker/workers-site/node_modules/*
/tools/worker/workers-site/worker/script.js
/health_check/client-hc.session
mongo_data/*
certs/*
data/*
logs/*
**/.DS_Store
/yyetsweb/yyets_sqlite.db
/yyetsweb/yyetsweb
/yyetsweb/assets.go
/yyetsweb/templates/sponsor/*
/yyetsweb/templates/svg/*
/yyetsweb/templates/index.css
/yyetsweb/templates/logo*
/yyetsweb/templates/*.json

/yyetsweb/templates/*
/yyetsweb/templates/data/*
/yyetsweb/builds/
/builds/checksum-sha256sum.txt
/yyetsweb/1.html
!/env/
!/mongo_data/
/env/yyets.env
!/docker-compose.override.yml
/docker-compose.override.yml
/yyetsweb/templates/dump/yyets_mongo.gz
/yyetsweb/templates/dump/yyets_mysql.zip
/yyetsweb/templates/dump/yyets_sqlite.zip
/yyetsweb/subtitle_data/attachment/201001/17/758231_1263706947i2nW.rar
/yyetsweb/subtitle_data/attachment/200912/4/212807_1259889699DJm8.rar


================================================
FILE: API.md
================================================
# 需求与待开发功能

## FE

- [x] group为admin特殊显示,评论接口已返回group信息
- [x] 评论楼中楼
- [x] 联合搜索,当本地数据库搜索不到数据时,会返回extra字段
- [x] 最新评论
- [x] 公告
- [x] 最新更新资源
- [x] API变更:登录时需要验证码
- [x] API变更:like API变更 PATCH `/api/user/` --> PATCH `/api/like/`
- [x] 删除评论(admin only)
- [x] ME:搜索API返回变更:目前包含了评论的结果,使用 `type`区分
- [ ] 搜索页面,通过评论ID,只显示该评论
- [ ] **独立公告和最近更新到新的页面**
- [ ] **对评论的反应**
- [ ] **分类**
- [ ] 添加下载地址到已有资源
- [ ] 新增资源
- [ ] 删除资源、删除已有资源的下载
- [ ] 更改用户信息(添加邮箱)
- [ ] 评论通知(浏览器通知,暂时隐藏了)

# BE

- [x] 联合搜索:字幕侠、new字幕组、追新番
- [x] grafana面板
- [x] 豆瓣接口
- [x] 评论通知:站内通知
- [x] 添加邮箱
- [x] 邮件通知
- [x] 添加下载地址到已有资源
- [x] 删除资源
- [x] 新建资源
- [ ] 找回密码

# 资源

## 获取指定id的资源

* GET `/api/resource?id=10004`
  数据结构参考 [sample.json](yyetsweb/templates/js/sample.json)

**对于非官方、由用户提交的下载,与 `files` `dateline` 同级会有一个 `creator` 用于标明是谁创建的**
**对于非官方、由用户提交的资源,与 `cnname` `enname` 同级会有一个 `creator` 用于标明是谁创建的**

如果没有,那么就是官方资源

## 搜索

同时搜索资源和评论,会以type字段区分,评论会额外返回 `comment_id`

* GET `/api/resource?keyword=权力的游戏`

```json
{
  "data": [
    {
      "id": 10733,
      "cnname": "权力的游戏",
      "enname": "Game of Thrones",
      "aliasname": "冰与火之歌 / 权力的游戏下载 / 权利的游戏 / 冰火",
      "channel": "tv",
      "channel_cn": "美剧",
      "area": "美国",
      "show_type": "",
      "expire": "1610372082",
      "views": 8691,
      "year": [
        2011
      ],
      "origin": "yyets"
    }
  ],
  "extra": [],
  "comment": [
    {
      "username": "Liyixin",
      "date": "2021-10-30 10:06:44",
      "comment": "我用阿里云盘分享了「权力的游戏.1-8季无删减版(1).1080P」,你可以不限速下载🚀\n复制这段内容打开「阿里云盘」App 即可获取\n链接:https://www.aliyundrive.com/s/hcUfFYnkhN5",
      "commentID": "617ca8b470a52cc3ef2b0dff",
      "resourceID": 10733,
      "resourceName": "权力的游戏",
      "origin": "comment"
    }
  ]
}
```

当数据库搜索不到资源时,会尝试从字幕侠、new字幕组和追新番搜索,返回如下

```json
{
  "data": [],
  "extra": [
    {
      "url": "https://www.zimuxia.cn/portfolio/%e4%b8%9c%e5%9f%8e%e6%a2%a6%e9%ad%87",
      "name": "东城梦魇",
      "class": "ZimuxiaOnline"
    },
    {
      "url": "https://www.zimuxia.cn/portfolio/%e9%bb%91%e8%89%b2%e6%ad%a2%e8%a1%80%e9%92%b3",
      "name": "黑色止血钳",
      "class": "ZimuxiaOnline"
    }
  ]
}
```

## 添加下载地址到已有资源

比如更新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条访问量最高的数据

* GET `/api/top`

```json
{
  "ALL": [
    {
      "data": {
        "info": {
          "id": 39894,
          "cnname": "速度与激情9",
          "enname": "F9: The Fast Saga",
          "aliasname": "F9狂野时速(港)/玩命关头9(台)/狂野时速9/速激9/FF9/Fast & Furious 9",
          "channel": "movie",
          "channel_cn": "电影",
          "area": "美国",
          "show_type": "",
          "expire": "1610401946",
          "views": 47466,
          "year": [
            2021
          ]
        }
      }
    },
    {
      "data": {
        "info": {
          "id": 38413,
          "cnname": "致命女人",
          "enname": "Why Women Kill",
          "aliasname": "女人为什么杀人/女人为何杀戮",
          "channel": "tv",
          "channel_cn": "美剧",
          "area": "美国",
          "show_type": "",
          "expire": "1610401185",
          "views": 39040,
          "year": [
            2019
          ]
        }
      }
    }
  ],
  "US": [],
  "JP": [],
  "KR": [],
  "UK": [],
  "class": {
    "ALL": "全部",
    "US": "美国",
    "JP": "日本",
    "KR": "韩国",
    "UK": "英国"
  }
}
```

# 个人中心

获取个人收藏

* GET `/api/like`

```json
{
  "LIKE": [
    {
      "data": {
        "info": {
          "id": 39523,
          "cnname": "禁忌女孩",
          "enname": "เด็กใหม่",
          "aliasname": "来路不明的转校生/Girl from Nowhere",
          "channel": "tv",
          "channel_cn": "泰剧",
          "area": "泰国",
          "show_type": "",
          "expire": "1610401752",
          "views": 979,
          "year": [
            2020
          ]
        }
      }
    }
  ]
}
```

# 用户

## 登录或注册

* POST `/api/user`,提交json,字段 `username`, `password`,`captcha_id` 和 `captcha`

当验证码失效时,会返回303 See Other

返回json

## 获取当前登录用户信息

登录用户可用,未登录会返回401

* GET `/api/user`

```json
{
  "username": "Benny",
  "date": "2021-06-12 13:55:50",
  "ip": "172.70.122.84",
  "browser": "Mozilla/5.0 (X11; Gentoo; rv:84.0) Gecko/20100101 Firefox/84.0",
  "like": [
    31346,
    39894,
    41382
  ],
  "group": [
    "admin"
  ],
  "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"
}
```

# 获取全部剧集名称

* GET `/api/name`
* GET `/api/name?human=1` 人类可读

# 添加或删除收藏

* PATCH `/api/like`,提交json,字段 `resource_id`

# 评论

评论的基本数据格式: `children` 字段为 array/list,可套娃另外一条评论,目前暂时只支持两层(也不打算支持更多的啦)。

评论的 `resource_id` 必须相同

## 普通评论

```json
{
  "username": "Benny",
  "date": "2021-06-17 10:54:19",
  "browser": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.13; rv:85.1) Gecko/20100101 Firefox/85.1",
  "content": "test",
  "resource_id": 233,
  "id": "60cab95baa7f515ea291392b",
  "children": [
  ],
  "childrenCount": 0
}

```

## 嵌套评论

```json
{
  "username": "Benny",
  "date": "2021-06-17 10:54:19",
  "browser": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.13; rv:85.1) Gecko/20100101 Firefox/85.1",
  "content": "test",
  "resource_id": 233,
  "id": "60cab95baa7f515ea291392b",
  "children": [
    {
      "username": "Alex",
      "date": "2021-05-31 16:58:21",
      "browser": "PostmanRuntime/7.28.0",
      "content": "评论17",
      "id": "60c838a12a5620b7e4ba5dfc",
      "resource_id": 233
    },
    {
      "username": "Paul",
      "date": "2021-05-22 16:58:21",
      "browser": "PostmanRuntime/7.28.0",
      "content": "评论14",
      "id": "60c838a12a5620b7e4ba1111",
      "resource_id": 233
    }
  ],
  "childrenCount": 2
}

```

## 获取评论

* GET `/api/comment`

分页,支持URL参数:

* resource_id: 资源id,id为233是留言板,id为-1会返回最新评论
* comment_id: 指定该参数,会只返回这条评论。如果是子评论,还会连带父评论一起返回
* size: 每页评论数量,默认5
* page: 当前页,默认1
* inner_size: 内嵌评论数量,默认5
* inner_page: 内嵌评论当前页,默认1
  **注意:如上两个inner参数是对整个页面生效的,如要进行某个父评论的子评论分页,请参考下面的子评论分页接口 返回 楼中楼评论,group表示用户所属组,admin是管理员,user是普通用户

```json
{
  "data": [
    {
      "username": "Benny",
      "date": "2021-06-22 18:26:42",
      "browser": "PostmanRuntime/7.28.0",
      "content": "父评论benny",
      "resource_id": 233,
      "type": "parent",
      "id": "60d1bae2d87ce6e9a2934a0f",
      "group": [
        "admin"
      ]
    },
    {
      "username": "Benny",
      "date": "2021-06-22 18:24:44",
      "browser": "PostmanRuntime/7.28.0",
      "content": "父评论benny",
      "resource_id": 233,
      "type": "parent",
      "group": [
        "admin"
      ],
      "childrenCount": 22,
      "children": [
        {
          "username": "test",
          "date": "2021-06-22 18:25:12",
          "browser": "PostmanRuntime/7.28.0",
          "content": "admin子评2论2",
          "resource_id": 233,
          "type": "child",
          "id": "60d1ba88d87ce6e9a2934a0c",
          "group": [
            "user"
          ]
        },
        {
          "username": "admin",
          "date": "2021-06-22 18:25:08",
          "browser": "PostmanRuntime/7.28.0",
          "content": "admin子评论2",
          "resource_id": 233,
          "type": "child",
          "id": "60d1ba84d87ce6e9a2934a0a",
          "group": [
            "user"
          ]
        }
      ],
      "id": "60d1ba6cd87ce6e9a2934a08"
    }
  ],
  "count": 2,
  "resource_id": 233
}
```

## 搜索评论

* GET `/api/comment/search`

分页,支持URL参数:

* size: 每页评论数量,默认5
* page: 当前页,默认1
* keyword: 关键字

返回值与获取评论相同

## 子评论分页

* GET `/api/comment/child`
  URL参数:
* parent_id:父评论id
* size: 每页评论数量,默认5
* page: 当前页,默认1

`/api/comment/child?parent_id=60dfc932802d2c69cf8774ce&size=2&page=2`

返回子评论

```json
{
  "data": [
    {
      "username": "Benny",
      "date": "2021-07-03 10:22:13",
      "browser": "PostmanRuntime/7.28.1",
      "content": "子15",
      "resource_id": 233,
      "type": "child",
      "id": "60dfc9d5802d2c69cf877514",
      "childrenCount": 17,
      "group": [
        "admin"
      ]
    },
    {
      "username": "Benny",
      "date": "2021-07-03 10:22:11",
      "browser": "PostmanRuntime/7.28.1",
      "content": "子14",
      "resource_id": 233,
      "type": "child",
      "id": "60dfc9d3802d2c69cf877512",
      "group": [
        "admin"
      ]
    }
  ],
  "count": 17
}
```

## 获取验证码

* GET `/api/captcha?id=1234abc`,id是随机生成的字符串 API 返回字符串,形如 `data:image/png;base64,iVBORw0KGgoAAA....`

## 提交评论

* POST `/api/comment`
  只有登录用户才可以发表评论,检查cookie `username` 是否为空来判断是否为登录用户;未登录用户提示“请登录后发表评论”

`resource_id` 从URL中获取,id是上一步验证码的那个随机字符串id, `captcha` 是用户输入的验证码

### 提交新评论

只需要提供如下四项信息即可

```json
{
  "resource_id": 39301,
  "content": "评论内容",
  "id": "1234abc",
  "captcha": "38op"
}
```

返回 HTTP 201添加评论成功,403/401遵循HTTP语义

```json
{
  "message": "评论成功/评论失败/etc"
}
```

### 提交楼中楼评论

还需要额外提供一个 `comment_id`,也就是 UUID,如 `60c838a12a5620b7e4ba5dfc`

```json
{
  "resource_id": 39301,
  "content": "评论内容",
  "id": "1234abc",
  "captcha": "38op",
  "comment_id": "60c838a12a5620b7e4ba5dfc"
}
```

## 删除评论,软删除

* DELETE `/api/comment`,提交json数据

```json
{
  "comment_id": "60cab935e9f929e09c91392a"
}
```  

不用关心comment_id是子评论还是父评论,会自动删除

返回被删除的数量,HTTP 200表示删除成功,404表示未找到这条留言

```json
{
  "status_code": 404,
  "message": "",
  "count": 0
}
```

## 最新评论

* GET `api/comment/newest`
  page size参数同上

```json
{
  "data": [
    {
      "username": "111",
      "date": "2021-07-11 10:22:59",
      "browser": "Mozi0.31.0",
      "content": "1111?",
      "resource_id": 233,
      "type": "parent",
      "id": "60ea53113178773",
      "group": [
        "user"
      ],
      "cnname": "留言板"
    },
    {
      "username": "11111222",
      "date": "2021-07-10 23:54:43",
      "browser": "Mozi3322.64",
      "content": "<reply value=\"60e939be4ad7f20773865d7a\">@abcd</reply>怎么下载啊\n",
      "resource_id": 37552,
      "type": "child",
      "id": "60e9c2c222111397e",
      "group": [
        "user"
      ],
      "cnname": "黑寡妇"
    },
    {
      "username": "1111",
      "date": "2021-07-10 23:41:06",
      "browser": "Moz) Chrom.864.67",
      "content": "我是1精彩",
      "resource_id": 41382,
      "type": "parent",
      "id": "60e9bf924ad7f2077381111",
      "group": [
        "user"
      ],
      "cnname": "洛基"
    }
  ],
  "count": 294
}
```

## 对评论的反应

仅对登录用户可用

* POST `/api/comment/reaction` 添加反应 201
* DELETE `/api/comment/reaction` 删除反应 202

verb 为任意字符串,包括emoji

```json
{
  "comment_id": "60c46d6a6d7c5dd22d69fd3b,父评论子评论均可",
  "verb": "😍👍"
}

```

返回:

* 201 成功添加反应
* 202 成功删除反应
* 404 评论没找到

用户添加的反应,在返回评论时可以看到,会额外多一个字段 `reactions`,子评论同理

```json
{
  "reactions": [
    {
      "verb": "😍",
      "users": [
        "user2"
      ]
    },
    {
      "verb": "🤔",
      "users": [
        "user3",
        "da"
      ]
    }
  ]
}
```

# metrics

## 添加metrics

* POST `/api/metrics`, json,字段 `type`

## 获取metrics

* GET `/api/metrics`,默认返回最近7天数据,可选URL参数 `from`, `to`,如 `from=2021-03-12&to=2021-03=18

# Grafana

* GET `/api/grafana/`
* GET `/api/grafana/search`
* GET `/api/grafana/query`

# 黑名单

* GET `/api/blacklist`

# 获取备份数据库信息

* GET `/api/db_dump`

```json
{
  "yyets_mongo.gz": {
    "checksum": "b32e9d8e24c607a9f29889a926c15179d9179791",
    "date": "2021-06-14 12:59:51",
    "size": "6.0B"
  },
  "yyets_mysql.zip": {
    "checksum": "6b24ae7cb7cef42951f7e2df183f0825512029e0",
    "date": "2021-06-14 12:59:51",
    "size": "11.0B"
  },
  "yyets_sqlite.zip": {
    "checksum": "7e1659ab5cbc98b21155c3debce3015c39f1ec05",
    "date": "2021-06-14 12:59:51",
    "size": "15.0B"
  }
}
```

# 公告

## 添加公告

* POST `/api/announcement`, json 字段 content

## 获取公告

* GET `/api/announcement`,接受URL参数 size、page

```json
{
  "data": [
    {
      "username": "Benny",
      "date": "2021-06-15 16:28:16",
      "browser": "PostmanRuntime/7.28.0",
      "content": "hello"
    }
  ],
  "count": 1
}
```

# 豆瓣

## 获取简介等信息

* GET `/api/douban?resource_id=34812`
  第一次请求会比较慢

```json
{
  "name": "逃避可耻却有用",
  "doubanId": 26816519,
  "doubanLink": "https://movie.douban.com/subject/26816519/",
  "posterLink": "https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2400201631.jpg",
  "resourceId": 34812,
  "rating": "8.4",
  "actors": [
    "新垣结衣",
    "星野源",
    "大谷亮平",
    "藤井隆",
    "真野惠里菜",
    "成田凌",
    "山贺琴子",
    "宇梶刚士",
    "富田靖子",
    "古田新太",
    "石田百合子",
    "细田善彦",
    "古馆宽治",
    "叶山奖之"
  ],
  "directors": [
    "金子文纪",
    "土井裕泰",
    "石井康晴"
  ],
  "genre": [
    "喜剧"
  ],
  "releaseDate": "2016-10-11(日本)",
  "episodeCount": " 11",
  "episodeDuration": " 45分钟",
  "writers": [
    "野木亚纪子",
    "海野纲弥"
  ],
  "year": "2016",
  "introduction": "森山实栗(新垣结衣饰)自研究生毕业之后就一直仕途不顺,最近更是惨遭解雇,处于“无业游民”的状态之下,日子过得十分凄惨。经由父亲的介绍,无处可去的实栗来到了名为津崎平匡(星野源饰)的单身男子家中,为其料理家事,就这样,二十五岁的实栗成为了一名家政妇。实栗心地善良手脚勤快,在她的安排和劳作下,平匡家中的一切被打理的井井有条,实栗因此获得了平匡的信赖,亦找到了生活的重心,重新振作了起来。然而好景不长,实栗的父母决定搬离此地,这也就意味着实栗必须“离职”。实在无法接受此事的实栗决定和平匡“契约结婚”,在外装做夫妻,在内依旧是雇主和职员。就这样,这对“孤男寡女”开始了他们的同居生活。"
}
```

## 获取海报

* GET `api/douban?resource_id=34812&type=image`
  会返回相应格式(jpeg、webp、png等)的图片,与上次数据中 `posterLink`所看到的内容相同

# 验证码

## 获取验证码

* GET `/api/captcha?id=1234abc`,id是随机生成的字符串 API 返回字符串,形如 `data:image/png;base64,iVBORw0KGgoAAA....`

## 校验验证码

除去评论中的校验验证码,如有额外需求,也可以使用 POST 方法校验

* POST `/api/captcha`

```json
{
  "id": "1234abc",
  "captcha": "38op"
}
```

# 豆瓣报错

## 提交

* POST `/api/douban/report`

```json
{
  "captcha_id": "用户输入的验证码",
  "id": "验证码id",
  "content": "内容难过-咔咔",
  "resource_id": 23133312
}
```

## 查询

* GET `/api/douban/report`

```json
{
  "data": [
    {
      "resource_id": 2333,
      "content": [
        "dddd",
        "1款大家咔咔",
        "1款大家dadadada-咔咔"
      ]
    },
    {
      "resource_id": 23133,
      "content": [
        "1款大家dadadada-咔咔"
      ]
    },
    {
      "resource_id": 23133312,
      "content": [
        "1款大家dadadada-咔咔"
      ]
    }
  ]
}
```

# 通知

只有登录用户可以获取,只有楼主能够获取到通知,其他楼层的人获取不到。

## 获取通知

* 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": "<reply value=\"6117b2a59195e6b1ab86eb30\">@user2</reply>u3 to u2",
      "resource_id": 233,
      "type": "child",
      "id": "6117b5a6598f80ca3ebb13ed",
      "reply_to_content": "<reply value=\"610135c9d1f873388feb5c78\">@user1</reply>ajnkwa"
    },
    {
      "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": "<reply value=\"610135c9d1f873388feb5c78\">@user1</reply>u3",
      "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": "<reply value=\"610135c9d1f873388feb5c78\">@user1</reply>ajnkwa",
      "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"
    }
  ]
}
```


================================================
FILE: DEVELOPMENT.md
================================================
# 项目手册

# 网站部署方式

## 一键脚本

**支持amd64/arm64,请先安装 docker、docker-compose和curl**

**为了安全考虑,安装完成后程序将监听在 127.0.0.1 。如有需要请自行修改 `docker-compose.yml`的127.0.0.1为0.0.0.0**

### Linux/macOS:

```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/tgbot-collection/YYeTsBot/master/scripts/install.sh)"
````

### Windows

请再安装一个 [git for windows](https://gitforwindows.org/),然后桌面空白处右键,选择 `git bash here`
再然后

```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/tgbot-collection/YYeTsBot/master/scripts/install.sh)"
````

## docker-compose

参考 `yyetsweb`目录下的 `README`

# bot

可以选择docker,也可以直接运行在机器上。

## docker-compose

* 参见 [这里](https://github.com/tgbot-collection/BotsRunner)
* 本目录下的 `docker-compose.yml` 也可以作为参考
* nginx reverse proxy可以[参考这里](https://github.com/BennyThink/WebsiteRunner)
* [参考这里获取数据库](yyetsweb/README.md)

```shell
# 启动数据库
docker-compose up -d mongo
# 导入数据库
docker yyets_mongo.gz 1234da:/tmp
# 进入容器
docker-compose exec mongo bash
mongorestore --gzip --archive=yyets_mongo.gz --nsFrom "share.*" --nsTo "zimuzu.*"
exit
# 开启服务
docker-compose up -d
```

## replica set 配置方式

```shell
ln -s docker-compose-replica.yml docker-compose.override.yml
docker-compose up -d mongo
# 进入shell
rs.initiate({
    _id: "rs0",
    members: [{
        _id: 0,
        host: "localhost:27017"
    },
    {
        _id: 1,
        host: "mongo2:27017"
    }]
})

# 调整优先级
cfg = rs.conf()
cfg.members[0].priority = 0.5
cfg.members[1].priority = 0.5
cfg.members[2].priority = 1 # 最高
rs.reconfig(cfg)

```

## 常规方式

### 1. 环境

推荐使用Python 3.6+,环境要求

* redis
* 可选MongoDB

```bash
pip install -r requirements.txt
```

### 2. 配置TOKEN

修改`config.py`,根据需求修改如下配置项

* TOKEN:bot token
* USERNAME:人人影视的有效的用户名
* PASSWORD :人人影视的有效的密码
* MAINTAINER:维护者的Telegram UserID
* REDIS:redis的地址,一般为localhost
* MONGODB: mongodb的地址

### 3. 导入数据(可选)

如果使用yyets,那么需要导入数据到MongoDB。可以在将数据导入到MySQL之后使用如下脚本导入数据到MongoDB

```shell
python3 web/prepare/convert_db.py
```

### 4. 运行

```bash
python /path/to/YYeTsBot/yyetsbot/bot.py
```

### 5. systemd 单元文件

参考 `yyets.service`

# 添加新的资源网站

欢迎各位开发提交新的资源网站!方法非常简单,重写 `BaseFansub`,实现`search_preview`和`search_result`,按照约定的格式返回数据。

然后把类名字添加到 `FANSUB_ORDER` 就可以了!是不是很简单!

# 防爬

## 1. referer

网站使用referer验证请求

## 2. rate limit

404的访问会被计数,超过10次会被拉入黑名单,持续3600秒,再次访问会持续叠加。

# 持续部署

使用[Docker Hub Webhook](https://docs.docker.com/docker-hub/webhooks/)
(顺便吐槽一句,这是个什么垃圾文档……自己实现validation吧)

参考listener [Webhook listener](https://github.com/tgbot-collection/Webhook)

# 归档资源下载

## Telegram 频道分享

*

包含了2021年1月11日为止的人人影视最新资源,MySQL为主。有兴趣的盆友可以用这个数据进行二次开发[戳我查看详情](https://t.me/mikuri520/668)

* 字幕侠离线数据库 [从这里下载](https://t.me/mikuri520/715),这个数据比较粗糙,并且字幕侠网站还在,因此不建议使用这个

## 本地下载

如果无法访问Telegram,可以使用如下网址下载数据

* [MongoDB](https://yyets.click/data/yyets_mongo.gz)
* [MySQL](https://yyets.click/data/yyets_mysql.zip)
* [SQLite](https://yyets.click/data/yyets_sqlite.zip)

# API 文档

参考 [API.md](API.md)


================================================
FILE: Dockerfile
================================================
FROM python:3.12-alpine AS pybuilder
RUN apk update && apk add  --no-cache tzdata ca-certificates alpine-sdk libressl-dev libffi-dev cargo && \
    apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \
    libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev libxcb-dev libpng-dev

COPY requirements.txt /requirements.txt
RUN pip3 install  --user -r /requirements.txt && rm /requirements.txt


FROM python:3.12-alpine AS runner
RUN apk update && apk add --no-cache libressl jpeg-dev openjpeg-dev libimagequant-dev tiff-dev freetype-dev libxcb-dev


FROM alpine AS nodebuilder
RUN apk add curl jq
RUN wget $(curl -s https://api.github.com/repos/tgbot-collection/YYeTsFE/releases/tags/ads-2025-03-27 | jq -r '.assets[] | select(.name == "build.zip") | .browser_download_url')
RUN unzip build.zip && rm build.zip

FROM runner
RUN apk add mongodb-tools mysql-client
COPY . /YYeTsBot

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
COPY --from=nodebuilder /build /YYeTsBot/yyetsweb/templates/

ENV TZ=Asia/Shanghai
WORKDIR /YYeTsBot/yyetsbot
CMD ["python", "yyetsbot.py"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Benny

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
OS = darwin linux windows
ARCH = amd64 arm64
WEB := $(shell cd yyetsweb;pwd)
DATE:=$(shell date +"%Y-%m-%d %H:%M:%S")
update:
	git pull
	git submodule update --remote

docker-dev:
	make update
	docker build --build-arg env=dev -t bennythink/yyetsbot .
	docker-compose up -d

docker:
	# production configuration
	cp .env YYeTsFE/.env
	# docker buildx create --use --name mybuilder
	docker buildx build --platform=linux/amd64,linux/arm64 -t bennythink/yyetsbot  . --push


clean:
	docker rmi bennythink/yyetsbot:latest || true
	rm -rf YYeTsFE/build
	rm -rf YYeTsFE/dist

	@rm -rf yyetsweb/builds
	@rm -f yyetsweb/assets.go

current:
	echo "Installing dependencies..."
	cd $(WEB);go get -u github.com/go-bindata/go-bindata/...
	echo "Build static files..."
	make asset
	echo "Build current platform executable..."
	cd $(WEB); go build .;

asset:
	cd $(WEB);go get -u github.com/go-bindata/go-bindata/... ;go install github.com/go-bindata/go-bindata/...
	cd $(WEB)/templates;~/go/bin/go-bindata -o assets.go ./...
	mv yyetsweb/templates/assets.go yyetsweb/assets.go

frontend:
	cd YYeTsFE; yarn && yarn run release
	cp -R YYeTsFE/build/* yyetsweb/templates/

all:
	make clean
	make frontend
	make asset
	@echo "Build all platform executables..."
	@for o in $(OS) ; do            \
        		for a in $(ARCH) ; do     \
        		  	echo "Building $$o-$$a..."; \
        		  	if [ "$$o" = "windows" ]; then \
                    	cd $(WEB);CGO_ENABLED=0 GOOS=$$o GOARCH=$$a go build -ldflags="-s -w -X 'main.buildTime=$(DATE)'" -o builds/yyetsweb-$$o-$$a.exe .;    \
                    else \
        				cd $(WEB);CGO_ENABLED=0 GOOS=$$o GOARCH=$$a go build -ldflags="-s -w -X 'main.buildTime=$(DATE)'" -o builds/yyetsweb-$$o-$$a .;    \
        			fi; \
        		done   \
        	done

	@make universal
	@make checksum


checksum: yyetsweb/builds/*
	@echo "Generating checksums..."
	if [ "$(shell uname)" = "Darwin" ]; then \
		shasum -a 256 $^ >>  $(WEB)/builds/checksum-sha256sum.txt ;\
	else \
		sha256sum  $^ >> $(WEB)/builds/checksum-sha256sum.txt; \
	fi


universal:
	@echo "Building macOS universal binary..."
	docker run --rm -v $(WEB)/builds:/app/ bennythink/lipo-linux -create -output \
		yyetsweb-darwin-universal \
		yyetsweb-darwin-amd64    yyetsweb-darwin-arm64

	file $(WEB)/builds/yyetsweb-darwin-universal

release:
	git tag $(shell git rev-parse --short HEAD)
	git push --tags


ci-test:
	docker run --rm bennythink/yyetsbot /bin/sh -c "cd /YYeTsBot/yyetsweb/tests;python -m unittest discover -p '*_test.py'"

test:
	cd $(WEB)/tests;python -m unittest discover -p '*_test.py'


================================================
FILE: README.md
================================================
# YYeTsBot

[![build docker image](https://github.com/tgbot-collection/YYeTsBot/actions/workflows/docker.yaml/badge.svg)](https://github.com/tgbot-collection/YYeTsBot/actions/workflows/docker.yaml)
[![Docker Pulls](https://img.shields.io/docker/pulls/bennythink/yyetsbot)](https://hub.docker.com/r/bennythink/yyetsbot)

![](assets/index.png)

👉 前端[在这里](https://github.com/tgbot-collection/YYeTsFE) 👈

# 使用说明

直接发送想要看的剧集名称就可以了,可选分享网页或者链接(ed2k和磁力链接)。


搜索资源时,会按照我预定的优先级(人人影视离线、字幕侠)进行搜索,当然也可以使用命令强制某个字幕组,如 `/yyets_offline 逃避可耻`

## 命令

```
start - 开始使用
help - 帮助
credits - 致谢
ping - 运行状态
settings - 获取公告
zimuxia_online - 字幕侠在线数据  
newzmz_online - new字幕组在线数据 
yyets_offline - 人人影视离线数据
```

# 截图

## 常规搜索

![](assets/1.png)

## 资源分享站截图

本网站永久免费,并且没有任何限制。
![](assets/new_resource.png)

![](assets/2.png)

支持收藏功能,会跨设备同步
![](assets/like.png)

## 指定字幕组搜索

目前只支持YYeTsOffline、ZimuxiaOnline和NewzmzOnline

![](assets/3.png)

# 如何下载磁力和电驴资源?迅雷提示资源敏感

## 电驴资源

请下载使用 [eMule](https://www.emule-project.net/home/perl/general.cgi?l=42) ,然后添加如下两个server list

* [server.met](http://www.server-met.de/)
* [server list for emule](https://www.emule-security.org/serverlist/)

![](assets/emule.jpeg)
速度还可以哦

## 磁力

使用百度网盘、115等离线,或使用utorrent等工具,记得更新下 [tracker list](https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt)
哦

# 小白使用

想要自己留一份资源,但是又不懂编程? 没关系!目前提供两种方式,请根据自己情况选择
“离线使用” 意味着可以断网使用,但是不会自动更新资源,需要手动更新数据库;“在线应用” 意味着需要有互联网才可以使用。

## 离线  完整运行包

这个版本是新的UI,拥有全部的最新功能。运行在你本地的电脑上,不依赖外界环境。
[参考文档](https://github.com/tgbot-collection/YYeTsBot/blob/master/DEVELOPMENT.md#%E4%B8%80%E9%94%AE%E8%84%9A%E6%9C%AC)

## 离线  一键运行包

一键运行包。拥有比较新的UI,只不过只有最基础的搜索、查看资源的功能。使用方法步骤如下

1. 请到 [GitHub Release](https://github.com/tgbot-collection/YYeTsBot/releases) ,找最新的 `YYeTsBot 离线一键运行包`
2. windows:双击第一步下载的exe文件; macos/Linux,cd到你的目录, `chmod +x yyetsweb ; ./yyetsweb`
3. 程序会自动下载数据库并启动。等到出现启动提示时, 打开浏览器 http://127.0.0.1:8888 就可以看到熟悉的搜索界面啦!

## 在线 原生应用程序

使用tauri封装的网页。使用方法如下

1. 请到 [GitHub Release](https://github.com/tgbot-collection/YYeTsBot/releases) ,找最新的 `YYeTsBot App`
2. windows下载msi,macos下载dmg或tar.gz,Linux下载AppImage或deb(Debian based)
3. 安装后,打开App,就可以看到熟悉的搜索界面啦!

# 开发

## 网站开发

如何部署、参与开发、具体API接口,可以 [参考这个文档](DEVELOPMENT.md)

## Python Library

也可以作为Python Library去调用

`pip3 install yyets`

```
>>> from yyets import YYeTs
>>> yy=YYeTs("逃避")
[2021-09-21 19:22:32 __init__.py:54 I] Fetching 逃避可耻却有用...https://yyets.click/api/resource?id=34812
[2021-09-21 19:22:33 __init__.py:54 I] Fetching 无法逃避...https://yyets.click/api/resource?id=29540
[2021-09-21 19:22:35 __init__.py:54 I] Fetching 逃避者...https://yyets.click/api/resource?id=37089

>>> yy.result
[<yyets.Resource object at 0x10cc7b130>, <yyets.Resource object at 0x10ca0e880>, <yyets.Resource object at 0x10cc7b040>]

>>> for y in yy.result:
        print(y)
    
逃避可耻却有用 - NIGERUHA HAJIDAGA YAKUNITATSU
无法逃避 - Inescapable
逃避者 - Shirkers

>>> yy.result[0].cnname
'逃避可耻却有用'

>>> yy.result[0].list
[{'season_num': '101', 'season_cn': '单剧', 'items': {'APP': [{'ite
```

# Credits

* [人人影视](http://www.zmz2019.com/)
* [追新番](http://www.fanxinzhui.com/)
* [FIX字幕侠](https://www.zimuxia.cn/)
* [new字幕组](https://newzmz.com/)

# 支持我

觉得本项目对你有帮助?你可以通过以下方式表达你的感受:

* 感谢字幕组
* 点一个star🌟和fork🍴
* 宣传,使用,提交问题报告
* 收藏[我的博客](https://dmesg.app/)
* [Telegram Channel](https://t.me/mikuri520)

## 捐助

* [给我买杯咖啡?](https://www.buymeacoffee.com/bennythink)
* [爱发电?](https://afdian.net/@BennyThink)
* [GitHub Sponsor](https://github.com/sponsors/BennyThink)
* [Stripe](https://buy.stripe.com/dR67vU4p13Ox73a6oq)

 <img src="./assets/CNY.png" width = 30%  alt="stripe"  />


# License

[MIT](LICENSE)


================================================
FILE: conf/yyets.dmesg.app.conf
================================================
server {
    listen 80;
    listen [::]:80;
    server_name yyetsdev.dmesg.app;
    index index.html index.htm index.php default.html default.htm default.php;
    

    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://yyets-web:8888;
    }

    access_log /var/log/nginx/yyetsdev.dmesg.app.log;
}

server {
    listen 443 ssl http2 ;
    listen [::]:443 ssl http2;
    server_name yyetsdev.dmesg.app;
    index index.html index.htm index.php default.html default.htm default.php;

    ssl_certificate /etc/nginx/certs/dmesg_cf_cert.pem;
    ssl_certificate_key /etc/nginx/certs/dmesg_cf_key.pem;

    ssl_session_timeout 20m;
    ssl_session_cache builtin:1000 shared:SSL:10m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    ssl_dhparam /etc/nginx/certs/dhparam.pem;
    ssl_ciphers 'TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_stapling on;
    ssl_stapling_verify on;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Xss-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;

    add_header Content-Security-Policy "default-src https: 'unsafe-inline' 'unsafe-eval' data:;";
    add_header Referrer-Policy "no-referrer-when-downgrade";



    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://yyets-web:8888;
    }



    access_log /var/log/nginx/yyetsdev.dmesg.app.log;
}


================================================
FILE: conf/yyets.env
================================================
mongo=mongo
redis=redis
email_user=username
email_password=passord
email_host=mailhog
email_port=1025


================================================
FILE: docker-compose.yml
================================================
version: '3.1'

services:
  redis:
    image: redis:7-alpine
    restart: always
    logging:
      driver: none

  mongo:
    image: mongo:6
    restart: always
    volumes:
      - ./mongo_data/mongodb:/data/db
    command: --quiet
    logging:
      driver: none
    ports:
      - "127.0.0.1:27017:27017"

  meili:
    image: getmeili/meilisearch:v1.0.2
    restart: always
    environment:
      - MEILI_HTTP_PAYLOAD_SIZE_LIMIT=1073741824 #1GiB
    volumes:
      - meilisearch_data:/meili_data

  mysql:
    image: ubuntu/mysql:8.0-22.04_beta
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 'root'
    logging:
      driver: none
    command: "--skip-log-bin --default-authentication-plugin=mysql_native_password"

  bot:
    image: bennythink/yyetsbot
    depends_on:
      - redis
      - mongo
    restart: always
    env_file:
      - env/yyets.env

  web:
    image: bennythink/yyetsbot
    restart: always
    env_file:
      - env/yyets.env
    depends_on:
      - mongo
      - redis
      - mysql
    working_dir: /YYeTsBot/yyetsweb/
    volumes:
      - ./subtitle_data:/YYeTsBot/yyetsweb/subtitle_data
    command: [ "python3","server.py","-h=0.0.0.0" ]
    ports:
      - "127.0.0.1:8888:8888"
      - "172.17.0.1:8888:8888"


volumes:
  meilisearch_data:


================================================
FILE: requirements.txt
================================================
requests==2.32.5
pytelegrambotapi==4.29.1
beautifulsoup4==4.14.0
tgbot-ping==1.0.7
redis==6.4.0
apscheduler==3.11.0
pymongo==4.15.1
tornado==6.5.2
captcha==0.7.1
passlib==1.7.4
fakeredis==2.31.3
filetype==1.2.0
requests[socks]
tqdm==4.67.1
retry==0.9.2
pymysql==1.1.2
git+https://github.com/tgbot-collection/python-akismet
openpyxl==3.1.5
zhconv==1.4.3
jinja2==3.1.6
coloredlogs==15.0.1
meilisearch==0.33.0
pillow==12.1.1
pytz==2025.2

================================================
FILE: scripts/install.sh
================================================
#!/bin/bash

function splash() {
  echo "本脚本会在 ${HOME}/YYeTs 部署人人影视web"
  echo "你确定要继续吗?输入YES确认"
  read -r confirm

  if [ "$confirm" = "YES" ]; then
    echo "继续安装"
  else
    echo "取消安装"
    exit 1
  fi

}

function prepare() {
  echo "[1/5] 准备中……"
  mkdir -p "${HOME}"/YYeTs
  cd "${HOME}"/YYeTs || exit
}

function prepare_compose() {
  echo "[2/5] 下载docker-compose.yml"
  curl -o docker-compose.yml https://raw.githubusercontent.com/tgbot-collection/YYeTsBot/master/docker-compose.yml
  sed -ie '58,59d' docker-compose.yml
}

function import_db() {
  echo "[3/5] 正在准备MongoDB"
  docker-compose up -d mongo

  echo "[4/5] 正在下载并导入数据库"
  curl -o /tmp/yyets_mongo.gz https://yyets.click/dump/yyets_mongo.gz
  file /tmp/yyets_mongo.gz
  docker cp /tmp/yyets_mongo.gz yyets_mongo_1:/tmp
  # special for windows
  result=$(uname -a | grep "Msys")
  if [[ "$result" != "" ]]; then
    echo "docker exec yyets_mongo_1 mongorestore --gzip --archive=/tmp/yyets_mongo.gz --nsFrom "share.*" --nsTo "zimuzu.*"" >windows.bat
    echo "docker exec yyets_mongo_1 rm /tmp/yyets_mongo.gz" >>windows.bat
    cmd "/C windows.bat"
    rm windows.bat
  else
    docker exec yyets_mongo_1 mongorestore --gzip --archive=/tmp/yyets_mongo.gz --nsFrom "share.*" --nsTo "zimuzu.*"
    docker exec yyets_mongo_1 rm /tmp/yyets_mongo.gz
  fi

  rm /tmp/yyets_mongo.gz
}

function up() {
  echo "[5/5] 启动中……"
  docker-compose up -d
  echo "部署成功。您可以访问 http://IP:8888 查看"
}

function deploy() {
  splash
  prepare
  prepare_compose
  import_db
  up
}

function cleanup() {
  echo "您确认要进行清理吗?网站将会停止运行,对应的的docker image也将会被清除。输入YES确认"
  read -r confirm

  if [ "$confirm" = "YES" ]; then
    echo "继续清理,可能会要求您进行sudo鉴权"
    docker-compose -f "${HOME}"/YYeTs/docker-compose.yml down
    sudo rm -rf "${HOME}"/YYeTs
    docker rmi bennythink/yyetsbot
    echo "清理完成。"
  else
    echo "取消清理"
    exit 1
  fi

}

function upgrade() {
  docker pull bennythink/yyetsbot
  docker-compose -f "${HOME}"/YYeTs/docker-compose.yml up -d
  echo "更新成功"
}

select MENU_ITEM in "部署YYeTs" "清理YYeTs" "更新YYeTs"; do
  echo "准备$MENU_ITEM YYeTsWeb..."
  case $MENU_ITEM in
  "部署YYeTs") deploy ;;
  "清理YYeTs") cleanup ;;
  "更新YYeTs") upgrade ;;
  *) echo "无效的操作" ;;
  esac
  break
done


================================================
FILE: scripts/migrate_sub.py
================================================
#!/usr/bin/env python3
# coding: utf-8

# YYeTsBot - migrate_sub.py

import pymongo
import pymysql
from pymysql.cursors import DictCursor

con = pymysql.connect(host="mysql", user="root", password="root", database="yyets", charset="utf8")
cur = con.cursor(cursor=DictCursor)
mongo_client = pymongo.MongoClient(host="mongo")
col = mongo_client["zimuzu"]["subtitle"]

cur.execute("select * from subtitle")

# 56134 rows
for sub in cur.fetchall():
    col.insert_one(sub)


================================================
FILE: setup.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Note: To use the 'upload' functionality of this file, you must:
#   $ pipenv install twine --dev

import io
import os
import sys
from shutil import rmtree

from setuptools import Command, setup

# Package meta-data.
NAME = "yyets"
DESCRIPTION = "https://yyets.click/ wrapper"
URL = "https://github.com/tgbot-collection/YYeTsBot"
EMAIL = "benny.think@gmail.com"
AUTHOR = "BennyThink"
REQUIRES_PYTHON = ">=3.6.0"
VERSION = "1.0.1"

# What packages are required for this module to be executed?
REQUIRED = ["requests"]

# What packages are optional?
EXTRAS = {
    # 'fancy feature': ['django'],
}

# The rest you shouldn't have to touch too much :)
# ------------------------------------------------
# Except, perhaps the License and Trove Classifiers!
# If you do change the License, remember to change the Trove Classifier for that!

here = os.path.abspath(os.path.dirname(__file__))

# Import the README and use it as the long-description.
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
try:
    with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
        long_description = "\n" + f.read()
except FileNotFoundError:
    long_description = DESCRIPTION

# Load the package's __version__.py module as a dictionary.
about = {}
if not VERSION:
    project_slug = NAME.lower().replace("-", "_").replace(" ", "_")
    with open(os.path.join(here, project_slug, "__version__.py")) as f:
        exec(f.read(), about)
else:
    about["__version__"] = VERSION


class UploadCommand(Command):
    """Support setup.py upload."""

    description = "Build and publish the package."
    user_options = []

    @staticmethod
    def status(s):
        """Prints things in bold."""
        print("\033[1m{0}\033[0m".format(s))

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        try:
            self.status("Removing previous builds…")
            rmtree(os.path.join(here, "dist"))
        except OSError:
            pass

        self.status("Building Source and Wheel (universal) distribution…")
        os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable))

        self.status("Uploading the package to PyPI via Twine…")
        os.system("twine upload dist/*")

        self.status("Pushing git tags…")
        os.system("git tag v{0}".format(about["__version__"]))
        os.system("git push --tags")

        sys.exit()


# Where the magic happens:
setup(
    name=NAME,
    version=about["__version__"],
    description=DESCRIPTION,
    long_description=long_description,
    long_description_content_type="text/markdown",
    author=AUTHOR,
    author_email=EMAIL,
    python_requires=REQUIRES_PYTHON,
    url=URL,
    # packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]),
    # If your package is a single module, use this instead of 'packages':
    packages=["yyets"],
    # entry_points={
    #     'console_scripts': ['mycli=mymodule:cli'],
    # },
    install_requires=REQUIRED,
    extras_require=EXTRAS,
    include_package_data=True,
    license="MIT",
    classifiers=[
        # Trove classifiers
        # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
        "License :: OSI Approved :: MIT License",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.6",
        "Programming Language :: Python :: Implementation :: CPython",
        "Programming Language :: Python :: Implementation :: PyPy",
    ],
    # $ setup.py publish support.
    cmdclass={
        "upload": UploadCommand,
    },
)


================================================
FILE: tea.yaml
================================================
# https://tea.xyz/what-is-this-file
---
version: 1.0.0
codeOwners:
  - '0x2F119b4DdC0d33A1cAE392999513e8D253C9b4Db'
  - '0x56E743FD305c6858A4baD2893F2b2498441dF0Ce'
quorum: 1


================================================
FILE: yyets/BagAndDrag/README.md
================================================
# BagAndDrag
Bag and Drag

[original repo](https://github.com/tgbot-collection/BagAndDrag)

打包带走:-)

================================================
FILE: yyets/BagAndDrag/bag.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# BagAndDrag - bag.py
# 1/10/21 15:29
#

__author__ = "Benny <benny.think@gmail.com>"

import contextlib
import json
import logging
import os
import pickle
import sys
import time
import traceback

import pymysql
import requests

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s')

COOKIES = os.path.join(os.path.dirname(__file__), 'cookies.dump')
USERNAME = os.environ.get("USERNAME") or "321"
PASSWORD = os.environ.get("PASSWORD") or "xa31sge"

GET_USER = "http://www.rrys2020.com/user/login/getCurUserTopInfo"
AJAX_LOGIN = "http://www.rrys2020.com/User/Login/ajaxLogin"
RESOURCE = "http://www.rrys2020.com/resource/{id}"
SHARE_URL = "http://www.rrys2020.com/resource/ushare"
# http://got002.com/api/v1/static/resource/detail?code=9YxN91
API_DATA = "http://got002.com/api/v1/static/resource/detail?code={code}"
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"


def save_cookies(requests_cookiejar):
    with open(COOKIES, 'wb') as f:
        pickle.dump(requests_cookiejar, f)


def load_cookies():
    with contextlib.suppress(Exception):
        with open(COOKIES, 'rb') as f:
            return pickle.load(f)


def login():
    data = {"account": USERNAME, "password": PASSWORD, "remember": 1}
    logging.info("login in as %s", data)
    r = requests.post(AJAX_LOGIN, data=data, headers={"User-Agent": ua})
    resp = r.json()
    if resp.get('status') == 1:
        logging.info("Login success! %s", r.cookies)
        save_cookies(r.cookies)
    else:
        logging.error("Login failed! %s", resp)
        sys.exit(1)
    r.close()


def is_cookie_valid() -> bool:
    cookie = load_cookies()
    r = requests.get(GET_USER, cookies=cookie, headers={"User-Agent": ua})
    return r.json()['status'] == 1


def insert_db(data: dict):
    try:
        # INSERT INTO resource VALUE(id,url,name,expire,data)
        sql = "INSERT INTO resource VALUE(%s,%s,%s,%s,%s,%s)"
        con = pymysql.Connect(host="127.0.0.1", user="root", password="root", database="yyets", charset="utf8mb4")
        cur = con.cursor()
        info = data["data"]["info"]
        id = info["id"]
        url = RESOURCE.format(id=id)
        name = '{cnname}\n{enname}\n{alias}'.format(cnname=info["cnname"], enname=info["enname"],
                                                    alias=info["aliasname"])
        expire = info["expire"]
        date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(expire)))
        d = json.dumps(data, ensure_ascii=False, indent=2)

        cur.execute(sql, (id, url, name, expire, date, d))
        con.commit()
    except Exception as e:
        logging.error("insert error %s", e)
        logging.error(traceback.format_exc())


def insert_error(rid, tb):
    logging.warning("Logging error into database %s", rid)
    sql = "INSERT INTO failure VALUE (%s,%s)"
    con = pymysql.Connect(host="127.0.0.1", user="root", password="root", database="yyets", charset="utf8mb4")
    cur = con.cursor()
    cur.execute(sql, (rid, tb))
    con.commit()


def __load_sample():
    with open("sample.json") as f:
        return json.load(f)


if __name__ == '__main__':
    d = __load_sample()
    insert_db(d)
    insert_error(2331, "eeeeee")


================================================
FILE: yyets/BagAndDrag/cfkv.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# BagAndDrag - cfkv.py
# 1/17/21 12:08
#

__author__ = "Benny <benny.think@gmail.com>"

import json
import os

import pymysql

con = pymysql.Connect(host="127.0.0.1", user="root", password="root", charset="utf8mb4", database="yyets",
                      cursorclass=pymysql.cursors.DictCursor)
cur = con.cursor()

SIZE = 3000
cur.execute("select count(id) from resource")
count = cur.fetchall()[0]["count(id)"]
LIMIT = count // SIZE + 1


def convert_kv():
    for i in range(1, LIMIT + 1):
        SQL = "select id,data from resource limit %d offset %d" % (SIZE, (i - 1) * SIZE)
        print(SQL)
        cur = con.cursor()
        cur.execute(SQL)
        data = cur.fetchall()
        write_data = []
        for datum in data:
            write_data.append({
                "key": str(datum["id"]),  # keys need to be str
                "value": datum['data']}
            )
        with open(f"kv/kv_data{i - 1}.json", "w") as f:
            json.dump(write_data, f, ensure_ascii=False)


def verify_kv_data():
    files = os.listdir("kv")
    rows = 0
    for file in files:
        if file.startswith("kv_data"):
            with open(f"kv/{file}") as f:
                data = json.load(f)
                rows += len(data)
    print(rows, count)
    # assert rows == count


def dump_index():
    cur = con.cursor()
    indexes = {}
    cur.execute("select name, id from resource")
    data = cur.fetchall()
    for datum in data:
        name = datum["name"]
        rid = datum["id"]
        indexes[name] = rid
    with open("kv/index.json", "w") as f:
        write_data = [
            {
                "key": "index",
                "value": json.dumps(indexes, ensure_ascii=False)
            }
        ]
        json.dump(write_data, f, ensure_ascii=False, indent=2)


def generate_command():
    files = os.listdir("kv")
    tpl = "wrangler kv:bulk put --namespace-id=01d666b5ebae464193998bb074f672cf {filename}"
    shell = []
    for file in files:
        if file.endswith(".json"):
            shell.append(tpl.format(filename=file) + "\n")
    with open("kv/bulk.sh", "w") as f:
        f.writelines(shell)


if __name__ == '__main__':
    convert_kv()
    verify_kv_data()
    dump_index()
    generate_command()


================================================
FILE: yyets/BagAndDrag/convert_db.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# BagAndDrag - convert_db.py
# 1/12/21 18:24
#

__author__ = "Benny <benny.think@gmail.com>"

# convert to mongodb and con_sqlite

import json
import sqlite3
from typing import List

import pymongo
import pymysql
import tqdm

con_mysql = pymysql.Connect(host="127.0.0.1", user="root", password="root", charset="utf8mb4", database="yyets",
                            cursorclass=pymysql.cursors.DictCursor
                            )

mongo_client = pymongo.MongoClient()
con_sqlite = sqlite3.connect("yyets.db")

SIZE = 2000


def create_sqlite_database():
    sql = ["""
   DROP TABLE IF EXISTS resource;
    """,
           """
         create table resource
           (
               id         int primary key,
               url        varchar(255) null unique ,
               name       text         null,
               expire     int          null,
               expire_cst varchar(255) null,
               data       longtext     null
       
           )
           """
           ]
    cur = con_sqlite.cursor()
    for s in sql:
        cur.execute(s)
    con_sqlite.commit()


def clear_mongodb():
    mongo_client.drop_database("yyets")


def sqlite_insert(data: List[dict]):
    cur = con_sqlite.cursor()
    sql = "INSERT INTO resource VALUES(?,?,?,?,?,?)"

    cur.executemany(sql, [list(i.values()) for i in data])
    con_sqlite.commit()


def mongodb_insert(data: List[dict]):
    db = mongo_client["yyets"]
    col = db["resource"]
    # deserialize data.data
    inserted = []
    for i in data:
        i["data"] = json.loads(i["data"])
        inserted.append(i)
    col.insert_many(inserted)


def main():
    create_sqlite_database()
    clear_mongodb()

    mysql_cur = con_mysql.cursor()
    mysql_cur.execute("select count(id) from resource")
    count = mysql_cur.fetchall()[0]["count(id)"]

    mysql_cur.execute("SELECT * FROM resource")

    with tqdm.tqdm(total=count * 2) as pbar:
        while True:
            data = mysql_cur.fetchmany(SIZE)
            if data:
                sqlite_insert(data)
                pbar.update(SIZE)
                mongodb_insert(data)
                pbar.update(SIZE)
            else:
                break


if __name__ == '__main__':
    main()
    con_mysql.close()
    con_sqlite.close()
    mongo_client.close()


================================================
FILE: yyets/BagAndDrag/create_db.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# BagAndDrag - create_db.py
# 1/10/21 15:23
#

__author__ = "Benny <benny.think@gmail.com>"

import pymysql

con = pymysql.Connect(host="127.0.0.1", user="root", password="root", charset="utf8mb4")

sql = [
    "CREATE DATABASE yyets CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;",
    "use yyets",
    """
    create table resource
    (
        id         int primary key,
        url        varchar(255) null unique ,
        name       text         null,
        expire     int          null,
        expire_cst varchar(255) null,
        data       longtext     null
    
    )charset utf8mb4;
    
    
    """,

    """
    create table failure
    (
        id        int primary key not null,
        traceback longtext        null
    )charset utf8mb4;
    """,

]
cur = con.cursor()
for s in sql:
    cur.execute(s)
con.close()


================================================
FILE: yyets/BagAndDrag/drag.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# BagAndDrag - drag.py
# 1/10/21 15:38
#

__author__ = "Benny <benny.think@gmail.com>"

import argparse
import logging
import time
import traceback
from concurrent.futures import ThreadPoolExecutor

import requests
from tqdm import tqdm

from bag import (API_DATA, SHARE_URL, insert_db, insert_error, is_cookie_valid,
                 load_cookies, login)

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s')
s = requests.session()
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
s.headers.update({"User-Agent": ua})

parser = argparse.ArgumentParser()
# start_id, end_id, interval, concurrency
parser.add_argument("-s", help="Start id", type=int, default=1)
parser.add_argument("-e", help="End id", type=int, default=10)
parser.add_argument("-i", help="Interval", default=5, type=int)
parser.add_argument("-c", help="Concurrency", default=2, type=int)
args = parser.parse_args()

executor = ThreadPoolExecutor(max_workers=args.c)


def get_api_json(resource_id):
    try:
        time.sleep(args.i)
        if not is_cookie_valid():
            login()
        logging.info("resource id is %s", resource_id)
        res = s.post(SHARE_URL, data={"rid": resource_id}, cookies=load_cookies()).json()
        share_code = res['data'].split('/')[-1]
        logging.info("Share code is %s", share_code)
        data = s.get(API_DATA.format(code=share_code)).json()

        insert_db(data)
    except Exception:
        insert_error(resource_id, traceback.format_exc())


def main():
    total = args.e + 1 - args.s
    list(tqdm(executor.map(get_api_json, range(args.s, args.e + 1)), total=total))


if __name__ == '__main__':
    main()


================================================
FILE: yyets/BagAndDrag/sample.json
================================================
{
  "status": 1,
  "info": "OK",
  "data": {
    "info": {
      "id": 34812,
      "cnname": "逃避可耻却有用",
      "enname": "NIGERUHA HAJIDAGA YAKUNITATSU",
      "aliasname": "逃避虽可耻但有用 / 雇佣妻子(港) / 月薪娇妻(台) / 逃跑是可耻但是有用 / 逃避虽可耻但很有用 / 逃避可耻但有用",
      "channel": "tv",
      "channel_cn": "日剧",
      "area": "日本",
      "show_type": "",
      "expire": "1610288883",
      "views": "0"
    },
    "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\u0026tr=http://tr.cili001.com:8070/announce\u0026tr=udp://p4p.arenabg.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr=http://tracker.openbittorrent.com/announce\u0026tr=udp://tracker.openbittorrent.com:80/announce\u0026tr=udp://tr.cili001.com:6666/announce\u0026tr=http://tracker.publicbt.com/announce\u0026tr=udp://open.demonii.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr=http://tracker.openbittorrent.com/announce\u0026tr=udp://tracker.openbittorrent.com:80/announce\u0026tr=udp://tr.cili001.com:6666/announce\u0026tr=http://tracker.publicbt.com/announce\u0026tr=udp://open.demonii.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr=http://tracker.openbittorrent.com/announce\u0026tr=udp://tracker.openbittorrent.com:80/announce\u0026tr=udp://tr.cili001.com:6666/announce\u0026tr=http://tracker.publicbt.com/announce\u0026tr=udp://open.demonii.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr=http://tr.cili001.com:8070/announce\u0026tr=udp://p4p.arenabg.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr=http://tracker.openbittorrent.com/announce\u0026tr=udp://tracker.openbittorrent.com:80/announce\u0026tr=udp://tr.cili001.com:6666/announce\u0026tr=http://tracker.publicbt.com/announce\u0026tr=udp://open.demonii.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr=http://tracker.openbittorrent.com/announce\u0026tr=udp://tracker.openbittorrent.com:80/announce\u0026tr=udp://tr.cili001.com:6666/announce\u0026tr=http://tracker.publicbt.com/announce\u0026tr=udp://open.demonii.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr=http://tracker.openbittorrent.com/announce\u0026tr=udp://tracker.openbittorrent.com:80/announce\u0026tr=udp://tr.cili001.com:6666/announce\u0026tr=http://tracker.publicbt.com/announce\u0026tr=udp://open.demonii.com:1337\u0026tr=udp://tracker.opentrackr.org:1337/announce\u0026tr=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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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\u0026tr.1=http://tracker.openbittorrent.com/announce\u0026tr.2=udp://tracker.openbittorrent.com:80/announce\u0026tr.3=udp://tr.cili001.com:6666/announce\u0026tr.4=http://tracker.publicbt.com/announce\u0026tr.5=udp://open.demonii.com:1337\u0026tr.6=udp://tracker.opentrackr.org:1337/announce\u0026tr.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"
        ]
      }
    ]
  }
}

================================================
FILE: yyets/BagAndDrag/zimuxia/convert_db.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# YYeTsBot - convert_db.py
# 2/5/21 13:46
#

__author__ = "Benny <benny.think@gmail.com>"

# convert to mongodb and con_sqlite

import pymongo
import pymysql
import tqdm
import json

from typing import List

con_mysql = pymysql.Connect(host="127.0.0.1", user="root", password="root", charset="utf8mb4", database="zimuxia",
                            cursorclass=pymysql.cursors.DictCursor
                            )

mongo_client = pymongo.MongoClient()

SIZE = 2000


def clear_mongodb():
    mongo_client.drop_database("zimuxia")


def clear_mysql():
    con_mysql.cursor().execute("truncate table resource;")
    con_mysql.commit()


def mysql_insert(data: List[dict]):
    sql = "INSERT INTO resource VALUES(NULL,%(url)s,%(name)s,NULL,NULL,%(data)s)"
    cur = con_mysql.cursor()
    for i in data:
        cur.execute(sql, i)
    con_mysql.commit()


def mongodb_insert(data: List[dict]):
    db = mongo_client["zimuxia"]
    col = db["resource"]
    col.insert_many(data)


def main():
    clear_mongodb()
    clear_mysql()
    with open("result.json") as f:
        data = json.load(f)
    # [{"url": "https://www.zimuxia.cn/portfolio/%e6%888b%e5%8f%8b", "name": "我家的女儿交不到男朋友", "data":""}]
    mysql_insert(data)
    mongodb_insert(data)


if __name__ == '__main__':
    main()
    con_mysql.close()
    mongo_client.close()


================================================
FILE: yyets/BagAndDrag/zimuxia/zimuxia.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# YYeTsBot - zimuxia.py
# 2/5/21 12:44
#

__author__ = "Benny <benny.think@gmail.com>"

import requests
import random
import time
import json
from bs4 import BeautifulSoup
from urllib.parse import quote, unquote
import tqdm
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s')

list_url = "https://www.zimuxia.cn/%e6%88%91%e4%bb%ac%e7%9a%84%e4%bd%9c%e5%93%81?set={}"
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"

s = requests.session()
s.headers.update({"User-Agent": ua})

data = []


def get_list():
    for index in tqdm.trange(1, 89):
        time.sleep(random.random())
        url = list_url.format(index)
        list_html = s.get(url).text
        get_episode(list_html)


def get_episode(html_text):
    soup = BeautifulSoup(html_text, 'html.parser')
    episodes = soup.find_all("div", class_="pg-item")

    for block in episodes:
        url = block.a['href']
        name = unquote(url).split("https://www.zimuxia.cn/portfolio/")[1]
        logging.info("fetching %s", name)
        t = {"url": url, "name": name, "data": s.get(url).text}
        data.append(t)


def write_json():
    with open("result.json", "w") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)


if __name__ == '__main__':
    get_list()
    write_json()


================================================
FILE: yyets/__init__.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# YYeTsBot - __init__.py
# 9/21/21 18:09
#

__author__ = "Benny <benny.think@gmail.com>"

import logging

import requests

API = "https://yyets.click/api/resource?"

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s %(filename)s:%(lineno)d %(levelname).1s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)


class Resource:
    def __init__(self):
        self.enname = None
        self.cnname = None

    def __str__(self):
        return f"{self.cnname} - {self.enname}"


class YYeTs:
    def __init__(self, keyword: "str"):
        self.result = []
        self.keyword = keyword
        self.search_api = f"{API}keyword={self.keyword}"
        self.resource_api = f"{API}id=%s"
        self.search()

    def search(self):
        data = requests.get(self.search_api).json()
        for info in data["data"]:
            r = Resource()
            setattr(r, "list", self.fetch(info))
            for k, v in info.items():
                setattr(r, k, v)
            self.result.append(r)

    def fetch(self, info):
        rid = info["id"]
        url = self.resource_api % rid
        headers = {"Referer": url}
        logging.info("Fetching %s...%s", info["cnname"], url)
        return requests.get(url, headers=headers).json()["data"]["list"]

    def __str__(self):
        return f"{self.keyword} - {self.search_api}"


if __name__ == "__main__":
    ins = YYeTs("逃避可耻")
    for i in ins.result:
        print(i)


================================================
FILE: yyets/healthcheck/check.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# YYeTsBot - check.py
# 1/22/21 16:36
#

__author__ = "Benny <benny.think@gmail.com>"

import logging
import os

import requests
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from telethon import TelegramClient, events

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(filename)s [%(levelname)s]: %(message)s")
logging.getLogger("apscheduler.executors.default").propagate = False
api_id = int(os.environ.get("API_ID") or "3")
api_hash = os.environ.get("API_HASH") or "4"
bot_name = os.environ.get("BOT_NAME") or "yyets_bot"
bot_token = os.environ.get("BOT_token") or "123"

client = TelegramClient(
    "client-hc", api_id, api_hash, device_model="Benny-health-check", system_version="89", app_version="1.0.0"
)
check_status = []


@client.on(events.NewMessage(incoming=True, pattern="(?i).*欢迎使用,直接发送想要的剧集标题给我就可以了.*", from_users=bot_name))
async def my_event_handler(event):
    logging.info("Okay it's working %s", event)
    check_status.clear()


async def send_health_check():
    if check_status:
        # restart it
        await bot_warning()
    else:
        await client.send_message(bot_name, "/start")
        check_status.append("check")


async def bot_warning():
    logging.warning("Bot seems to be down. Restarting now....")
    message = "Bot is down!!!"
    url = f"https://api.telegram.org/bot{bot_token}/sendMessage?chat_id=260260121&text={message}"
    resp = requests.get(url).json()
    logging.warning(resp)


async def website_check():
    home = "https://yyets.click/"
    top = "https://yyets.click/api/top"
    message = ""
    try:
        resp1 = requests.get(home)
        resp2 = requests.get(top)
    except Exception as e:
        message += f"Website is down. Requests error:{e}\n"
        resp1 = resp2 = ""

    if getattr(resp1, "status_code", 0) != 200:
        content = getattr(resp1, "content", None)
        message += f"Website home is down!!! {content}\n"
    if getattr(resp2, "status_code", 0) != 200:
        content = getattr(resp1, "content", None)
        message += f"Website top is down!!! {content}\n"
    if message:
        url = f"https://api.telegram.org/bot{bot_token}/sendMessage?chat_id=260260121&text={message}"
        resp = requests.get(url).json()
        logging.error(resp)
        logging.error(message)
    else:
        logging.info("It's working home: %s bytes; top: %s bytes", len(resp1.content), len(resp2.content))


if __name__ == "__main__":
    scheduler = AsyncIOScheduler()
    scheduler.add_job(send_health_check, "interval", seconds=300)
    scheduler.add_job(website_check, "interval", seconds=60)
    scheduler.start()
    client.start()
    client.run_until_disconnected()


================================================
FILE: yyets/healthcheck/restart_service.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# untitled - restart_service.py
# 9/18/21 11:54
#

__author__ = "Benny <benny.think@gmail.com>"

import logging.handlers
import subprocess

import requests

filename = "yyets_restart.log"
formatter = logging.Formatter("[%(asctime)s %(filename)s:%(lineno)d %(levelname).1s] %(message)s", "%Y-%m-%d %H:%M:%S")
handler = logging.handlers.RotatingFileHandler(filename, maxBytes=1024 * 1024 * 100)
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)

URL = "https://yyets.click/api/top"
# URL = "https://www.baidu.com/"
req = requests.get(URL)

if req.status_code != 200:
    logger.error("error! %s", req)
    cmd = "/usr/bin/docker-compose -f /home/WebsiteRunner/docker-compose.yml restart mongo yyets-web nginx"
    subprocess.check_output(cmd.split())
else:
    logger.info("YYeTs is running %s", req)


================================================
FILE: yyets/management/format.json
================================================
{
  "status": 1,
  "info": "OK",
  "data": {
    "info": {
      "id": 99999,
      "cnname": "中文名",
      "enname": "英文名",
      "aliasname": "别名",
      "channel": "movie/tv",
      "channel_cn": "电影/美剧",
      "area": "法国",
      "show_type": "",
      "expire": "1610401225",
      "views": 0
    },
    "list": [
      {
        "season_num": "0",
        "season_cn": "正片",
        "items": {
          "MP4": [
            {
              "itemid": "428324",
              "episode": "0",
              "name": "name",
              "size": "1.43GB",
              "yyets_trans": 0,
              "dateline": "1565351112",
              "files": [
                {
                  "way": "1",
                  "way_cn": "电驴",
                  "address": "",
                  "passwd": ""
                },
                {
                  "way": "2",
                  "way_cn": "磁力",
                  "address": "",
                  "passwd": ""
                },
                {
                  "way": "9",
                  "way_cn": "网盘",
                  "address": "",
                  "passwd": ""
                },
                {
                  "way": "115",
                  "way_cn": "微云",
                  "address": "",
                  "passwd": ""
                }
              ]
            }
          ]
        },
        "formats": [
          "MP4"
        ]
      }
    ]
  }
}

================================================
FILE: yyets/management/ui.py
================================================
#!/usr/local/bin/python3
# coding: utf-8

# YYeTsBot - ui.py
# 2/8/21 17:55
#

__author__ = "Benny <benny.think@gmail.com>"

import json
import time

import PySimpleGUI as sg

# All the stuff inside your window.
channel_map = {
    "movie": "电影",
    "tv": "电视剧"
}

complete = {
    "status": 1,
    "info": "OK",
    "data": {
        "info": {},
        "list": [
            {
                "season_num": "1",
                "season_cn": "第一季",
                "items": {},
                "formats": [
                    "MP4"
                ]
            }
        ]
    }
}

dl = {
    "itemid": "",
    "episode": "0",
    "name": "",
    "size": "",
    "yyets_trans": 0,
    "dateline": str(int(time.time())),
    "files": [
        {
            "way": "1",
            "way_cn": "电驴",
            "address": "",
            "passwd": ""
        },
        {
            "way": "2",
            "way_cn": "磁力",
            "address": "",
            "passwd": ""
        },
        {
            "way": "9",
            "way_cn": "百度网盘",
            "address": "",
            "passwd": ""
        },
        {
            "way": "115",
            "way_cn": "115网盘",
            "address": "",
            "passwd": ""
        }
    ]
}
item_structure = {
    "MP4": [
    ]
}


def get_value():
    for i in range(1, int(episode_input[1].get()) + 1):
        d = dl.copy()
        d["episode"] = str(i)
        d["name"] = "{}第{}集".format(cn_input[1].get(), i)
        item_structure["MP4"].append(d)

    info_structure = {
        "id": 0,
        "cnname": cn_input[1].get(),
        "enname": en_input[1].get(),
        "aliasname": alias_input[1].get(),
        "channel": channel_input[1].get(),
        "channel_cn": channel_map.get(channel_input[1].get(), ""),
        "area": area_input[1].get(),
        "show_type": "",
        "expire": "1610401225",
        "views": 0
    }

    complete["data"]["info"] = info_structure
    complete["data"]["list"][0]["items"] = item_structure

    with open("sample.json", "w") as f:
        json.dump(complete, f, indent=4, ensure_ascii=False)


cn_input = [sg.Text('cn name'), sg.InputText()]
en_input = [sg.Text('en name'), sg.InputText()]
alias_input = [sg.Text('alias name'), sg.InputText()]
channel_input = [sg.Text('channel'), sg.Combo(['movie', 'tv'], "tv")]
area_input = [sg.Text('area'), sg.Combo(['美国', '日本', '韩国', '英国'], "美国")]
episode_input = [sg.Text('episode count'), sg.InputText()]

layout = [cn_input, en_input, alias_input, channel_input, area_input, episode_input,
          [sg.Button('Ok')]
          ]

# Create the Window
window = sg.Window('Management', layout)
# Event Loop to process "events" and get the "values" of the inputs
while True:
    event, values = window.read()
    if event == sg.WIN_CLOSED or event == 'Cancel':  # if user closes window or clicks cancel
        break
    if event == "Ok":
        print('You entered ', values[0], values[1], values[2])
        get_value()
        break
window.close()


================================================
FILE: yyets/worker/.cargo-ok
================================================


================================================
FILE: yyets/worker/README.md
================================================
# Cloudflare Worker部署方式
**This worker is deprecated. No further updates from now on.**

## 1. 安装wrangler等工具
[Cloudflare Docs](https://developers.cloudflare.com/workers/cli-wrangler)
## 2. 导入数据到MySQL

## 3. 生成KV数据
参考 `BagAndDrag/cfkv.py`

## 4. 导入数据
```shell
cd kv_data
bash bulk.sh
```
**注意,Cloudflare KV免费版有每日1000条写入的限制**

## 4. 配置代码
* 修改`worker/public/js/search.js`中的`baseURL`为你的URL
* 如绑定自定义域名,还需要修改`worker/workers-site/index.js`中的`Access-Control-Allow-Origin`为你的域名

## 5.  发布到Worker Site
配置 `wrangler.toml`,修改`account_id`, `kv_namespaces`等,然后:
```shell
wrangler publish
```
就可以了。

================================================
FILE: yyets/worker/public/404.html
================================================
<!doctype html>
<html>
    <head>
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        <link href="https://fonts.googleapis.com/css?family=Pacifico&display=swap" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" rel="stylesheet">
        <style>
            h1 {
                font-family: Pacifico, sans-serif;
                font-size: 4em;
                color: #3eb5f1;
                margin: 0;
            }

            h2 {
                font-weight: 300;
                font-family: sans-serif;
            }

            .centered {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                text-align: center;
            }

            #ferris {
                width: 75%;
            }
        </style>
    </head>
    <body>
        <div class="centered">
            <h1>404 Not Found</h1>
            <h2>Oh dang! We couldn't find that page.</h2>
            <img id="ferris" alt="a sad crab is unable to unable to lasso a paper airplane. 404 not found." src="img/404-wrangler-ferris.gif">
        </div>
    </body>
</html>


================================================
FILE: yyets/worker/public/css/3rd/animate.css
================================================
@charset "UTF-8";

/*!
 * animate.css -http://daneden.me/animate
 * Version - 3.5.1
 * Licensed under the MIT license - http://opensource.org/licenses/MIT
 *
 * Copyright (c) 2016 Daniel Eden
 */

.animated {
  -webkit-animation-duration: 1s;
  animation-duration: 1s;
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both;
}

.animated.infinite {
  -webkit-animation-iteration-count: infinite;
  animation-iteration-count: infinite;
}

.animated.hinge {
  -webkit-animation-duration: 2s;
  animation-duration: 2s;
}

.animated.flipOutX,
.animated.flipOutY,
.animated.bounceIn,
.animated.bounceOut {
  -webkit-animation-duration: .75s;
  animation-duration: .75s;
}

@-webkit-keyframes bounce {
  from, 20%, 53%, 80%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    -webkit-transform: translate3d(0,0,0);
    transform: translate3d(0,0,0);
  }

  40%, 43% {
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -30px, 0);
    transform: translate3d(0, -30px, 0);
  }

  70% {
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -15px, 0);
    transform: translate3d(0, -15px, 0);
  }

  90% {
    -webkit-transform: translate3d(0,-4px,0);
    transform: translate3d(0,-4px,0);
  }
}

@keyframes bounce {
  from, 20%, 53%, 80%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    -webkit-transform: translate3d(0,0,0);
    transform: translate3d(0,0,0);
  }

  40%, 43% {
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -30px, 0);
    transform: translate3d(0, -30px, 0);
  }

  70% {
    -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
    -webkit-transform: translate3d(0, -15px, 0);
    transform: translate3d(0, -15px, 0);
  }

  90% {
    -webkit-transform: translate3d(0,-4px,0);
    transform: translate3d(0,-4px,0);
  }
}

.bounce {
  -webkit-animation-name: bounce;
  animation-name: bounce;
  -webkit-transform-origin: center bottom;
  transform-origin: center bottom;
}

@-webkit-keyframes flash {
  from, 50%, to {
    opacity: 1;
  }

  25%, 75% {
    opacity: 0;
  }
}

@keyframes flash {
  from, 50%, to {
    opacity: 1;
  }

  25%, 75% {
    opacity: 0;
  }
}

.flash {
  -webkit-animation-name: flash;
  animation-name: flash;
}

/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */

@-webkit-keyframes pulse {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  50% {
    -webkit-transform: scale3d(1.05, 1.05, 1.05);
    transform: scale3d(1.05, 1.05, 1.05);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

@keyframes pulse {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  50% {
    -webkit-transform: scale3d(1.05, 1.05, 1.05);
    transform: scale3d(1.05, 1.05, 1.05);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

.pulse {
  -webkit-animation-name: pulse;
  animation-name: pulse;
}

@-webkit-keyframes rubberBand {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  30% {
    -webkit-transform: scale3d(1.25, 0.75, 1);
    transform: scale3d(1.25, 0.75, 1);
  }

  40% {
    -webkit-transform: scale3d(0.75, 1.25, 1);
    transform: scale3d(0.75, 1.25, 1);
  }

  50% {
    -webkit-transform: scale3d(1.15, 0.85, 1);
    transform: scale3d(1.15, 0.85, 1);
  }

  65% {
    -webkit-transform: scale3d(.95, 1.05, 1);
    transform: scale3d(.95, 1.05, 1);
  }

  75% {
    -webkit-transform: scale3d(1.05, .95, 1);
    transform: scale3d(1.05, .95, 1);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

@keyframes rubberBand {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  30% {
    -webkit-transform: scale3d(1.25, 0.75, 1);
    transform: scale3d(1.25, 0.75, 1);
  }

  40% {
    -webkit-transform: scale3d(0.75, 1.25, 1);
    transform: scale3d(0.75, 1.25, 1);
  }

  50% {
    -webkit-transform: scale3d(1.15, 0.85, 1);
    transform: scale3d(1.15, 0.85, 1);
  }

  65% {
    -webkit-transform: scale3d(.95, 1.05, 1);
    transform: scale3d(.95, 1.05, 1);
  }

  75% {
    -webkit-transform: scale3d(1.05, .95, 1);
    transform: scale3d(1.05, .95, 1);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

.rubberBand {
  -webkit-animation-name: rubberBand;
  animation-name: rubberBand;
}

@-webkit-keyframes shake {
  from, to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  10%, 30%, 50%, 70%, 90% {
    -webkit-transform: translate3d(-10px, 0, 0);
    transform: translate3d(-10px, 0, 0);
  }

  20%, 40%, 60%, 80% {
    -webkit-transform: translate3d(10px, 0, 0);
    transform: translate3d(10px, 0, 0);
  }
}

@keyframes shake {
  from, to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  10%, 30%, 50%, 70%, 90% {
    -webkit-transform: translate3d(-10px, 0, 0);
    transform: translate3d(-10px, 0, 0);
  }

  20%, 40%, 60%, 80% {
    -webkit-transform: translate3d(10px, 0, 0);
    transform: translate3d(10px, 0, 0);
  }
}

.shake {
  -webkit-animation-name: shake;
  animation-name: shake;
}

@-webkit-keyframes headShake {
  0% {
    -webkit-transform: translateX(0);
    transform: translateX(0);
  }

  6.5% {
    -webkit-transform: translateX(-6px) rotateY(-9deg);
    transform: translateX(-6px) rotateY(-9deg);
  }

  18.5% {
    -webkit-transform: translateX(5px) rotateY(7deg);
    transform: translateX(5px) rotateY(7deg);
  }

  31.5% {
    -webkit-transform: translateX(-3px) rotateY(-5deg);
    transform: translateX(-3px) rotateY(-5deg);
  }

  43.5% {
    -webkit-transform: translateX(2px) rotateY(3deg);
    transform: translateX(2px) rotateY(3deg);
  }

  50% {
    -webkit-transform: translateX(0);
    transform: translateX(0);
  }
}

@keyframes headShake {
  0% {
    -webkit-transform: translateX(0);
    transform: translateX(0);
  }

  6.5% {
    -webkit-transform: translateX(-6px) rotateY(-9deg);
    transform: translateX(-6px) rotateY(-9deg);
  }

  18.5% {
    -webkit-transform: translateX(5px) rotateY(7deg);
    transform: translateX(5px) rotateY(7deg);
  }

  31.5% {
    -webkit-transform: translateX(-3px) rotateY(-5deg);
    transform: translateX(-3px) rotateY(-5deg);
  }

  43.5% {
    -webkit-transform: translateX(2px) rotateY(3deg);
    transform: translateX(2px) rotateY(3deg);
  }

  50% {
    -webkit-transform: translateX(0);
    transform: translateX(0);
  }
}

.headShake {
  -webkit-animation-timing-function: ease-in-out;
  animation-timing-function: ease-in-out;
  -webkit-animation-name: headShake;
  animation-name: headShake;
}

@-webkit-keyframes swing {
  20% {
    -webkit-transform: rotate3d(0, 0, 1, 15deg);
    transform: rotate3d(0, 0, 1, 15deg);
  }

  40% {
    -webkit-transform: rotate3d(0, 0, 1, -10deg);
    transform: rotate3d(0, 0, 1, -10deg);
  }

  60% {
    -webkit-transform: rotate3d(0, 0, 1, 5deg);
    transform: rotate3d(0, 0, 1, 5deg);
  }

  80% {
    -webkit-transform: rotate3d(0, 0, 1, -5deg);
    transform: rotate3d(0, 0, 1, -5deg);
  }

  to {
    -webkit-transform: rotate3d(0, 0, 1, 0deg);
    transform: rotate3d(0, 0, 1, 0deg);
  }
}

@keyframes swing {
  20% {
    -webkit-transform: rotate3d(0, 0, 1, 15deg);
    transform: rotate3d(0, 0, 1, 15deg);
  }

  40% {
    -webkit-transform: rotate3d(0, 0, 1, -10deg);
    transform: rotate3d(0, 0, 1, -10deg);
  }

  60% {
    -webkit-transform: rotate3d(0, 0, 1, 5deg);
    transform: rotate3d(0, 0, 1, 5deg);
  }

  80% {
    -webkit-transform: rotate3d(0, 0, 1, -5deg);
    transform: rotate3d(0, 0, 1, -5deg);
  }

  to {
    -webkit-transform: rotate3d(0, 0, 1, 0deg);
    transform: rotate3d(0, 0, 1, 0deg);
  }
}

.swing {
  -webkit-transform-origin: top center;
  transform-origin: top center;
  -webkit-animation-name: swing;
  animation-name: swing;
}

@-webkit-keyframes tada {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  10%, 20% {
    -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
    transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
  }

  30%, 50%, 70%, 90% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
  }

  40%, 60%, 80% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

@keyframes tada {
  from {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }

  10%, 20% {
    -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
    transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
  }

  30%, 50%, 70%, 90% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
  }

  40%, 60%, 80% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
  }

  to {
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

.tada {
  -webkit-animation-name: tada;
  animation-name: tada;
}

/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */

@-webkit-keyframes wobble {
  from {
    -webkit-transform: none;
    transform: none;
  }

  15% {
    -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
    transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
  }

  30% {
    -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
    transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
  }

  45% {
    -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
    transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
  }

  60% {
    -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
    transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
  }

  75% {
    -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
    transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes wobble {
  from {
    -webkit-transform: none;
    transform: none;
  }

  15% {
    -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
    transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg);
  }

  30% {
    -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
    transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg);
  }

  45% {
    -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
    transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg);
  }

  60% {
    -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
    transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg);
  }

  75% {
    -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
    transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

.wobble {
  -webkit-animation-name: wobble;
  animation-name: wobble;
}

@-webkit-keyframes jello {
  from, 11.1%, to {
    -webkit-transform: none;
    transform: none;
  }

  22.2% {
    -webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
    transform: skewX(-12.5deg) skewY(-12.5deg);
  }

  33.3% {
    -webkit-transform: skewX(6.25deg) skewY(6.25deg);
    transform: skewX(6.25deg) skewY(6.25deg);
  }

  44.4% {
    -webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
    transform: skewX(-3.125deg) skewY(-3.125deg);
  }

  55.5% {
    -webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
    transform: skewX(1.5625deg) skewY(1.5625deg);
  }

  66.6% {
    -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
    transform: skewX(-0.78125deg) skewY(-0.78125deg);
  }

  77.7% {
    -webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
    transform: skewX(0.390625deg) skewY(0.390625deg);
  }

  88.8% {
    -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
    transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
  }
}

@keyframes jello {
  from, 11.1%, to {
    -webkit-transform: none;
    transform: none;
  }

  22.2% {
    -webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
    transform: skewX(-12.5deg) skewY(-12.5deg);
  }

  33.3% {
    -webkit-transform: skewX(6.25deg) skewY(6.25deg);
    transform: skewX(6.25deg) skewY(6.25deg);
  }

  44.4% {
    -webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
    transform: skewX(-3.125deg) skewY(-3.125deg);
  }

  55.5% {
    -webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
    transform: skewX(1.5625deg) skewY(1.5625deg);
  }

  66.6% {
    -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
    transform: skewX(-0.78125deg) skewY(-0.78125deg);
  }

  77.7% {
    -webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
    transform: skewX(0.390625deg) skewY(0.390625deg);
  }

  88.8% {
    -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
    transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
  }
}

.jello {
  -webkit-animation-name: jello;
  animation-name: jello;
  -webkit-transform-origin: center;
  transform-origin: center;
}

@-webkit-keyframes bounceIn {
  from, 20%, 40%, 60%, 80%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  0% {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }

  20% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1);
    transform: scale3d(1.1, 1.1, 1.1);
  }

  40% {
    -webkit-transform: scale3d(.9, .9, .9);
    transform: scale3d(.9, .9, .9);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(1.03, 1.03, 1.03);
    transform: scale3d(1.03, 1.03, 1.03);
  }

  80% {
    -webkit-transform: scale3d(.97, .97, .97);
    transform: scale3d(.97, .97, .97);
  }

  to {
    opacity: 1;
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

@keyframes bounceIn {
  from, 20%, 40%, 60%, 80%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  0% {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }

  20% {
    -webkit-transform: scale3d(1.1, 1.1, 1.1);
    transform: scale3d(1.1, 1.1, 1.1);
  }

  40% {
    -webkit-transform: scale3d(.9, .9, .9);
    transform: scale3d(.9, .9, .9);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(1.03, 1.03, 1.03);
    transform: scale3d(1.03, 1.03, 1.03);
  }

  80% {
    -webkit-transform: scale3d(.97, .97, .97);
    transform: scale3d(.97, .97, .97);
  }

  to {
    opacity: 1;
    -webkit-transform: scale3d(1, 1, 1);
    transform: scale3d(1, 1, 1);
  }
}

.bounceIn {
  -webkit-animation-name: bounceIn;
  animation-name: bounceIn;
}

@-webkit-keyframes bounceInDown {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  0% {
    opacity: 0;
    -webkit-transform: translate3d(0, -3000px, 0);
    transform: translate3d(0, -3000px, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(0, 25px, 0);
    transform: translate3d(0, 25px, 0);
  }

  75% {
    -webkit-transform: translate3d(0, -10px, 0);
    transform: translate3d(0, -10px, 0);
  }

  90% {
    -webkit-transform: translate3d(0, 5px, 0);
    transform: translate3d(0, 5px, 0);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes bounceInDown {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  0% {
    opacity: 0;
    -webkit-transform: translate3d(0, -3000px, 0);
    transform: translate3d(0, -3000px, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(0, 25px, 0);
    transform: translate3d(0, 25px, 0);
  }

  75% {
    -webkit-transform: translate3d(0, -10px, 0);
    transform: translate3d(0, -10px, 0);
  }

  90% {
    -webkit-transform: translate3d(0, 5px, 0);
    transform: translate3d(0, 5px, 0);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

.bounceInDown {
  -webkit-animation-name: bounceInDown;
  animation-name: bounceInDown;
}

@-webkit-keyframes bounceInLeft {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  0% {
    opacity: 0;
    -webkit-transform: translate3d(-3000px, 0, 0);
    transform: translate3d(-3000px, 0, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(25px, 0, 0);
    transform: translate3d(25px, 0, 0);
  }

  75% {
    -webkit-transform: translate3d(-10px, 0, 0);
    transform: translate3d(-10px, 0, 0);
  }

  90% {
    -webkit-transform: translate3d(5px, 0, 0);
    transform: translate3d(5px, 0, 0);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes bounceInLeft {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  0% {
    opacity: 0;
    -webkit-transform: translate3d(-3000px, 0, 0);
    transform: translate3d(-3000px, 0, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(25px, 0, 0);
    transform: translate3d(25px, 0, 0);
  }

  75% {
    -webkit-transform: translate3d(-10px, 0, 0);
    transform: translate3d(-10px, 0, 0);
  }

  90% {
    -webkit-transform: translate3d(5px, 0, 0);
    transform: translate3d(5px, 0, 0);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

.bounceInLeft {
  -webkit-animation-name: bounceInLeft;
  animation-name: bounceInLeft;
}

@-webkit-keyframes bounceInRight {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  from {
    opacity: 0;
    -webkit-transform: translate3d(3000px, 0, 0);
    transform: translate3d(3000px, 0, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(-25px, 0, 0);
    transform: translate3d(-25px, 0, 0);
  }

  75% {
    -webkit-transform: translate3d(10px, 0, 0);
    transform: translate3d(10px, 0, 0);
  }

  90% {
    -webkit-transform: translate3d(-5px, 0, 0);
    transform: translate3d(-5px, 0, 0);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes bounceInRight {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  from {
    opacity: 0;
    -webkit-transform: translate3d(3000px, 0, 0);
    transform: translate3d(3000px, 0, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(-25px, 0, 0);
    transform: translate3d(-25px, 0, 0);
  }

  75% {
    -webkit-transform: translate3d(10px, 0, 0);
    transform: translate3d(10px, 0, 0);
  }

  90% {
    -webkit-transform: translate3d(-5px, 0, 0);
    transform: translate3d(-5px, 0, 0);
  }

  to {
    -webkit-transform: none;
    transform: none;
  }
}

.bounceInRight {
  -webkit-animation-name: bounceInRight;
  animation-name: bounceInRight;
}

@-webkit-keyframes bounceInUp {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  from {
    opacity: 0;
    -webkit-transform: translate3d(0, 3000px, 0);
    transform: translate3d(0, 3000px, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(0, -20px, 0);
    transform: translate3d(0, -20px, 0);
  }

  75% {
    -webkit-transform: translate3d(0, 10px, 0);
    transform: translate3d(0, 10px, 0);
  }

  90% {
    -webkit-transform: translate3d(0, -5px, 0);
    transform: translate3d(0, -5px, 0);
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@keyframes bounceInUp {
  from, 60%, 75%, 90%, to {
    -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
    animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
  }

  from {
    opacity: 0;
    -webkit-transform: translate3d(0, 3000px, 0);
    transform: translate3d(0, 3000px, 0);
  }

  60% {
    opacity: 1;
    -webkit-transform: translate3d(0, -20px, 0);
    transform: translate3d(0, -20px, 0);
  }

  75% {
    -webkit-transform: translate3d(0, 10px, 0);
    transform: translate3d(0, 10px, 0);
  }

  90% {
    -webkit-transform: translate3d(0, -5px, 0);
    transform: translate3d(0, -5px, 0);
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

.bounceInUp {
  -webkit-animation-name: bounceInUp;
  animation-name: bounceInUp;
}

@-webkit-keyframes bounceOut {
  20% {
    -webkit-transform: scale3d(.9, .9, .9);
    transform: scale3d(.9, .9, .9);
  }

  50%, 55% {
    opacity: 1;
    -webkit-transform: scale3d(1.1, 1.1, 1.1);
    transform: scale3d(1.1, 1.1, 1.1);
  }

  to {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }
}

@keyframes bounceOut {
  20% {
    -webkit-transform: scale3d(.9, .9, .9);
    transform: scale3d(.9, .9, .9);
  }

  50%, 55% {
    opacity: 1;
    -webkit-transform: scale3d(1.1, 1.1, 1.1);
    transform: scale3d(1.1, 1.1, 1.1);
  }

  to {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }
}

.bounceOut {
  -webkit-animation-name: bounceOut;
  animation-name: bounceOut;
}

@-webkit-keyframes bounceOutDown {
  20% {
    -webkit-transform: translate3d(0, 10px, 0);
    transform: translate3d(0, 10px, 0);
  }

  40%, 45% {
    opacity: 1;
    -webkit-transform: translate3d(0, -20px, 0);
    transform: translate3d(0, -20px, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, 2000px, 0);
    transform: translate3d(0, 2000px, 0);
  }
}

@keyframes bounceOutDown {
  20% {
    -webkit-transform: translate3d(0, 10px, 0);
    transform: translate3d(0, 10px, 0);
  }

  40%, 45% {
    opacity: 1;
    -webkit-transform: translate3d(0, -20px, 0);
    transform: translate3d(0, -20px, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, 2000px, 0);
    transform: translate3d(0, 2000px, 0);
  }
}

.bounceOutDown {
  -webkit-animation-name: bounceOutDown;
  animation-name: bounceOutDown;
}

@-webkit-keyframes bounceOutLeft {
  20% {
    opacity: 1;
    -webkit-transform: translate3d(20px, 0, 0);
    transform: translate3d(20px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(-2000px, 0, 0);
    transform: translate3d(-2000px, 0, 0);
  }
}

@keyframes bounceOutLeft {
  20% {
    opacity: 1;
    -webkit-transform: translate3d(20px, 0, 0);
    transform: translate3d(20px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(-2000px, 0, 0);
    transform: translate3d(-2000px, 0, 0);
  }
}

.bounceOutLeft {
  -webkit-animation-name: bounceOutLeft;
  animation-name: bounceOutLeft;
}

@-webkit-keyframes bounceOutRight {
  20% {
    opacity: 1;
    -webkit-transform: translate3d(-20px, 0, 0);
    transform: translate3d(-20px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(2000px, 0, 0);
    transform: translate3d(2000px, 0, 0);
  }
}

@keyframes bounceOutRight {
  20% {
    opacity: 1;
    -webkit-transform: translate3d(-20px, 0, 0);
    transform: translate3d(-20px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(2000px, 0, 0);
    transform: translate3d(2000px, 0, 0);
  }
}

.bounceOutRight {
  -webkit-animation-name: bounceOutRight;
  animation-name: bounceOutRight;
}

@-webkit-keyframes bounceOutUp {
  20% {
    -webkit-transform: translate3d(0, -10px, 0);
    transform: translate3d(0, -10px, 0);
  }

  40%, 45% {
    opacity: 1;
    -webkit-transform: translate3d(0, 20px, 0);
    transform: translate3d(0, 20px, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, -2000px, 0);
    transform: translate3d(0, -2000px, 0);
  }
}

@keyframes bounceOutUp {
  20% {
    -webkit-transform: translate3d(0, -10px, 0);
    transform: translate3d(0, -10px, 0);
  }

  40%, 45% {
    opacity: 1;
    -webkit-transform: translate3d(0, 20px, 0);
    transform: translate3d(0, 20px, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, -2000px, 0);
    transform: translate3d(0, -2000px, 0);
  }
}

.bounceOutUp {
  -webkit-animation-name: bounceOutUp;
  animation-name: bounceOutUp;
}

@-webkit-keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.fadeIn {
  -webkit-animation-name: fadeIn;
  animation-name: fadeIn;
}

@-webkit-keyframes fadeInDown {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInDown {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInDown {
  -webkit-animation-name: fadeInDown;
  animation-name: fadeInDown;
}

@-webkit-keyframes fadeInDownBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, -2000px, 0);
    transform: translate3d(0, -2000px, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInDownBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, -2000px, 0);
    transform: translate3d(0, -2000px, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInDownBig {
  -webkit-animation-name: fadeInDownBig;
  animation-name: fadeInDownBig;
}

@-webkit-keyframes fadeInLeft {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInLeft {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInLeft {
  -webkit-animation-name: fadeInLeft;
  animation-name: fadeInLeft;
}

@-webkit-keyframes fadeInLeftBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-2000px, 0, 0);
    transform: translate3d(-2000px, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInLeftBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-2000px, 0, 0);
    transform: translate3d(-2000px, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInLeftBig {
  -webkit-animation-name: fadeInLeftBig;
  animation-name: fadeInLeftBig;
}

@-webkit-keyframes fadeInRight {
  from {
    opacity: 0;
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInRight {
  from {
    opacity: 0;
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInRight {
  -webkit-animation-name: fadeInRight;
  animation-name: fadeInRight;
}

@-webkit-keyframes fadeInRightBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(2000px, 0, 0);
    transform: translate3d(2000px, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInRightBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(2000px, 0, 0);
    transform: translate3d(2000px, 0, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInRightBig {
  -webkit-animation-name: fadeInRightBig;
  animation-name: fadeInRightBig;
}

@-webkit-keyframes fadeInUp {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInUp {
  -webkit-animation-name: fadeInUp;
  animation-name: fadeInUp;
}

@-webkit-keyframes fadeInUpBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, 2000px, 0);
    transform: translate3d(0, 2000px, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes fadeInUpBig {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, 2000px, 0);
    transform: translate3d(0, 2000px, 0);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.fadeInUpBig {
  -webkit-animation-name: fadeInUpBig;
  animation-name: fadeInUpBig;
}

@-webkit-keyframes fadeOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

.fadeOut {
  -webkit-animation-name: fadeOut;
  animation-name: fadeOut;
}

@-webkit-keyframes fadeOutDown {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }
}

@keyframes fadeOutDown {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }
}

.fadeOutDown {
  -webkit-animation-name: fadeOutDown;
  animation-name: fadeOutDown;
}

@-webkit-keyframes fadeOutDownBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, 2000px, 0);
    transform: translate3d(0, 2000px, 0);
  }
}

@keyframes fadeOutDownBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, 2000px, 0);
    transform: translate3d(0, 2000px, 0);
  }
}

.fadeOutDownBig {
  -webkit-animation-name: fadeOutDownBig;
  animation-name: fadeOutDownBig;
}

@-webkit-keyframes fadeOutLeft {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }
}

@keyframes fadeOutLeft {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }
}

.fadeOutLeft {
  -webkit-animation-name: fadeOutLeft;
  animation-name: fadeOutLeft;
}

@-webkit-keyframes fadeOutLeftBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(-2000px, 0, 0);
    transform: translate3d(-2000px, 0, 0);
  }
}

@keyframes fadeOutLeftBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(-2000px, 0, 0);
    transform: translate3d(-2000px, 0, 0);
  }
}

.fadeOutLeftBig {
  -webkit-animation-name: fadeOutLeftBig;
  animation-name: fadeOutLeftBig;
}

@-webkit-keyframes fadeOutRight {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
  }
}

@keyframes fadeOutRight {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
  }
}

.fadeOutRight {
  -webkit-animation-name: fadeOutRight;
  animation-name: fadeOutRight;
}

@-webkit-keyframes fadeOutRightBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(2000px, 0, 0);
    transform: translate3d(2000px, 0, 0);
  }
}

@keyframes fadeOutRightBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(2000px, 0, 0);
    transform: translate3d(2000px, 0, 0);
  }
}

.fadeOutRightBig {
  -webkit-animation-name: fadeOutRightBig;
  animation-name: fadeOutRightBig;
}

@-webkit-keyframes fadeOutUp {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
  }
}

@keyframes fadeOutUp {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
  }
}

.fadeOutUp {
  -webkit-animation-name: fadeOutUp;
  animation-name: fadeOutUp;
}

@-webkit-keyframes fadeOutUpBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, -2000px, 0);
    transform: translate3d(0, -2000px, 0);
  }
}

@keyframes fadeOutUpBig {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(0, -2000px, 0);
    transform: translate3d(0, -2000px, 0);
  }
}

.fadeOutUpBig {
  -webkit-animation-name: fadeOutUpBig;
  animation-name: fadeOutUpBig;
}

@-webkit-keyframes flip {
  from {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -360deg);
    -webkit-animation-timing-function: ease-out;
    animation-timing-function: ease-out;
  }

  40% {
    -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
    transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
    -webkit-animation-timing-function: ease-out;
    animation-timing-function: ease-out;
  }

  50% {
    -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
    transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  80% {
    -webkit-transform: perspective(400px) scale3d(.95, .95, .95);
    transform: perspective(400px) scale3d(.95, .95, .95);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  to {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }
}

@keyframes flip {
  from {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -360deg);
    -webkit-animation-timing-function: ease-out;
    animation-timing-function: ease-out;
  }

  40% {
    -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
    transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
    -webkit-animation-timing-function: ease-out;
    animation-timing-function: ease-out;
  }

  50% {
    -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
    transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  80% {
    -webkit-transform: perspective(400px) scale3d(.95, .95, .95);
    transform: perspective(400px) scale3d(.95, .95, .95);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  to {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }
}

.animated.flip {
  -webkit-backface-visibility: visible;
  backface-visibility: visible;
  -webkit-animation-name: flip;
  animation-name: flip;
}

@-webkit-keyframes flipInX {
  from {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
    opacity: 0;
  }

  40% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  60% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
    transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
    opacity: 1;
  }

  80% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
    transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
  }

  to {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }
}

@keyframes flipInX {
  from {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
    opacity: 0;
  }

  40% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  60% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
    transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
    opacity: 1;
  }

  80% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
    transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
  }

  to {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }
}

.flipInX {
  -webkit-backface-visibility: visible !important;
  backface-visibility: visible !important;
  -webkit-animation-name: flipInX;
  animation-name: flipInX;
}

@-webkit-keyframes flipInY {
  from {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
    opacity: 0;
  }

  40% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  60% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
    transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
    opacity: 1;
  }

  80% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
  }

  to {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }
}

@keyframes flipInY {
  from {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
    opacity: 0;
  }

  40% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -20deg);
    -webkit-animation-timing-function: ease-in;
    animation-timing-function: ease-in;
  }

  60% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
    transform: perspective(400px) rotate3d(0, 1, 0, 10deg);
    opacity: 1;
  }

  80% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -5deg);
  }

  to {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }
}

.flipInY {
  -webkit-backface-visibility: visible !important;
  backface-visibility: visible !important;
  -webkit-animation-name: flipInY;
  animation-name: flipInY;
}

@-webkit-keyframes flipOutX {
  from {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }

  30% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    opacity: 1;
  }

  to {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    opacity: 0;
  }
}

@keyframes flipOutX {
  from {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }

  30% {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
    opacity: 1;
  }

  to {
    -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
    opacity: 0;
  }
}

.flipOutX {
  -webkit-animation-name: flipOutX;
  animation-name: flipOutX;
  -webkit-backface-visibility: visible !important;
  backface-visibility: visible !important;
}

@-webkit-keyframes flipOutY {
  from {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }

  30% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
    opacity: 1;
  }

  to {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    opacity: 0;
  }
}

@keyframes flipOutY {
  from {
    -webkit-transform: perspective(400px);
    transform: perspective(400px);
  }

  30% {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
    transform: perspective(400px) rotate3d(0, 1, 0, -15deg);
    opacity: 1;
  }

  to {
    -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    transform: perspective(400px) rotate3d(0, 1, 0, 90deg);
    opacity: 0;
  }
}

.flipOutY {
  -webkit-backface-visibility: visible !important;
  backface-visibility: visible !important;
  -webkit-animation-name: flipOutY;
  animation-name: flipOutY;
}

@-webkit-keyframes lightSpeedIn {
  from {
    -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg);
    transform: translate3d(100%, 0, 0) skewX(-30deg);
    opacity: 0;
  }

  60% {
    -webkit-transform: skewX(20deg);
    transform: skewX(20deg);
    opacity: 1;
  }

  80% {
    -webkit-transform: skewX(-5deg);
    transform: skewX(-5deg);
    opacity: 1;
  }

  to {
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

@keyframes lightSpeedIn {
  from {
    -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg);
    transform: translate3d(100%, 0, 0) skewX(-30deg);
    opacity: 0;
  }

  60% {
    -webkit-transform: skewX(20deg);
    transform: skewX(20deg);
    opacity: 1;
  }

  80% {
    -webkit-transform: skewX(-5deg);
    transform: skewX(-5deg);
    opacity: 1;
  }

  to {
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

.lightSpeedIn {
  -webkit-animation-name: lightSpeedIn;
  animation-name: lightSpeedIn;
  -webkit-animation-timing-function: ease-out;
  animation-timing-function: ease-out;
}

@-webkit-keyframes lightSpeedOut {
  from {
    opacity: 1;
  }

  to {
    -webkit-transform: translate3d(100%, 0, 0) skewX(30deg);
    transform: translate3d(100%, 0, 0) skewX(30deg);
    opacity: 0;
  }
}

@keyframes lightSpeedOut {
  from {
    opacity: 1;
  }

  to {
    -webkit-transform: translate3d(100%, 0, 0) skewX(30deg);
    transform: translate3d(100%, 0, 0) skewX(30deg);
    opacity: 0;
  }
}

.lightSpeedOut {
  -webkit-animation-name: lightSpeedOut;
  animation-name: lightSpeedOut;
  -webkit-animation-timing-function: ease-in;
  animation-timing-function: ease-in;
}

@-webkit-keyframes rotateIn {
  from {
    -webkit-transform-origin: center;
    transform-origin: center;
    -webkit-transform: rotate3d(0, 0, 1, -200deg);
    transform: rotate3d(0, 0, 1, -200deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: center;
    transform-origin: center;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

@keyframes rotateIn {
  from {
    -webkit-transform-origin: center;
    transform-origin: center;
    -webkit-transform: rotate3d(0, 0, 1, -200deg);
    transform: rotate3d(0, 0, 1, -200deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: center;
    transform-origin: center;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

.rotateIn {
  -webkit-animation-name: rotateIn;
  animation-name: rotateIn;
}

@-webkit-keyframes rotateInDownLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, -45deg);
    transform: rotate3d(0, 0, 1, -45deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

@keyframes rotateInDownLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, -45deg);
    transform: rotate3d(0, 0, 1, -45deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

.rotateInDownLeft {
  -webkit-animation-name: rotateInDownLeft;
  animation-name: rotateInDownLeft;
}

@-webkit-keyframes rotateInDownRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, 45deg);
    transform: rotate3d(0, 0, 1, 45deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

@keyframes rotateInDownRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, 45deg);
    transform: rotate3d(0, 0, 1, 45deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

.rotateInDownRight {
  -webkit-animation-name: rotateInDownRight;
  animation-name: rotateInDownRight;
}

@-webkit-keyframes rotateInUpLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, 45deg);
    transform: rotate3d(0, 0, 1, 45deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

@keyframes rotateInUpLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, 45deg);
    transform: rotate3d(0, 0, 1, 45deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

.rotateInUpLeft {
  -webkit-animation-name: rotateInUpLeft;
  animation-name: rotateInUpLeft;
}

@-webkit-keyframes rotateInUpRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, -90deg);
    transform: rotate3d(0, 0, 1, -90deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

@keyframes rotateInUpRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, -90deg);
    transform: rotate3d(0, 0, 1, -90deg);
    opacity: 0;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: none;
    transform: none;
    opacity: 1;
  }
}

.rotateInUpRight {
  -webkit-animation-name: rotateInUpRight;
  animation-name: rotateInUpRight;
}

@-webkit-keyframes rotateOut {
  from {
    -webkit-transform-origin: center;
    transform-origin: center;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: center;
    transform-origin: center;
    -webkit-transform: rotate3d(0, 0, 1, 200deg);
    transform: rotate3d(0, 0, 1, 200deg);
    opacity: 0;
  }
}

@keyframes rotateOut {
  from {
    -webkit-transform-origin: center;
    transform-origin: center;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: center;
    transform-origin: center;
    -webkit-transform: rotate3d(0, 0, 1, 200deg);
    transform: rotate3d(0, 0, 1, 200deg);
    opacity: 0;
  }
}

.rotateOut {
  -webkit-animation-name: rotateOut;
  animation-name: rotateOut;
}

@-webkit-keyframes rotateOutDownLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, 45deg);
    transform: rotate3d(0, 0, 1, 45deg);
    opacity: 0;
  }
}

@keyframes rotateOutDownLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, 45deg);
    transform: rotate3d(0, 0, 1, 45deg);
    opacity: 0;
  }
}

.rotateOutDownLeft {
  -webkit-animation-name: rotateOutDownLeft;
  animation-name: rotateOutDownLeft;
}

@-webkit-keyframes rotateOutDownRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, -45deg);
    transform: rotate3d(0, 0, 1, -45deg);
    opacity: 0;
  }
}

@keyframes rotateOutDownRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, -45deg);
    transform: rotate3d(0, 0, 1, -45deg);
    opacity: 0;
  }
}

.rotateOutDownRight {
  -webkit-animation-name: rotateOutDownRight;
  animation-name: rotateOutDownRight;
}

@-webkit-keyframes rotateOutUpLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, -45deg);
    transform: rotate3d(0, 0, 1, -45deg);
    opacity: 0;
  }
}

@keyframes rotateOutUpLeft {
  from {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
    -webkit-transform: rotate3d(0, 0, 1, -45deg);
    transform: rotate3d(0, 0, 1, -45deg);
    opacity: 0;
  }
}

.rotateOutUpLeft {
  -webkit-animation-name: rotateOutUpLeft;
  animation-name: rotateOutUpLeft;
}

@-webkit-keyframes rotateOutUpRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, 90deg);
    transform: rotate3d(0, 0, 1, 90deg);
    opacity: 0;
  }
}

@keyframes rotateOutUpRight {
  from {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    opacity: 1;
  }

  to {
    -webkit-transform-origin: right bottom;
    transform-origin: right bottom;
    -webkit-transform: rotate3d(0, 0, 1, 90deg);
    transform: rotate3d(0, 0, 1, 90deg);
    opacity: 0;
  }
}

.rotateOutUpRight {
  -webkit-animation-name: rotateOutUpRight;
  animation-name: rotateOutUpRight;
}

@-webkit-keyframes hinge {
  0% {
    -webkit-transform-origin: top left;
    transform-origin: top left;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out;
  }

  20%, 60% {
    -webkit-transform: rotate3d(0, 0, 1, 80deg);
    transform: rotate3d(0, 0, 1, 80deg);
    -webkit-transform-origin: top left;
    transform-origin: top left;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out;
  }

  40%, 80% {
    -webkit-transform: rotate3d(0, 0, 1, 60deg);
    transform: rotate3d(0, 0, 1, 60deg);
    -webkit-transform-origin: top left;
    transform-origin: top left;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out;
    opacity: 1;
  }

  to {
    -webkit-transform: translate3d(0, 700px, 0);
    transform: translate3d(0, 700px, 0);
    opacity: 0;
  }
}

@keyframes hinge {
  0% {
    -webkit-transform-origin: top left;
    transform-origin: top left;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out;
  }

  20%, 60% {
    -webkit-transform: rotate3d(0, 0, 1, 80deg);
    transform: rotate3d(0, 0, 1, 80deg);
    -webkit-transform-origin: top left;
    transform-origin: top left;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out;
  }

  40%, 80% {
    -webkit-transform: rotate3d(0, 0, 1, 60deg);
    transform: rotate3d(0, 0, 1, 60deg);
    -webkit-transform-origin: top left;
    transform-origin: top left;
    -webkit-animation-timing-function: ease-in-out;
    animation-timing-function: ease-in-out;
    opacity: 1;
  }

  to {
    -webkit-transform: translate3d(0, 700px, 0);
    transform: translate3d(0, 700px, 0);
    opacity: 0;
  }
}

.hinge {
  -webkit-animation-name: hinge;
  animation-name: hinge;
}

/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */

@-webkit-keyframes rollIn {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
    transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

@keyframes rollIn {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
    transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg);
  }

  to {
    opacity: 1;
    -webkit-transform: none;
    transform: none;
  }
}

.rollIn {
  -webkit-animation-name: rollIn;
  animation-name: rollIn;
}

/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */

@-webkit-keyframes rollOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
    transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
  }
}

@keyframes rollOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
    -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
    transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg);
  }
}

.rollOut {
  -webkit-animation-name: rollOut;
  animation-name: rollOut;
}

@-webkit-keyframes zoomIn {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }

  50% {
    opacity: 1;
  }
}

@keyframes zoomIn {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }

  50% {
    opacity: 1;
  }
}

.zoomIn {
  -webkit-animation-name: zoomIn;
  animation-name: zoomIn;
}

@-webkit-keyframes zoomInDown {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

@keyframes zoomInDown {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

.zoomInDown {
  -webkit-animation-name: zoomInDown;
  animation-name: zoomInDown;
}

@-webkit-keyframes zoomInLeft {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
    transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

@keyframes zoomInLeft {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
    transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

.zoomInLeft {
  -webkit-animation-name: zoomInLeft;
  animation-name: zoomInLeft;
}

@-webkit-keyframes zoomInRight {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
    transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

@keyframes zoomInRight {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
    transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

.zoomInRight {
  -webkit-animation-name: zoomInRight;
  animation-name: zoomInRight;
}

@-webkit-keyframes zoomInUp {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

@keyframes zoomInUp {
  from {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  60% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

.zoomInUp {
  -webkit-animation-name: zoomInUp;
  animation-name: zoomInUp;
}

@-webkit-keyframes zoomOut {
  from {
    opacity: 1;
  }

  50% {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }

  to {
    opacity: 0;
  }
}

@keyframes zoomOut {
  from {
    opacity: 1;
  }

  50% {
    opacity: 0;
    -webkit-transform: scale3d(.3, .3, .3);
    transform: scale3d(.3, .3, .3);
  }

  to {
    opacity: 0;
  }
}

.zoomOut {
  -webkit-animation-name: zoomOut;
  animation-name: zoomOut;
}

@-webkit-keyframes zoomOutDown {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  to {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
    -webkit-transform-origin: center bottom;
    transform-origin: center bottom;
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

@keyframes zoomOutDown {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  to {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
    -webkit-transform-origin: center bottom;
    transform-origin: center bottom;
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

.zoomOutDown {
  -webkit-animation-name: zoomOutDown;
  animation-name: zoomOutDown;
}

@-webkit-keyframes zoomOutLeft {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: scale(.1) translate3d(-2000px, 0, 0);
    transform: scale(.1) translate3d(-2000px, 0, 0);
    -webkit-transform-origin: left center;
    transform-origin: left center;
  }
}

@keyframes zoomOutLeft {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: scale(.1) translate3d(-2000px, 0, 0);
    transform: scale(.1) translate3d(-2000px, 0, 0);
    -webkit-transform-origin: left center;
    transform-origin: left center;
  }
}

.zoomOutLeft {
  -webkit-animation-name: zoomOutLeft;
  animation-name: zoomOutLeft;
}

@-webkit-keyframes zoomOutRight {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: scale(.1) translate3d(2000px, 0, 0);
    transform: scale(.1) translate3d(2000px, 0, 0);
    -webkit-transform-origin: right center;
    transform-origin: right center;
  }
}

@keyframes zoomOutRight {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
    transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
  }

  to {
    opacity: 0;
    -webkit-transform: scale(.1) translate3d(2000px, 0, 0);
    transform: scale(.1) translate3d(2000px, 0, 0);
    -webkit-transform-origin: right center;
    transform-origin: right center;
  }
}

.zoomOutRight {
  -webkit-animation-name: zoomOutRight;
  animation-name: zoomOutRight;
}

@-webkit-keyframes zoomOutUp {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  to {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
    -webkit-transform-origin: center bottom;
    transform-origin: center bottom;
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

@keyframes zoomOutUp {
  40% {
    opacity: 1;
    -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
    -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
    animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
  }

  to {
    opacity: 0;
    -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
    transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
    -webkit-transform-origin: center bottom;
    transform-origin: center bottom;
    -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
    animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
  }
}

.zoomOutUp {
  -webkit-animation-name: zoomOutUp;
  animation-name: zoomOutUp;
}

@-webkit-keyframes slideInDown {
  from {
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@keyframes slideInDown {
  from {
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

.slideInDown {
  -webkit-animation-name: slideInDown;
  animation-name: slideInDown;
}

@-webkit-keyframes slideInLeft {
  from {
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@keyframes slideInLeft {
  from {
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

.slideInLeft {
  -webkit-animation-name: slideInLeft;
  animation-name: slideInLeft;
}

@-webkit-keyframes slideInRight {
  from {
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@keyframes slideInRight {
  from {
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

.slideInRight {
  -webkit-animation-name: slideInRight;
  animation-name: slideInRight;
}

@-webkit-keyframes slideInUp {
  from {
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@keyframes slideInUp {
  from {
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

.slideInUp {
  -webkit-animation-name: slideInUp;
  animation-name: slideInUp;
}

@-webkit-keyframes slideOutDown {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }
}

@keyframes slideOutDown {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(0, 100%, 0);
    transform: translate3d(0, 100%, 0);
  }
}

.slideOutDown {
  -webkit-animation-name: slideOutDown;
  animation-name: slideOutDown;
}

@-webkit-keyframes slideOutLeft {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }
}

@keyframes slideOutLeft {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }
}

.slideOutLeft {
  -webkit-animation-name: slideOutLeft;
  animation-name: slideOutLeft;
}

@-webkit-keyframes slideOutRight {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
  }
}

@keyframes slideOutRight {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(100%, 0, 0);
    transform: translate3d(100%, 0, 0);
  }
}

.slideOutRight {
  -webkit-animation-name: slideOutRight;
  animation-name: slideOutRight;
}

@-webkit-keyframes slideOutUp {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
  }
}

@keyframes slideOutUp {
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(0, -100%, 0);
    transform: translate3d(0, -100%, 0);
  }
}

.slideOutUp {
  -webkit-animation-name: slideOutUp;
  animation-name: slideOutUp;
}


================================================
FILE: yyets/worker/public/css/3rd/icons.css
================================================
@charset "utf-8";
@IMPORT url("../font-awesome.min.css");



================================================
FILE: yyets/worker/public/css/3rd/widgets.css
================================================
@charset "utf-8";




================================================
FILE: yyets/worker/public/css/aYin.css
================================================
@charset "utf-8";

/*webkit核心浏览器滚动条设置*/
/*
::-webkit-scrollbar{width: 10px;height:10px;}
::-webkit-scrollbar-track{border-radius: 0px;background: rgba(0,0,0,0.1);}
::-webkit-scrollbar-thumb{border-radius: 5px;background: rgba(0,0,0,0.2);}
::-webkit-scrollbar-thumb:hover{border-radius: 5px;background: rgba(0,0,0,0.4);}
*/


================================================
FILE: yyets/worker/public/css/data.json
================================================
{
  "status": 1,
  "info": "OK",
  "data": {
    "info": {
      "id": 10060,
      "cnname": "铁皮人",
      "enname": "Tin Man",
      "aliasname": "",
      "channel": "tv",
      "channel_cn": "美剧",
      "area": "美国",
      "show_type": "",
      "expire": "1610371223",
      "views": "0"
    },
    "list": [
      {
        "season_num": "102",
        "season_cn": "MINI剧",
        "items": {
          "APP": [
            {
              "itemid": "323953",
              "episode": "3",
              "name": "yyets://N=铁皮人.Tin.Man.Part3.中英字幕.BD.720p-人人影视.mp4|S=1327396629|H=1dd3a8b0ab765f4fd77f62e15b6635b932f5f9f7|",
              "size": "",
   
Download .txt
gitextract_3u5rcyng/

├── .dockerignore
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       └── docker.yaml
├── .gitignore
├── API.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── conf/
│   ├── yyets.dmesg.app.conf
│   └── yyets.env
├── docker-compose.yml
├── requirements.txt
├── scripts/
│   ├── install.sh
│   └── migrate_sub.py
├── setup.py
├── tea.yaml
├── yyets/
│   ├── BagAndDrag/
│   │   ├── README.md
│   │   ├── bag.py
│   │   ├── cfkv.py
│   │   ├── convert_db.py
│   │   ├── create_db.py
│   │   ├── drag.py
│   │   ├── sample.json
│   │   └── zimuxia/
│   │       ├── convert_db.py
│   │       └── zimuxia.py
│   ├── __init__.py
│   ├── healthcheck/
│   │   ├── check.py
│   │   └── restart_service.py
│   ├── management/
│   │   ├── format.json
│   │   └── ui.py
│   └── worker/
│       ├── .cargo-ok
│       ├── README.md
│       ├── public/
│       │   ├── 404.html
│       │   ├── css/
│       │   │   ├── 3rd/
│       │   │   │   ├── animate.css
│       │   │   │   ├── icons.css
│       │   │   │   └── widgets.css
│       │   │   ├── aYin.css
│       │   │   ├── data.json
│       │   │   ├── down-list-20180530.css
│       │   │   ├── index.json
│       │   │   └── jquery.mCustomScrollbar.css
│       │   ├── fonts/
│       │   │   └── test.txt
│       │   ├── index.html
│       │   ├── js/
│       │   │   ├── aYin.js
│       │   │   ├── rshare.js
│       │   │   ├── search.js
│       │   │   └── vue.js
│       │   ├── resource.html
│       │   └── search.html
│       ├── workers-site/
│       │   ├── index.js
│       │   └── package.json
│       └── wrangler.toml
├── yyetsbot/
│   ├── config.py
│   ├── fansub.py
│   ├── utils.py
│   └── yyetsbot.py
└── yyetsweb/
    ├── README.md
    ├── YYeTs-grafana.json
    ├── commands/
    │   ├── common.py
    │   ├── douban_fix.py
    │   ├── grafana_test_data.py
    │   └── share_excel.py
    ├── common/
    │   ├── __init__.py
    │   ├── dump_db.py
    │   ├── sync.py
    │   └── utils.py
    ├── databases/
    │   ├── __init__.py
    │   ├── base.py
    │   ├── comment.py
    │   ├── douban.py
    │   ├── grafana.py
    │   ├── oauth.py
    │   ├── other.py
    │   ├── resources.py
    │   └── user.py
    ├── go.mod
    ├── go.sum
    ├── handlers/
    │   ├── __init__.py
    │   ├── base.py
    │   ├── comment.py
    │   ├── douban.py
    │   ├── grafana.py
    │   ├── oauth.py
    │   ├── other.py
    │   ├── resources.py
    │   └── user.py
    ├── server.go
    ├── server.py
    └── tests/
        └── router_test.py
Download .txt
SYMBOL INDEX (829 symbols across 41 files)

FILE: setup.py
  class UploadCommand (line 56) | class UploadCommand(Command):
    method status (line 63) | def status(s):
    method initialize_options (line 67) | def initialize_options(self):
    method finalize_options (line 70) | def finalize_options(self):
    method run (line 73) | def run(self):

FILE: yyets/BagAndDrag/bag.py
  function save_cookies (line 37) | def save_cookies(requests_cookiejar):
  function load_cookies (line 42) | def load_cookies():
  function login (line 48) | def login():
  function is_cookie_valid (line 62) | def is_cookie_valid() -> bool:
  function insert_db (line 68) | def insert_db(data: dict):
  function insert_error (line 90) | def insert_error(rid, tb):
  function __load_sample (line 99) | def __load_sample():

FILE: yyets/BagAndDrag/cfkv.py
  function convert_kv (line 25) | def convert_kv():
  function verify_kv_data (line 42) | def verify_kv_data():
  function dump_index (line 54) | def dump_index():
  function generate_command (line 73) | def generate_command():

FILE: yyets/BagAndDrag/convert_db.py
  function create_sqlite_database (line 30) | def create_sqlite_database():
  function clear_mongodb (line 53) | def clear_mongodb():
  function sqlite_insert (line 57) | def sqlite_insert(data: List[dict]):
  function mongodb_insert (line 65) | def mongodb_insert(data: List[dict]):
  function main (line 76) | def main():

FILE: yyets/BagAndDrag/drag.py
  function get_api_json (line 38) | def get_api_json(resource_id):
  function main (line 54) | def main():

FILE: yyets/BagAndDrag/zimuxia/convert_db.py
  function clear_mongodb (line 28) | def clear_mongodb():
  function clear_mysql (line 32) | def clear_mysql():
  function mysql_insert (line 37) | def mysql_insert(data: List[dict]):
  function mongodb_insert (line 45) | def mongodb_insert(data: List[dict]):
  function main (line 51) | def main():

FILE: yyets/BagAndDrag/zimuxia/zimuxia.py
  function get_list (line 30) | def get_list():
  function get_episode (line 38) | def get_episode(html_text):
  function write_json (line 50) | def write_json():

FILE: yyets/__init__.py
  class Resource (line 23) | class Resource:
    method __init__ (line 24) | def __init__(self):
    method __str__ (line 28) | def __str__(self):
  class YYeTs (line 32) | class YYeTs:
    method __init__ (line 33) | def __init__(self, keyword: "str"):
    method search (line 40) | def search(self):
    method fetch (line 49) | def fetch(self, info):
    method __str__ (line 56) | def __str__(self):

FILE: yyets/healthcheck/check.py
  function my_event_handler (line 31) | async def my_event_handler(event):
  function send_health_check (line 36) | async def send_health_check():
  function bot_warning (line 45) | async def bot_warning():
  function website_check (line 53) | async def website_check():

FILE: yyets/management/ui.py
  function get_value (line 79) | def get_value():

FILE: yyets/worker/public/js/aYin.js
  function oldClass (line 86) | function oldClass(obj){
  function actions (line 91) | function actions(obj,what){
  function raw (line 269) | function raw(s) {return s;}
  function decoded (line 270) | function decoded(s) {return decodeURIComponent(s.replace(pluses, ' '));}
  function carousel (line 378) | function carousel(he,co,ti,di,ch,cw){
  function carousel (line 438) | function carousel($eo,opts,timer){

FILE: yyets/worker/public/js/search.js
  function loadJSON (line 12) | function loadJSON(path, success, error) {
  function doSearch (line 29) | function doSearch() {
  function reloadIndex (line 53) | function reloadIndex() {

FILE: yyets/worker/public/js/vue.js
  function isUndef (line 18) | function isUndef (v) {
  function isDef (line 22) | function isDef (v) {
  function isTrue (line 26) | function isTrue (v) {
  function isFalse (line 30) | function isFalse (v) {
  function isPrimitive (line 37) | function isPrimitive (value) {
  function isObject (line 52) | function isObject (obj) {
  function toRawType (line 61) | function toRawType (value) {
  function isPlainObject (line 69) | function isPlainObject (obj) {
  function isRegExp (line 73) | function isRegExp (v) {
  function isValidArrayIndex (line 80) | function isValidArrayIndex (val) {
  function isPromise (line 85) | function isPromise (val) {
  function toString (line 96) | function toString (val) {
  function toNumber (line 108) | function toNumber (val) {
  function makeMap (line 117) | function makeMap (
  function remove (line 144) | function remove (arr, item) {
  function hasOwn (line 157) | function hasOwn (obj, key) {
  function cached (line 164) | function cached (fn) {
  function polyfillBind (line 204) | function polyfillBind (fn, ctx) {
  function nativeBind (line 218) | function nativeBind (fn, ctx) {
  function toArray (line 229) | function toArray (list, start) {
  function extend (line 242) | function extend (to, _from) {
  function toObject (line 252) | function toObject (arr) {
  function noop (line 269) | function noop (a, b, c) {}
  function genStaticKeys (line 286) | function genStaticKeys (modules) {
  function looseEqual (line 296) | function looseEqual (a, b) {
  function looseIndexOf (line 336) | function looseIndexOf (arr, val) {
  function once (line 346) | function once (fn) {
  function isReserved (line 489) | function isReserved (str) {
  function def (line 497) | function def (obj, key, val, enumerable) {
  function parsePath (line 510) | function parsePath (path) {
  function isNative (line 581) | function isNative (Ctor) {
  function Set (line 597) | function Set () {
  function pushTarget (line 755) | function pushTarget (target) {
  function popTarget (line 760) | function popTarget () {
  function createTextVNode (line 821) | function createTextVNode (val) {
  function cloneVNode (line 829) | function cloneVNode (vnode) {
  function toggleObserving (line 912) | function toggleObserving (value) {
  function protoAugment (line 966) | function protoAugment (target, src) {
  function copyAugment (line 977) | function copyAugment (target, src, keys) {
  function observe (line 989) | function observe (value, asRootData) {
  function defineReactive$$1 (line 1014) | function defineReactive$$1 (
  function set (line 1080) | function set (target, key, val) {
  function del (line 1114) | function del (target, key) {
  function dependArray (line 1145) | function dependArray (value) {
  function mergeData (line 1182) | function mergeData (to, from) {
  function mergeDataOrFn (line 1212) | function mergeDataOrFn (
  function mergeHook (line 1279) | function mergeHook (
  function dedupeHooks (line 1295) | function dedupeHooks (hooks) {
  function mergeAssets (line 1316) | function mergeAssets (
  function checkComponents (line 1406) | function checkComponents (options) {
  function validateComponentName (line 1412) | function validateComponentName (name) {
  function normalizeProps (line 1431) | function normalizeProps (options, vm) {
  function normalizeInject (line 1468) | function normalizeInject (options, vm) {
  function normalizeDirectives (line 1495) | function normalizeDirectives (options) {
  function assertObjectType (line 1507) | function assertObjectType (name, value, vm) {
  function mergeOptions (line 1521) | function mergeOptions (
  function resolveAsset (line 1575) | function resolveAsset (
  function validateProp (line 1607) | function validateProp (
  function getPropDefaultValue (line 1649) | function getPropDefaultValue (vm, prop, key) {
  function assertProp (line 1682) | function assertProp (
  function assertType (line 1733) | function assertType (value, type) {
  function getType (line 1761) | function getType (fn) {
  function isSameType (line 1766) | function isSameType (a, b) {
  function getTypeIndex (line 1770) | function getTypeIndex (type, expectedTypes) {
  function getInvalidTypeMessage (line 1782) | function getInvalidTypeMessage (name, value, expectedTypes) {
  function styleValue (line 1803) | function styleValue (value, type) {
  function isExplicable (line 1813) | function isExplicable (value) {
  function isBoolean (line 1818) | function isBoolean () {
  function handleError (line 1827) | function handleError (err, vm, info) {
  function invokeWithErrorHandling (line 1854) | function invokeWithErrorHandling (
  function globalHandleError (line 1876) | function globalHandleError (err, vm, info) {
  function logError (line 1891) | function logError (err, vm, info) {
  function flushCallbacks (line 1910) | function flushCallbacks () {
  function nextTick (line 1984) | function nextTick (cb, ctx) {
  function traverse (line 2131) | function traverse (val) {
  function _traverse (line 2136) | function _traverse (val, seen) {
  function createFnInvoker (line 2176) | function createFnInvoker (fns, vm) {
  function updateListeners (line 2195) | function updateListeners (
  function mergeVNodeHook (line 2236) | function mergeVNodeHook (def, hookKey, hook) {
  function extractPropsFromVNodeData (line 2271) | function extractPropsFromVNodeData (
  function checkProp (line 2312) | function checkProp (
  function simpleNormalizeChildren (line 2351) | function simpleNormalizeChildren (children) {
  function normalizeChildren (line 2364) | function normalizeChildren (children) {
  function isTextNode (line 2372) | function isTextNode (node) {
  function normalizeArrayChildren (line 2376) | function normalizeArrayChildren (children, nestedIndex) {
  function initProvide (line 2426) | function initProvide (vm) {
  function initInjections (line 2435) | function initInjections (vm) {
  function resolveInject (line 2456) | function resolveInject (inject, vm) {
  function resolveSlots (line 2499) | function resolveSlots (
  function isWhitespace (line 2539) | function isWhitespace (node) {
  function normalizeScopedSlots (line 2545) | function normalizeScopedSlots (
  function normalizeScopedSlot (line 2595) | function normalizeScopedSlot(normalSlots, key, fn) {
  function proxyNormalSlot (line 2620) | function proxyNormalSlot(slots, key) {
  function renderList (line 2629) | function renderList (
  function renderSlot (line 2674) | function renderSlot (
  function resolveFilter (line 2711) | function resolveFilter (id) {
  function isKeyNotMatch (line 2717) | function isKeyNotMatch (expect, actual) {
  function checkKeyCodes (line 2730) | function checkKeyCodes (
  function bindObjectProps (line 2752) | function bindObjectProps (
  function renderStatic (line 2808) | function renderStatic (
  function markOnce (line 2833) | function markOnce (
  function markStatic (line 2842) | function markStatic (
  function markStaticNode (line 2858) | function markStaticNode (node, key, isOnce) {
  function bindObjectListeners (line 2866) | function bindObjectListeners (data, value) {
  function resolveScopedSlots (line 2887) | function resolveScopedSlots (
  function bindDynamicKeys (line 2915) | function bindDynamicKeys (baseObj, values) {
  function prependModifier (line 2934) | function prependModifier (value, symbol) {
  function installRenderHelpers (line 2940) | function installRenderHelpers (target) {
  function FunctionalRenderContext (line 2962) | function FunctionalRenderContext (
  function createFunctionalComponent (line 3038) | function createFunctionalComponent (
  function cloneAndMarkFunctionalResult (line 3079) | function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, ...
  function mergeProps (line 3095) | function mergeProps (to, from) {
  function createComponent (line 3176) | function createComponent (
  function createComponentInstanceForVnode (line 3275) | function createComponentInstanceForVnode (
  function installComponentHooks (line 3293) | function installComponentHooks (data) {
  function mergeHook$1 (line 3305) | function mergeHook$1 (f1, f2) {
  function transformModel (line 3317) | function transformModel (options, data) {
  function createElement (line 3344) | function createElement (
  function _createElement (line 3363) | function _createElement (
  function applyNS (line 3453) | function applyNS (vnode, ns, force) {
  function registerDeepBindings (line 3474) | function registerDeepBindings (data) {
  function initRender (line 3485) | function initRender (vm) {
  function renderMixin (line 3519) | function renderMixin (Vue) {
  function ensureCtor (line 3593) | function ensureCtor (comp, base) {
  function createAsyncPlaceholder (line 3605) | function createAsyncPlaceholder (
  function resolveAsyncComponent (line 3618) | function resolveAsyncComponent (
  function isAsyncPlaceholder (line 3742) | function isAsyncPlaceholder (node) {
  function getFirstComponentChild (line 3748) | function getFirstComponentChild (children) {
  function initEvents (line 3763) | function initEvents (vm) {
  function add (line 3775) | function add (event, fn) {
  function remove$1 (line 3779) | function remove$1 (event, fn) {
  function createOnceHandler (line 3783) | function createOnceHandler (event, fn) {
  function updateComponentListeners (line 3793) | function updateComponentListeners (
  function eventsMixin (line 3803) | function eventsMixin (Vue) {
  function setActiveInstance (line 3901) | function setActiveInstance(vm) {
  function initLifecycle (line 3909) | function initLifecycle (vm) {
  function lifecycleMixin (line 3935) | function lifecycleMixin (Vue) {
  function mountComponent (line 4018) | function mountComponent (
  function updateChildComponent (line 4092) | function updateChildComponent (
  function isInInactiveTree (line 4172) | function isInInactiveTree (vm) {
  function activateChildComponent (line 4179) | function activateChildComponent (vm, direct) {
  function deactivateChildComponent (line 4197) | function deactivateChildComponent (vm, direct) {
  function callHook (line 4213) | function callHook (vm, hook) {
  function resetSchedulerState (line 4244) | function resetSchedulerState () {
  function flushSchedulerQueue (line 4287) | function flushSchedulerQueue () {
  function callUpdatedHooks (line 4346) | function callUpdatedHooks (queue) {
  function queueActivatedComponent (line 4361) | function queueActivatedComponent (vm) {
  function callActivatedHooks (line 4368) | function callActivatedHooks (queue) {
  function queueWatcher (line 4380) | function queueWatcher (watcher) {
  function proxy (line 4625) | function proxy (target, sourceKey, key) {
  function initState (line 4635) | function initState (vm) {
  function initProps (line 4651) | function initProps (vm, propsOptions) {
  function initData (line 4699) | function initData (vm) {
  function getData (line 4741) | function getData (data, vm) {
  function initComputed (line 4756) | function initComputed (vm, computed) {
  function defineComputed (line 4797) | function defineComputed (
  function createComputedGetter (line 4827) | function createComputedGetter (key) {
  function createGetterInvoker (line 4842) | function createGetterInvoker(fn) {
  function initMethods (line 4848) | function initMethods (vm, methods) {
  function initWatch (line 4876) | function initWatch (vm, watch) {
  function createWatcher (line 4889) | function createWatcher (
  function stateMixin (line 4905) | function stateMixin (Vue) {
  function initMixin (line 4960) | function initMixin (Vue) {
  function initInternalComponent (line 5017) | function initInternalComponent (vm, options) {
  function resolveConstructorOptions (line 5036) | function resolveConstructorOptions (Ctor) {
  function resolveModifiedOptions (line 5060) | function resolveModifiedOptions (Ctor) {
  function Vue (line 5073) | function Vue (options) {
  function initUse (line 5089) | function initUse (Vue) {
  function initMixin$1 (line 5111) | function initMixin$1 (Vue) {
  function initExtend (line 5120) | function initExtend (Vue) {
  function initProps$1 (line 5196) | function initProps$1 (Comp) {
  function initComputed$1 (line 5203) | function initComputed$1 (Comp) {
  function initAssetRegisters (line 5212) | function initAssetRegisters (Vue) {
  function getComponentName (line 5246) | function getComponentName (opts) {
  function matches (line 5250) | function matches (pattern, name) {
  function pruneCache (line 5262) | function pruneCache (keepAliveInstance, filter) {
  function pruneCacheEntry (line 5277) | function pruneCacheEntry (
  function initGlobalAPI (line 5378) | function initGlobalAPI (Vue) {
  function genClassForVnode (line 5503) | function genClassForVnode (vnode) {
  function mergeClassData (line 5521) | function mergeClassData (child, parent) {
  function renderClass (line 5530) | function renderClass (
  function concat (line 5541) | function concat (a, b) {
  function stringifyClass (line 5545) | function stringifyClass (value) {
  function stringifyArray (line 5559) | function stringifyArray (value) {
  function stringifyObject (line 5571) | function stringifyObject (value) {
  function getTagNamespace (line 5618) | function getTagNamespace (tag) {
  function isUnknownElement (line 5630) | function isUnknownElement (tag) {
  function query (line 5662) | function query (el) {
  function createElement$1 (line 5679) | function createElement$1 (tagName, vnode) {
  function createElementNS (line 5691) | function createElementNS (namespace, tagName) {
  function createTextNode (line 5695) | function createTextNode (text) {
  function createComment (line 5699) | function createComment (text) {
  function insertBefore (line 5703) | function insertBefore (parentNode, newNode, referenceNode) {
  function removeChild (line 5707) | function removeChild (node, child) {
  function appendChild (line 5711) | function appendChild (node, child) {
  function parentNode (line 5715) | function parentNode (node) {
  function nextSibling (line 5719) | function nextSibling (node) {
  function tagName (line 5723) | function tagName (node) {
  function setTextContent (line 5727) | function setTextContent (node, text) {
  function setStyleScope (line 5731) | function setStyleScope (node, scopeId) {
  function registerRef (line 5767) | function registerRef (vnode, isRemoval) {
  function sameVnode (line 5810) | function sameVnode (a, b) {
  function sameInputType (line 5827) | function sameInputType (a, b) {
  function createKeyToOldIdx (line 5835) | function createKeyToOldIdx (children, beginIdx, endIdx) {
  function createPatchFunction (line 5845) | function createPatchFunction (backend) {
  function updateDirectives (line 6573) | function updateDirectives (oldVnode, vnode) {
  function _update (line 6579) | function _update (oldVnode, vnode) {
  function normalizeDirectives$1 (line 6642) | function normalizeDirectives$1 (
  function getRawDirName (line 6665) | function getRawDirName (dir) {
  function callHook$1 (line 6669) | function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) {
  function updateAttrs (line 6687) | function updateAttrs (oldVnode, vnode) {
  function setAttr (line 6728) | function setAttr (el, key, value) {
  function baseSetAttr (line 6757) | function baseSetAttr (el, key, value) {
  function updateClass (line 6789) | function updateClass (oldVnode, vnode) {
  function parseFilters (line 6829) | function parseFilters (exp) {
  function wrapFilter (line 6911) | function wrapFilter (exp, filter) {
  function baseWarn (line 6928) | function baseWarn (msg, range) {
  function pluckModuleFunction (line 6933) | function pluckModuleFunction (
  function addProp (line 6942) | function addProp (el, name, value, range, dynamic) {
  function addAttr (line 6947) | function addAttr (el, name, value, range, dynamic) {
  function addRawAttr (line 6956) | function addRawAttr (el, name, value, range) {
  function addDirective (line 6961) | function addDirective (
  function prependModifierMarker (line 6982) | function prependModifierMarker (symbol, name, dynamic) {
  function addHandler (line 6988) | function addHandler (
  function getRawBindingAttr (line 7071) | function getRawBindingAttr (
  function getBindingAttr (line 7080) | function getBindingAttr (
  function getAndRemoveAttr (line 7102) | function getAndRemoveAttr (
  function getAndRemoveAttrByRegex (line 7123) | function getAndRemoveAttrByRegex (
  function rangeSetItem (line 7137) | function rangeSetItem (
  function genComponentModel (line 7157) | function genComponentModel (
  function genAssignmentCode (line 7189) | function genAssignmentCode (
  function parseModel (line 7220) | function parseModel (val) {
  function next (line 7260) | function next () {
  function eof (line 7264) | function eof () {
  function isStringStart (line 7268) | function isStringStart (chr) {
  function parseBracket (line 7272) | function parseBracket (chr) {
  function parseString (line 7290) | function parseString (chr) {
  function model (line 7309) | function model (
  function genCheckboxModel (line 7362) | function genCheckboxModel (
  function genRadioModel (line 7393) | function genRadioModel (
  function genSelect (line 7405) | function genSelect (
  function genDefaultModel (line 7422) | function genDefaultModel (
  function normalizeEvents (line 7481) | function normalizeEvents (on) {
  function createOnceHandler$1 (line 7500) | function createOnceHandler$1 (event, handler, capture) {
  function add$1 (line 7515) | function add$1 (
  function remove$2 (line 7560) | function remove$2 (
  function updateDOMListeners (line 7573) | function updateDOMListeners (oldVnode, vnode) {
  function updateDOMProps (line 7594) | function updateDOMProps (oldVnode, vnode) {
  function shouldUpdateValue (line 7667) | function shouldUpdateValue (elm, checkVal) {
  function isNotInFocusAndDirty (line 7675) | function isNotInFocusAndDirty (elm, checkVal) {
  function isDirtyWithModifiers (line 7685) | function isDirtyWithModifiers (elm, newVal) {
  function normalizeStyleData (line 7720) | function normalizeStyleData (data) {
  function normalizeStyleBinding (line 7730) | function normalizeStyleBinding (bindingStyle) {
  function getStyle (line 7744) | function getStyle (vnode, checkChild) {
  function updateStyle (line 7817) | function updateStyle (oldVnode, vnode) {
  function addClass (line 7873) | function addClass (el, cls) {
  function removeClass (line 7898) | function removeClass (el, cls) {
  function resolveTransition (line 7931) | function resolveTransition (def$$1) {
  function nextFrame (line 7991) | function nextFrame (fn) {
  function addTransitionClass (line 7997) | function addTransitionClass (el, cls) {
  function removeTransitionClass (line 8005) | function removeTransitionClass (el, cls) {
  function whenTransitionEnds (line 8012) | function whenTransitionEnds (
  function getTransitionInfo (line 8045) | function getTransitionInfo (el, expectedType) {
  function getTimeout (line 8095) | function getTimeout (delays, durations) {
  function toMs (line 8110) | function toMs (s) {
  function enter (line 8116) | function enter (vnode, toggleDisplay) {
  function leave (line 8267) | function leave (vnode, rm) {
  function checkDuration (line 8372) | function checkDuration (val, name, vnode) {
  function isValidDuration (line 8388) | function isValidDuration (val) {
  function getHookArgumentsLength (line 8398) | function getHookArgumentsLength (fn) {
  function _enter (line 8415) | function _enter (_, vnode) {
  function setSelected (line 8520) | function setSelected (el, binding, vm) {
  function actuallySetSelected (line 8530) | function actuallySetSelected (el, binding, vm) {
  function hasNoMatchingOption (line 8563) | function hasNoMatchingOption (value, options) {
  function getValue (line 8567) | function getValue (option) {
  function onCompositionStart (line 8573) | function onCompositionStart (e) {
  function onCompositionEnd (line 8577) | function onCompositionEnd (e) {
  function trigger (line 8584) | function trigger (el, type) {
  function locateNode (line 8593) | function locateNode (vnode) {
  function getRealChild (line 8681) | function getRealChild (vnode) {
  function extractTransitionData (line 8690) | function extractTransitionData (comp) {
  function placeholder (line 8706) | function placeholder (h, rawChild) {
  function hasParentTransition (line 8714) | function hasParentTransition (vnode) {
  function isSameChild (line 8722) | function isSameChild (child, oldChild) {
  function callPendingCbs (line 8989) | function callPendingCbs (c) {
  function recordPosition (line 9000) | function recordPosition (c) {
  function applyTranslation (line 9004) | function applyTranslation (c) {
  function parseText (line 9086) | function parseText (
  function transformNode (line 9123) | function transformNode (el, options) {
  function genData (line 9147) | function genData (el) {
  function transformNode$1 (line 9166) | function transformNode$1 (el, options) {
  function genData$1 (line 9192) | function genData$1 (el) {
  function decodeAttr (line 9281) | function decodeAttr (value, shouldDecodeNewlines) {
  function parseHTML (line 9286) | function parseHTML (html, options) {
  function createASTElement (line 9574) | function createASTElement (
  function parse (line 9593) | function parse (
  function processPre (line 9916) | function processPre (el) {
  function processRawAttrs (line 9922) | function processRawAttrs (el) {
  function processElement (line 9943) | function processElement (
  function processKey (line 9968) | function processKey (el) {
  function processRef (line 9995) | function processRef (el) {
  function processFor (line 10003) | function processFor (el) {
  function parseFor (line 10020) | function parseFor (exp) {
  function processIf (line 10039) | function processIf (el) {
  function processIfConditions (line 10058) | function processIfConditions (el, parent) {
  function findPrevElement (line 10074) | function findPrevElement (children) {
  function addIfCondition (line 10092) | function addIfCondition (el, condition) {
  function processOnce (line 10099) | function processOnce (el) {
  function processSlotContent (line 10108) | function processSlotContent (el) {
  function getSlotName (line 10227) | function getSlotName (binding) {
  function processSlotOutlet (line 10247) | function processSlotOutlet (el) {
  function processComponent (line 10261) | function processComponent (el) {
  function processAttrs (line 10271) | function processAttrs (el) {
  function checkInFor (line 10404) | function checkInFor (el) {
  function parseModifiers (line 10415) | function parseModifiers (name) {
  function makeAttrsMap (line 10424) | function makeAttrsMap (attrs) {
  function isTextTag (line 10438) | function isTextTag (el) {
  function isForbiddenTag (line 10442) | function isForbiddenTag (el) {
  function guardIESVGBug (line 10456) | function guardIESVGBug (attrs) {
  function checkForAliasModel (line 10468) | function checkForAliasModel (el, value) {
  function preTransformNode (line 10487) | function preTransformNode (el, options) {
  function cloneASTElement (line 10549) | function cloneASTElement (el) {
  function text (line 10565) | function text (el, dir) {
  function html (line 10573) | function html (el, dir) {
  function optimize (line 10618) | function optimize (root, options) {
  function genStaticKeys$1 (line 10628) | function genStaticKeys$1 (keys) {
  function markStatic$1 (line 10635) | function markStatic$1 (node) {
  function markStaticRoots (line 10667) | function markStaticRoots (node, isInFor) {
  function isStatic (line 10697) | function isStatic (node) {
  function isDirectChildOfTemplateFor (line 10714) | function isDirectChildOfTemplateFor (node) {
  function genHandlers (line 10781) | function genHandlers (
  function genHandler (line 10804) | function genHandler (handler) {
  function genKeyFilter (line 10863) | function genKeyFilter (keys) {
  function genFilterCode (line 10873) | function genFilterCode (key) {
  function on (line 10892) | function on (el, dir) {
  function bind$1 (line 10901) | function bind$1 (el, dir) {
  function generate (line 10936) | function generate (
  function genElement (line 10948) | function genElement (el, state) {
  function genStatic (line 10988) | function genStatic (el, state) {
  function genOnce (line 11003) | function genOnce (el, state) {
  function genIf (line 11030) | function genIf (
  function genIfConditions (line 11040) | function genIfConditions (
  function genFor (line 11067) | function genFor (
  function genData$2 (line 11099) | function genData$2 (el, state) {
  function genDirectives (line 11183) | function genDirectives (el, state) {
  function genInlineTemplate (line 11208) | function genInlineTemplate (el, state) {
  function genScopedSlots (line 11222) | function genScopedSlots (
  function hash (line 11276) | function hash(str) {
  function containsSlotChild (line 11285) | function containsSlotChild (el) {
  function genScopedSlot (line 11295) | function genScopedSlot (
  function genChildren (line 11320) | function genChildren (
  function getNormalizationType (line 11353) | function getNormalizationType (
  function needsNormalization (line 11376) | function needsNormalization (el) {
  function genNode (line 11380) | function genNode (node, state) {
  function genText (line 11390) | function genText (text) {
  function genComment (line 11396) | function genComment (comment) {
  function genSlot (line 11400) | function genSlot (el, state) {
  function genComponent (line 11426) | function genComponent (
  function genProps (line 11435) | function genProps (props) {
  function transformSpecialNewlines (line 11456) | function transformSpecialNewlines (text) {
  function detectErrors (line 11483) | function detectErrors (ast, warn) {
  function checkNode (line 11489) | function checkNode (node, warn) {
  function checkEvent (line 11518) | function checkEvent (exp, text, warn, range) {
  function checkFor (line 11531) | function checkFor (node, text, warn, range) {
  function checkIdentifier (line 11538) | function checkIdentifier (
  function checkExpression (line 11554) | function checkExpression (exp, text, warn, range) {
  function checkFunctionParameterExpression (line 11576) | function checkFunctionParameterExpression (exp, text, warn, range) {
  function generateCodeFrame (line 11593) | function generateCodeFrame (
  function repeat$1 (line 11630) | function repeat$1 (str, n) {
  function createFunction (line 11647) | function createFunction (code, errors) {
  function createCompileToFunctionFn (line 11656) | function createCompileToFunctionFn (compile) {
  function createCompilerCreator (line 11758) | function createCompilerCreator (baseCompile) {
  function getShouldDecode (line 11859) | function getShouldDecode (href) {
  function getOuterHTML (line 11951) | function getOuterHTML (el) {

FILE: yyets/worker/workers-site/index.js
  constant DEBUG (line 10) | const DEBUG = false
  function handleEvent (line 27) | async function handleEvent(event) {
  function update_downloads (line 96) | async function update_downloads(value) {

FILE: yyetsbot/fansub.py
  class Redis (line 47) | class Redis:
    method __init__ (line 48) | def __init__(self):
    method __del__ (line 54) | def __del__(self):
    method preview_cache (line 58) | def preview_cache(cls, timeout=3600 * 24):
    method result_cache (line 86) | def result_cache(cls, timeout=3600 * 24):
  class BaseFansub (line 122) | class BaseFansub:
    method __init__ (line 133) | def __init__(self):
    method id (line 137) | def id(self):
    method get_html (line 141) | def get_html(self, link: str, encoding=None) -> str:
    method search_preview (line 150) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 156) | def search_result(self, url_or_hash: str) -> dict:
    method __login_check (line 168) | def __login_check(self):
    method __manual_login (line 171) | def __manual_login(self):
    method __save_cookies__ (line 174) | def __save_cookies__(self, requests_cookiejar):
    method __load_cookies__ (line 178) | def __load_cookies__(self):
  class YYeTsOffline (line 183) | class YYeTsOffline(BaseFansub):
    method __init__ (line 184) | def __init__(self, db="zimuzu", col="yyets"):
    method search_preview (line 191) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 230) | def search_result(self, resource_url) -> dict:
    method __del__ (line 252) | def __del__(self):
  class ZimuxiaOnline (line 256) | class ZimuxiaOnline(BaseFansub):
    method search_preview (line 258) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 277) | def search_result(self, resource_url: str) -> dict:
  class ZhuixinfanOnline (line 286) | class ZhuixinfanOnline(BaseFansub):
    method search_preview (line 288) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 308) | def search_result(self, url: str) -> dict:
  class NewzmzOnline (line 319) | class NewzmzOnline(BaseFansub):
    method search_preview (line 321) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 340) | def search_result(self, url: str) -> dict:
  class BD2020 (line 349) | class BD2020(NewzmzOnline):
    method search_preview (line 351) | def search_preview(self, search_text: str) -> dict:
  class XL720 (line 369) | class XL720(BD2020):
    method search_preview (line 371) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 389) | def search_result(self, url: str) -> dict:
  class FansubEntrance (line 397) | class FansubEntrance(BaseFansub):
    method search_preview (line 400) | def search_preview(self, search_text: str) -> dict:
    method search_result (line 416) | def search_result(self, resource_url_hash: str) -> dict:
  function class_to_tg (line 432) | def class_to_tg(sub_class: str):

FILE: yyetsbot/utils.py
  function save_error_dump (line 18) | def save_error_dump(uid, err: str):
  function get_error_dump (line 22) | def get_error_dump(uid) -> str:
  function redis_announcement (line 30) | def redis_announcement(content="", op="get"):
  function today_request (line 39) | def today_request(request_type: str):
  function reset_request (line 51) | def reset_request():
  function show_usage (line 55) | def show_usage():

FILE: yyetsbot/yyetsbot.py
  function send_welcome (line 36) | def send_welcome(message):
  function send_help (line 50) | def send_help(message):
  function send_ping (line 65) | def send_ping(message):
  function settings (line 81) | def settings(message):
  function delete_announcement (line 109) | def delete_announcement(call):
  function send_credits (line 117) | def send_credits(message):
  function varies_fansub (line 136) | def varies_fansub(message):
  function download_to_io (line 154) | def download_to_io(photo):
  function send_my_response (line 166) | def send_my_response(message):
  function send_search (line 190) | def send_search(message):
  function ban_user (line 201) | def ban_user(message):
  function base_send_search (line 228) | def base_send_search(message, instance=None):
  function magic_recycle (line 310) | def magic_recycle(fan, call, url_hash):
  function choose_link (line 321) | def choose_link(call):
  function approve_spam (line 344) | def approve_spam(call):
  function ban_spam (line 353) | def ban_spam(call):
  function send_unwelcome (line 362) | def send_unwelcome(call):
  function report_error (line 380) | def report_error(call):

FILE: yyetsweb/commands/common.py
  class Mongo (line 15) | class Mongo:
    method __init__ (line 16) | def __init__(self):
    method __del__ (line 25) | def __del__(self):

FILE: yyetsweb/commands/grafana_test_data.py
  function generate_date_series (line 18) | def generate_date_series(start: str, end: str) -> list:

FILE: yyetsweb/common/dump_db.py
  function SQLite (line 35) | def SQLite():
  function MySQL (line 39) | def MySQL():
  function MongoDB (line 43) | def MongoDB():
  function read_resource (line 47) | def read_resource():
  function read_comment (line 78) | def read_comment():
  function prepare_mysql (line 98) | def prepare_mysql():
  function prepare_sqlite (line 135) | def prepare_sqlite():
  function dump_resource (line 171) | def dump_resource():
  function insert_func (line 192) | def insert_func(batch_data, mb, sql1, sql2, col_name=None):
  function dump_comment (line 210) | def dump_comment():
  function zip_file (line 226) | def zip_file():
  function cleanup (line 246) | def cleanup():
  function entry_dump (line 261) | def entry_dump():

FILE: yyetsweb/common/sync.py
  class BaseSync (line 19) | class BaseSync:
    method __init__ (line 20) | def __init__(self):
    method sleep (line 61) | def sleep(times=1):
  class Zhuixinfan (line 65) | class Zhuixinfan(BaseSync):
    method run (line 66) | def run(self):
    method build_data (line 80) | def build_data(self, html, link):
    method update_yyets (line 146) | def update_yyets(self, data):
  class YYSub (line 170) | class YYSub(BaseSync):
    method get_lastest_id (line 171) | def get_lastest_id(self):
    method get_channel_cn (line 178) | def get_channel_cn(self, channel, area):
    method run (line 185) | def run(self):
    method insert_data (line 215) | def insert_data(self, data):
  function sync_douban (line 220) | def sync_douban():

FILE: yyetsweb/common/utils.py
  function setup_logger (line 33) | def setup_logger():
  function hide_phone (line 41) | def hide_phone(data: list):
  function mask_phone (line 49) | def mask_phone(num):
  function ts_date (line 53) | def ts_date(ts=None):
  function _format_addr (line 59) | def _format_addr(s):
  function generate_body (line 64) | def generate_body(context):
  function send_mail (line 70) | def send_mail(to: str, subject: str, context: dict):
  function check_spam (line 93) | def check_spam(ip, ua, author, content) -> int:
  class Cloudflare (line 114) | class Cloudflare(Redis):
    method __init__ (line 118) | def __init__(self):
    method get_old_ips (line 128) | def get_old_ips(self) -> dict:
    method ban_new_ip (line 148) | def ban_new_ip(self, ip):
    method clear_fw (line 160) | def clear_fw(self):

FILE: yyetsweb/databases/base.py
  class Mongo (line 13) | class Mongo:
    method __init__ (line 14) | def __init__(self):
    method is_admin (line 18) | def is_admin(self, username: str) -> bool:
    method is_user_blocked (line 23) | def is_user_blocked(self, username: str) -> str:
    method is_old_user (line 28) | def is_old_user(self, username: str) -> bool:
  class Redis (line 32) | class Redis:
    method __init__ (line 33) | def __init__(self):
    method cache (line 38) | def cache(cls, timeout: int):
  class SearchEngine (line 57) | class SearchEngine(Mongo):
    method __init__ (line 111) | def __init__(self):
    method __del__ (line 119) | def __del__(self):
    method __get_yyets (line 122) | def __get_yyets(self):
    method __get_comment (line 140) | def __get_comment(self):
    method __get_douban (line 148) | def __get_douban(self):
    method __get_subtitle (line 156) | def __get_subtitle(self):
    method add_yyets (line 167) | def add_yyets(self):
    method add_comment (line 172) | def add_comment(self):
    method add_douban (line 177) | def add_douban(self):
    method add_subtitle (line 182) | def add_subtitle(self):
    method search_yyets (line 187) | def search_yyets(self, keyword: "str"):
    method search_comment (line 190) | def search_comment(self, keyword: "str"):
    method search_douban (line 193) | def search_douban(self, keyword: "str"):
    method search_subtitle (line 196) | def search_subtitle(self, keyword: "str"):
    method run_import (line 199) | def run_import(self):
    method __monitor (line 207) | def __monitor(self, col, fun):
    method monitor_yyets (line 221) | def monitor_yyets(self):
    method monitor_douban (line 230) | def monitor_douban(self):
    method monitor_comment (line 243) | def monitor_comment(self):

FILE: yyetsweb/databases/comment.py
  class Comment (line 16) | class Comment(Mongo):
    method __init__ (line 17) | def __init__(self):
    method convert_objectid (line 24) | def convert_objectid(data):
    method find_children (line 34) | def find_children(self, parent_data):
    method get_user_group (line 61) | def get_user_group(self, data):
    method add_reactions (line 72) | def add_reactions(self, data):
    method get_comment (line 83) | def get_comment(self, resource_id: int, page: int, size: int, **kwargs...
    method add_comment (line 126) | def add_comment(
    method delete_comment (line 245) | def delete_comment(self, comment_id):
  class CommentReaction (line 281) | class CommentReaction(Mongo):
    method react_comment (line 282) | def react_comment(self, username, data):
  class CommentChild (line 305) | class CommentChild(Comment, Mongo):
    method __init__ (line 306) | def __init__(self):
    method get_comment (line 312) | def get_comment(self, parent_id: str, page: int, size: int) -> dict:
  class CommentNewest (line 336) | class CommentNewest(Comment, Mongo):
    method __init__ (line 337) | def __init__(self):
    method get_comment (line 344) | def get_comment(self, page: int, size: int, keyword="") -> dict:
    method extra_info (line 363) | def extra_info(self, data):
  class CommentSearch (line 371) | class CommentSearch(CommentNewest):
    method get_comment (line 372) | def get_comment(self, page: int, size: int, keyword="") -> dict:
    method fill_children (line 393) | def fill_children(self, data):
  class Notification (line 405) | class Notification(Mongo):
    method get_notification (line 406) | def get_notification(self, username, page, size):
    method get_content (line 438) | def get_content(self, id_list):
    method update_notification (line 457) | def update_notification(self, username, verb, comment_id):

FILE: yyetsweb/databases/douban.py
  class Douban (line 18) | class Douban(Mongo):
    method get_douban_data (line 19) | def get_douban_data(self, rid: int) -> dict:
    method get_douban_image (line 24) | def get_douban_image(self, rid: int) -> bytes:
    method find_douban (line 29) | def find_douban(self, resource_id: int):
    method get_craw_data (line 66) | def get_craw_data(cname, douban_id, resource_id, search_html, session):
  class DoubanReport (line 124) | class DoubanReport(Mongo):
    method get_error (line 125) | def get_error(self) -> dict:
    method report_error (line 128) | def report_error(self, captcha: str, captcha_id: int, content: str, re...

FILE: yyetsweb/databases/grafana.py
  class GrafanaQuery (line 11) | class GrafanaQuery(Mongo):
    method get_grafana_data (line 12) | def get_grafana_data(self, date_series) -> str:
  class Metrics (line 18) | class Metrics(Mongo):
    method set_metrics (line 19) | def set_metrics(self, metrics_type: str, data: str):
    method get_metrics (line 26) | def get_metrics(self, from_date: str, to_date: str) -> dict:

FILE: yyetsweb/databases/oauth.py
  class OAuthRegister (line 9) | class OAuthRegister(Mongo):
    method add_user (line 10) | def add_user(self, username, ip, browser, uid, source: "str"):
  class GitHubOAuth2Login (line 42) | class GitHubOAuth2Login(OAuthRegister):
  class MSOAuth2Login (line 46) | class MSOAuth2Login(OAuthRegister):
  class GoogleOAuth2Login (line 50) | class GoogleOAuth2Login(OAuthRegister):
  class TwitterOAuth2Login (line 54) | class TwitterOAuth2Login(OAuthRegister):
  class FacebookAuth2Login (line 58) | class FacebookAuth2Login(OAuthRegister):

FILE: yyetsweb/databases/other.py
  class Announcement (line 27) | class Announcement(Mongo):
    method get_announcement (line 28) | def get_announcement(self, page: int, size: int) -> dict:
    method add_announcement (line 47) | def add_announcement(self, username, content, ip, browser):
  class Blacklist (line 58) | class Blacklist(Redis):
    method get_black_list (line 59) | def get_black_list(self):
  class Category (line 71) | class Category(Mongo):
    method get_category (line 72) | def get_category(self, query: dict):
  class SpamProcess (line 99) | class SpamProcess(Mongo):
    method ban_spam (line 100) | def ban_spam(self, obj_id: "str"):
    method restore_spam (line 110) | def restore_spam(self, obj_id: "str"):
    method request_approval (line 119) | def request_approval(document: "dict"):
  class Other (line 140) | class Other(Mongo, Redis):
    method reset_top (line 141) | def reset_top(self):
    method import_ban_user (line 160) | def import_ban_user(self):
    method fill_user_hash (line 167) | def fill_user_hash(self):
  class Captcha (line 177) | class Captcha(Redis):
    method get_captcha (line 178) | def get_captcha(self, captcha_id):
    method verify_code (line 185) | def verify_code(self, user_input, captcha_id) -> dict:

FILE: yyetsweb/databases/resources.py
  class SubtitleDownload (line 17) | class SubtitleDownload(Mongo):
    method add_download (line 18) | def add_download(self, _id):
  class Resource (line 22) | class Resource(SearchEngine):
    method fansub_search (line 23) | def fansub_search(self, class_name: str, kw: str):
    method get_resource_data (line 32) | def get_resource_data(self, resource_id: int, username: str) -> dict:
    method search_resource (line 48) | def search_resource(self, keyword: str, search_type: "str") -> dict:
    method meili_search (line 55) | def meili_search(self, keyword: "str", search_type: "str") -> dict:
    method mongodb_search (line 88) | def mongodb_search(self, keyword: str, search_type: str) -> dict:
    method patch_resource (line 176) | def patch_resource(self, new_data: dict):
    method add_resource (line 199) | def add_resource(self, new_data: dict):
    method delete_resource (line 205) | def delete_resource(self, data: dict):
    method get_appropriate_id (line 226) | def get_appropriate_id(self):
    method convert_season (line 236) | def convert_season(number: [int, str]):
  class Top (line 243) | class Top(Mongo):
    method get_most (line 246) | def get_most(self) -> list:
    method get_top_resource (line 258) | def get_top_resource(self) -> dict:
  class ResourceLatest (line 277) | class ResourceLatest(Mongo, Redis):
    method get_latest_resource (line 278) | def get_latest_resource(self) -> dict:
    method query_db (line 291) | def query_db(self) -> dict:
    method refresh_latest_resource (line 320) | def refresh_latest_resource(self):
  class Name (line 327) | class Name(Mongo):
    method get_names (line 328) | def get_names(self, is_readable: [str, bool]) -> dict:

FILE: yyetsweb/databases/user.py
  class Like (line 18) | class Like(Mongo):
    method get_user_like (line 21) | def get_user_like(self, username: str) -> list:
    method add_remove_fav (line 30) | def add_remove_fav(self, resource_id: int, username: str) -> dict:
  class User (line 47) | class User(Mongo, Redis):
    method login_user (line 48) | def login_user(
    method get_user_info (line 118) | def get_user_info(self, username: str) -> dict:
    method update_user_last (line 125) | def update_user_last(self, username: str, now_ip: str) -> None:
    method update_user_info (line 131) | def update_user_info(self, username: str, data: dict) -> dict:
  class UserAvatar (line 179) | class UserAvatar(User, Mongo):
    method add_avatar (line 180) | def add_avatar(self, username, avatar):
    method get_avatar (line 185) | def get_avatar(self, username, user_hash=None):
  class UserEmail (line 204) | class UserEmail(Mongo, Redis):
    method verify_email (line 205) | def verify_email(self, username, code):

FILE: yyetsweb/handlers/base.py
  class BaseHandler (line 22) | class BaseHandler(web.RequestHandler):
    method __init__ (line 28) | def __init__(self, application, request, **kwargs):
    method add_tauri (line 39) | def add_tauri(self):
    method prepare (line 45) | def prepare(self):
    method data_received (line 51) | def data_received(self, chunk):
    method check_request (line 54) | def check_request(self):
    method get_real_ip (line 62) | def get_real_ip(self):
    method ban (line 68) | def ban(self):
    method get_current_user (line 84) | def get_current_user(self) -> str:
    method __user_check (line 88) | def __user_check(self):
    method __ip_check (line 94) | def __ip_check(self):
    method write_error (line 99) | def write_error(self, status_code, **kwargs):
  class IndexHandler (line 109) | class IndexHandler(BaseHandler):
    method send_index (line 111) | def send_index(self):
    method get (line 117) | def get(self):
  class NotFoundHandler (line 122) | class NotFoundHandler(BaseHandler):
    method get (line 123) | def get(self):

FILE: yyetsweb/handlers/comment.py
  class CommentHandler (line 15) | class CommentHandler(BaseHandler):
    method get_comment (line 19) | def get_comment(self):
    method add_comment (line 46) | def add_comment(self):
    method delete_comment (line 72) | def delete_comment(self):
    method get (line 88) | def get(self):
    method post (line 94) | def post(self):
    method delete (line 100) | def delete(self):
  class CommentReactionHandler (line 105) | class CommentReactionHandler(BaseHandler):
    method comment_reaction (line 109) | def comment_reaction(self):
    method post (line 118) | def post(self):
    method delete (line 124) | def delete(self):
  class CommentChildHandler (line 129) | class CommentChildHandler(CommentHandler):
    method get_comment (line 133) | def get_comment(self):
    method get (line 146) | def get(self):
  class CommentNewestHandler (line 151) | class CommentNewestHandler(CommentHandler):
    method get_comment (line 155) | def get_comment(self):
    method get (line 164) | def get(self):
  class CommentSearchHandler (line 169) | class CommentSearchHandler(CommentHandler):
    method search_comment (line 173) | def search_comment(self):
    method get (line 182) | def get(self):
  class NotificationHandler (line 187) | class NotificationHandler(BaseHandler):
    method get_notification (line 191) | def get_notification(self):
    method update_notification (line 199) | def update_notification(self):
    method get (line 211) | def get(self):
    method patch (line 217) | def patch(self):

FILE: yyetsweb/handlers/douban.py
  class DoubanHandler (line 15) | class DoubanHandler(BaseHandler):
    method douban_data (line 19) | def douban_data(self):
    method get_image (line 27) | def get_image(self) -> bytes:
    method get (line 32) | def get(self):
  class DoubanReportHandler (line 43) | class DoubanReportHandler(BaseHandler):
    method get_error (line 47) | def get_error(self):
    method report_error (line 51) | def report_error(self):
    method post (line 63) | def post(self):
    method get (line 68) | def get(self):

FILE: yyetsweb/handlers/grafana.py
  class MetricsHandler (line 17) | class MetricsHandler(BaseHandler):
    method set_metrics (line 21) | def set_metrics(self):
    method get_metrics (line 31) | def get_metrics(self):
    method get (line 47) | def get(self):
    method post (line 52) | def post(self):
    method options (line 57) | def options(self):
  class GrafanaIndexHandler (line 64) | class GrafanaIndexHandler(BaseHandler):
    method get (line 67) | def get(self):
  class GrafanaSearchHandler (line 71) | class GrafanaSearchHandler(BaseHandler):
    method post (line 74) | def post(self):
  class GrafanaQueryHandler (line 97) | class GrafanaQueryHandler(BaseHandler):
    method generate_date_series (line 101) | def generate_date_series(start: str, end: str) -> list:
    method time_str_int (line 115) | def time_str_int(text):
    method post (line 118) | def post(self):

FILE: yyetsweb/handlers/oauth.py
  class OAuth2Handler (line 16) | class OAuth2Handler(BaseHandler, OAuth2Mixin):
    method add_oauth_user (line 22) | def add_oauth_user(self, username, unique, source):
    method get_authenticated_user (line 31) | def get_authenticated_user(self, client_id: str, client_secret: str, c...
    method oauth2_sync_request (line 41) | def oauth2_sync_request(self, access_token, extra_fields=None):
    method get_secret (line 48) | def get_secret(self, settings_key):
  class GitHubOAuth2LoginHandler (line 56) | class GitHubOAuth2LoginHandler(OAuth2Handler):
    method get (line 61) | def get(self):
  class MSOAuth2LoginHandler (line 79) | class MSOAuth2LoginHandler(OAuth2Handler):
    method get (line 84) | def get(self):
  class GoogleOAuth2LoginHandler (line 108) | class GoogleOAuth2LoginHandler(GoogleOAuth2Mixin, OAuth2Handler):
    method get (line 109) | async def get(self):
  class TwitterOAuth2LoginHandler (line 131) | class TwitterOAuth2LoginHandler(TwitterMixin, OAuth2Handler):
    method get (line 132) | async def get(self):
  class FacebookAuth2LoginHandler (line 142) | class FacebookAuth2LoginHandler(OAuth2Handler):
    method get (line 147) | def get(self):

FILE: yyetsweb/handlers/other.py
  class AnnouncementHandler (line 21) | class AnnouncementHandler(BaseHandler):
    method get_announcement (line 25) | def get_announcement(self):
    method add_announcement (line 31) | def add_announcement(self):
    method get (line 47) | def get(self):
    method post (line 53) | def post(self):
  class DBDumpHandler (line 58) | class DBDumpHandler(BaseHandler):
    method sizeof_fmt (line 60) | def sizeof_fmt(num: int, suffix="B"):
    method checksum (line 68) | def checksum(file_path) -> str:
    method get_hash (line 81) | def get_hash(self):
    method get (line 101) | def get(self):
  class CategoryHandler (line 106) | class CategoryHandler(BaseHandler):
    method get_data (line 110) | def get_data(self):
    method get (line 118) | def get(self):
  class CaptchaHandler (line 123) | class CaptchaHandler(BaseHandler):
    method verify_captcha (line 127) | def verify_captcha(self):
    method captcha (line 141) | def captcha(self):
    method get (line 150) | def get(self):
    method post (line 155) | def post(self):
  class BlacklistHandler (line 160) | class BlacklistHandler(BaseHandler):
    method get_black_list (line 164) | def get_black_list(self):
    method get (line 168) | def get(self):
  class SpamProcessHandler (line 173) | class SpamProcessHandler(BaseHandler):
    method process (line 176) | def process(self, method):
    method post (line 192) | def post(self):
    method delete (line 196) | def delete(self):

FILE: yyetsweb/handlers/resources.py
  class SubtitleDownloadHandler (line 15) | class SubtitleDownloadHandler(BaseHandler):
    method find_and_download (line 19) | def find_and_download(self):
    method post (line 34) | def post(self):
  class ResourceHandler (line 39) | class ResourceHandler(BaseHandler):
    method get_resource_data (line 43) | def get_resource_data(self):
    method search_resource (line 56) | def search_resource(self):
    method get (line 63) | def get(self):
  class ResourceLatestHandler (line 73) | class ResourceLatestHandler(BaseHandler):
    method get_latest (line 77) | def get_latest(self):
    method get (line 84) | def get(self):
  class TopHandler (line 89) | class TopHandler(BaseHandler):
    method get_user_like (line 92) | def get_user_like(self) -> list:
    method get_most (line 96) | def get_most(self) -> list:
    method get_top_resource (line 100) | def get_top_resource(self):
    method get (line 104) | def get(self):
  class NameHandler (line 109) | class NameHandler(BaseHandler):
    method get_names (line 113) | def get_names(self):
    method get (line 118) | def get(self):
  class AdsenseStatusHandler (line 123) | class AdsenseStatusHandler(BaseHandler):
    method get_adsense_status (line 125) | def get_adsense_status(self):
    method get (line 129) | def get(self):

FILE: yyetsweb/handlers/user.py
  class UserHandler (line 14) | class UserHandler(BaseHandler):
    method set_login (line 17) | def set_login(self, username):
    method login (line 21) | def login(self):
    method update_info (line 39) | def update_info(self):
    method get_user_info (line 45) | def get_user_info(self) -> dict:
    method post (line 56) | def post(self):
    method get (line 61) | def get(self):
    method patch (line 73) | def patch(self):
  class UserAvatarHandler (line 78) | class UserAvatarHandler(BaseHandler):
    method update_avatar (line 82) | def update_avatar(self):
    method get_avatar (line 96) | def get_avatar(self, username):
    method post (line 106) | def post(self, _):
    method get (line 111) | def get(self, username):
    method head (line 116) | def head(self, username):
  class LikeHandler (line 121) | class LikeHandler(BaseHandler):
    method like_data (line 125) | def like_data(self):
    method get (line 131) | def get(self):
    method add_remove_fav (line 136) | def add_remove_fav(self):
    method patch (line 151) | def patch(self):
  class UserEmailHandler (line 156) | class UserEmailHandler(BaseHandler):
    method verify_email (line 160) | def verify_email(self):
    method post (line 167) | def post(self):

FILE: yyetsweb/server.go
  constant dbFile (line 19) | dbFile = "yyets_sqlite.db"
  type basicInfo (line 26) | type basicInfo struct
  type Announcement (line 33) | type Announcement struct
  function bindataStaticHandler (line 39) | func bindataStaticHandler(c *gin.Context) {
  function search (line 77) | func search(c *gin.Context) {
  function resource (line 96) | func resource(c *gin.Context) {
  function entrance (line 111) | func entrance(c *gin.Context) {
  function announcement (line 127) | func announcement(c *gin.Context) {
  function newest (line 138) | func newest(c *gin.Context) {
  function douban (line 144) | func douban(c *gin.Context) {
  function queryTop (line 179) | func queryTop(area string) []gin.H {
  function top (line 196) | func top(c *gin.Context) {
  function downloadDB (line 213) | func downloadDB() {
  function main (line 242) | func main() {

FILE: yyetsweb/server.py
  class RunServer (line 75) | class RunServer:
    method run_server (line 145) | def run_server(port, host):

FILE: yyetsweb/tests/router_test.py
  class YYeTsTest (line 14) | class YYeTsTest(AsyncHTTPTestCase):
    method get_app (line 15) | def get_app(self):
  class TestIndex (line 19) | class TestIndex(YYeTsTest):
    method test_homepage (line 20) | def test_homepage(self):
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,103K chars).
[
  {
    "path": ".dockerignore",
    "chars": 136,
    "preview": "mongo_data/*\ncerts/*\ndata/*\nlogs/*\nYYeTsFE/node_modules/*\n.github/*\nassets/*\nconf/*\ntests/*\nyyetsweb/yyets.sqlite\nyyetsw"
  },
  {
    "path": ".gitattributes",
    "chars": 685,
    "preview": "yyets/worker/public/css/** linguist-vendored\nyyets/worker/public/fonts/* linguist-vendored\nyyets/worker/public/img/* lin"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 116,
    "preview": "# These are supported funding model platforms\n\ngithub: BennyThink\ncustom: https://buy.stripe.com/dR67vU4p13Ox73a6oq\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 165,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    groups:\n  "
  },
  {
    "path": ".github/workflows/docker.yaml",
    "chars": 2321,
    "preview": "name: Build and push docker image\non:\n  push:\n    branches:\n      - 'master'\n    paths-ignore:\n      - '**.md'\n      - '"
  },
  {
    "path": ".gitignore",
    "chars": 2447,
    "preview": "# Byte-compiled / optimized / DLL files\r\n__pycache__/\r\n*.py[cod]\r\n*$py.class\r\n\r\n# C extensions\r\n*.so\r\n\r\n# Distribution /"
  },
  {
    "path": "API.md",
    "chars": 23362,
    "preview": "# 需求与待开发功能\n\n## FE\n\n- [x] group为admin特殊显示,评论接口已返回group信息\n- [x] 评论楼中楼\n- [x] 联合搜索,当本地数据库搜索不到数据时,会返回extra字段\n- [x] 最新评论\n- [x]"
  },
  {
    "path": "DEVELOPMENT.md",
    "chars": 2895,
    "preview": "# 项目手册\n\n# 网站部署方式\n\n## 一键脚本\n\n**支持amd64/arm64,请先安装 docker、docker-compose和curl**\n\n**为了安全考虑,安装完成后程序将监听在 127.0.0.1 。如有需要请自行修改 "
  },
  {
    "path": "Dockerfile",
    "chars": 1237,
    "preview": "FROM python:3.12-alpine AS pybuilder\nRUN apk update && apk add  --no-cache tzdata ca-certificates alpine-sdk libressl-de"
  },
  {
    "path": "LICENSE",
    "chars": 1083,
    "preview": "MIT License\r\n\r\nCopyright (c) 2021 Benny\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r"
  },
  {
    "path": "Makefile",
    "chars": 2593,
    "preview": "OS = darwin linux windows\nARCH = amd64 arm64\nWEB := $(shell cd yyetsweb;pwd)\nDATE:=$(shell date +\"%Y-%m-%d %H:%M:%S\")\nup"
  },
  {
    "path": "README.md",
    "chars": 3774,
    "preview": "# YYeTsBot\r\n\r\n[![build docker image](https://github.com/tgbot-collection/YYeTsBot/actions/workflows/docker.yaml/badge.sv"
  },
  {
    "path": "conf/yyets.dmesg.app.conf",
    "chars": 2191,
    "preview": "server {\n    listen 80;\n    listen [::]:80;\n    server_name yyetsdev.dmesg.app;\n    index index.html index.htm index.php"
  },
  {
    "path": "conf/yyets.env",
    "chars": 102,
    "preview": "mongo=mongo\nredis=redis\nemail_user=username\nemail_password=passord\nemail_host=mailhog\nemail_port=1025\n"
  },
  {
    "path": "docker-compose.yml",
    "chars": 1298,
    "preview": "version: '3.1'\n\nservices:\n  redis:\n    image: redis:7-alpine\n    restart: always\n    logging:\n      driver: none\n\n  mong"
  },
  {
    "path": "requirements.txt",
    "chars": 457,
    "preview": "requests==2.32.5\r\npytelegrambotapi==4.29.1\r\nbeautifulsoup4==4.14.0\r\ntgbot-ping==1.0.7\r\nredis==6.4.0\r\napscheduler==3.11.0"
  },
  {
    "path": "scripts/install.sh",
    "chars": 2228,
    "preview": "#!/bin/bash\n\nfunction splash() {\n  echo \"本脚本会在 ${HOME}/YYeTs 部署人人影视web\"\n  echo \"你确定要继续吗?输入YES确认\"\n  read -r confirm\n\n  if"
  },
  {
    "path": "scripts/migrate_sub.py",
    "chars": 469,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\n\n# YYeTsBot - migrate_sub.py\n\nimport pymongo\nimport pymysql\nfrom pymysql.cursors "
  },
  {
    "path": "setup.py",
    "chars": 3729,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n# Note: To use the 'upload' functionality of this file, you must:\n#   $ p"
  },
  {
    "path": "tea.yaml",
    "chars": 175,
    "preview": "# https://tea.xyz/what-is-this-file\n---\nversion: 1.0.0\ncodeOwners:\n  - '0x2F119b4DdC0d33A1cAE392999513e8D253C9b4Db'\n  - "
  },
  {
    "path": "yyets/BagAndDrag/README.md",
    "chars": 99,
    "preview": "# BagAndDrag\nBag and Drag\n\n[original repo](https://github.com/tgbot-collection/BagAndDrag)\n\n打包带走:-)"
  },
  {
    "path": "yyets/BagAndDrag/bag.py",
    "chars": 3340,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# BagAndDrag - bag.py\n# 1/10/21 15:29\n#\n\n__author__ = \"Benny <benny.think@gmai"
  },
  {
    "path": "yyets/BagAndDrag/cfkv.py",
    "chars": 2286,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# BagAndDrag - cfkv.py\n# 1/17/21 12:08\n#\n\n__author__ = \"Benny <benny.think@gma"
  },
  {
    "path": "yyets/BagAndDrag/convert_db.py",
    "chars": 2343,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# BagAndDrag - convert_db.py\n# 1/12/21 18:24\n#\n\n__author__ = \"Benny <benny.thi"
  },
  {
    "path": "yyets/BagAndDrag/create_db.py",
    "chars": 885,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# BagAndDrag - create_db.py\n# 1/10/21 15:23\n#\n\n__author__ = \"Benny <benny.thin"
  },
  {
    "path": "yyets/BagAndDrag/drag.py",
    "chars": 1812,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# BagAndDrag - drag.py\n# 1/10/21 15:38\n#\n\n__author__ = \"Benny <benny.think@gma"
  },
  {
    "path": "yyets/BagAndDrag/sample.json",
    "chars": 44825,
    "preview": "{\n  \"status\": 1,\n  \"info\": \"OK\",\n  \"data\": {\n    \"info\": {\n      \"id\": 34812,\n      \"cnname\": \"逃避可耻却有用\",\n      \"enname\":"
  },
  {
    "path": "yyets/BagAndDrag/zimuxia/convert_db.py",
    "chars": 1378,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - convert_db.py\n# 2/5/21 13:46\n#\n\n__author__ = \"Benny <benny.think@"
  },
  {
    "path": "yyets/BagAndDrag/zimuxia/zimuxia.py",
    "chars": 1447,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - zimuxia.py\n# 2/5/21 12:44\n#\n\n__author__ = \"Benny <benny.think@gma"
  },
  {
    "path": "yyets/__init__.py",
    "chars": 1491,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - __init__.py\n# 9/21/21 18:09\n#\n\n__author__ = \"Benny <benny.think@g"
  },
  {
    "path": "yyets/healthcheck/check.py",
    "chars": 2731,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - check.py\n# 1/22/21 16:36\n#\n\n__author__ = \"Benny <benny.think@gmai"
  },
  {
    "path": "yyets/healthcheck/restart_service.py",
    "chars": 909,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# untitled - restart_service.py\n# 9/18/21 11:54\n#\n\n__author__ = \"Benny <benny."
  },
  {
    "path": "yyets/management/format.json",
    "chars": 1435,
    "preview": "{\n  \"status\": 1,\n  \"info\": \"OK\",\n  \"data\": {\n    \"info\": {\n      \"id\": 99999,\n      \"cnname\": \"中文名\",\n      \"enname\": \"英文"
  },
  {
    "path": "yyets/management/ui.py",
    "chars": 3010,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - ui.py\n# 2/8/21 17:55\n#\n\n__author__ = \"Benny <benny.think@gmail.co"
  },
  {
    "path": "yyets/worker/.cargo-ok",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "yyets/worker/README.md",
    "chars": 582,
    "preview": "# Cloudflare Worker部署方式\n**This worker is deprecated. No further updates from now on.**\n\n## 1. 安装wrangler等工具\n[Cloudflare "
  },
  {
    "path": "yyets/worker/public/404.html",
    "chars": 1226,
    "preview": "<!doctype html>\n<html>\n    <head>\n        <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\">\n        <link href=\"h"
  },
  {
    "path": "yyets/worker/public/css/3rd/animate.css",
    "chars": 72259,
    "preview": "@charset \"UTF-8\";\n\n/*!\n * animate.css -http://daneden.me/animate\n * Version - 3.5.1\n * Licensed under the MIT license - "
  },
  {
    "path": "yyets/worker/public/css/3rd/icons.css",
    "chars": 59,
    "preview": "@charset \"utf-8\";\n@IMPORT url(\"../font-awesome.min.css\");\n\n"
  },
  {
    "path": "yyets/worker/public/css/3rd/widgets.css",
    "chars": 20,
    "preview": "@charset \"utf-8\";\n\n\n"
  },
  {
    "path": "yyets/worker/public/css/aYin.css",
    "chars": 323,
    "preview": "@charset \"utf-8\";\n\n/*webkit核心浏览器滚动条设置*/\n/*\n::-webkit-scrollbar{width: 10px;height:10px;}\n::-webkit-scrollbar-track{borde"
  },
  {
    "path": "yyets/worker/public/css/data.json",
    "chars": 7307,
    "preview": "{\n  \"status\": 1,\n  \"info\": \"OK\",\n  \"data\": {\n    \"info\": {\n      \"id\": 10060,\n      \"cnname\": \"铁皮人\",\n      \"enname\": \"Ti"
  },
  {
    "path": "yyets/worker/public/css/down-list-20180530.css",
    "chars": 161913,
    "preview": "@charset \"utf-8\";\n@IMPORT url(\"3rd/icons.css\");\n@IMPORT url(\"3rd/animate.css\");\n@IMPORT url(\"3rd/widgets.css\");\n\nhtml{fo"
  },
  {
    "path": "yyets/worker/public/css/index.json",
    "chars": 382,
    "preview": "{\n  \"越狱\\nPrison Break\\n逃\": 10004,\n  \"死亡地带\\nThe Dead Zone\\n\": 10010,\n  \"伪装者\\nThe Pretender\\n\": 10017,\n  \"橘子郡男孩\\nTHE OC\\nT"
  },
  {
    "path": "yyets/worker/public/css/jquery.mCustomScrollbar.css",
    "chars": 53583,
    "preview": "/*\n== malihu jquery custom scrollbar plugin ==\nPlugin URI: http://manos.malihu.gr/jquery-custom-content-scroller\n*/\n\n\n\n/"
  },
  {
    "path": "yyets/worker/public/fonts/test.txt",
    "chars": 4,
    "preview": "1234"
  },
  {
    "path": "yyets/worker/public/index.html",
    "chars": 1691,
    "preview": "<!doctype html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>人人影视下载分享</title>\n\n    <link rel=\"icon\" type=\"image/x"
  },
  {
    "path": "yyets/worker/public/js/aYin.js",
    "chars": 16361,
    "preview": "/*!\n * copyright 2017 aYin's Lib\n * ayin86@163.com  yinzhijun@dhcc.com.cn\n * only for authorized use.\n * contain open so"
  },
  {
    "path": "yyets/worker/public/js/rshare.js",
    "chars": 9311,
    "preview": "var _thunder_id_ = 37361;\nvar SHARE = {};\n\nSHARE.ParseTpl = function(str, data) {\n  var tplEngine = function(tpl, data) "
  },
  {
    "path": "yyets/worker/public/js/search.js",
    "chars": 1775,
    "preview": "const baseURL = \"https://yyets.click/\";\nconst resourceURL = baseURL + \"resource.html?id=\";\nconst indexURL = baseURL + \"?"
  },
  {
    "path": "yyets/worker/public/js/vue.js",
    "chars": 342147,
    "preview": "/*!\n * Vue.js v2.6.12\n * (c) 2014-2020 Evan You\n * Released under the MIT License.\n */\n(function (global, factory) {\n  t"
  },
  {
    "path": "yyets/worker/public/resource.html",
    "chars": 39752,
    "preview": "<!DOCTYPE html>\n<html lang=\"cn\">\n<head>\n    <meta charset=\"utf-8\">\n    <title>最帅的资源信息限时分享站</title>\n    <meta http-equiv="
  },
  {
    "path": "yyets/worker/public/search.html",
    "chars": 2352,
    "preview": "<!doctype html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>人人影视下载分享</title>\n    <link rel=\"icon\" type=\"image/x-"
  },
  {
    "path": "yyets/worker/workers-site/index.js",
    "chars": 3254,
    "preview": "import {getAssetFromKV} from '@cloudflare/kv-asset-handler'\n\n/**\n * The DEBUG flag will do two things that help during d"
  },
  {
    "path": "yyets/worker/workers-site/package.json",
    "chars": 235,
    "preview": "{\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"description\": \"A template for kick starting a Cloudflare Workers project\","
  },
  {
    "path": "yyets/worker/wrangler.toml",
    "chars": 285,
    "preview": "name = \"yyets\"\ntype = \"webpack\"\naccount_id = \"e8d3ba82fe9e9a41cceb0047c2a2ab4f\"\nworkers_dev = true\nroute = \"\"\nzone_id = "
  },
  {
    "path": "yyetsbot/config.py",
    "chars": 1787,
    "preview": "# coding: utf-8\n# YYeTsBot - config.py\n# 2019/8/15 18:42\n\n__author__ = \"Benny <benny.think@gmail.com>\"\n\nimport os\n\n# web"
  },
  {
    "path": "yyetsbot/fansub.py",
    "chars": 16670,
    "preview": "# coding: utf-8\n# YYeTsBot - fansub.py\n# 2019/8/15 18:30\n\n__author__ = \"Benny <benny.think@gmail.com>\"\n\nimport contextli"
  },
  {
    "path": "yyetsbot/utils.py",
    "chars": 1415,
    "preview": "# coding: utf-8\n# YYeTsBot - utils.py\n# 2019/8/15 20:27\n\n__author__ = 'Benny <benny.think@gmail.com>'\n\nimport pathlib\n\ni"
  },
  {
    "path": "yyetsbot/yyetsbot.py",
    "chars": 14821,
    "preview": "# coding: utf-8\n# YYeTsBot - bot.py\n# 2019/8/15 18:27\n\n__author__ = \"Benny <benny.think@gmail.com>\"\n\nimport io\nimport js"
  },
  {
    "path": "yyetsweb/README.md",
    "chars": 1462,
    "preview": "# web端\n\n**注意:源代码中包含Google Analytics分析代码,`index.html`, `search.html`和`resource.html`。如果自己使用,要记得去除哦**\n\n# requirements\n\n* t"
  },
  {
    "path": "yyetsweb/YYeTs-grafana.json",
    "chars": 10955,
    "preview": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_JSON\",\n      \"label\": \"JSON\",\n      \"description\": \"\",\n      \"type\": \"datasour"
  },
  {
    "path": "yyetsweb/commands/common.py",
    "chars": 495,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - common.py\n# 3/26/22 10:40\n#\n\n__author__ = \"Benny <benny.think@gma"
  },
  {
    "path": "yyetsweb/commands/douban_fix.py",
    "chars": 1177,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - douban_fix.py\n# 7/11/21 09:37\n#\n\n__author__ = \"Benny <benny.think"
  },
  {
    "path": "yyetsweb/commands/grafana_test_data.py",
    "chars": 1036,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - grafana_test_data.py\n# 3/14/21 18:25\n#\n\n__author__ = \"Benny <benn"
  },
  {
    "path": "yyetsweb/commands/share_excel.py",
    "chars": 1453,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - share_excel.py\n# 12/18/21 19:21\n#\n\n__author__ = \"Benny <benny.thi"
  },
  {
    "path": "yyetsweb/common/__init__.py",
    "chars": 140,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\n\n# YYeTsBot - __init__.py\n# 2023-03-17  18:57\n\nfrom common.utils import setup_log"
  },
  {
    "path": "yyetsweb/common/dump_db.py",
    "chars": 7548,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - dump_db.py\n# 2/4/22 18:10\n#\n\n__author__ = \"Benny <benny.think@gma"
  },
  {
    "path": "yyetsweb/common/sync.py",
    "chars": 9633,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport contextlib\nimport logging\nimport os\nimport random\nimport re\nimport time\nfr"
  },
  {
    "path": "yyetsweb/common/utils.py",
    "chars": 5266,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - utils.py\n# 6/16/21 21:42\n#\n\n__author__ = \"Benny <benny.think@gmai"
  },
  {
    "path": "yyetsweb/databases/__init__.py",
    "chars": 1227,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\n\n# YYeTsBot - __init__.py\n# 2023-03-17  18:57\n\nimport logging\nimport os\nimport pa"
  },
  {
    "path": "yyetsweb/databases/base.py",
    "chars": 7991,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport json\nimport logging\nimport os\nimport time\n\nimport meilisearch\n\nfrom databa"
  },
  {
    "path": "yyetsweb/databases/comment.py",
    "chars": 16633,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport contextlib\nimport os\nimport re\nfrom http import HTTPStatus\n\nimport pymongo"
  },
  {
    "path": "yyetsweb/databases/douban.py",
    "chars": 5856,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport contextlib\nimport logging\nimport re\nfrom http import HTTPStatus\nfrom urlli"
  },
  {
    "path": "yyetsweb/databases/grafana.py",
    "chars": 1316,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport time\nfrom datetime import date, timedelta\n\nimport pymongo\n\nfrom databases."
  },
  {
    "path": "yyetsweb/databases/oauth.py",
    "chars": 1555,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nfrom hashlib import sha256\n\nfrom common.utils import ts_date\nfrom databases.base "
  },
  {
    "path": "yyetsweb/databases/other.py",
    "chars": 6958,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport base64\nimport json\nimport logging\nimport os\nimport random\nimport re\nimport"
  },
  {
    "path": "yyetsweb/databases/resources.py",
    "chars": 13237,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport json\nimport logging\nimport os\nimport random\n\nimport pymongo\nimport zhconv\n"
  },
  {
    "path": "yyetsweb/databases/user.py",
    "chars": 8694,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport os\nimport random\nimport re\nfrom hashlib import md5, sha256\nfrom http impor"
  },
  {
    "path": "yyetsweb/go.mod",
    "chars": 1815,
    "preview": "module yyetsweb\n\ngo 1.19\n\nrequire (\n\tgithub.com/gin-gonic/gin v1.9.1\n\tgithub.com/glebarez/go-sqlite v1.19.1\n\tgithub.com/"
  },
  {
    "path": "yyetsweb/go.sum",
    "chars": 15395,
    "preview": "github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=\ngithub.com/bytedance/sonic v1.9"
  },
  {
    "path": "yyetsweb/handlers/__init__.py",
    "chars": 237,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport json\n\nfrom tornado import escape\n\nfrom common.utils import Cloudflare, set"
  },
  {
    "path": "yyetsweb/handlers/base.py",
    "chars": 3539,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport contextlib\nimport importlib\nimport json\nimport logging\nimport pathlib\nfrom"
  },
  {
    "path": "yyetsweb/handlers/comment.py",
    "chars": 6342,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nfrom http import HTTPStatus\nfrom pathlib import Path\n\nfrom tornado import gen, we"
  },
  {
    "path": "yyetsweb/handlers/douban.py",
    "chars": 1999,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nfrom http import HTTPStatus\nfrom pathlib import Path\n\nimport filetype\nfrom tornad"
  },
  {
    "path": "yyetsweb/handlers/grafana.py",
    "chars": 3890,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport json\nimport time\nfrom datetime import date, timedelta\nfrom http import HTT"
  },
  {
    "path": "yyetsweb/handlers/oauth.py",
    "chars": 6278,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport logging\nimport os\nfrom pathlib import Path\nfrom urllib.parse import urlenc"
  },
  {
    "path": "yyetsweb/handlers/other.py",
    "chars": 5525,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport logging\nimport os\nimport pathlib\nimport time\nfrom hashlib import sha1\nfrom"
  },
  {
    "path": "yyetsweb/handlers/resources.py",
    "chars": 3578,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nimport os\nfrom http import HTTPStatus\nfrom pathlib import Path\n\nfrom tornado impo"
  },
  {
    "path": "yyetsweb/handlers/user.py",
    "chars": 4868,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\nfrom http import HTTPStatus\nfrom pathlib import Path\n\nfrom tornado import gen, we"
  },
  {
    "path": "yyetsweb/server.go",
    "chars": 7190,
    "preview": "package main\n\nimport (\n\t\"archive/zip\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\""
  },
  {
    "path": "yyetsweb/server.py",
    "chars": 7141,
    "preview": "#!/usr/local/bin/python3\n# coding: utf-8\n\n# YYeTsBot - server.py\n# 2/5/21 21:02\n#\n\n__author__ = \"Benny <benny.think@gmai"
  },
  {
    "path": "yyetsweb/tests/router_test.py",
    "chars": 571,
    "preview": "#!/usr/bin/env python3\n# coding: utf-8\n\nimport pathlib\nimport sys\nimport unittest\n\nfrom tornado.testing import AsyncHTTP"
  }
]

About this extraction

This page contains the full source code of the tgbot-collection/YYeTsBot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (1015.6 KB), approximately 308.3k tokens, and a symbol index with 829 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!