├── app
├── __init__.py
├── message
│ ├── client
│ │ ├── __init__.py
│ │ ├── _base.py
│ │ ├── pushdeer.py
│ │ ├── iyuu.py
│ │ └── serverchan.py
│ ├── __init__.py
│ └── message_center.py
├── plugins
│ ├── modules
│ │ ├── __init__.py
│ │ ├── iyuu
│ │ │ └── __init__.py
│ │ └── _autosignin
│ │ │ ├── __init__.py
│ │ │ ├── pterclub.py
│ │ │ ├── _base.py
│ │ │ ├── haidan.py
│ │ │ ├── hhanclub.py
│ │ │ ├── hdcity.py
│ │ │ ├── carpt.py
│ │ │ ├── hdtime.py
│ │ │ └── hdfans.py
│ └── __init__.py
├── media
│ ├── tmdbv3api
│ │ ├── objs
│ │ │ ├── __init__.py
│ │ │ ├── find.py
│ │ │ ├── genre.py
│ │ │ ├── episode.py
│ │ │ └── discover.py
│ │ ├── exceptions.py
│ │ └── __init__.py
│ ├── doubanapi
│ │ └── __init__.py
│ ├── __init__.py
│ └── meta
│ │ ├── __init__.py
│ │ └── customization.py
├── mediaserver
│ ├── client
│ │ └── __init__.py
│ └── __init__.py
├── sites
│ ├── siteuserinfo
│ │ ├── __init__.py
│ │ ├── nexus_project.py
│ │ └── nexus_rabbit.py
│ ├── __init__.py
│ └── site_limiter.py
├── indexer
│ ├── __init__.py
│ └── client
│ │ ├── __init__.py
│ │ ├── _plugins.py
│ │ └── _haidan.py
├── downloader
│ ├── __init__.py
│ └── client
│ │ └── __init__.py
├── conf
│ ├── __init__.py
│ └── systemconfig.py
├── utils
│ ├── exception_utils.py
│ ├── nfo_reader.py
│ ├── number_utils.py
│ ├── cache_manager.py
│ ├── json_utils.py
│ ├── image_utils.py
│ ├── __init__.py
│ ├── dom_utils.py
│ ├── tokens.py
│ ├── rsstitle_utils.py
│ └── time_utils.py
├── helper
│ ├── redis_helper.py
│ ├── thread_helper.py
│ ├── __init__.py
│ ├── submodule_helper.py
│ ├── display_helper.py
│ ├── progress_helper.py
│ └── plugin_helper.py
└── db
│ └── __init__.py
├── tests
├── __init__.py
├── cases
│ └── __init__.py
├── meta_recogonize_v3
│ ├── __init__.py
│ ├── cases
│ │ ├── __init__.py
│ │ ├── testcase2.py
│ │ └── testcase1.py
│ └── test_meta_v3.py
├── run.py
└── test_metainfo.py
├── web
├── __init__.py
├── backend
│ ├── __init__.py
│ ├── user.cp310-win_amd64.pyd
│ ├── user.cpython-310-darwin.so
│ ├── user.cpython-310-aarch64-linux-gnu.so
│ └── user.cpython-310-x86_64-linux-gnu.so
├── robots.txt
├── static
│ ├── components
│ │ ├── plugin
│ │ │ └── index.js
│ │ ├── accordion
│ │ │ └── index.js
│ │ ├── card
│ │ │ ├── index.js
│ │ │ ├── normal
│ │ │ │ ├── state.js
│ │ │ │ └── placeholder.js
│ │ │ └── person
│ │ │ │ └── index.js
│ │ ├── layout
│ │ │ ├── index.js
│ │ │ └── navbar
│ │ │ │ └── button.js
│ │ ├── page
│ │ │ └── index.js
│ │ ├── custom
│ │ │ └── index.js
│ │ ├── lit-index.js
│ │ └── index.js
│ ├── favicon.ico
│ ├── img
│ │ ├── pt.jpg
│ │ ├── tv.png
│ │ ├── movie.jpg
│ │ ├── music.png
│ │ ├── person.png
│ │ ├── tmdb.png
│ │ ├── tmdb.webp
│ │ ├── users.png
│ │ ├── no-image.png
│ │ ├── startup.jpg
│ │ ├── filetree
│ │ │ ├── db.png
│ │ │ ├── code.png
│ │ │ ├── css.png
│ │ │ ├── doc.png
│ │ │ ├── file.png
│ │ │ ├── film.png
│ │ │ ├── html.png
│ │ │ ├── java.png
│ │ │ ├── pdf.png
│ │ │ ├── php.png
│ │ │ ├── ppt.png
│ │ │ ├── psd.png
│ │ │ ├── ruby.png
│ │ │ ├── txt.png
│ │ │ ├── xls.png
│ │ │ ├── zip.png
│ │ │ ├── flash.png
│ │ │ ├── linux.png
│ │ │ ├── music.png
│ │ │ ├── picture.png
│ │ │ ├── script.png
│ │ │ ├── spinner.gif
│ │ │ ├── directory.png
│ │ │ ├── file-lock.png
│ │ │ ├── application.png
│ │ │ ├── folder_open.png
│ │ │ └── directory-lock.png
│ │ ├── icon-imdb.png
│ │ ├── icons
│ │ │ ├── 1024.png
│ │ │ ├── 128.png
│ │ │ ├── 144.png
│ │ │ ├── 152.png
│ │ │ ├── 167.png
│ │ │ ├── 172.png
│ │ │ ├── 180.png
│ │ │ ├── 196.png
│ │ │ ├── 216.png
│ │ │ ├── 256.png
│ │ │ ├── 512.png
│ │ │ ├── 196_ALT.png
│ │ │ └── 512_ALT.png
│ │ ├── logo
│ │ │ ├── logo.png
│ │ │ ├── logo-16x16.png
│ │ │ ├── logo-32x32.png
│ │ │ └── logo-black.png
│ │ ├── plugins
│ │ │ ├── nfo.png
│ │ │ ├── tag.png
│ │ │ ├── cloud.png
│ │ │ ├── emby.png
│ │ │ ├── hosts.png
│ │ │ ├── iyuu.png
│ │ │ ├── like.jpg
│ │ │ ├── movie.jpg
│ │ │ ├── regex.png
│ │ │ ├── backup.png
│ │ │ ├── douban.png
│ │ │ ├── jackett.png
│ │ │ ├── prowlarr.png
│ │ │ ├── random.png
│ │ │ ├── refresh.png
│ │ │ ├── scraper.png
│ │ │ ├── signin.png
│ │ │ ├── teamwork.png
│ │ │ ├── webhook.png
│ │ │ ├── cloudflare.jpg
│ │ │ ├── diskusage.jpg
│ │ │ ├── mteam_rss.png
│ │ │ ├── synctimer.png
│ │ │ ├── SpeedLimiter.jpg
│ │ │ ├── mediasyncdel.png
│ │ │ ├── opensubtitles.png
│ │ │ ├── autosubtitles.jpeg
│ │ │ ├── torrentremover.png
│ │ │ ├── torrenttransfer.jpg
│ │ │ └── chinesesubfinder.png
│ │ ├── sites
│ │ │ ├── 1ptba.ico
│ │ │ ├── dmhy.ico
│ │ │ ├── eztv.ico
│ │ │ ├── iyuu.png
│ │ │ ├── nyaa.png
│ │ │ ├── piggo.ico
│ │ │ ├── rarbg.ico
│ │ │ ├── zmpt.ico
│ │ │ ├── acgrip.ico
│ │ │ ├── hddolby.ico
│ │ │ ├── hdfans.ico
│ │ │ ├── hhclub.ico
│ │ │ ├── icc2022.ico
│ │ │ ├── leaves.ico
│ │ │ ├── lemonhd.ico
│ │ │ ├── sharkpt.ico
│ │ │ ├── audiences.ico
│ │ │ ├── freefarm.ico
│ │ │ ├── mikanani.ico
│ │ │ ├── wintersakura.ico
│ │ │ └── torrentgalaxy.png
│ │ ├── message
│ │ │ ├── bark.webp
│ │ │ ├── iyuu.png
│ │ │ ├── ntfy.webp
│ │ │ ├── slack.png
│ │ │ ├── chanify.png
│ │ │ ├── gotify.png
│ │ │ ├── pushdeer.png
│ │ │ ├── pushplus.jpg
│ │ │ ├── telegram.png
│ │ │ ├── wechat.png
│ │ │ ├── serverchan.png
│ │ │ └── synologychat.png
│ │ ├── webhook_icon.png
│ │ ├── downloader
│ │ │ ├── 115.jpg
│ │ │ ├── aria2.png
│ │ │ ├── pikpak.png
│ │ │ ├── qbittorrent.png
│ │ │ └── transmission.png
│ │ ├── indexer
│ │ │ ├── indexer.jpg
│ │ │ ├── indexer.png
│ │ │ ├── jackett.png
│ │ │ └── prowlarr.png
│ │ ├── mediaserver
│ │ │ ├── emby.png
│ │ │ ├── plex.png
│ │ │ ├── jellyfin.jpg
│ │ │ ├── jellyfin.png
│ │ │ ├── emby_backdrop.png
│ │ │ ├── plex_backdrop.png
│ │ │ └── jellyfin_backdrop.jpg
│ │ ├── splash
│ │ │ ├── apple-splash-1136-640.png
│ │ │ ├── apple-splash-1334-750.png
│ │ │ ├── apple-splash-1792-828.png
│ │ │ ├── apple-splash-640-1136.png
│ │ │ ├── apple-splash-750-1334.png
│ │ │ ├── apple-splash-828-1792.png
│ │ │ ├── apple-splash-1125-2436.png
│ │ │ ├── apple-splash-1170-2532.png
│ │ │ ├── apple-splash-1242-2208.png
│ │ │ ├── apple-splash-1242-2688.png
│ │ │ ├── apple-splash-1284-2778.png
│ │ │ ├── apple-splash-1536-2048.png
│ │ │ ├── apple-splash-1620-2160.png
│ │ │ ├── apple-splash-1668-2224.png
│ │ │ ├── apple-splash-1668-2388.png
│ │ │ ├── apple-splash-2048-1536.png
│ │ │ ├── apple-splash-2048-2732.png
│ │ │ ├── apple-splash-2160-1620.png
│ │ │ ├── apple-splash-2208-1242.png
│ │ │ ├── apple-splash-2224-1668.png
│ │ │ ├── apple-splash-2388-1668.png
│ │ │ ├── apple-splash-2436-1125.png
│ │ │ ├── apple-splash-2532-1170.png
│ │ │ ├── apple-splash-2688-1242.png
│ │ │ ├── apple-splash-2732-2048.png
│ │ │ └── apple-splash-2778-1284.png
│ │ └── no-image.svg
│ ├── js
│ │ ├── tabler
│ │ │ └── demo-theme.min.js
│ │ └── fullcalendar
│ │ │ └── locales
│ │ │ └── zh-cn.js
│ ├── site.webmanifest
│ └── css
│ │ └── nprogress.css
└── templates
│ ├── discovery
│ ├── ranking.html
│ ├── mediainfo.html
│ └── person.html
│ ├── 500.html
│ ├── 404.html
│ ├── site
│ └── sitelist.html
│ └── macro
│ └── oops.html
├── third_party.txt
├── .flaskenv
├── version.py
├── third_party
└── feapder
│ ├── feapder
│ ├── VERSION
│ ├── commands
│ │ ├── __init__.py
│ │ ├── create
│ │ │ ├── __init__.py
│ │ │ ├── create_setting.py
│ │ │ ├── create_init.py
│ │ │ ├── create_cookies.py
│ │ │ ├── create_params.py
│ │ │ ├── create_json.py
│ │ │ └── create_project.py
│ │ └── retry.py
│ ├── network
│ │ ├── __init__.py
│ │ ├── downloader
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ └── _requests.py
│ │ ├── proxy_pool
│ │ │ ├── __init__.py
│ │ │ └── base.py
│ │ └── user_pool
│ │ │ └── __init__.py
│ ├── templates
│ │ ├── project_template
│ │ │ ├── items
│ │ │ │ └── __init__.py
│ │ │ ├── spiders
│ │ │ │ └── __init__.py
│ │ │ ├── README.md
│ │ │ ├── CHECK_DATA.md
│ │ │ └── main.py
│ │ ├── item_template.tmpl
│ │ ├── update_item_template.tmpl
│ │ ├── air_spider_template.tmpl
│ │ ├── spider_template.tmpl
│ │ └── batch_spider_template.tmpl
│ ├── core
│ │ ├── __init__.py
│ │ ├── spiders
│ │ │ └── __init__.py
│ │ └── handle_failed_requests.py
│ ├── db
│ │ ├── __init__.py
│ │ └── memorydb.py
│ ├── utils
│ │ ├── __init__.py
│ │ └── webdriver
│ │ │ └── __init__.py
│ ├── buffer
│ │ └── __init__.py
│ ├── requirements.txt
│ ├── __init__.py
│ ├── dedup
│ │ ├── basefilter.py
│ │ ├── litefilter.py
│ │ └── README.md
│ └── pipelines
│ │ ├── console_pipeline.py
│ │ └── __init__.py
│ ├── .gitignore
│ ├── MANIFEST.in
│ └── LICENSE
├── docker
├── rootfs
│ └── etc
│ │ └── s6-overlay
│ │ └── s6-rc.d
│ │ ├── svc-redis
│ │ ├── type
│ │ ├── notification-fd
│ │ ├── dependencies.d
│ │ │ └── init-020-fixuser
│ │ ├── finish
│ │ └── run
│ │ ├── svc-nastools
│ │ ├── type
│ │ ├── notification-fd
│ │ ├── dependencies.d
│ │ │ └── init-020-fixuser
│ │ ├── finish
│ │ └── run
│ │ ├── user
│ │ └── contents.d
│ │ │ ├── svc-redis
│ │ │ ├── init-010-update
│ │ │ ├── svc-nastools
│ │ │ └── init-020-fixuser
│ │ ├── init-010-update
│ │ ├── type
│ │ └── up
│ │ └── init-020-fixuser
│ │ ├── type
│ │ ├── dependencies.d
│ │ └── init-010-update
│ │ ├── up
│ │ └── run
├── volume.png
└── compose.yml
├── scripts
├── sqls
│ ├── update_systemdict.sql
│ ├── update_downloader.sql
│ ├── update_userpris.sql
│ ├── update_userrss.sql
│ ├── stop_all_service.sql
│ └── update_subscribe.sql
├── script.py.mako
└── versions
│ ├── a19a48dbb41b_1_2_7.py
│ ├── 1f5cc26cdd3d_1_2_3.py
│ ├── 69508d1aed24_1_2_1.py
│ ├── 7c14267ffbe4_1_2_8.py
│ ├── 702b7666a634_1_2_5.py
│ ├── ff1b04a637f8_1_3_0.py
│ ├── d68a85a8f10d_1_2_6.py
│ ├── c8ebf2f4340e_1_3_3.py
│ ├── 7480d259772e_1_3_4.py
│ ├── ae61cfa6ada6_1_2_4.py
│ ├── d116d793ba9f_1_3_5.py
│ ├── eb3437042cc8_1_3_1.py
│ ├── 13a58bd5311f_1_2_2.py
│ └── 57c4146172c2_1_3_2.py
├── package
├── rely
│ ├── upx.exe
│ ├── hook-cn2an.py
│ ├── hook-iso639.py
│ ├── hook-zhconv.py
│ └── template.jinja2
├── nas-tools.ico
├── builder
│ ├── Dockerfile
│ └── alpine.Dockerfile
└── trayicon.py
├── .gitmodules
├── .github
└── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.yml
│ └── bug_report.yml
├── package_list.txt
├── .gitignore
├── package_list_debian.txt
├── dbscript_gen.py
├── diff.md
└── Q&A.md
/app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/cases/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party.txt:
--------------------------------------------------------------------------------
1 | feapder
--------------------------------------------------------------------------------
/web/backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.flaskenv:
--------------------------------------------------------------------------------
1 | FLASK_APP=web/main.py
--------------------------------------------------------------------------------
/app/message/client/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/plugins/modules/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/objs/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/mediaserver/client/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/plugins/modules/iyuu/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/sites/siteuserinfo/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/meta_recogonize_v3/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/version.py:
--------------------------------------------------------------------------------
1 | APP_VERSION = 'v3.5.6'
2 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/meta_recogonize_v3/cases/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/VERSION:
--------------------------------------------------------------------------------
1 | 1.8.9-beta1
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/app/indexer/__init__.py:
--------------------------------------------------------------------------------
1 | from .indexer import Indexer
2 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-redis/type:
--------------------------------------------------------------------------------
1 | longrun
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-nastools/type:
--------------------------------------------------------------------------------
1 | longrun
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-redis/notification-fd:
--------------------------------------------------------------------------------
1 | 3
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-redis:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/downloader/__init__.py:
--------------------------------------------------------------------------------
1 | from .downloader import Downloader
2 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/init-010-update/type:
--------------------------------------------------------------------------------
1 | oneshot
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/init-020-fixuser/type:
--------------------------------------------------------------------------------
1 | oneshot
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-nastools/notification-fd:
--------------------------------------------------------------------------------
1 | 3
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-010-update:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-nastools:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/indexer/client/__init__.py:
--------------------------------------------------------------------------------
1 | from .builtin import BuiltinIndexer
2 |
--------------------------------------------------------------------------------
/app/mediaserver/__init__.py:
--------------------------------------------------------------------------------
1 | from .media_server import MediaServer
2 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-020-fixuser:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/project_template/items/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/static/components/plugin/index.js:
--------------------------------------------------------------------------------
1 | export * from "./modal/index.js";
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-redis/dependencies.d/init-020-fixuser:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/project_template/spiders/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/exceptions.py:
--------------------------------------------------------------------------------
1 | class TMDbException(Exception):
2 | pass
3 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-nastools/dependencies.d/init-020-fixuser:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/sqls/update_systemdict.sql:
--------------------------------------------------------------------------------
1 | DELETE FROM SYSTEM_DICT WHERE TYPE like '刷流%';
--------------------------------------------------------------------------------
/web/static/components/accordion/index.js:
--------------------------------------------------------------------------------
1 | export * from "./seasons/index.js";
2 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/init-020-fixuser/dependencies.d/init-010-update:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker/volume.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/docker/volume.png
--------------------------------------------------------------------------------
/package/rely/upx.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/package/rely/upx.exe
--------------------------------------------------------------------------------
/app/media/doubanapi/__init__.py:
--------------------------------------------------------------------------------
1 | from .apiv2 import DoubanApi
2 | from .webapi import DoubanWeb
3 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/init-010-update/up:
--------------------------------------------------------------------------------
1 | /etc/s6-overlay/s6-rc.d/init-010-update/run
--------------------------------------------------------------------------------
/package/nas-tools.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/package/nas-tools.ico
--------------------------------------------------------------------------------
/scripts/sqls/update_downloader.sql:
--------------------------------------------------------------------------------
1 | UPDATE DOWNLOADER SET MATCH_PATH = 0 WHERE MATCH_PATH IS NULL
--------------------------------------------------------------------------------
/web/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/favicon.ico
--------------------------------------------------------------------------------
/web/static/img/pt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/pt.jpg
--------------------------------------------------------------------------------
/web/static/img/tv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/tv.png
--------------------------------------------------------------------------------
/app/conf/__init__.py:
--------------------------------------------------------------------------------
1 | from .systemconfig import SystemConfig
2 | from .moduleconf import ModuleConf
3 |
--------------------------------------------------------------------------------
/app/message/__init__.py:
--------------------------------------------------------------------------------
1 | from .message import Message
2 | from .message_center import MessageCenter
3 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/init-020-fixuser/up:
--------------------------------------------------------------------------------
1 | /etc/s6-overlay/s6-rc.d/init-020-fixuser/run
--------------------------------------------------------------------------------
/scripts/sqls/update_userpris.sql:
--------------------------------------------------------------------------------
1 | UPDATE main.CONFIG_USERS SET PRIS = replace(PRIS, '推荐', '探索') WHERE 1
--------------------------------------------------------------------------------
/web/static/img/movie.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/movie.jpg
--------------------------------------------------------------------------------
/web/static/img/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/music.png
--------------------------------------------------------------------------------
/web/static/img/person.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/person.png
--------------------------------------------------------------------------------
/web/static/img/tmdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/tmdb.png
--------------------------------------------------------------------------------
/web/static/img/tmdb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/tmdb.webp
--------------------------------------------------------------------------------
/web/static/img/users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/users.png
--------------------------------------------------------------------------------
/scripts/sqls/update_userrss.sql:
--------------------------------------------------------------------------------
1 | UPDATE CONFIG_USER_RSS SET PROCESS_COUNT = '0' WHERE PROCESS_COUNT is null
--------------------------------------------------------------------------------
/web/static/components/card/index.js:
--------------------------------------------------------------------------------
1 | export * from "./normal/index.js";
2 | export * from "./person/index.js";
--------------------------------------------------------------------------------
/web/static/img/no-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/no-image.png
--------------------------------------------------------------------------------
/web/static/img/startup.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/startup.jpg
--------------------------------------------------------------------------------
/web/templates/discovery/ranking.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/static/components/layout/index.js:
--------------------------------------------------------------------------------
1 | export * from "./navbar/index.js";
2 | export * from "./searchbar/index.js";
--------------------------------------------------------------------------------
/web/static/img/filetree/db.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/db.png
--------------------------------------------------------------------------------
/web/static/img/icon-imdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icon-imdb.png
--------------------------------------------------------------------------------
/web/static/img/icons/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/1024.png
--------------------------------------------------------------------------------
/web/static/img/icons/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/128.png
--------------------------------------------------------------------------------
/web/static/img/icons/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/144.png
--------------------------------------------------------------------------------
/web/static/img/icons/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/152.png
--------------------------------------------------------------------------------
/web/static/img/icons/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/167.png
--------------------------------------------------------------------------------
/web/static/img/icons/172.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/172.png
--------------------------------------------------------------------------------
/web/static/img/icons/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/180.png
--------------------------------------------------------------------------------
/web/static/img/icons/196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/196.png
--------------------------------------------------------------------------------
/web/static/img/icons/216.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/216.png
--------------------------------------------------------------------------------
/web/static/img/icons/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/256.png
--------------------------------------------------------------------------------
/web/static/img/icons/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/512.png
--------------------------------------------------------------------------------
/web/static/img/logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/logo/logo.png
--------------------------------------------------------------------------------
/web/static/img/plugins/nfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/nfo.png
--------------------------------------------------------------------------------
/web/static/img/plugins/tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/tag.png
--------------------------------------------------------------------------------
/web/static/img/sites/1ptba.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/1ptba.ico
--------------------------------------------------------------------------------
/web/static/img/sites/dmhy.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/dmhy.ico
--------------------------------------------------------------------------------
/web/static/img/sites/eztv.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/eztv.ico
--------------------------------------------------------------------------------
/web/static/img/sites/iyuu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/iyuu.png
--------------------------------------------------------------------------------
/web/static/img/sites/nyaa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/nyaa.png
--------------------------------------------------------------------------------
/web/static/img/sites/piggo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/piggo.ico
--------------------------------------------------------------------------------
/web/static/img/sites/rarbg.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/rarbg.ico
--------------------------------------------------------------------------------
/web/static/img/sites/zmpt.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/zmpt.ico
--------------------------------------------------------------------------------
/app/downloader/client/__init__.py:
--------------------------------------------------------------------------------
1 | from .qbittorrent import Qbittorrent
2 | from .transmission import Transmission
3 |
--------------------------------------------------------------------------------
/web/static/img/filetree/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/code.png
--------------------------------------------------------------------------------
/web/static/img/filetree/css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/css.png
--------------------------------------------------------------------------------
/web/static/img/filetree/doc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/doc.png
--------------------------------------------------------------------------------
/web/static/img/filetree/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/file.png
--------------------------------------------------------------------------------
/web/static/img/filetree/film.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/film.png
--------------------------------------------------------------------------------
/web/static/img/filetree/html.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/html.png
--------------------------------------------------------------------------------
/web/static/img/filetree/java.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/java.png
--------------------------------------------------------------------------------
/web/static/img/filetree/pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/pdf.png
--------------------------------------------------------------------------------
/web/static/img/filetree/php.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/php.png
--------------------------------------------------------------------------------
/web/static/img/filetree/ppt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/ppt.png
--------------------------------------------------------------------------------
/web/static/img/filetree/psd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/psd.png
--------------------------------------------------------------------------------
/web/static/img/filetree/ruby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/ruby.png
--------------------------------------------------------------------------------
/web/static/img/filetree/txt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/txt.png
--------------------------------------------------------------------------------
/web/static/img/filetree/xls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/xls.png
--------------------------------------------------------------------------------
/web/static/img/filetree/zip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/zip.png
--------------------------------------------------------------------------------
/web/static/img/icons/196_ALT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/196_ALT.png
--------------------------------------------------------------------------------
/web/static/img/icons/512_ALT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/icons/512_ALT.png
--------------------------------------------------------------------------------
/web/static/img/message/bark.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/bark.webp
--------------------------------------------------------------------------------
/web/static/img/message/iyuu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/iyuu.png
--------------------------------------------------------------------------------
/web/static/img/message/ntfy.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/ntfy.webp
--------------------------------------------------------------------------------
/web/static/img/message/slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/slack.png
--------------------------------------------------------------------------------
/web/static/img/plugins/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/cloud.png
--------------------------------------------------------------------------------
/web/static/img/plugins/emby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/emby.png
--------------------------------------------------------------------------------
/web/static/img/plugins/hosts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/hosts.png
--------------------------------------------------------------------------------
/web/static/img/plugins/iyuu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/iyuu.png
--------------------------------------------------------------------------------
/web/static/img/plugins/like.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/like.jpg
--------------------------------------------------------------------------------
/web/static/img/plugins/movie.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/movie.jpg
--------------------------------------------------------------------------------
/web/static/img/plugins/regex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/regex.png
--------------------------------------------------------------------------------
/web/static/img/sites/acgrip.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/acgrip.ico
--------------------------------------------------------------------------------
/web/static/img/sites/hddolby.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/hddolby.ico
--------------------------------------------------------------------------------
/web/static/img/sites/hdfans.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/hdfans.ico
--------------------------------------------------------------------------------
/web/static/img/sites/hhclub.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/hhclub.ico
--------------------------------------------------------------------------------
/web/static/img/sites/icc2022.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/icc2022.ico
--------------------------------------------------------------------------------
/web/static/img/sites/leaves.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/leaves.ico
--------------------------------------------------------------------------------
/web/static/img/sites/lemonhd.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/lemonhd.ico
--------------------------------------------------------------------------------
/web/static/img/sites/sharkpt.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/sharkpt.ico
--------------------------------------------------------------------------------
/web/static/img/webhook_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/webhook_icon.png
--------------------------------------------------------------------------------
/web/static/img/downloader/115.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/downloader/115.jpg
--------------------------------------------------------------------------------
/web/static/img/downloader/aria2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/downloader/aria2.png
--------------------------------------------------------------------------------
/web/static/img/filetree/flash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/flash.png
--------------------------------------------------------------------------------
/web/static/img/filetree/linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/linux.png
--------------------------------------------------------------------------------
/web/static/img/filetree/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/music.png
--------------------------------------------------------------------------------
/web/static/img/filetree/picture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/picture.png
--------------------------------------------------------------------------------
/web/static/img/filetree/script.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/script.png
--------------------------------------------------------------------------------
/web/static/img/filetree/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/spinner.gif
--------------------------------------------------------------------------------
/web/static/img/indexer/indexer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/indexer/indexer.jpg
--------------------------------------------------------------------------------
/web/static/img/indexer/indexer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/indexer/indexer.png
--------------------------------------------------------------------------------
/web/static/img/indexer/jackett.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/indexer/jackett.png
--------------------------------------------------------------------------------
/web/static/img/indexer/prowlarr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/indexer/prowlarr.png
--------------------------------------------------------------------------------
/web/static/img/logo/logo-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/logo/logo-16x16.png
--------------------------------------------------------------------------------
/web/static/img/logo/logo-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/logo/logo-32x32.png
--------------------------------------------------------------------------------
/web/static/img/logo/logo-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/logo/logo-black.png
--------------------------------------------------------------------------------
/web/static/img/mediaserver/emby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/emby.png
--------------------------------------------------------------------------------
/web/static/img/mediaserver/plex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/plex.png
--------------------------------------------------------------------------------
/web/static/img/message/chanify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/chanify.png
--------------------------------------------------------------------------------
/web/static/img/message/gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/gotify.png
--------------------------------------------------------------------------------
/web/static/img/message/pushdeer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/pushdeer.png
--------------------------------------------------------------------------------
/web/static/img/message/pushplus.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/pushplus.jpg
--------------------------------------------------------------------------------
/web/static/img/message/telegram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/telegram.png
--------------------------------------------------------------------------------
/web/static/img/message/wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/wechat.png
--------------------------------------------------------------------------------
/web/static/img/plugins/backup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/backup.png
--------------------------------------------------------------------------------
/web/static/img/plugins/douban.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/douban.png
--------------------------------------------------------------------------------
/web/static/img/plugins/jackett.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/jackett.png
--------------------------------------------------------------------------------
/web/static/img/plugins/prowlarr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/prowlarr.png
--------------------------------------------------------------------------------
/web/static/img/plugins/random.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/random.png
--------------------------------------------------------------------------------
/web/static/img/plugins/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/refresh.png
--------------------------------------------------------------------------------
/web/static/img/plugins/scraper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/scraper.png
--------------------------------------------------------------------------------
/web/static/img/plugins/signin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/signin.png
--------------------------------------------------------------------------------
/web/static/img/plugins/teamwork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/teamwork.png
--------------------------------------------------------------------------------
/web/static/img/plugins/webhook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/webhook.png
--------------------------------------------------------------------------------
/web/static/img/sites/audiences.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/audiences.ico
--------------------------------------------------------------------------------
/web/static/img/sites/freefarm.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/freefarm.ico
--------------------------------------------------------------------------------
/web/static/img/sites/mikanani.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/mikanani.ico
--------------------------------------------------------------------------------
/web/templates/discovery/mediainfo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "third_party/feapder"]
2 | path = third_party/feapder
3 | url = https://github.com/jxxghp/feapder
4 |
--------------------------------------------------------------------------------
/web/backend/user.cp310-win_amd64.pyd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/backend/user.cp310-win_amd64.pyd
--------------------------------------------------------------------------------
/web/static/img/downloader/pikpak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/downloader/pikpak.png
--------------------------------------------------------------------------------
/web/static/img/filetree/directory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/directory.png
--------------------------------------------------------------------------------
/web/static/img/filetree/file-lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/file-lock.png
--------------------------------------------------------------------------------
/web/static/img/message/serverchan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/serverchan.png
--------------------------------------------------------------------------------
/web/static/img/plugins/cloudflare.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/cloudflare.jpg
--------------------------------------------------------------------------------
/web/static/img/plugins/diskusage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/diskusage.jpg
--------------------------------------------------------------------------------
/web/static/img/plugins/mteam_rss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/mteam_rss.png
--------------------------------------------------------------------------------
/web/static/img/plugins/synctimer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/synctimer.png
--------------------------------------------------------------------------------
/web/static/img/sites/wintersakura.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/wintersakura.ico
--------------------------------------------------------------------------------
/package/rely/hook-cn2an.py:
--------------------------------------------------------------------------------
1 | from PyInstaller.utils.hooks import collect_data_files
2 |
3 | datas = collect_data_files("cn2an")
4 |
--------------------------------------------------------------------------------
/package/rely/hook-iso639.py:
--------------------------------------------------------------------------------
1 | from PyInstaller.utils.hooks import collect_data_files
2 |
3 | datas = collect_data_files("iso639")
4 |
--------------------------------------------------------------------------------
/package/rely/hook-zhconv.py:
--------------------------------------------------------------------------------
1 | from PyInstaller.utils.hooks import collect_data_files
2 |
3 | datas = collect_data_files("zhconv")
4 |
--------------------------------------------------------------------------------
/web/backend/user.cpython-310-darwin.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/backend/user.cpython-310-darwin.so
--------------------------------------------------------------------------------
/web/static/img/filetree/application.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/application.png
--------------------------------------------------------------------------------
/web/static/img/filetree/folder_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/folder_open.png
--------------------------------------------------------------------------------
/web/static/img/mediaserver/jellyfin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/jellyfin.jpg
--------------------------------------------------------------------------------
/web/static/img/mediaserver/jellyfin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/jellyfin.png
--------------------------------------------------------------------------------
/web/static/img/message/synologychat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/message/synologychat.png
--------------------------------------------------------------------------------
/web/static/img/plugins/SpeedLimiter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/SpeedLimiter.jpg
--------------------------------------------------------------------------------
/web/static/img/plugins/mediasyncdel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/mediasyncdel.png
--------------------------------------------------------------------------------
/web/static/img/plugins/opensubtitles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/opensubtitles.png
--------------------------------------------------------------------------------
/web/static/img/sites/torrentgalaxy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/sites/torrentgalaxy.png
--------------------------------------------------------------------------------
/app/plugins/__init__.py:
--------------------------------------------------------------------------------
1 | from .event_manager import EventManager, EventHandler, Event
2 | from .plugin_manager import PluginManager
3 |
--------------------------------------------------------------------------------
/scripts/sqls/stop_all_service.sql:
--------------------------------------------------------------------------------
1 | UPDATE SITE_BRUSH_TASK SET STATE = 'N' WHERE 1;
2 | UPDATE TORRENT_REMOVE_TASK SET ENABLED = 0 WHERE 1;
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/project_template/README.md:
--------------------------------------------------------------------------------
1 | # xxx爬虫文档
2 | ## 调研
3 |
4 | ## 数据库设计
5 |
6 | ## 爬虫逻辑
7 |
8 | ## 项目架构
--------------------------------------------------------------------------------
/web/static/img/downloader/qbittorrent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/downloader/qbittorrent.png
--------------------------------------------------------------------------------
/web/static/img/downloader/transmission.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/downloader/transmission.png
--------------------------------------------------------------------------------
/web/static/img/filetree/directory-lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/filetree/directory-lock.png
--------------------------------------------------------------------------------
/web/static/img/plugins/autosubtitles.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/autosubtitles.jpeg
--------------------------------------------------------------------------------
/web/static/img/plugins/torrentremover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/torrentremover.png
--------------------------------------------------------------------------------
/web/static/img/plugins/torrenttransfer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/torrenttransfer.jpg
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-redis/finish:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # shellcheck shell=bash
3 |
4 | pkill -f 'redis-server'
--------------------------------------------------------------------------------
/web/static/img/mediaserver/emby_backdrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/emby_backdrop.png
--------------------------------------------------------------------------------
/web/static/img/mediaserver/plex_backdrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/plex_backdrop.png
--------------------------------------------------------------------------------
/web/static/img/plugins/chinesesubfinder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/plugins/chinesesubfinder.png
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-nastools/finish:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # shellcheck shell=bash
3 |
4 | pkill -f 'python3 run.py'
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1136-640.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1136-640.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1334-750.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1334-750.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1792-828.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1792-828.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-640-1136.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-640-1136.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-750-1334.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-750-1334.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-828-1792.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-828-1792.png
--------------------------------------------------------------------------------
/web/backend/user.cpython-310-aarch64-linux-gnu.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/backend/user.cpython-310-aarch64-linux-gnu.so
--------------------------------------------------------------------------------
/web/backend/user.cpython-310-x86_64-linux-gnu.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/backend/user.cpython-310-x86_64-linux-gnu.so
--------------------------------------------------------------------------------
/web/static/components/page/index.js:
--------------------------------------------------------------------------------
1 | export * from "./discovery/index.js";
2 | export * from "./mediainfo/index.js";
3 | export * from "./person/index.js";
--------------------------------------------------------------------------------
/web/static/img/mediaserver/jellyfin_backdrop.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/mediaserver/jellyfin_backdrop.jpg
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1125-2436.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1125-2436.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1170-2532.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1170-2532.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1242-2208.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1242-2208.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1242-2688.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1242-2688.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1284-2778.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1284-2778.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1536-2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1536-2048.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1620-2160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1620-2160.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1668-2224.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1668-2224.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-1668-2388.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-1668-2388.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2048-1536.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2048-1536.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2048-2732.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2048-2732.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2160-1620.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2160-1620.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2208-1242.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2208-1242.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2224-1668.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2224-1668.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2388-1668.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2388-1668.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2436-1125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2436-1125.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2532-1170.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2532-1170.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2688-1242.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2688-1242.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2732-2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2732-2048.png
--------------------------------------------------------------------------------
/web/static/img/splash/apple-splash-2778-1284.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xforee/nas-tools/HEAD/web/static/img/splash/apple-splash-2778-1284.png
--------------------------------------------------------------------------------
/web/static/components/custom/index.js:
--------------------------------------------------------------------------------
1 | export * from "./img/index.js";
2 | export * from "./plex-library-img/index.js";
3 | export * from "./slide/index.js";
--------------------------------------------------------------------------------
/app/media/__init__.py:
--------------------------------------------------------------------------------
1 | from .category import Category
2 | from .media import Media
3 | from .scraper import Scraper
4 | from .douban import DouBan
5 | from .bangumi import Bangumi
6 |
--------------------------------------------------------------------------------
/scripts/sqls/update_subscribe.sql:
--------------------------------------------------------------------------------
1 | UPDATE RSS_MOVIES SET DOWNLOAD_SETTING = null WHERE DOWNLOAD_SETTING = -1;
2 | UPDATE RSS_TVS SET DOWNLOAD_SETTING = null WHERE DOWNLOAD_SETTING = -1;
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 项目讨论
4 | url: https://github.com/0xforee/nas-tools/discussions/new/choose
5 | about: discussion
6 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/core/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | '''
3 | Created on 2020/4/23 12:09 AM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | '''
--------------------------------------------------------------------------------
/third_party/feapder/feapder/db/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2020/4/23 12:09 AM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
--------------------------------------------------------------------------------
/third_party/feapder/feapder/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | '''
3 | Created on 2019/11/5 4:41 PM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | '''
--------------------------------------------------------------------------------
/third_party/feapder/feapder/buffer/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | '''
3 | Created on 2020/4/23 12:09 AM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | '''
--------------------------------------------------------------------------------
/package_list.txt:
--------------------------------------------------------------------------------
1 | bash
2 | chromium-chromedriver
3 | curl
4 | ffmpeg
5 | fuse3
6 | git
7 | inotify-tools
8 | netcat-openbsd
9 | procps-ng
10 | redis
11 | s6-overlay
12 | shadow
13 | sudo
14 | tzdata
15 | wget
16 | xvfb
17 | yq
18 | zip
--------------------------------------------------------------------------------
/web/templates/discovery/person.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/sites/__init__.py:
--------------------------------------------------------------------------------
1 | from app.sites.site_userinfo import SiteUserInfo
2 | from .sites import Sites
3 | from .site_cookie import SiteCookie
4 | from .site_subtitle import SiteSubtitle
5 | from .siteconf import SiteConf
6 | from .site_limiter import SiteRateLimiter
7 |
--------------------------------------------------------------------------------
/third_party/feapder/.gitignore:
--------------------------------------------------------------------------------
1 | files/*
2 | .DS_Store
3 | .idea/*
4 | */.idea/*
5 | venv/*
6 | venv2/*
7 | *.pyc
8 | *test.py
9 | *.log
10 | **/proxy_file
11 | build/
12 | dist/
13 | *.egg-info/
14 | .vscode/
15 | media/
16 | .MWebMetaData/
17 | push.sh
18 | assets/
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/downloader/__init__.py:
--------------------------------------------------------------------------------
1 | from ._requests import RequestsDownloader
2 | from ._requests import RequestsSessionDownloader
3 |
4 | # 下面是非必要依赖
5 | try:
6 | from ._selenium import SeleniumDownloader
7 | except ModuleNotFoundError:
8 | pass
9 |
--------------------------------------------------------------------------------
/web/static/components/lit-index.js:
--------------------------------------------------------------------------------
1 | export * from "./custom/index.js";
2 | export * from "./card/index.js";
3 | export * from "./page/index.js";
4 | export * from "./layout/index.js";
5 | export * from "./plugin/index.js";
6 | export * from "./accordion/index.js";
7 | export * from "./cmd-dialog/index.js";
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/**
2 | *.pyc
3 | *.c
4 | /test.py
5 | /setup.py
6 | /build_sites.py
7 | /build/**
8 | /venv/**
9 | /cloudflarespeedtest/**
10 | .python-version
11 | .vscode/
12 | Dockerfile-dev
13 | config/sites.json
14 | nastools_test.py
15 | /node_modules/**
16 | package-lock.json
17 | package.json
--------------------------------------------------------------------------------
/package_list_debian.txt:
--------------------------------------------------------------------------------
1 | apt-transport-https
2 | apt-utils
3 | bash
4 | ca-certificates
5 | chromium
6 | chromium-driver
7 | curl
8 | dumb-init
9 | ffmpeg
10 | fuse3
11 | git
12 | inotify-tools
13 | locales
14 | musl-dev
15 | netcat
16 | procps
17 | redis
18 | sudo
19 | tzdata
20 | wget
21 | xvfb
22 | zip
--------------------------------------------------------------------------------
/third_party/feapder/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE
3 |
4 | include feapder/requirements.txt
5 | include feapder/VERSION
6 |
7 | recursive-include feapder/utils/js *
8 | recursive-include feapder/templates *
9 | recursive-include tests *
10 |
11 | global-exclude __pycache__ *.py[cod]
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/proxy_pool/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2023/7/25 10:16
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | from .base import BaseProxyPool
11 | from .proxy_pool import ProxyPool
12 |
--------------------------------------------------------------------------------
/web/static/components/card/normal/state.js:
--------------------------------------------------------------------------------
1 | import { LitState } from "../../utility/lit-state.js"
2 |
3 | class CardState extends LitState {
4 | static get stateVars() {
5 | return {
6 | more_id: undefined
7 | };
8 | }
9 | }
10 |
11 | export const cardState = new CardState();
--------------------------------------------------------------------------------
/tests/run.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from tests.test_metainfo import MetaInfoTest
4 |
5 | if __name__ == '__main__':
6 | suite = unittest.TestSuite()
7 | # 测试名称识别
8 | suite.addTest(MetaInfoTest('test_metainfo'))
9 |
10 | # 运行测试
11 | runner = unittest.TextTestRunner()
12 | runner.run(suite)
13 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/objs/find.py:
--------------------------------------------------------------------------------
1 | from app.media.tmdbv3api.tmdb import TMDb
2 |
3 |
4 | class Find(TMDb):
5 | _urls = {
6 | "find": "/find/%s"
7 | }
8 |
9 | def find_by_imdbid(self, imdbid):
10 | return self._call(
11 | self._urls["find"] % imdbid,
12 | "external_source=imdb_id")
13 |
--------------------------------------------------------------------------------
/web/static/components/index.js:
--------------------------------------------------------------------------------
1 | // 导入所有组件
2 | const body_div = document.createElement("div");
3 | [
4 | "custom/chips/index.html",
5 | ]
6 | .forEach((name) => {
7 | const my_wc = document.createElement("div");
8 | $(my_wc).load("../static/components/" + name);
9 | body_div.appendChild(my_wc);
10 | })
11 | document.body.appendChild(body_div);
--------------------------------------------------------------------------------
/app/utils/exception_utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import traceback
3 |
4 |
5 | class ExceptionUtils:
6 | @classmethod
7 | def exception_traceback(cls, e):
8 | # print(f"\nException: {str(e)}\nCallstack:\n{traceback.format_exc()}\n")
9 | import log
10 | log.error(f"\nException: {str(e)}\nCallstack:\n{traceback.format_exc()}\n")
11 |
--------------------------------------------------------------------------------
/app/helper/redis_helper.py:
--------------------------------------------------------------------------------
1 | from app.utils import SystemUtils
2 |
3 |
4 | class RedisHelper:
5 |
6 | @staticmethod
7 | def is_valid():
8 | """
9 | 判斷redis是否有效
10 | """
11 | if SystemUtils.is_docker():
12 | return True if SystemUtils.execute("which redis-server") else False
13 | else:
14 | return False
15 |
--------------------------------------------------------------------------------
/app/media/meta/__init__.py:
--------------------------------------------------------------------------------
1 | from .metainfo import MetaInfo
2 | from .metaanime import MetaAnime
3 | from ._base import MetaBase
4 | from .metavideo import MetaVideo
5 | from .metavideov2 import MetaVideoV2
6 | from .release_groups import ReleaseGroupsMatcher
7 | from .mediaItem import MediaMainItem, MediaEpisodeItem, MediaVideoItem,\
8 | MediaAudioItem, MediaLocalizationItem, MediaOtherItem, MediaItem
9 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/__init__.py:
--------------------------------------------------------------------------------
1 | from .tmdb import TMDb
2 | from .exceptions import TMDbException
3 | from .objs.movie import Movie
4 | from .objs.search import Search
5 | from .objs.tv import TV
6 | from .objs.person import Person
7 | from .objs.find import Find
8 | from .objs.discover import Discover
9 | from .objs.trending import Trending
10 | from .objs.episode import Episode
11 | from .objs.genre import Genre
12 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-nastools/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # shellcheck shell=bash
3 |
4 | umask ${UMASK}
5 |
6 | if [ -f ${NASTOOL_CONFIG} ]; then
7 | NT_PORT=$(yq '.app.web_port' /config/config.yaml)
8 | else
9 | NT_PORT=3000
10 | fi
11 |
12 | exec \
13 | s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost ${NT_PORT}" \
14 | cd ${WORKDIR} s6-setuidgid nt python3 run.py
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/user_pool/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = [
2 | "GuestUserPool",
3 | "GuestUser",
4 | "NormalUserPool",
5 | "NormalUser",
6 | "GoldUserPool",
7 | "GoldUser",
8 | "GoldUserStatus",
9 | ]
10 |
11 | from .gold_user_pool import GoldUserPool, GoldUser, GoldUserStatus
12 | from .guest_user_pool import GuestUserPool, GuestUser
13 | from .normal_user_pool import NormalUserPool, NormalUser
14 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/utils/webdriver/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2022/9/7 4:39 PM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | from .selenium_driver import SeleniumDriver
11 | from .webdirver import InterceptRequest, InterceptResponse
12 | from .webdriver_pool import WebDriverPool
13 |
14 | # 为了兼容老代码
15 | WebDriver = SeleniumDriver
16 |
--------------------------------------------------------------------------------
/app/utils/nfo_reader.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 |
3 |
4 | class NfoReader:
5 | def __init__(self, xml_file_path):
6 | self.xml_file_path = xml_file_path
7 | self.tree = ET.parse(xml_file_path)
8 | self.root = self.tree.getroot()
9 |
10 | def get_element_value(self, element_path):
11 | element = self.root.find(element_path)
12 | return element.text if element is not None else None
13 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/requirements.txt:
--------------------------------------------------------------------------------
1 | better-exceptions>=0.2.2
2 | DBUtils>=2.0
3 | parsel>=1.5.2
4 | PyExecJS>=1.5.1
5 | pymongo>=3.10.1
6 | PyMySQL>=0.9.3
7 | redis>=2.10.6,<4.0.0
8 | requests>=2.22.0
9 | selenium>=3.141.0
10 | bs4>=0.0.1
11 | ipython>=7.14.0
12 | bitarray>=1.5.3
13 | redis-py-cluster>=2.1.0
14 | cryptography>=3.3.2
15 | urllib3>=1.25.8
16 | loguru>=0.5.3
17 | influxdb>=5.3.1
18 | pyperclip>=1.8.2
19 | webdriver-manager>=3.5.3
20 | terminal-layout>=2.1.3
--------------------------------------------------------------------------------
/app/helper/thread_helper.py:
--------------------------------------------------------------------------------
1 | from concurrent.futures import ThreadPoolExecutor
2 |
3 | from app.utils.commons import singleton
4 |
5 |
6 | @singleton
7 | class ThreadHelper:
8 | _thread_num = 100
9 | executor = None
10 |
11 | def __init__(self):
12 | self.executor = ThreadPoolExecutor(max_workers=self._thread_num)
13 |
14 | def init_config(self):
15 | pass
16 |
17 | def start_thread(self, func, kwargs):
18 | self.executor.submit(func, *kwargs)
19 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/item_template.tmpl:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on {DATE}
4 | ---------
5 | @summary:
6 | ---------
7 | @author: {USER}
8 | """
9 |
10 | from feapder import Item
11 |
12 |
13 | class ${item_name}Item(Item):
14 | """
15 | This class was generated by feapder
16 | command: feapder create -i ${command}
17 | """
18 |
19 | __table_name__ = "${table_name}"
20 |
21 | def __init__(self, *args, **kwargs):
22 | ${propertys}
23 |
--------------------------------------------------------------------------------
/app/utils/number_utils.py:
--------------------------------------------------------------------------------
1 | class NumberUtils:
2 |
3 | @staticmethod
4 | def max_ele(a, b):
5 | """
6 | 返回非空最大值
7 | """
8 | if not a:
9 | return b
10 | if not b:
11 | return a
12 | return max(int(a), int(b))
13 |
14 | @staticmethod
15 | def get_size_gb(size):
16 | """
17 | 将字节转换为GB
18 | """
19 | if not size:
20 | return 0.0
21 | return float(size) / 1024 / 1024 / 1024
22 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/update_item_template.tmpl:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on {DATE}
4 | ---------
5 | @summary:
6 | ---------
7 | @author: {USER}
8 | """
9 |
10 | from feapder import UpdateItem
11 |
12 |
13 | class ${item_name}Item(UpdateItem):
14 | """
15 | This class was generated by feapder
16 | command: feapder create -i ${command}
17 | """
18 |
19 | __table_name__ = "${table_name}"
20 |
21 | def __init__(self, *args, **kwargs):
22 | ${propertys}
23 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/core/spiders/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2020/4/22 12:08 AM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | __all__ = ["AirSpider", "TaskSpider", "Spider", "BatchSpider"]
12 |
13 | from feapder.core.spiders.air_spider import AirSpider
14 | from feapder.core.spiders.spider import Spider
15 | from feapder.core.spiders.task_spider import TaskSpider
16 | from feapder.core.spiders.batch_spider import BatchSpider
17 |
--------------------------------------------------------------------------------
/web/static/components/layout/navbar/button.js:
--------------------------------------------------------------------------------
1 | import { html } from "../../utility/lit-core.min.js";
2 | import { CustomElement } from "../../utility/utility.js";
3 |
4 |
5 | export class LayoutNavbarButton extends CustomElement {
6 | render() {
7 | return html`
8 |
11 | `;
12 | }
13 | }
14 |
15 |
16 | window.customElements.define("layout-navbar-button", LayoutNavbarButton);
--------------------------------------------------------------------------------
/app/utils/cache_manager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import time
3 |
4 | from cacheout import CacheManager, LRUCache, Cache
5 |
6 | CACHES = {
7 | "tmdb_supply": {'maxsize': 200}
8 | }
9 |
10 | cacheman = CacheManager(CACHES, cache_class=LRUCache)
11 |
12 | TokenCache = Cache(maxsize=256, ttl=4*3600, timer=time.time, default=None)
13 |
14 | ConfigLoadCache = Cache(maxsize=1, ttl=10, timer=time.time, default=None)
15 |
16 | CategoryLoadCache = Cache(maxsize=2, ttl=3, timer=time.time, default=None)
17 |
18 | OpenAISessionCache = Cache(maxsize=100, ttl=3600, timer=time.time, default=None)
19 |
--------------------------------------------------------------------------------
/dbscript_gen.py:
--------------------------------------------------------------------------------
1 | import os
2 | from config import Config
3 | from alembic.config import Config as AlembicConfig
4 | from alembic.command import revision as alembic_revision
5 |
6 | db_version = input("请输入版本号:")
7 | db_location = os.path.join(Config().get_config_path(), 'user.db').replace('\\', '/')
8 | script_location = os.path.join(os.path.dirname(__file__), 'scripts').replace('\\', '/')
9 | alembic_cfg = AlembicConfig()
10 | alembic_cfg.set_main_option('script_location', script_location)
11 | alembic_cfg.set_main_option('sqlalchemy.url', f"sqlite:///{db_location}")
12 | alembic_revision(alembic_cfg, db_version, True)
13 |
--------------------------------------------------------------------------------
/scripts/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade() -> None:
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade() -> None:
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/app/utils/json_utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | from enum import Enum
3 |
4 |
5 | class JsonUtils:
6 |
7 | @staticmethod
8 | def json_serializable(obj):
9 | """
10 | 将普通对象转化为支持json序列化的对象
11 | @param obj: 待转化的对象
12 | @return: 支持json序列化的对象
13 | """
14 |
15 | def _try(o):
16 | if isinstance(o, Enum):
17 | return o.value
18 | try:
19 | return o.__dict__
20 | except Exception as err:
21 | print(str(err))
22 | return str(o)
23 |
24 | return json.loads(json.dumps(obj, default=lambda o: _try(o)))
25 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/objs/genre.py:
--------------------------------------------------------------------------------
1 | from app.media.tmdbv3api.tmdb import TMDb
2 |
3 |
4 | class Genre(TMDb):
5 | _urls = {
6 | "movie_list": "/genre/movie/list",
7 | "tv_list": "/genre/tv/list"
8 | }
9 |
10 | def movie_list(self):
11 | """
12 | Get the list of official genres for movies.
13 | :return:
14 | """
15 | return self._get_obj(self._call(self._urls["movie_list"], ""), "genres")
16 |
17 | def tv_list(self):
18 | """
19 | Get the list of official genres for TV shows.
20 | :return:
21 | """
22 | return self._get_obj(self._call(self._urls["tv_list"], ""), "genres")
23 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = [
2 | "CreateProject",
3 | "CreateSpider",
4 | "CreateItem",
5 | "CreateInit",
6 | "CreateJson",
7 | "CreateTable",
8 | "CreateCookies",
9 | "CreateSetting",
10 | "CreateParams",
11 | ]
12 |
13 | from .create_table import CreateTable
14 | from .create_json import CreateJson
15 | from .create_spider import CreateSpider
16 | from .create_init import CreateInit
17 | from .create_item import CreateItem
18 | from .create_project import CreateProject
19 | from .create_cookies import CreateCookies
20 | from .create_setting import CreateSetting
21 | from .create_params import CreateParams
22 |
--------------------------------------------------------------------------------
/app/helper/__init__.py:
--------------------------------------------------------------------------------
1 | from .chrome_helper import ChromeHelper, init_chrome
2 | from .meta_helper import MetaHelper
3 | from .progress_helper import ProgressHelper
4 | from .security_helper import SecurityHelper
5 | from .thread_helper import ThreadHelper
6 | from .db_helper import DbHelper
7 | from .dict_helper import DictHelper
8 | from .display_helper import DisplayHelper
9 | from .site_helper import SiteHelper
10 | from .ocr_helper import OcrHelper
11 | from .words_helper import WordsHelper
12 | from .submodule_helper import SubmoduleHelper
13 | from .ffmpeg_helper import FfmpegHelper
14 | from .redis_helper import RedisHelper
15 | from .rss_helper import RssHelper
16 | from .plugin_helper import PluginHelper
17 |
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/init-020-fixuser/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # shellcheck shell=bash
3 |
4 | function INFO() {
5 | echo -e "[\033[32mINFO\033[0m] ${1}"
6 | }
7 | function ERROR() {
8 | echo -e "[\033[31mERROR\033[0m] ${1}"
9 | }
10 | function WARN() {
11 | echo -e "[\033[33mWARN\033[0m] ${1}"
12 | }
13 |
14 | INFO "以PUID=${PUID},PGID=${PGID}的身份启动程序..."
15 |
16 | # 更改 nt userid 和 groupid
17 | groupmod -o -g "$PGID" nt
18 | usermod -o -u "$PUID" nt
19 |
20 | # 创建目录、权限设置
21 | if grep -Eqi "Debian" /etc/issue || grep -Eq "Debian" /etc/os-release; then
22 | chown -R nt:nt /usr/bin/chromedriver
23 | fi
24 | chown -R nt:nt "${WORKDIR}" "${HOME}" /config /usr/lib/chromium /etc/hosts
--------------------------------------------------------------------------------
/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-redis/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # shellcheck shell=bash
3 |
4 | function __debug {
5 |
6 | exec \
7 | s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost 6379" \
8 | s6-setuidgid root $(which redis-server)
9 |
10 | }
11 |
12 | function __false {
13 |
14 | exec \
15 | s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost 6379" \
16 | s6-setuidgid root $(which redis-server) > /dev/null 2>&1
17 |
18 | }
19 |
20 | if [ -f ${NASTOOL_CONFIG} ]; then
21 | NT_LOG=$(yq '.app.loglevel' /config/config.yaml)
22 | if [[ "${NT_LOG}" == "debug" ]]; then
23 | __debug
24 | else
25 | __false
26 | fi
27 | else
28 | __false
29 | fi
30 |
--------------------------------------------------------------------------------
/package/rely/template.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% if not head %}
5 |
10 | {% else %}
11 | {{ hear | safe }}
12 | {% endif %}
13 |
14 |
15 | {{ body | safe }}
16 | {% for diagram in diagrams %}
17 |
18 |
{{ diagram.title }}
19 |
{{ diagram.text }}
20 |
21 | {{ diagram.svg }}
22 |
23 |
24 | {% endfor %}
25 |
26 |
27 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/air_spider_template.tmpl:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on {DATE}
4 | ---------
5 | @summary:
6 | ---------
7 | @author: {USER}
8 | """
9 |
10 | import feapder
11 |
12 |
13 | class ${spider_name}(feapder.AirSpider):
14 | def start_requests(self):
15 | yield feapder.Request("https://spidertools.cn")
16 |
17 | def parse(self, request, response):
18 | # 提取网站title
19 | print(response.xpath("//title/text()").extract_first())
20 | # 提取网站描述
21 | print(response.xpath("//meta[@name='description']/@content").extract_first())
22 | print("网站地址: ", response.url)
23 |
24 |
25 | if __name__ == "__main__":
26 | ${spider_name}().start()
--------------------------------------------------------------------------------
/web/static/js/tabler/demo-theme.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Tabler v1.0.0-beta16 (https://tabler.io)
3 | * @version 1.0.0-beta16
4 | * @link https://tabler.io
5 | * Copyright 2018-2022 The Tabler Authors
6 | * Copyright 2018-2022 codecalm.net Paweł Kuna
7 | * Licensed under MIT (https://github.com/tabler/tabler/blob/master/LICENSE)
8 | */
9 | !function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";var e,t="tablerTheme",n=new Proxy(new URLSearchParams(window.location.search),{get:function(e,t){return e.get(t)}});if(n.theme)localStorage.setItem(t,n.theme),e=n.theme;else{var o=localStorage.getItem(t);e=o||"light"}document.body.classList.remove("theme-dark","theme-light"),document.body.classList.add("theme-".concat(e))}));
--------------------------------------------------------------------------------
/app/utils/image_utils.py:
--------------------------------------------------------------------------------
1 | from PIL import Image
2 | from collections import Counter
3 |
4 |
5 | class ImageUtils:
6 |
7 | @staticmethod
8 | def calculate_theme_color(image_path):
9 | # 打开图片并转换为RGB模式
10 | img = Image.open(image_path).convert('RGB')
11 | # 缩小图片尺寸以加快计算速度
12 | img = img.resize((100, 100), resample=Image.BILINEAR)
13 | # 获取所有像素颜色值
14 | pixels = img.getdata()
15 | # 统计每种颜色在像素中出现的频率
16 | pixel_count = Counter(pixels)
17 | # 找到出现频率最高的颜色,作为主题色
18 | dominant_color = pixel_count.most_common(1)[0][0]
19 | # 将主题色转换为16进制表示
20 | theme_color = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
21 | # 返回主题色
22 | return theme_color
23 |
--------------------------------------------------------------------------------
/docker/compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | nas-tools:
4 | image: 0xforee/nas-tools:latest
5 | ports:
6 | - 3000:3000 # 默认的webui控制端口
7 | volumes:
8 | - ./config:/config # 冒号左边请修改为你想保存配置的路径
9 | - /你的媒体目录:/你想设置的容器内能见到的目录 # 媒体目录,多个目录需要分别映射进来,需要满足配置文件说明中的要求
10 | environment:
11 | - PUID=0 # 想切换为哪个用户来运行程序,该用户的uid
12 | - PGID=0 # 想切换为哪个用户来运行程序,该用户的gid
13 | - UMASK=000 # 掩码权限,默认000,可以考虑设置为022
14 | - NASTOOL_AUTO_UPDATE=false # 如需在启动容器时自动升级程程序请设置为true
15 | #- REPO_URL=https://ghproxy.com/https://github.com/0xforee/nas-tools.git # 当你访问github网络很差时,可以考虑解释本行注释
16 | restart: always
17 | network_mode: bridge
18 | hostname: nas-tools
19 | container_name: nas-tools
--------------------------------------------------------------------------------
/scripts/versions/a19a48dbb41b_1_2_7.py:
--------------------------------------------------------------------------------
1 | """1.2.7
2 |
3 | Revision ID: a19a48dbb41b
4 | Revises: d68a85a8f10d
5 | Create Date: 2023-05-09 14:44:03.251571
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = 'a19a48dbb41b'
14 | down_revision = 'd68a85a8f10d'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | op.create_index(op.f('ix_TRANSFER_HISTORY_DATE'), 'TRANSFER_HISTORY', ['DATE'], unique=False)
23 | except Exception as e:
24 | pass
25 | # ### end Alembic commands ###
26 |
27 |
28 | def downgrade() -> None:
29 | pass
30 |
--------------------------------------------------------------------------------
/web/static/js/fullcalendar/locales/zh-cn.js:
--------------------------------------------------------------------------------
1 | FullCalendar.globalLocales.push(function () {
2 | 'use strict';
3 |
4 | return {
5 | code: 'zh-cn',
6 | week: {
7 | // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
8 | dow: 1, // Monday is the first day of the week.
9 | doy: 4, // The week that contains Jan 4th is the first week of the year.
10 | },
11 | buttonText: {
12 | prev: '<',
13 | next: '>',
14 | today: '今天',
15 | month: '月',
16 | week: '周',
17 | day: '日',
18 | list: '日程',
19 | },
20 | weekText: '周',
21 | allDayText: '全天',
22 | moreLinkText: function (n) {
23 | return '另外 ' + n + ' 个'
24 | },
25 | noEventsText: '没有事件显示',
26 | };
27 |
28 | }());
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: 功能改进
2 | description: Feature Request
3 | title: "[功能改进]: "
4 | labels: ["feature request"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | 请说明你希望添加的功能。
10 | - type: input
11 | id: version
12 | attributes:
13 | label: 当前程序版本
14 | description: 目前使用的程序版本
15 | validations:
16 | required: true
17 | - type: textarea
18 | id: feature-request
19 | attributes:
20 | label: 功能改进
21 | description: 请详细描述需要改进或者添加的功能。
22 | placeholder: "功能改进"
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: references
27 | attributes:
28 | label: 参考资料
29 | description: 可以列举一些参考资料,但是不要引用同类但商业化软件的任何内容。
30 | placeholder: "参考资料"
31 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/create_setting.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2021/4/23 13:20
4 | ---------
5 | @summary: 生成配置文件
6 | ---------
7 | @author: mkdir700
8 | @email: mkdir700@gmail.com
9 | """
10 |
11 | import os
12 | import shutil
13 |
14 |
15 | class CreateSetting:
16 | def create(self):
17 | if os.path.exists("setting.py"):
18 | confirm = input("配置文件已存在 是否覆盖 (y/n). ")
19 | if confirm != "y":
20 | print("取消覆盖 退出")
21 | return
22 |
23 | template_file_path = os.path.abspath(
24 | os.path.join(__file__, "../../../templates/project_template/setting.py")
25 | )
26 | shutil.copy(template_file_path, "./", follow_symlinks=False)
27 | print("配置文件生成成功")
28 |
--------------------------------------------------------------------------------
/app/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .dom_utils import DomUtils
2 | from .episode_format import EpisodeFormat
3 | from .http_utils import RequestUtils
4 | from .json_utils import JsonUtils
5 | from .number_utils import NumberUtils
6 | from .path_utils import PathUtils
7 | from .string_utils import StringUtils
8 | from .system_utils import SystemUtils
9 | from .tokens import Tokens
10 | from .torrent import Torrent
11 | from .cache_manager import cacheman, TokenCache, ConfigLoadCache, CategoryLoadCache, OpenAISessionCache
12 | from .exception_utils import ExceptionUtils
13 | from .rsstitle_utils import RssTitleUtils
14 | from .nfo_reader import NfoReader
15 | from .ip_utils import IpUtils
16 | from .image_utils import ImageUtils
17 | from .scheduler_utils import SchedulerUtils
18 | from .mteam_utils import MteamUtils
19 |
--------------------------------------------------------------------------------
/web/static/components/card/normal/placeholder.js:
--------------------------------------------------------------------------------
1 | import { html } from "../../utility/lit-core.min.js";
2 | import { CustomElement } from "../../utility/utility.js";
3 |
4 | export class NormalCardPlaceholder extends CustomElement {
5 | constructor() {
6 | super();
7 | }
8 |
9 | static render_placeholder() {
10 | return html`
11 |
14 | `;
15 | }
16 |
17 | render() {
18 | return html`
19 |
20 | ${NormalCardPlaceholder.render_placeholder()}
21 |
22 | `;
23 | }
24 | }
25 |
26 | window.customElements.define("normal-card-placeholder", NormalCardPlaceholder);
--------------------------------------------------------------------------------
/scripts/versions/1f5cc26cdd3d_1_2_3.py:
--------------------------------------------------------------------------------
1 | """1.2.3
2 |
3 | Revision ID: 1f5cc26cdd3d
4 | Revises: 13a58bd5311f
5 | Create Date: 2023-04-07 08:23:05.282129
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '1f5cc26cdd3d'
14 | down_revision = '13a58bd5311f'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | # 1.2.2
22 | try:
23 | with op.batch_alter_table("SITE_BRUSH_TASK") as batch_op:
24 | batch_op.add_column(sa.Column('SAVEPATH', sa.Text, nullable=True))
25 | except Exception as e:
26 | pass
27 | # ### end Alembic commands ###
28 |
29 |
30 | def downgrade() -> None:
31 | pass
32 |
--------------------------------------------------------------------------------
/scripts/versions/69508d1aed24_1_2_1.py:
--------------------------------------------------------------------------------
1 | """1.2.1
2 |
3 | Revision ID: 69508d1aed24
4 | Revises: 6abeaa9ece15
5 | Create Date: 2023-03-24 11:12:51.646014
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '69508d1aed24'
14 | down_revision = '6abeaa9ece15'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | # 1.2.1
22 | try:
23 | with op.batch_alter_table("SITE_BRUSH_TASK") as batch_op:
24 | batch_op.add_column(sa.Column('RSSURL', sa.Text, nullable=True))
25 | except Exception as e:
26 | pass
27 | # ### end Alembic commands ###
28 |
29 |
30 | def downgrade() -> None:
31 | pass
32 |
--------------------------------------------------------------------------------
/scripts/versions/7c14267ffbe4_1_2_8.py:
--------------------------------------------------------------------------------
1 | """1.2.8
2 |
3 | Revision ID: 7c14267ffbe4
4 | Revises: a19a48dbb41b
5 | Create Date: 2023-05-13 11:42:58.215215
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '7c14267ffbe4'
14 | down_revision = 'a19a48dbb41b'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("SITE_BRUSH_TASK") as batch_op:
23 | batch_op.add_column(sa.Column('UP_LIMIT', sa.Text, nullable=True))
24 | batch_op.add_column(sa.Column('DL_LIMIT', sa.Text, nullable=True))
25 | except Exception as e:
26 | pass
27 |
28 |
29 | def downgrade() -> None:
30 | pass
31 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/create_init.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2018-08-28 17:38:43
4 | ---------
5 | @summary: 创建__init__.py
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | from feapder.utils.tools import dumps_json
12 |
13 |
14 | class CreateInit:
15 | def create(self):
16 | __all__ = []
17 |
18 | import os
19 |
20 | path = os.getcwd()
21 | for file in os.listdir(path):
22 | if file.endswith(".py") and not file.startswith("__init__"):
23 | model = file.split(".")[0]
24 | __all__.append(model)
25 |
26 | del os
27 |
28 | with open("__init__.py", "w", encoding="utf-8") as file:
29 | text = "__all__ = %s" % dumps_json(__all__)
30 | file.write(text)
31 |
--------------------------------------------------------------------------------
/scripts/versions/702b7666a634_1_2_5.py:
--------------------------------------------------------------------------------
1 | """1.2.5
2 |
3 | Revision ID: 702b7666a634
4 | Revises: ae61cfa6ada6
5 | Create Date: 2023-04-14 13:08:44.689878
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '702b7666a634'
14 | down_revision = 'ae61cfa6ada6'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("DOWNLOAD_SETTING") as batch_op:
23 | batch_op.drop_column('CONTENT_LAYOUT')
24 | except Exception as e:
25 | pass
26 | # ### end Alembic commands ###
27 |
28 |
29 | def downgrade() -> None:
30 | # ### commands auto generated by Alembic - please adjust! ###
31 | pass
32 | # ### end Alembic commands ###
33 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/downloader/base.py:
--------------------------------------------------------------------------------
1 | import abc
2 | from abc import ABC
3 |
4 | from feapder.network.response import Response
5 |
6 |
7 | class Downloader:
8 | @abc.abstractmethod
9 | def download(self, request) -> Response:
10 | """
11 |
12 | Args:
13 | request: feapder.Request
14 |
15 | Returns: feapder.Response
16 |
17 | """
18 | raise NotImplementedError
19 |
20 | def close(self, response: Response):
21 | pass
22 |
23 |
24 | class RenderDownloader(Downloader, ABC):
25 | def put_back(self, driver):
26 | """
27 | 释放浏览器对象
28 | """
29 | pass
30 |
31 | def close(self, driver):
32 | """
33 | 关闭浏览器
34 | """
35 | pass
36 |
37 | def close_all(self):
38 | """
39 | 关闭所有浏览器
40 | """
41 | pass
42 |
--------------------------------------------------------------------------------
/scripts/versions/ff1b04a637f8_1_3_0.py:
--------------------------------------------------------------------------------
1 | """1.3.0
2 |
3 | Revision ID: ff1b04a637f8
4 | Revises: 7c14267ffbe4
5 | Create Date: 2023-09-17 09:35:42.773359
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = 'ff1b04a637f8'
14 | down_revision = '7c14267ffbe4'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table('CONFIG_SYNC_PATHS') as batch_op:
23 | batch_op.add_column(sa.Column('LOCATING', sa.Integer(), nullable=True))
24 | except Exception as e:
25 | pass
26 | # ### end Alembic commands ###
27 |
28 |
29 | def downgrade() -> None:
30 | # ### commands auto generated by Alembic - please adjust! ###
31 | pass
32 | # ### end Alembic commands ###
33 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/spider_template.tmpl:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on {DATE}
4 | ---------
5 | @summary:
6 | ---------
7 | @author: {USER}
8 | """
9 |
10 | import feapder
11 |
12 |
13 | class ${spider_name}(feapder.Spider):
14 | # 自定义数据库,若项目中有setting.py文件,此自定义可删除
15 | __custom_setting__ = dict(
16 | REDISDB_IP_PORTS="localhost:6379", REDISDB_USER_PASS="", REDISDB_DB=0
17 | )
18 |
19 | def start_requests(self):
20 | yield feapder.Request("https://spidertools.cn")
21 |
22 | def parse(self, request, response):
23 | # 提取网站title
24 | print(response.xpath("//title/text()").extract_first())
25 | # 提取网站描述
26 | print(response.xpath("//meta[@name='description']/@content").extract_first())
27 | print("网站地址: ", response.url)
28 |
29 |
30 | if __name__ == "__main__":
31 | ${spider_name}(redis_key="xxx:xxx").start()
32 |
--------------------------------------------------------------------------------
/scripts/versions/d68a85a8f10d_1_2_6.py:
--------------------------------------------------------------------------------
1 | """1.2.6
2 |
3 | Revision ID: d68a85a8f10d
4 | Revises: 702b7666a634
5 | Create Date: 2023-04-16 14:03:56.871650
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = 'd68a85a8f10d'
14 | down_revision = '702b7666a634'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("CONFIG_SYNC_PATHS") as batch_op:
23 | batch_op.add_column(sa.Column('COMPATIBILITY', sa.Integer, nullable=True))
24 | except Exception as e:
25 | pass
26 | # ### end Alembic commands ###
27 |
28 |
29 | def downgrade() -> None:
30 | # ### commands auto generated by Alembic - please adjust! ###
31 | pass
32 | # ### end Alembic commands ###
33 |
--------------------------------------------------------------------------------
/scripts/versions/c8ebf2f4340e_1_3_3.py:
--------------------------------------------------------------------------------
1 | """1.3.3
2 |
3 | Revision ID: c8ebf2f4340e
4 | Revises: 57c4146172c2
5 | Create Date: 2024-04-05 18:16:51.866389
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = 'c8ebf2f4340e'
14 | down_revision = '57c4146172c2'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("CONFIG_SITE") as batch_op:
23 | batch_op.add_column(sa.Column('APIKEY', sa.Text, nullable=True))
24 | except Exception as e:
25 | pass
26 | # ### end Alembic commands ###
27 |
28 |
29 | def downgrade() -> None:
30 | # ### commands auto generated by Alembic - please adjust! ###
31 | op.drop_column('CONFIG_SITE', 'APIKEY')
32 | # ### end Alembic commands ###
33 |
--------------------------------------------------------------------------------
/scripts/versions/7480d259772e_1_3_4.py:
--------------------------------------------------------------------------------
1 | """1.3.4
2 |
3 | Revision ID: 7480d259772e
4 | Revises: c8ebf2f4340e
5 | Create Date: 2024-07-19 22:26:15.089515
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '7480d259772e'
14 | down_revision = 'c8ebf2f4340e'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("SITE_BRUSH_TASK") as batch_op:
23 | batch_op.add_column(sa.Column('BRUSHTASK_FREE_DDL_DELETE', sa.Text, nullable=True))
24 | except Exception as e:
25 | pass
26 |
27 |
28 | def downgrade() -> None:
29 | # ### commands auto generated by Alembic - please adjust! ###
30 | op.drop_column('SITE_BRUSH_TASK', 'BRUSHTASK_FREE_DDL_DELETE')
31 | # ### end Alembic commands ###
32 |
--------------------------------------------------------------------------------
/app/message/client/_base.py:
--------------------------------------------------------------------------------
1 | from abc import ABCMeta, abstractmethod
2 |
3 |
4 | class _IMessageClient(metaclass=ABCMeta):
5 |
6 | @abstractmethod
7 | def match(self, ctype):
8 | """
9 | 匹配实例
10 | """
11 | pass
12 |
13 | @abstractmethod
14 | def send_msg(self, title, text, image, url, user_id):
15 | """
16 | 消息发送入口,支持文本、图片、链接跳转、指定发送对象
17 | :param title: 消息标题
18 | :param text: 消息内容
19 | :param image: 图片地址
20 | :param url: 点击消息跳转URL
21 | :param user_id: 消息发送对象的ID,为空则发给所有人
22 | :return: 发送状态,错误信息
23 | """
24 | pass
25 |
26 | @abstractmethod
27 | def send_list_msg(self, medias: list, user_id="", title="", url=""):
28 | """
29 | 发送列表类消息
30 | :param title: 消息标题
31 | :param medias: 媒体列表
32 | :param user_id: 消息发送对象的ID,为空则发给所有人
33 | :param url: 跳转链接地址
34 | """
35 | pass
36 |
--------------------------------------------------------------------------------
/scripts/versions/ae61cfa6ada6_1_2_4.py:
--------------------------------------------------------------------------------
1 | """1.2.4
2 |
3 | Revision ID: ae61cfa6ada6
4 | Revises: 1f5cc26cdd3d
5 | Create Date: 2023-04-11 10:24:45.522668
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 | # revision identifiers, used by Alembic.
12 | revision = 'ae61cfa6ada6'
13 | down_revision = '1f5cc26cdd3d'
14 | branch_labels = None
15 | depends_on = None
16 |
17 |
18 | def upgrade() -> None:
19 | # ### commands auto generated by Alembic - please adjust! ###
20 | try:
21 | with op.batch_alter_table("DOWNLOAD_HISTORY") as batch_op:
22 | batch_op.add_column(sa.Column('SE', sa.Text, nullable=True))
23 | batch_op.add_column(sa.Column('SAVE_PATH', sa.Text, nullable=True))
24 | batch_op.create_index('ix_DOWNLOAD_HISTORY_SAVE_PATH', ['SAVE_PATH'])
25 | except Exception as e:
26 | pass
27 | # ### end Alembic commands ###
28 |
29 |
30 | def downgrade() -> None:
31 | pass
32 |
--------------------------------------------------------------------------------
/scripts/versions/d116d793ba9f_1_3_5.py:
--------------------------------------------------------------------------------
1 | """1.3.5
2 |
3 | Revision ID: d116d793ba9f
4 | Revises: 7480d259772e
5 | Create Date: 2024-10-13 11:27:54.003444
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = 'd116d793ba9f'
14 | down_revision = '7480d259772e'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("SITE_USER_INFO_STATS") as batch_op:
23 | batch_op.add_column(sa.Column('LAST_SEEN', sa.Text, nullable=True))
24 | except Exception as e:
25 | pass
26 | # ### end Alembic commands ###
27 |
28 |
29 | def downgrade() -> None:
30 | # ### commands auto generated by Alembic - please adjust! ###
31 | op.drop_column('SITE_USER_INFO_STATS', 'LAST_SEEN')
32 | # ### end Alembic commands ###
33 |
--------------------------------------------------------------------------------
/app/sites/siteuserinfo/nexus_project.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import re
3 |
4 | from app.sites.siteuserinfo._base import SITE_BASE_ORDER
5 | from app.sites.siteuserinfo.nexus_php import NexusPhpSiteUserInfo
6 | from app.utils.types import SiteSchema
7 |
8 |
9 | class NexusProjectSiteUserInfo(NexusPhpSiteUserInfo):
10 | schema = SiteSchema.NexusProject
11 | order = SITE_BASE_ORDER + 25
12 |
13 | @classmethod
14 | def match(cls, html_text):
15 | return 'Nexus Project' in html_text
16 |
17 | def _parse_site_page(self, html_text):
18 | html_text = self._prepare_html_text(html_text)
19 |
20 | user_detail = re.search(r"userdetails.php\?id=(\d+)", html_text)
21 | if user_detail and user_detail.group().strip():
22 | self._user_detail_page = user_detail.group().strip().lstrip('/')
23 | self.userid = user_detail.group(1)
24 |
25 | self._torrent_seeding_page = f"viewusertorrents.php?id={self.userid}&show=seeding"
26 |
--------------------------------------------------------------------------------
/app/utils/dom_utils.py:
--------------------------------------------------------------------------------
1 | class DomUtils:
2 |
3 | @staticmethod
4 | def tag_value(tag_item, tag_name, attname="", default=None):
5 | """
6 | 解析XML标签值
7 | """
8 | tagNames = tag_item.getElementsByTagName(tag_name)
9 | if tagNames:
10 | if attname:
11 | attvalue = tagNames[0].getAttribute(attname)
12 | if attvalue:
13 | return attvalue
14 | else:
15 | firstChild = tagNames[0].firstChild
16 | if firstChild:
17 | return firstChild.data
18 | return default
19 |
20 | @staticmethod
21 | def add_node(doc, parent, name, value=None):
22 | """
23 | 添加一个DOM节点
24 | """
25 | node = doc.createElement(name)
26 | parent.appendChild(node)
27 | if value is not None:
28 | text = doc.createTextNode(str(value))
29 | node.appendChild(text)
30 | return node
31 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/proxy_pool/base.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2023/7/25 10:03
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import abc
12 |
13 | from feapder.utils.log import log
14 |
15 |
16 | class BaseProxyPool:
17 | @abc.abstractmethod
18 | def get_proxy(self):
19 | """
20 | 获取代理
21 | Returns:
22 | {"http": "xxx", "https": "xxx"}
23 | """
24 | raise NotImplementedError
25 |
26 | @abc.abstractmethod
27 | def del_proxy(self, proxy):
28 | """
29 | @summary: 删除代理
30 | ---------
31 | @param proxy: ip:port
32 | """
33 | raise NotImplementedError
34 |
35 | def tag_proxy(self, **kwargs):
36 | """
37 | @summary: 标记代理
38 | ---------
39 | @param kwargs:
40 | @return:
41 | """
42 | log.warning("暂不支持标记代理")
43 | pass
44 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/project_template/CHECK_DATA.md:
--------------------------------------------------------------------------------
1 | # 数据审核
2 | ## 表说明:
3 |
4 | > 表名 含义(更新策略)
5 |
6 | ## 一、准确性
7 |
8 | **字段设计是否满足需求? 表之间的关联字段是否满足要求? (需要人工检查)**
9 |
10 | > 注意:是否设计了自增 id,id 的类型是否设置为 bigint?
11 | > 注意:unique index 是否需要设计?
12 | > 注意:各张表之间是否需要设计关联字段;
13 |
14 | * [ ] 是
15 | * [ ] 否
16 |
17 | **各字段采集内容及存储格式是否满足要求?是否与网页一致?是否有信息缺失?**
18 |
19 | > 备注:可尝试对每个字段进行升降序排列,然后抽样检查;
20 |
21 | **是否考虑了网站同一类数据可能出现的数据格式不一致情况?**
22 |
23 | > 建议:代码对各个字段不做兼容性处理、数据不一致则抛出异常并记录
24 |
25 | * [ ] 是
26 | * [ ] 否
27 |
28 | ## 二、全量性
29 |
30 | **如果是增量采集,是否最早信息和最晚信息都采集了,同时条目总数是否正确;**
31 | **如果是批次采集,是否每个批次都有?**
32 |
33 | >备注:需要去网页端评估单个批次的总量;
34 | >参考sql语句:SELECT count(1), batch_date from [table_name] GROUP BY batch_date;
35 |
36 | **如果与另外一张表有关联关系,是否信息关联完整?**
37 |
38 | ## 三、稳定性
39 |
40 | * [ ] 是否能够长期稳定采集?
41 | * [ ] 是否加IP代理?
42 | * [ ] 是否支持断点续跑?
43 | * [ ] 是否能确保按时启动,定期采集?
44 | * [ ] 是否已开启报警?
45 |
46 | ## 四、采集频次、类型、存储方式
47 |
48 | * [ ] 采集频次是否满足要求?
49 | * [ ] 采集类型是否满足要求:增量采集 or 批次采集?
50 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2020/4/21 10:41 PM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | import os
11 | import re
12 | import sys
13 |
14 | sys.path.insert(0, re.sub(r"([\\/]items$)|([\\/]spiders$)", "", os.getcwd()))
15 |
16 | __all__ = [
17 | "AirSpider",
18 | "Spider",
19 | "TaskSpider",
20 | "BatchSpider",
21 | "BaseParser",
22 | "TaskParser",
23 | "BatchParser",
24 | "Request",
25 | "Response",
26 | "Item",
27 | "UpdateItem",
28 | "ArgumentParser",
29 | ]
30 |
31 | from feapder.core.spiders import AirSpider, Spider, TaskSpider, BatchSpider
32 | from feapder.core.base_parser import BaseParser, TaskParser, BatchParser
33 | from feapder.network.request import Request
34 | from feapder.network.response import Response
35 | from feapder.network.item import Item, UpdateItem
36 | from feapder.utils.custom_argparse import ArgumentParser
37 |
--------------------------------------------------------------------------------
/web/static/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NAStool",
3 | "short_name": "NAStool",
4 | "start_url": "../",
5 | "icons": [
6 | {
7 | "src": "./img/logo/logo-black.png",
8 | "sizes": "192x192",
9 | "type": "image/png",
10 | "purpose": "any"
11 | },
12 | {
13 | "src": "./img/icons/196.png",
14 | "sizes": "192x192",
15 | "type": "image/png",
16 | "purpose": "maskable"
17 | },
18 | {
19 | "src": "./img/logo/logo-black.png",
20 | "sizes": "512x512",
21 | "type": "image/png",
22 | "purpose": "any"
23 | },
24 | {
25 | "src": "./img/icons/512.png",
26 | "sizes": "512x512",
27 | "type": "image/png",
28 | "purpose": "maskable"
29 | }
30 | ],
31 | "theme_color": "#000000",
32 | "background_color": "#000000",
33 | "display": "standalone"
34 | }
--------------------------------------------------------------------------------
/third_party/feapder/feapder/dedup/basefilter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2022/9/21 11:17 AM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | import abc
11 | from typing import List, Union
12 |
13 |
14 | class BaseFilter:
15 | @abc.abstractmethod
16 | def add(
17 | self, keys: Union[List[str], str], *args, **kwargs
18 | ) -> Union[List[bool], bool]:
19 | """
20 |
21 | Args:
22 | keys: list / 单个值
23 | *args:
24 | **kwargs:
25 |
26 | Returns:
27 | list / 单个值 (如果数据已存在 返回 0 否则返回 1, 可以理解为是否添加成功)
28 | """
29 | pass
30 |
31 | @abc.abstractmethod
32 | def get(self, keys: Union[List[str], str]) -> Union[List[bool], bool]:
33 | """
34 | 检查数据是否存在
35 | Args:
36 | keys: list / 单个值
37 |
38 | Returns:
39 | list / 单个值 (如果数据已存在 返回 1 否则返回 0)
40 | """
41 | pass
42 |
--------------------------------------------------------------------------------
/app/utils/tokens.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from config import SPLIT_CHARS
4 |
5 |
6 | class Tokens:
7 | _text = ""
8 | _index = 0
9 | _tokens = []
10 |
11 | def __init__(self, text):
12 | self._text = text
13 | self._tokens = []
14 | self.load_text(text)
15 |
16 | def load_text(self, text):
17 | splited_text = re.split(r'%s' % SPLIT_CHARS, text)
18 | for sub_text in splited_text:
19 | if sub_text:
20 | self._tokens.append(sub_text)
21 |
22 | def cur(self):
23 | if self._index >= len(self._tokens):
24 | return None
25 | else:
26 | token = self._tokens[self._index]
27 | return token
28 |
29 | def get_next(self):
30 | token = self.cur()
31 | if token:
32 | self._index = self._index + 1
33 | return token
34 |
35 | def peek(self):
36 | index = self._index + 1
37 | if index >= len(self._tokens):
38 | return None
39 | else:
40 | return self._tokens[index]
41 |
--------------------------------------------------------------------------------
/app/helper/submodule_helper.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import importlib
3 | import pkgutil
4 |
5 |
6 | class SubmoduleHelper:
7 | @classmethod
8 | def import_submodules(cls, package, filter_func=lambda name, obj: True):
9 | """
10 | 导入子模块
11 | :param package: 父包名
12 | :param filter_func: 子模块过滤函数,入参为模块名和模块对象,返回True则导入,否则不导入
13 | :return:
14 | """
15 |
16 | submodules = []
17 | packages = importlib.import_module(package).__path__
18 | for importer, package_name, _ in pkgutil.iter_modules(packages):
19 | full_package_name = f'{package}.{package_name}'
20 | if full_package_name.startswith('_'):
21 | continue
22 | module = importlib.import_module(full_package_name)
23 | for name, obj in module.__dict__.items():
24 | if name.startswith('_'):
25 | continue
26 | if isinstance(obj, type) and filter_func(name, obj):
27 | submodules.append(obj)
28 |
29 | return submodules
30 |
--------------------------------------------------------------------------------
/web/templates/500.html:
--------------------------------------------------------------------------------
1 |
2 | {% import 'macro/svg.html' as SVG %}
3 | {% import 'macro/head.html' as HEAD %}
4 |
5 |
6 |
7 | {{ HEAD.meta_link() }}
8 | 500 - NAStool
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
出错啦!
19 |
20 | 系统出错了,请检查运行日志看看吧...
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/tests/meta_recogonize_v3/test_meta_v3.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from unittest import TestCase
4 |
5 | from tests.meta_recogonize_v3 import test_name
6 | from tests.meta_recogonize_v3.cases import testcase1
7 | from tests.meta_recogonize_v3.cases import testcase2
8 |
9 |
10 | class MetaV3Test(TestCase):
11 | def setUp(self) -> None:
12 | pass
13 |
14 | def tearDown(self) -> None:
15 | pass
16 |
17 | def test_metainfo(self):
18 | starttime = datetime.datetime.now()
19 | for index, case in enumerate(testcase1.cases):
20 | result = test_name.parseMovie(testcase1.match, case)
21 | self.assertEqual(result, testcase1.cases_result[index], msg=f'Case Fail: {case}')
22 |
23 | starttime = datetime.datetime.now()
24 | for index, case in enumerate(testcase2.cases):
25 | result = test_name.parseMovie(testcase2.match, case)
26 | self.assertEqual(result, testcase2.cases_result[index], msg=f'Case Fail: {case}')
27 |
28 | print(f'duration: {datetime.datetime.now() - starttime}')
29 |
--------------------------------------------------------------------------------
/web/templates/404.html:
--------------------------------------------------------------------------------
1 |
2 | {% import 'macro/svg.html' as SVG %}
3 | {% import 'macro/head.html' as HEAD %}
4 |
5 |
6 |
7 | {{ HEAD.meta_link() }}
8 | 404 - NAStool
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
出错啦!
19 |
20 | 没有找到这个页面,请检查是不是输错地址了...
21 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/create_cookies.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2021/4/25 10:22 上午
4 | ---------
5 | @summary: 将浏览器的cookie转为request的cookie
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import json
12 |
13 | import pyperclip
14 |
15 | from feapder.utils.tools import get_cookies_from_str, print_pretty
16 |
17 |
18 | class CreateCookies:
19 | def get_data(self):
20 | """
21 | @summary: 从剪切板中读取内容
22 | ---------
23 | ---------
24 | @result:
25 | """
26 | input("请复制浏览器cookie (列表或字符串格式), 复制后按任意键读取剪切板内容\n")
27 |
28 | text = pyperclip.paste()
29 | print(text + "\n")
30 |
31 | return text
32 |
33 | def create(self):
34 | data = self.get_data()
35 | cookies = {}
36 | try:
37 | data_json = json.loads(data)
38 |
39 | for data in data_json:
40 | cookies[data.get("name")] = data.get("value")
41 |
42 | except:
43 | cookies = get_cookies_from_str(data)
44 |
45 | print_pretty(cookies)
46 |
--------------------------------------------------------------------------------
/scripts/versions/eb3437042cc8_1_3_1.py:
--------------------------------------------------------------------------------
1 | """1.3.1
2 |
3 | Revision ID: eb3437042cc8
4 | Revises: ff1b04a637f8
5 | Create Date: 2023-11-22 17:07:42.765426
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = 'eb3437042cc8'
14 | down_revision = 'ff1b04a637f8'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table('DOWNLOADER') as batch_op:
23 | batch_op.add_column(sa.Column('ONLY_NASTOOL', sa.Integer(), nullable=True))
24 | except Exception as e:
25 | pass
26 | try:
27 | with op.batch_alter_table('TORRENT_REMOVE_TASK') as batch_op:
28 | batch_op.add_column(sa.Column('ONLY_NASTOOL', sa.Integer(), nullable=True))
29 | except Exception as e:
30 | pass
31 | # ### end Alembic commands ###
32 |
33 |
34 | def downgrade() -> None:
35 | # ### commands auto generated by Alembic - please adjust! ###
36 | pass
37 | # ### end Alembic commands ###
38 |
--------------------------------------------------------------------------------
/third_party/feapder/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Modifications:
4 |
5 | Copyright (c) 2020 Boris
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/scripts/versions/13a58bd5311f_1_2_2.py:
--------------------------------------------------------------------------------
1 | """1.2.2
2 |
3 | Revision ID: 13a58bd5311f
4 | Revises: 69508d1aed24
5 | Create Date: 2023-04-04 08:49:43.453901
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '13a58bd5311f'
14 | down_revision = '69508d1aed24'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | # 1.2.2
22 | try:
23 | with op.batch_alter_table("RSS_TVS") as batch_op:
24 | batch_op.add_column(sa.Column('FILTER_INCLUDE', sa.Text, nullable=True))
25 | batch_op.add_column(sa.Column('FILTER_EXCLUDE', sa.Text, nullable=True))
26 | except Exception as e:
27 | pass
28 | try:
29 | with op.batch_alter_table("RSS_MOVIES") as batch_op:
30 | batch_op.add_column(sa.Column('FILTER_INCLUDE', sa.Text, nullable=True))
31 | batch_op.add_column(sa.Column('FILTER_EXCLUDE', sa.Text, nullable=True))
32 | except Exception as e:
33 | pass
34 | # ### end Alembic commands ###
35 |
36 |
37 | def downgrade() -> None:
38 | pass
39 |
--------------------------------------------------------------------------------
/app/utils/rsstitle_utils.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from app.utils.exception_utils import ExceptionUtils
4 |
5 |
6 | class RssTitleUtils:
7 |
8 | @staticmethod
9 | def keepfriends_title(title):
10 | """
11 | 处理pt.keepfrds.com的RSS标题
12 | """
13 | if not title:
14 | return ""
15 | try:
16 | title_search = re.search(r"\[(.*)]", title, re.IGNORECASE)
17 | if title_search:
18 | if title_search.span()[0] == 0:
19 | title_all = re.findall(r"\[(.*?)]", title, re.IGNORECASE)
20 | if title_all and len(title_all) > 1:
21 | torrent_name = title_all[-1]
22 | torrent_desc = title.replace(f"[{torrent_name}]", "").strip()
23 | title = "%s %s" % (torrent_name, torrent_desc)
24 | else:
25 | torrent_name = title_search.group(1)
26 | torrent_desc = title.replace(title_search.group(), "").strip()
27 | title = "%s %s" % (torrent_name, torrent_desc)
28 | except Exception as err:
29 | ExceptionUtils.exception_traceback(err)
30 | return title
31 |
--------------------------------------------------------------------------------
/app/helper/display_helper.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from pyvirtualdisplay import Display
4 |
5 | from app.utils.commons import singleton
6 | from app.utils import ExceptionUtils
7 | from config import XVFB_PATH
8 |
9 |
10 | @singleton
11 | class DisplayHelper(object):
12 | _display = None
13 |
14 | def __init__(self):
15 | self.init_config()
16 |
17 | def init_config(self):
18 | self.stop_service()
19 | if self.can_display():
20 | try:
21 | self._display = Display(visible=False, size=(1024, 768))
22 | self._display.start()
23 | os.environ["NASTOOL_DISPLAY"] = "true"
24 | except Exception as err:
25 | ExceptionUtils.exception_traceback(err)
26 |
27 | def get_display(self):
28 | return self._display
29 |
30 | def stop_service(self):
31 | os.environ["NASTOOL_DISPLAY"] = ""
32 | if self._display:
33 | self._display.stop()
34 |
35 | @staticmethod
36 | def can_display():
37 | for path in XVFB_PATH:
38 | if os.path.exists(path):
39 | return True
40 | return False
41 |
42 | def __del__(self):
43 | self.stop_service()
44 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/db/memorydb.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2020/4/21 11:42 PM
4 | ---------
5 | @summary: 基于内存的队列,代替redis
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | from queue import PriorityQueue
11 |
12 | from feapder import setting
13 |
14 |
15 | class MemoryDB:
16 | def __init__(self):
17 | self.priority_queue = PriorityQueue(maxsize=setting.TASK_MAX_CACHED_SIZE)
18 |
19 | def add(self, item, ignore_max_size=False):
20 | """
21 | 添加任务
22 | :param item: 数据: 支持小于号比较的类 或者 (priority, item)
23 | :param ignore_max_size: queue满时是否等待,为True时无视队列的maxsize,直接往里塞
24 | :return:
25 | """
26 | if ignore_max_size:
27 | self.priority_queue._put(item)
28 | self.priority_queue.unfinished_tasks += 1
29 | else:
30 | self.priority_queue.put(item)
31 |
32 | def get(self):
33 | """
34 | 获取任务
35 | :return:
36 | """
37 | try:
38 | item = self.priority_queue.get(timeout=1)
39 | return item
40 | except:
41 | return
42 |
43 | def empty(self):
44 | return self.priority_queue.empty()
45 |
--------------------------------------------------------------------------------
/web/static/img/no-image.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/app/db/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import log
3 | from config import Config
4 | from .main_db import MainDb
5 | from .main_db import DbPersist
6 | from .media_db import MediaDb
7 | from alembic.config import Config as AlembicConfig
8 | from alembic.command import upgrade as alembic_upgrade
9 |
10 |
11 | def init_db():
12 | """
13 | 初始化数据库
14 | """
15 | log.console('开始初始化数据库...')
16 | MediaDb().init_db()
17 | MainDb().init_db()
18 | log.console('数据库初始化完成')
19 |
20 |
21 | def init_data():
22 | """
23 | 初始化数据
24 | """
25 | log.console('开始初始化数据...')
26 | MainDb().init_data()
27 | log.console('数据初始化完成')
28 |
29 |
30 | def update_db():
31 | """
32 | 更新数据库
33 | """
34 | db_location = os.path.normpath(os.path.join(Config().get_config_path(), 'user.db'))
35 | script_location = os.path.normpath(os.path.join(Config().get_root_path(), 'scripts'))
36 | log.console('开始更新数据库...')
37 | try:
38 | alembic_cfg = AlembicConfig()
39 | alembic_cfg.set_main_option('script_location', script_location)
40 | alembic_cfg.set_main_option('sqlalchemy.url', f"sqlite:///{db_location}")
41 | alembic_upgrade(alembic_cfg, 'head')
42 | log.console('数据库更新完成')
43 | except Exception as e:
44 | log.console(f'数据库更新失败:{e}')
45 |
--------------------------------------------------------------------------------
/tests/test_metainfo.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from unittest import TestCase
4 |
5 | from app.media.meta import MetaInfo
6 | from tests.cases.meta_cases import meta_cases
7 |
8 |
9 | class MetaInfoTest(TestCase):
10 | def setUp(self) -> None:
11 | pass
12 |
13 | def tearDown(self) -> None:
14 | pass
15 |
16 | def test_metainfo(self):
17 | for info in meta_cases:
18 | if not info.get("title"):
19 | continue
20 | meta_info = MetaInfo(title=info.get("title"), subtitle=info.get("subtitle"))
21 | target = {
22 | "type": meta_info.type.value,
23 | "cn_name": meta_info.cn_name or "",
24 | "en_name": meta_info.en_name or "",
25 | "year": meta_info.year or "",
26 | "part": meta_info.part or "",
27 | "season": meta_info.get_season_string(),
28 | "episode": meta_info.get_episode_string(),
29 | "restype": meta_info.get_edtion_string(),
30 | "pix": meta_info.resource_pix or "",
31 | "video_codec": meta_info.video_encode or "",
32 | "audio_codec": meta_info.audio_encode or ""
33 | }
34 | self.assertEqual(target, info.get("target"))
35 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/create_params.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2021/4/25 10:22 上午
4 | ---------
5 | @summary: 将浏览器的cookie转为request的cookie
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import sys
12 |
13 | from feapder.utils.tools import dumps_json
14 |
15 |
16 | class CreateParams:
17 | def get_data(self):
18 | """
19 | @summary: 从控制台读取多行
20 | ---------
21 | ---------
22 | @result:
23 | """
24 | print("请输入请求地址")
25 | data = []
26 | while True:
27 | line = sys.stdin.readline().strip()
28 | if not line:
29 | break
30 |
31 | data.append(line)
32 |
33 | return "".join(data)
34 |
35 | def get_params(self, url):
36 | params_json = {}
37 | params = url.split("?")[-1].split("&")
38 | for param in params:
39 | key_value = param.split("=", 1)
40 | params_json[key_value[0]] = key_value[1]
41 |
42 | return params_json
43 |
44 | def create(self):
45 | data = self.get_data()
46 |
47 | params = self.get_params(data)
48 | url = data.split("?")[0]
49 |
50 | print(f'url = "{url}"')
51 | print(f"params = {dumps_json(params)}")
52 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/pipelines/console_pipeline.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2021/3/18 12:39 上午
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | from feapder.pipelines import BasePipeline
12 | from typing import Dict, List, Tuple
13 | from feapder.utils.log import log
14 |
15 |
16 | class ConsolePipeline(BasePipeline):
17 | """
18 | pipeline 是单线程的,批量保存数据的操作,不建议在这里写网络请求代码,如下载图片等
19 | """
20 |
21 | def save_items(self, table, items: List[Dict]) -> bool:
22 | """
23 | 保存数据
24 | Args:
25 | table: 表名
26 | items: 数据,[{},{},...]
27 |
28 | Returns: 是否保存成功 True / False
29 | 若False,不会将本批数据入到去重库,以便再次入库
30 |
31 | """
32 | log.info("【调试输出】共导出 %s 条数据 到 %s" % (len(items), table))
33 | return True
34 |
35 | def update_items(self, table, items: List[Dict], update_keys=Tuple) -> bool:
36 | """
37 | 更新数据
38 | Args:
39 | table: 表名
40 | items: 数据,[{},{},...]
41 | update_keys: 更新的字段, 如 ("title", "publish_time")
42 |
43 | Returns: 是否更新成功 True / False
44 | 若False,不会将本批数据入到去重库,以便再次入库
45 |
46 | """
47 | log.info("【调试输出】共导出 %s 条数据 到 %s" % (len(items), table))
48 | return True
49 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/pipelines/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2021/3/17 10:57 下午
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import abc
12 | from typing import Dict, List, Tuple
13 |
14 |
15 | class BasePipeline(metaclass=abc.ABCMeta):
16 | """
17 | pipeline 是单线程的,批量保存数据的操作,不建议在这里写网络请求代码,如下载图片等
18 | """
19 |
20 | @abc.abstractmethod
21 | def save_items(self, table, items: List[Dict]) -> bool:
22 | """
23 | 保存数据
24 | Args:
25 | table: 表名
26 | items: 数据,[{},{},...]
27 |
28 | Returns: 是否保存成功 True / False
29 | 若False,不会将本批数据入到去重库,以便再次入库
30 |
31 | """
32 |
33 | return True
34 |
35 | def update_items(self, table, items: List[Dict], update_keys=Tuple) -> bool:
36 | """
37 | 更新数据, 与UpdateItem配合使用,若爬虫中没使用UpdateItem,则可不实现此接口
38 | Args:
39 | table: 表名
40 | items: 数据,[{},{},...]
41 | update_keys: 更新的字段, 如 ("title", "publish_time")
42 |
43 | Returns: 是否更新成功 True / False
44 | 若False,不会将本批数据入到去重库,以便再次入库
45 |
46 | """
47 |
48 | return True
49 |
50 | def close(self):
51 | """
52 | 关闭,爬虫结束时调用
53 | Returns:
54 |
55 | """
56 | pass
57 |
--------------------------------------------------------------------------------
/package/builder/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10.11-bullseye AS Builder
2 |
3 | ARG branch
4 |
5 | ENV NASTOOL_CONFIG=/nas-tools/config/config.yaml
6 | ENV py_site_packages=/usr/local/lib/python3.10/site-packages
7 |
8 | RUN python -m pip install --upgrade pip setuptools
9 | RUN pip install wheel cython pyinstaller==5.7.0
10 | RUN git clone --depth=1 -b ${branch} https://github.com/0xforee/nas-tools --recurse-submodule /nas-tools
11 | WORKDIR /nas-tools
12 | RUN pip install -r package/requirements.txt
13 | RUN pip install pyparsing
14 | RUN cp ./package/rely/hook-cn2an.py ${py_site_packages}/PyInstaller/hooks/ && \
15 | cp ./package/rely/hook-zhconv.py ${py_site_packages}/PyInstaller/hooks/ && \
16 | cp ./package/rely/hook-iso639.py ${py_site_packages}/PyInstaller/hooks/ && \
17 | cp ./third_party.txt ./package/ && \
18 | mkdir -p ${py_site_packages}/setuptools/_vendor/pyparsing/diagram/ && \
19 | cp ./package/rely/template.jinja2 ${py_site_packages}/setuptools/_vendor/pyparsing/diagram/ && \
20 | cp -r ./web/. ${py_site_packages}/web/ && \
21 | cp -r ./config/. ${py_site_packages}/config/ && \
22 | cp -r ./scripts/. ${py_site_packages}/scripts/
23 | WORKDIR /nas-tools/package
24 | RUN pyinstaller nas-tools.spec
25 | RUN ls -al /nas-tools/package/dist/
26 | WORKDIR /rootfs
27 | RUN cp /nas-tools/package/dist/nas-tools .
28 |
29 | FROM scratch
30 |
31 | COPY --from=Builder /rootfs/nas-tools /nas-tools
--------------------------------------------------------------------------------
/tests/meta_recogonize_v3/cases/testcase2.py:
--------------------------------------------------------------------------------
1 | match = {
2 | 'en_name': "Interstellar",
3 | 'cn_name': "星际穿越",
4 | "pin_name": "Xinjichuanyue",
5 | 'year': '2014'
6 | }
7 |
8 | cases = [
9 | "Interstellar 2014 2160p HDR UHD BluRay DTS-HD MA 5.1 2Audio x265 10bit-HDS",
10 | "Interstellar 2014 2160p UHD Blu-ray HDR10 HEVC DTS-HD MA 5.1",
11 | "Interstellar 2014 IMAX UHD BluRay 2160p 2Audio DTS-HD MA 5.1 x265 10bit HDR-BeiTai",
12 | "Interstellar Wars 2016 1080p friDay WEB-DL H.264 AAC-YingWEB",
13 | "Interstellar 2014 BluRay 2160p x265 10bit 4Audio mUHD-FRDS",
14 | "Bermuda-interstellar 2022 2160p WEB-DL H.265 AAC-PTerWEB",
15 | "Interstellar 2014 PROPER 1080p BluRay DTS x264-SoP",
16 | "Interstellar 2014 IMAX BluRay 1080p x265 10bit AC3 iNT-TLF",
17 | "Interstellar IMAX 2014 BluRay 1080p x264 DTS 2Audios-CMCT",
18 | "Interstellar 2014 V2 UHD Bluray 2160p DTS-HD MA 5.1 HDR x265 10bit-CHD",
19 | "Interstellar 2014 3Disc 2160p UHD Blu-ray HEVC DTS-HD MA 5.1-CMCT",
20 | "Interstellar 2014 2160p WEB-DL H.265 AAC-AilMWeb",
21 | "Interstellar 2014 BluRay 1080p x265 10bit 2Audio MNHD-FRDS",
22 | "Interstellar 2014 BluRay 1080p av1 2Audio-tinyAV",
23 | "Interstellar 2014 IMAX UHD BluRay 2160p 10bit HDR 2Audio DTS-HD MA 5.1 TrueHD 5 1 x265-beAst",
24 | "Interstellar 2014 UHD Blu-ray 2016p HEVC DTS-HD MA 5.1-CMCT",
25 | "Interstellar 2014 1080p WEB-DL H.264 AAC-AGSVWEB",
26 | ]
27 |
28 | cases_result = [
29 | 1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1
30 | ]
--------------------------------------------------------------------------------
/package/builder/alpine.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10.11-alpine AS Builder
2 |
3 | ARG branch
4 |
5 | ENV NASTOOL_CONFIG=/nas-tools/config/config.yaml
6 | ENV py_site_packages=/usr/local/lib/python3.10/site-packages
7 |
8 | RUN apk add build-base git libxslt-dev libxml2-dev musl-dev gcc libffi-dev
9 | RUN pip install --upgrade pip setuptools
10 | RUN pip install wheel cython pyinstaller==5.7.0
11 | RUN git clone --depth=1 -b ${branch} https://github.com/0xforee/nas-tools --recurse-submodule /nas-tools
12 | WORKDIR /nas-tools
13 | RUN pip install -r package/requirements.txt
14 | RUN pip install pyparsing
15 | RUN cp ./package/rely/hook-cn2an.py ${py_site_packages}/PyInstaller/hooks/ && \
16 | cp ./package/rely/hook-zhconv.py ${py_site_packages}/PyInstaller/hooks/ && \
17 | cp ./package/rely/hook-iso639.py ${py_site_packages}/PyInstaller/hooks/ && \
18 | cp ./third_party.txt ./package/ && \
19 | mkdir -p ${py_site_packages}/setuptools/_vendor/pyparsing/diagram/ && \
20 | cp ./package/rely/template.jinja2 ${py_site_packages}/setuptools/_vendor/pyparsing/diagram/ && \
21 | cp -r ./web/. ${py_site_packages}/web/ && \
22 | cp -r ./config/. ${py_site_packages}/config/ && \
23 | cp -r ./scripts/. ${py_site_packages}/scripts/
24 | WORKDIR /nas-tools/package
25 | RUN pyinstaller nas-tools.spec
26 | RUN ls -al /nas-tools/package/dist/
27 | WORKDIR /rootfs
28 | RUN cp /nas-tools/package/dist/nas-tools .
29 |
30 | FROM scratch
31 |
32 | COPY --from=Builder /rootfs/nas-tools /nas-tools
--------------------------------------------------------------------------------
/app/media/meta/customization.py:
--------------------------------------------------------------------------------
1 | import regex as re
2 | from app.utils.commons import singleton
3 |
4 |
5 | @singleton
6 | class CustomizationMatcher(object):
7 | """
8 | 识别自定义占位符
9 | """
10 | customization = None
11 | custom_separator = None
12 |
13 | def __init__(self):
14 | self.customization = None
15 | self.custom_separator = None
16 |
17 | def match(self, title=None):
18 | """
19 | :param title: 资源标题或文件名
20 | :return: 匹配结果
21 | """
22 | if not title:
23 | return ""
24 | if not self.customization:
25 | return ""
26 | customization_re = re.compile(r"%s" % self.customization)
27 | # 处理重复多次的情况,保留先后顺序(按添加自定义占位符的顺序)
28 | unique_customization = {}
29 | for item in re.findall(customization_re, title):
30 | if not isinstance(item, tuple):
31 | item = (item,)
32 | for i in range(len(item)):
33 | if item[i] and unique_customization.get(item[i]) is None:
34 | unique_customization[item[i]] = i
35 | unique_customization = list(dict(sorted(unique_customization.items(), key=lambda x: x[1])).keys())
36 | separator = self.custom_separator or "@"
37 | return separator.join(unique_customization)
38 |
39 | def update_custom(self, customization=None, separator=None):
40 | """
41 | 更新自定义占位符
42 | """
43 | self.customization = customization
44 | self.custom_separator = separator
45 |
--------------------------------------------------------------------------------
/scripts/versions/57c4146172c2_1_3_2.py:
--------------------------------------------------------------------------------
1 | """1.3.2
2 |
3 | Revision ID: 57c4146172c2
4 | Revises: eb3437042cc8
5 | Create Date: 2024-04-05 13:50:38.217641
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 |
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = '57c4146172c2'
14 | down_revision = 'eb3437042cc8'
15 | branch_labels = None
16 | depends_on = None
17 |
18 |
19 | def upgrade() -> None:
20 | # ### commands auto generated by Alembic - please adjust! ###
21 | try:
22 | with op.batch_alter_table("SITE_BRUSH_TASK") as batch_op:
23 | batch_op.add_column(sa.Column('FRACTION_RULE', sa.Text, nullable=True))
24 | except Exception as e:
25 | pass
26 | try:
27 | with op.batch_alter_table("SITE_BRUSH_TASK") as batch_op:
28 | batch_op.add_column(sa.Column('BRUSHTASK_FREE_LIMIT_SPEED', sa.Text, nullable=True))
29 | except Exception as e:
30 | pass
31 |
32 | try:
33 | with op.batch_alter_table("SITE_BRUSH_TORRENTS") as batch_op:
34 | batch_op.add_column(sa.Column('FREE_DEADLINE', sa.Text, nullable=True))
35 | except Exception as e:
36 | pass
37 | # ### end Alembic commands ###
38 |
39 |
40 | def downgrade() -> None:
41 | # ### commands auto generated by Alembic - please adjust! ###
42 | op.drop_column('SITE_BRUSH_TORRENTS', 'FREE_DEADLINE')
43 | op.drop_column('SITE_BRUSH_TASK', 'BRUSHTASK_FREE_LIMIT_SPEED')
44 | op.drop_column('SITE_BRUSH_TASK', 'FRACTION_RULE')
45 | # ### end Alembic commands ###
46 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/network/downloader/_requests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2022/4/10 5:57 下午
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import requests
12 | from requests.adapters import HTTPAdapter
13 |
14 | from feapder.network.downloader.base import Downloader
15 | from feapder.network.response import Response
16 |
17 |
18 | class RequestsDownloader(Downloader):
19 | def download(self, request) -> Response:
20 | response = requests.request(
21 | request.method, request.url, **request.requests_kwargs
22 | )
23 | response = Response(response)
24 | return response
25 |
26 |
27 | class RequestsSessionDownloader(Downloader):
28 | session = None
29 |
30 | @property
31 | def _session(self):
32 | if not self.__class__.session:
33 | self.__class__.session = requests.Session()
34 | # pool_connections – 缓存的 urllib3 连接池个数 pool_maxsize – 连接池中保存的最大连接数
35 | http_adapter = HTTPAdapter(pool_connections=1000, pool_maxsize=1000)
36 | # 任何使用该session会话的 HTTP 请求,只要其 URL 是以给定的前缀开头,该传输适配器就会被使用到。
37 | self.__class__.session.mount("http", http_adapter)
38 |
39 | return self.__class__.session
40 |
41 | def download(self, request) -> Response:
42 | response = self._session.request(
43 | request.method, request.url, **request.requests_kwargs
44 | )
45 | response = Response(response)
46 | return response
47 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/create_json.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2018-08-28 17:38:43
4 | ---------
5 | @summary: 字符串转json
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import pyperclip
12 |
13 | import feapder.utils.tools as tools
14 |
15 |
16 | class CreateJson:
17 | def get_data(self):
18 | """
19 | @summary: 从控制台读取多行
20 | ---------
21 | ---------
22 | @result:
23 | """
24 | input("请复制需要转换的内容(xxx:xxx格式,支持多行),复制后按任意键读取剪切板内容\n")
25 |
26 | text = pyperclip.paste()
27 | print(text + "\n")
28 |
29 | data = []
30 | for line in text.split("\n"):
31 | line = line.strip().replace("\t", " " * 4)
32 | if not line:
33 | break
34 |
35 | data.append(line)
36 |
37 | return data
38 |
39 | def create(self, sort_keys=False):
40 | contents = self.get_data()
41 |
42 | json = {}
43 | for content in contents:
44 | content = content.strip()
45 | if not content or content.startswith(":"):
46 | continue
47 |
48 | regex = "([^:\s]*)[:|\s]*(.*)"
49 |
50 | result = tools.get_info(content, regex, fetch_one=True)
51 | if result[0] in json:
52 | json[result[0]] = json[result[0]] + "&" + result[1]
53 | else:
54 | json[result[0]] = result[1].strip()
55 |
56 | print(tools.dumps_json(json, sort_keys=sort_keys))
57 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/retry.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2022/11/18 12:33 PM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | import argparse
11 |
12 | from feapder.core.handle_failed_items import HandleFailedItems
13 | from feapder.core.handle_failed_requests import HandleFailedRequests
14 |
15 |
16 | def retry_failed_requests(redis_key):
17 | handle_failed_requests = HandleFailedRequests(redis_key)
18 | handle_failed_requests.reput_failed_requests_to_requests()
19 |
20 |
21 | def retry_failed_items(redis_key):
22 | handle_failed_items = HandleFailedItems(redis_key)
23 | handle_failed_items.reput_failed_items_to_db()
24 | handle_failed_items.close()
25 |
26 |
27 | def parse_args():
28 | parser = argparse.ArgumentParser(
29 | description="重试失败的请求或入库失败的item",
30 | usage="usage: feapder retry [options] [args]",
31 | )
32 | parser.add_argument(
33 | "-r",
34 | "--request",
35 | help="重试失败的request 如 feapder retry --request ",
36 | metavar="",
37 | )
38 | parser.add_argument(
39 | "-i", "--item", help="重试失败的item 如 feapder retry --item ", metavar=""
40 | )
41 | args = parser.parse_args()
42 | return args
43 |
44 |
45 | def main():
46 | args = parse_args()
47 | if args.request:
48 | retry_failed_requests(args.request)
49 | if args.item:
50 | retry_failed_items(args.item)
51 |
52 |
53 | if __name__ == "__main__":
54 | main()
55 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/commands/create/create_project.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2018-08-28 17:38:43
4 | ---------
5 | @summary: 创建项目
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 |
11 | import getpass
12 | import os
13 | import shutil
14 |
15 | import feapder.utils.tools as tools
16 |
17 |
18 | def deal_file_info(file):
19 | file = file.replace("{DATE}", tools.get_current_date())
20 | file = file.replace("{USER}", os.getenv("FEAPDER_USER") or getpass.getuser())
21 |
22 | return file
23 |
24 |
25 | class CreateProject:
26 | def copy_callback(self, src, dst, *, follow_symlinks=True):
27 | if src.endswith(".py"):
28 | with open(src, "r", encoding="utf-8") as src_file, open(
29 | dst, "w", encoding="utf8"
30 | ) as dst_file:
31 | content = src_file.read()
32 | content = deal_file_info(content)
33 | dst_file.write(content)
34 |
35 | else:
36 | shutil.copy2(src, dst, follow_symlinks=follow_symlinks)
37 |
38 | def create(self, project_name):
39 | if os.path.exists(project_name):
40 | print("%s 项目已经存在" % project_name)
41 | else:
42 | template_path = os.path.abspath(
43 | os.path.join(__file__, "../../../templates/project_template")
44 | )
45 | shutil.copytree(
46 | template_path, project_name, copy_function=self.copy_callback
47 | )
48 |
49 | print("\n%s 项目生成成功" % project_name)
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/objs/episode.py:
--------------------------------------------------------------------------------
1 | from app.media.tmdbv3api.as_obj import AsObj
2 | from app.media.tmdbv3api.tmdb import TMDb
3 | from app.utils import StringUtils
4 |
5 | class Episode(TMDb):
6 | _urls = {
7 | "images": "/tv/%s/season/%s/episode/%s/images",
8 | "details": "/tv/%s/season/%s/episode/%s"
9 | }
10 |
11 | def images(self, tv_id, season_num, episode_num, include_image_language=None):
12 | """
13 | Get the images that belong to a TV episode.
14 | :param tv_id: int
15 | :param season_num: int
16 | :param episode_num: int
17 | :param include_image_language: str
18 | :return:
19 | """
20 | try:
21 | images = AsObj(
22 | **self._call(
23 | self._urls["details"] % (tv_id, season_num, episode_num),
24 | "language=%s" % include_image_language if include_image_language else "" + "&append_to_response=images"
25 | )
26 | )
27 | if not images:
28 | return None
29 | still_path = images.get("still_path")
30 | if isinstance(still_path, str):
31 | return [{"file_path": still_path}]
32 | elif isinstance(still_path, list):
33 | return [
34 | {"file_path": str(file_path)}
35 | for file_path in images
36 | if StringUtils.is_string_and_not_empty(file_path)
37 | ]
38 | else:
39 | return None
40 | except Exception as e:
41 | return None
42 |
--------------------------------------------------------------------------------
/web/templates/site/sitelist.html:
--------------------------------------------------------------------------------
1 | {% import 'macro/oops.html' as OOPS %}
2 |
3 |
4 |
13 |
14 |
15 | {% if Count > 0 %}
16 |
39 | {% else %}
40 | {{ OOPS.nodatafound('没有站点', '没有找到任何站点,请正确维护站点信息。') }}
41 | {% endif %}
42 |
--------------------------------------------------------------------------------
/app/indexer/client/_plugins.py:
--------------------------------------------------------------------------------
1 |
2 | from app.plugins import PluginManager
3 | from config import Config
4 |
5 | class PluginsSpider(object):
6 |
7 | # 私有方法
8 | _level = 99
9 | _plugin = {}
10 | _proxy = None
11 | _indexer = None
12 |
13 | def __int__(self, indexer):
14 | self._indexer = indexer
15 | if indexer.proxy:
16 | self._proxy = Config().get_proxies()
17 | self._plugin = PluginManager().get_plugin_apps(self._level).get(self._indexer.parser)
18 |
19 | def status(self, indexer):
20 | try:
21 | plugin = PluginManager().get_plugin_apps(self._level).get(indexer.parser)
22 | return True if plugin else False
23 | except Exception as e:
24 | return False
25 |
26 | def search(self, keyword, indexer, page=0):
27 | try:
28 | result_array = PluginManager().run_plugin_method(pid=indexer.parser, method='search', keyword=keyword, indexer=indexer, page=page)
29 | if not result_array:
30 | return False, []
31 | return True, result_array
32 | except Exception as e:
33 | return False, []
34 |
35 | def sites(self):
36 | result = []
37 | try:
38 | plugins = PluginManager().get_plugin_apps(self._level)
39 | for key in plugins:
40 | if plugins.get(key)['installed']:
41 | result_array = PluginManager().run_plugin_method(pid=plugins.get(key)['id'], method='get_indexers')
42 | if result_array:
43 | result.extend(result_array)
44 | except Exception as e:
45 | pass
46 | return result
--------------------------------------------------------------------------------
/app/helper/progress_helper.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | from app.utils.commons import singleton
4 | from app.utils.types import ProgressKey
5 |
6 |
7 | @singleton
8 | class ProgressHelper(object):
9 | _process_detail = {}
10 |
11 | def __init__(self):
12 | self._process_detail = {}
13 |
14 | def init_config(self):
15 | pass
16 |
17 | def __reset(self, ptype=ProgressKey.Search):
18 | if isinstance(ptype, Enum):
19 | ptype = ptype.value
20 | self._process_detail[ptype] = {
21 | "enable": False,
22 | "value": 0,
23 | "text": "请稍候..."
24 | }
25 |
26 | def start(self, ptype=ProgressKey.Search):
27 | self.__reset(ptype)
28 | if isinstance(ptype, Enum):
29 | ptype = ptype.value
30 | self._process_detail[ptype]['enable'] = True
31 |
32 | def end(self, ptype=ProgressKey.Search):
33 | if isinstance(ptype, Enum):
34 | ptype = ptype.value
35 | if not self._process_detail.get(ptype):
36 | return
37 | self._process_detail[ptype]['enable'] = False
38 |
39 | def update(self, value=None, text=None, ptype=ProgressKey.Search):
40 | if isinstance(ptype, Enum):
41 | ptype = ptype.value
42 | if not self._process_detail.get(ptype, {}).get('enable'):
43 | return
44 | if value:
45 | self._process_detail[ptype]['value'] = value
46 | if text:
47 | self._process_detail[ptype]['text'] = text
48 |
49 | def get_process(self, ptype=ProgressKey.Search):
50 | if isinstance(ptype, Enum):
51 | ptype = ptype.value
52 | return self._process_detail.get(ptype)
53 |
--------------------------------------------------------------------------------
/app/helper/plugin_helper.py:
--------------------------------------------------------------------------------
1 | from cachetools import cached, TTLCache
2 |
3 | from app.utils import RequestUtils
4 |
5 |
6 | # 2023年08月30日 nastool原作者服务已失效
7 | class PluginHelper:
8 |
9 | @staticmethod
10 | def install(plugin_id):
11 | """
12 | 插件安装统计计数
13 | """
14 | # return RequestUtils(timeout=5).get(f"https://nastool.org/plugin/{plugin_id}/install")
15 | pass
16 |
17 | @staticmethod
18 | def report(plugins):
19 | """
20 | 批量上报插件安装统计数据
21 | """
22 | # return RequestUtils(content_type="application/json",
23 | # timeout=5).post(f"https://nastool.org/plugin/update",
24 | # json={
25 | # "plugins": [
26 | # {
27 | # "plugin_id": plugin,
28 | # "count": 1
29 | # } for plugin in plugins
30 | # ]
31 | # })
32 | return {}
33 |
34 | @staticmethod
35 | @cached(cache=TTLCache(maxsize=1, ttl=3600))
36 | def statistic():
37 | """
38 | 获取插件安装统计数据
39 | """
40 | # ret = RequestUtils(accept_type="application/json",
41 | # timeout=5).get_res("https://nastool.org/plugin/statistic")
42 | # if ret:
43 | # try:
44 | # return ret.json()
45 | # except Exception as e:
46 | # print(e)
47 | # return {}
48 | return {}
49 |
--------------------------------------------------------------------------------
/app/message/message_center.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import time
3 | from collections import deque
4 |
5 | from app.utils.commons import singleton
6 |
7 |
8 | @singleton
9 | class MessageCenter:
10 | _message_queue = deque(maxlen=50)
11 | _message_index = 0
12 |
13 | def __init__(self):
14 | pass
15 |
16 | def insert_system_message(self, title, content=None):
17 | """
18 | 新增系统消息
19 | :param title: 标题
20 | :param content: 内容
21 | """
22 | title = title.replace("\n", "
").strip() if title else ""
23 | content = content.replace("\n", "
").strip() if content else ""
24 | self.__append_message_queue(title, content)
25 |
26 | def __append_message_queue(self, title, content):
27 | """
28 | 将消息增加到队列
29 | """
30 | self._message_queue.appendleft({"title": title,
31 | "content": content,
32 | "time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))})
33 |
34 | def get_system_messages(self, num=20, lst_time=None):
35 | """
36 | 查询系统消息
37 | :param num:条数
38 | :param lst_time: 最后时间
39 | """
40 | if not lst_time:
41 | return list(self._message_queue)[-num:]
42 | else:
43 | ret_messages = []
44 | for message in list(self._message_queue):
45 | if (datetime.datetime.strptime(message.get("time"), '%Y-%m-%d %H:%M:%S') - datetime.datetime.strptime(
46 | lst_time, '%Y-%m-%d %H:%M:%S')).seconds > 0:
47 | ret_messages.append(message)
48 | else:
49 | break
50 | return ret_messages
51 |
--------------------------------------------------------------------------------
/app/message/client/pushdeer.py:
--------------------------------------------------------------------------------
1 | from pypushdeer import PushDeer
2 |
3 | from app.message.client._base import _IMessageClient
4 | from app.utils import StringUtils, ExceptionUtils
5 |
6 |
7 | class PushDeerClient(_IMessageClient):
8 | schema = "pushdeer"
9 |
10 | _server = None
11 | _apikey = None
12 | _client_config = {}
13 |
14 | def __init__(self, config):
15 | self._client_config = config
16 | self.init_config()
17 |
18 | def init_config(self):
19 | if self._client_config:
20 | self._server = StringUtils.get_base_url(self._client_config.get('server'))
21 | self._apikey = self._client_config.get('apikey')
22 |
23 | @classmethod
24 | def match(cls, ctype):
25 | return True if ctype == cls.schema else False
26 |
27 | def send_msg(self, title, text="", image="", url="", user_id=""):
28 | """
29 | 发送PushDeer消息
30 | :param title: 消息标题
31 | :param text: 消息内容
32 | :param image: 未使用
33 | :param url: 未使用
34 | :param user_id: 未使用
35 | :return: 发送状态、错误信息
36 | """
37 | if not title and not text:
38 | return False, "标题和内容不能同时为空"
39 | try:
40 | if not self._server or not self._apikey:
41 | return False, "参数未配置"
42 | pushdeer = PushDeer(server=self._server, pushkey=self._apikey)
43 | res = pushdeer.send_markdown(title, desp=text)
44 | if res:
45 | return True, "成功"
46 | else:
47 | return False, "失败"
48 | except Exception as msg_e:
49 | ExceptionUtils.exception_traceback(msg_e)
50 | return False, str(msg_e)
51 |
52 | def send_list_msg(self, **kwargs):
53 | pass
54 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 问题反馈
2 | description: File a bug report
3 | title: "[错误报告]: 请在此处简单描述你的问题"
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | 请确认以下信息:
10 | 1. 请按此模板提交issues,不按模板提交的问题将直接关闭。
11 | 2. 如果你的问题可以直接在以往 issue 中找到,那么你的 issue 将会被直接关闭。
12 | 3. 提交问题务必描述清楚、附上日志,描述不清导致无法理解和分析的问题会被直接关闭。
13 | - type: checkboxes
14 | id: ensure
15 | attributes:
16 | label: 确认
17 | description: 在提交 issue 之前,请确认你已经阅读并确认以下内容
18 | options:
19 | - label: 我的版本是最新版本,我的版本号与 [version](https://github.com/0xforee/nas-tools/releases/latest) 相同。
20 | required: true
21 | - label: 我已经 [issue](https://github.com/0xforee/nas-tools/issues) 中搜索过,确认我的问题没有被提出过。
22 | required: true
23 | - label: 我已经修改标题,将标题中的 描述 替换为我遇到的问题。
24 | required: true
25 | - type: input
26 | id: version
27 | attributes:
28 | label: 当前程序版本
29 | description: 遇到问题时程序所在的版本号
30 | validations:
31 | required: true
32 | - type: dropdown
33 | id: type
34 | attributes:
35 | label: 问题类型
36 | description: 你在以下哪个部分碰到了问题
37 | options:
38 | - 主程序运行问题
39 | - 插件问题
40 | - Docker或运行环境问题
41 | - 其他问题
42 | validations:
43 | required: true
44 | - type: textarea
45 | id: what-happened
46 | attributes:
47 | label: 问题描述
48 | description: 请详细描述你碰到的问题
49 | placeholder: "问题描述"
50 | validations:
51 | required: true
52 | - type: textarea
53 | id: logs
54 | attributes:
55 | label: 发生问题时系统日志和配置文件
56 | description: 问题出现时,程序运行日志请复制到这里。抓取日志参考:https://github.com/0xforee/nas-tools/wiki/%E5%A6%82%E4%BD%95%E6%8A%93%E5%8F%96%E6%97%A5%E5%BF%97%EF%BC%9F
57 | render: bash
58 |
--------------------------------------------------------------------------------
/app/indexer/client/_haidan.py:
--------------------------------------------------------------------------------
1 | from pyquery import PyQuery
2 | from app.utils.exception_utils import ExceptionUtils
3 | import copy
4 | import log
5 | from jinja2 import Template
6 |
7 | from app.indexer.client._spider import TorrentSpider
8 |
9 |
10 | class HaiDanSpider(TorrentSpider):
11 |
12 | def parse(self, request, response):
13 | """
14 | 解析整个页面
15 | """
16 | try:
17 | # 获取站点文本
18 | html_text = response.extract()
19 | html_text = self.clean_all_sites_free(html_text)
20 | if not html_text:
21 | self.is_error = True
22 | self.is_complete = True
23 | return
24 | # 解析站点文本对象
25 | html_doc = PyQuery(html_text)
26 | # 种子筛选器
27 | torrents_selector = self.list.get('selector', '')
28 | # 遍历种子html列表
29 | for torn in html_doc(torrents_selector):
30 | group = PyQuery(torn)
31 | for torrent in group('div.torrent_wrap'):
32 | # log.debug(f"[haidan] {torrent}")
33 | torrent_query = PyQuery(torrent)
34 | torrent_info = self.Getinfo(torrent_query)
35 | # use group name + torrent name override
36 | self.Gettitle_with_group(group, torrent_query)
37 | self.torrents_info_array.append(copy.deepcopy(torrent_info))
38 | if len(self.torrents_info_array) >= int(self.result_num):
39 | break
40 |
41 | except Exception as err:
42 | self.is_error = True
43 | ExceptionUtils.exception_traceback(err)
44 | log.warn(f"【Spider】错误:{self.indexername} {str(err)}")
45 | finally:
46 | self.is_complete = True
--------------------------------------------------------------------------------
/diff.md:
--------------------------------------------------------------------------------
1 | 
2 | # NAS媒体库管理工具
3 |
4 |
5 | [](https://github.com/0xforee/nas-tools/stargazers)
6 | [](https://github.com/0xforee/nas-tools/network/members)
7 | [](https://github.com/0xforee/nas-tools/issues)
8 | [](https://github.com/0xforee/nas-tools/blob/master/LICENSE.md)
9 | [](https://hub.docker.com/r/0xforee/nas-tools)
10 | [](https://hub.docker.com/r/0xforee/nas-tools)
11 |
12 | Docker:https://hub.docker.com/repository/docker/0xforee/nas-tools
13 |
14 | ## 基于官方主线版本增强
15 | 1. 基于主线最新 3.2.3 版本制作,配置可以完美迁移
16 | 1. 取消了用户认证
17 | 2. 取消新手刷流限制
18 | 1. 刷流增加部分下载能力(拆包),可以灵活配置下载种子的大小和比例,最大化提升刷流效率。(部分站点可能禁止,请慎用!!!)
19 | 2. 刷流增加限免限时检测能力,超过限免,自动降速为 1B/s,防止流量偷跑,默认为关。(目前已测试: mteam 和 hdmayi)
20 | 3. 刷流界面增加信息展示
21 | * 增加展示已保种大小
22 | * 种子明细增加展示种子大小,限免过期时间
23 | 3. 保留了 BT 能力和内置 BT 站点,可以继续索引和下载 BT 磁链和种子文件
24 | 4. 支持 jackett 和 prowlarr 索引器
25 | * 可以通过插件和内置索引器同时使用
26 | 5. 一些入口增加快捷跳转能力,方便将 nastools 作为媒体管理主要入口。具体如下:
27 | * 下载管理-正在下载:增加跳转下载器查看种子详情能力
28 | * 我的媒体库:增加标题跳转媒体库能力
29 | * 索引器:jackett 和 prowlarr 增加跳转各服务能力
30 | * 媒体服务器:每个服务器配置界面增加跳转服务能力
31 | 6. 支持 Mteam 新架构
32 | * 站点配置中配置 api-key 即可
33 | * api-key 从馒头-控制台-实验室-存取令牌 生成获取
34 | * 完美支持站点签到
35 | * 可从控制台登陆设备活动记录中查询到签到(登陆)记录
36 |
--------------------------------------------------------------------------------
/tests/meta_recogonize_v3/cases/testcase1.py:
--------------------------------------------------------------------------------
1 | match = {
2 | 'en_name': "Léon",
3 | 'cn_name': "这个杀手不太冷",
4 | "pin_name": "Zhegeshashoubutaileng",
5 | 'year': '1994'
6 | }
7 |
8 | cases = [
9 | "Léon 1994 2160p UHD Blu-ray HDR10 HEVC TrueHD 7.1 Atmos-CrsS",
10 | "Leon The Professional 1994 EXTENDED 1080p BluRay DTS x264-LEGi0N",
11 | "The Godfather Coda The Death of Michael Corleone 1990 UHD BluRay 2160p 10bit DoVi 5Audio TrueHD 5.1 x2",
12 | "Leon aka The Professional Directors Cut 1994 Bluray 1080p x265 AAC 5.1 2Audios-GREENOTEA",
13 | "Leon 1994 EXTENDED REMASTERED BluRay 1080p 2Audio TrueHD Atmos 7.1 x265 10bit-BeiTai",
14 | "Operation Napoleon 2023 1080p friDay WEB-DL H.264 AAC-PTerWEB",
15 | "Leon 1994 1080p WEB-DL AVC AAC2.0-HDKylinWeb",
16 | "Shake Rattle & Roll Extreme 2023 1080p NF WEB-DL DDP5.1 H.264-HHWEB",
17 | "Operation Napoleon 2023 1080p Blu-ray AVC DTS-HD MA 5.1",
18 | "Leon 1994 WEB-DL 2160p HEVC AAC-ZmWeb",
19 | "Leon The Professional 1994 BluRay 2160p x265 10bit HDR 3Audio mUHD-FRDS",
20 | "Leon The Professional 1994 EXTENDED REMASTERED BluRay 1080p x264 AC3 2Audios-CMCT",
21 | "Leonera 2008 1080p AMZN WEB-DL DDP2.0 H.264-PTerWEB",
22 | "The Godfather Part III 1990 REPACK 2160p UHD BluRay REMUX DV HDR HEVC TrueHD 5.1-FraMeSToR",
23 | "Leon The Professional 1994 Extended 2160p HDR UHD BluRay TrueHD 7.1 Atmos 2Audio x265 10bit-HDS",
24 | "In guerra per amore 2016 WEB-DL 2160p H.265 AAC-HDHWEB",
25 | "Napoleon 2023 2160p iT WEB-DL DDP5.1 Atmos HDR H 265-HHWEB",
26 | "Napoleon 2023 1080p iTunes WEB-DL DDP 5.1 Atmos H.264-CHDWEB",
27 | "Leon 1996 Extended Remastered BluRay 1080p x265 10bit 2Audio MNHD-FRDS",
28 | "Leon the Professional DC EXTENDED 1994 UHD Blu-ray 2160p 10bit HDR 5Audio TrueHDAtmos 7.1 x265-beAst",
29 | ]
30 |
31 | cases_result = [
32 | 1,1,0,1,1,0,1,0,0,1,1,1,0,0,1,0,0,0,0,1
33 | ]
--------------------------------------------------------------------------------
/app/conf/systemconfig.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from app.helper import DictHelper
4 | from app.utils.commons import singleton
5 | from app.utils.types import SystemConfigKey
6 |
7 |
8 | @singleton
9 | class SystemConfig:
10 | # 系统设置
11 | systemconfig = {}
12 |
13 | def __init__(self):
14 | self.dicthelper = DictHelper()
15 | self.init_config()
16 |
17 | def init_config(self):
18 | """
19 | 缓存系统设置
20 | """
21 | for item in self.dicthelper.list("SystemConfig"):
22 | if not item:
23 | continue
24 | if self.__is_obj(item.VALUE):
25 | self.systemconfig[item.KEY] = json.loads(item.VALUE)
26 | else:
27 | self.systemconfig[item.KEY] = item.VALUE
28 |
29 | @staticmethod
30 | def __is_obj(obj):
31 | if isinstance(obj, list) or isinstance(obj, dict):
32 | return True
33 | else:
34 | return str(obj).startswith("{") or str(obj).startswith("[")
35 |
36 | def set(self, key: [SystemConfigKey, str], value):
37 | """
38 | 设置系统设置
39 | """
40 | if isinstance(key, SystemConfigKey):
41 | key = key.value
42 | # 更新内存
43 | self.systemconfig[key] = value
44 | # 写入数据库
45 | if self.__is_obj(value):
46 | if value is not None:
47 | value = json.dumps(value)
48 | else:
49 | value = ''
50 | self.dicthelper.set("SystemConfig", key, value)
51 |
52 | def get(self, key: [SystemConfigKey, str] = None):
53 | """
54 | 获取系统设置
55 | """
56 | if not key:
57 | return self.systemconfig
58 | if isinstance(key, SystemConfigKey):
59 | key = key.value
60 | return self.systemconfig.get(key)
61 |
--------------------------------------------------------------------------------
/Q&A.md:
--------------------------------------------------------------------------------
1 | ## 常见问题
2 |
3 | ### 1. 启动inotify报错/无法自动目录同步
4 |
5 | > 问题描述
6 | > 无法启动,日志报inotify instance limit reached、inotify watch limit reached等与inotify相关错误
7 | > 目录同步无法自动同步或只有部份目录正常,但在服务中手动启动可以正常同步
8 |
9 | 解决办法:
10 | * 宿主机上(不是docker容器里),执行以下命令:
11 | ```bash
12 | echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
13 | echo fs.inotify.max_user_instances=524288 | sudo tee -a /etc/sysctl.conf
14 | sudo sysctl -p
15 | ```
16 | * 插件-定时目录同步
17 |
18 | ### 2. 启动报错数据库no such column
19 | > 问题描述
20 | > 启动报错数据库no such column
21 |
22 | 解决办法:
23 | * [Sqlite浏览器](https://github.com/sqlitebrowser/sqlitebrowser)打开config文件夹下user.db
24 | * 删除alembic_version表后重启
25 |
26 | ### 3. Nginx-Proxy-Manager无法申请/更新Let's Encrypt证书
27 | > 问题描述
28 | > Nginx-Proxy-Manager无法申请/更新Let's Encrypt证书
29 |
30 | 解决办法:
31 | * 无法通过DNSpod申请, 进入容器,执行命令
32 | ```bash
33 | pip install certbot-dns-dnspod
34 | ```
35 |
36 | * 无法更新/自动更新
37 | ```bash
38 | 进入容器,执行命令 pip install zope
39 | ```
40 |
41 | * pip使用代理
42 | ```
43 | pip install xxx --proxy=http://ip:port
44 | ```
45 |
46 | ### 4. 消息通知内容无法跳转
47 | > 问题描述
48 | > 消息通知内容无法跳转(包含“点击选择下载”的消息中,没有包含查看详情的入口)
49 |
50 | 解决办法:
51 | * 设置-基础设置-系统-外网访问地址
52 |
53 | ### 5. 电影/电视剧订阅一直队列中
54 | > 问题描述
55 | > 电影/电视剧订阅添加后,一直在队列中
56 | > 需手动刷新订阅开始搜索或订阅
57 |
58 | 解决办法:
59 | * 订阅如启用有订阅站点, 请在设置-基础设置-服务-订阅RSS周期设置启用
60 | * 订阅如启用有搜索站点, 请在设置-基础设置-服务-订阅搜索周期设置启用
61 | * 添加后点击订阅,选择刷新
62 |
63 | ### 6. 目录同步文件重复转移
64 | > 问题描述
65 | > 设置-基础设置-媒体-重命名格式中包含{releaseGroup}
66 | > 文件转移方式为目录同步
67 | > 转移后,出现重复的转移文件(制作组等后缀不同)
68 |
69 | 解决办法:
70 | * 目录同步设置时,目的目录不要在源目录下
71 |
72 | ### 7. 识别转移错误码-1
73 | > 问题描述
74 | > 识别转移错误码-1
75 |
76 | 解决办法:
77 | * 硬链接跨盘,转移前后目录根目录需相同
78 | * 群晖中,不同的共享文件夹会被系统认为是跨盘
79 |
80 | ### 8. 电视剧订阅在完结前自动删除
81 | > 问题描述
82 | > 电视剧订阅在完结前自动删除
83 |
84 | 解决办法:
85 | * TMDB词条未更新集数/下载资源无法识别集数
86 | * 订阅中设置总集数
--------------------------------------------------------------------------------
/web/static/css/nprogress.css:
--------------------------------------------------------------------------------
1 | /* Make clicks pass-through */
2 | #nprogress {
3 | pointer-events: none;
4 | }
5 |
6 | #nprogress .bar {
7 | background: var(--tblr-primary) !important;
8 | position: fixed;
9 | z-index: 1031;
10 | top: calc(env(safe-area-inset-top) + var(--safe-area-inset-top));
11 | left: 0;
12 | width: 100%;
13 | height: 2px;
14 | }
15 |
16 | /* Fancy blur effect */
17 | #nprogress .peg {
18 | display: block;
19 | position: absolute;
20 | right: 0;
21 | width: 5px;
22 | height: 100%;
23 | box-shadow: 0 0 10px var(--tblr-primary), 0 0 5px var(--tblr-primary);
24 | opacity: 1.0;
25 |
26 | -webkit-transform: rotate(0deg) translate(0px, -1px);
27 | -ms-transform: rotate(0deg) translate(0px, -1px);
28 | transform: rotate(0deg) translate(0px, -1px);
29 | }
30 |
31 | /* Remove these to get rid of the spinner */
32 | #nprogress .spinner {
33 | display: block;
34 | position: fixed;
35 | z-index: 1031;
36 | top: 15px;
37 | right: 15px;
38 | }
39 |
40 | #nprogress .spinner-icon {
41 | width: 18px;
42 | height: 18px;
43 | box-sizing: border-box;
44 |
45 | border: solid 2px transparent;
46 | border-top-color: #29d;
47 | border-left-color: #29d;
48 | border-radius: 50%;
49 |
50 | -webkit-animation: nprogress-spinner 400ms linear infinite;
51 | animation: nprogress-spinner 400ms linear infinite;
52 | }
53 |
54 | .nprogress-custom-parent {
55 | overflow: hidden;
56 | position: relative;
57 | }
58 |
59 | .nprogress-custom-parent #nprogress .spinner,
60 | .nprogress-custom-parent #nprogress .bar {
61 | position: absolute;
62 | }
63 |
64 | @-webkit-keyframes nprogress-spinner {
65 | 0% { -webkit-transform: rotate(0deg); }
66 | 100% { -webkit-transform: rotate(360deg); }
67 | }
68 | @keyframes nprogress-spinner {
69 | 0% { transform: rotate(0deg); }
70 | 100% { transform: rotate(360deg); }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/web/static/components/card/person/index.js:
--------------------------------------------------------------------------------
1 | import { html } from "../../utility/lit-core.min.js";
2 | import { CustomElement, Golbal } from "../../utility/utility.js";
3 |
4 | export class PersonCard extends CustomElement {
5 |
6 | static properties = {
7 | person_id: { attribute: "person-id" },
8 | person_image: { attribute: "person-image" },
9 | person_name: { attribute: "person-name" },
10 | person_role: { attribute: "person-role" },
11 | lazy: {},
12 | };
13 |
14 | constructor() {
15 | super();
16 | this.lazy = "0";
17 | }
18 |
19 | render() {
20 | return html`
21 |
22 |
23 |
24 |
31 |
32 |
34 | ${this.person_name}
35 |
36 |
38 | ${this.person_role}
39 |
40 |
41 |
42 | `;
43 | }
44 |
45 | }
46 |
47 | window.customElements.define("person-card", PersonCard);
--------------------------------------------------------------------------------
/web/templates/macro/oops.html:
--------------------------------------------------------------------------------
1 |
2 | {% macro nodatafound(title, text) %}
3 |
4 |
5 |
6 |

7 |
8 |
{{ title }}
9 |
10 | {{ text }}
11 |
12 |
13 |
14 |
15 | {% endmacro %}
16 |
17 |
18 | {% macro empty(title, text) %}
19 |
20 |
21 |
22 |

23 |
24 |
{{ title }}
25 |
26 | {{ text }}
27 |
28 |
29 |
30 |
31 | {% endmacro %}
32 |
33 |
34 | {% macro systemerror(title, text) %}
35 |
36 |
37 |
38 |

39 |
40 |
{{ title }}
41 |
42 | {{ text }}
43 |
44 |
45 |
46 |
47 | {% endmacro %}
48 |
49 |
50 | {% macro loading() %}
51 |
52 |
53 |
54 |

55 |
56 |
57 | 正在加载
58 |
59 |
60 |
61 |
62 | {% endmacro %}
63 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/core/handle_failed_requests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2018-08-13 11:43:01
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | import feapder.setting as setting
11 | from feapder.buffer.request_buffer import RequestBuffer
12 | from feapder.db.redisdb import RedisDB
13 | from feapder.network.request import Request
14 | from feapder.utils.log import log
15 |
16 |
17 | class HandleFailedRequests:
18 | def __init__(self, redis_key):
19 | if redis_key.endswith(":z_failed_requests"):
20 | redis_key = redis_key.replace(":z_failed_requests", "")
21 |
22 | self._redisdb = RedisDB()
23 | self._request_buffer = RequestBuffer(redis_key)
24 |
25 | self._table_failed_request = setting.TAB_FAILED_REQUESTS.format(
26 | redis_key=redis_key
27 | )
28 |
29 | def get_failed_requests(self, count=10000):
30 | failed_requests = self._redisdb.zget(self._table_failed_request, count=count)
31 | failed_requests = [eval(failed_request) for failed_request in failed_requests]
32 | return failed_requests
33 |
34 | def reput_failed_requests_to_requests(self):
35 | log.debug("正在重置失败的requests...")
36 | total_count = 0
37 | while True:
38 | try:
39 | failed_requests = self.get_failed_requests()
40 | if not failed_requests:
41 | break
42 |
43 | for request in failed_requests:
44 | request["retry_times"] = 0
45 | request_obj = Request.from_dict(request)
46 | self._request_buffer.put_request(request_obj)
47 |
48 | total_count += 1
49 | except Exception as e:
50 | log.exception(e)
51 |
52 | self._request_buffer.flush()
53 |
54 | log.debug("重置%s条失败requests为待抓取requests" % total_count)
55 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/pterclub.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
4 | from app.utils import StringUtils, RequestUtils
5 | from config import Config
6 |
7 |
8 | class PTerClub(_ISiteSigninHandler):
9 | """
10 | 猫签到
11 | """
12 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
13 | site_url = "pterclub.com"
14 |
15 |
16 | @classmethod
17 | def match(cls, url):
18 | """
19 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
20 | :param url: 站点Url
21 | :return: 是否匹配,如匹配则会调用该类的signin方法
22 | """
23 | return True if StringUtils.url_equal(url, cls.site_url) else False
24 |
25 | def signin(self, site_info: dict):
26 | """
27 | 执行签到操作
28 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
29 | :return: 签到结果信息
30 | """
31 | site = site_info.get("name")
32 | site_cookie = site_info.get("cookie")
33 | ua = site_info.get("ua")
34 | proxy = Config().get_proxies() if site_info.get("proxy") else None
35 |
36 | # 签到
37 | sign_res = RequestUtils(cookies=site_cookie,
38 | headers=ua,
39 | proxies=proxy
40 | ).get_res(url="https://pterclub.com/attendance-ajax.php")
41 | if not sign_res or sign_res.status_code != 200:
42 | self.error(f"签到失败,签到接口请求失败")
43 | return False, f'【{site}】签到失败,请检查cookie是否失效'
44 |
45 | sign_dict = json.loads(sign_res.text)
46 | if sign_dict['status'] == '1':
47 | # {"status":"1","data":" (签到已成功300)","message":"这是您的第237次签到,
48 | # 已连续签到237天。
本次签到获得300克猫粮。
"}
49 | self.info(f"签到成功")
50 | return True, f'【{site}】签到成功'
51 | else:
52 | # {"status":"0","data":"抱歉","message":"您今天已经签到过了,请勿重复刷新。"}
53 | self.info(f"今日已签到")
54 | return True, f'【{site}】今日已签到'
55 |
--------------------------------------------------------------------------------
/app/message/client/iyuu.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import urlencode
2 |
3 | from app.message.client._base import _IMessageClient
4 | from app.utils import RequestUtils, ExceptionUtils
5 |
6 |
7 | class IyuuMsg(_IMessageClient):
8 | schema = "iyuu"
9 |
10 | _token = None
11 | _client_config = {}
12 |
13 | def __init__(self, config):
14 | self._client_config = config
15 | self.init_config()
16 |
17 | def init_config(self):
18 | if self._client_config:
19 | self._token = self._client_config.get('token')
20 |
21 | @classmethod
22 | def match(cls, ctype):
23 | return True if ctype == cls.schema else False
24 |
25 | def send_msg(self, title, text="", image="", url="", user_id=""):
26 | """
27 | 发送爱语飞飞消息
28 | :param title: 消息标题
29 | :param text: 消息内容
30 | :param image: 未使用
31 | :param url: 未使用
32 | :param user_id: 未使用
33 | """
34 | if not title and not text:
35 | return False, "标题和内容不能同时为空"
36 | if not self._token:
37 | return False, "参数未配置"
38 | try:
39 | sc_url = "http://iyuu.cn/%s.send?%s" % (self._token, urlencode({"text": title, "desp": text}))
40 | res = RequestUtils().get_res(sc_url)
41 | if res and res.status_code == 200:
42 | ret_json = res.json()
43 | errno = ret_json.get('errcode')
44 | error = ret_json.get('errmsg')
45 | if errno == 0:
46 | return True, error
47 | else:
48 | return False, error
49 | elif res is not None:
50 | return False, f"错误码:{res.status_code},错误原因:{res.reason}"
51 | else:
52 | return False, "未获取到返回信息"
53 | except Exception as msg_e:
54 | ExceptionUtils.exception_traceback(msg_e)
55 | return False, str(msg_e)
56 |
57 | def send_list_msg(self, **kwargs):
58 | pass
59 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/_base.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import re
3 | from abc import ABCMeta, abstractmethod
4 |
5 | import log
6 | from app.utils import StringUtils
7 |
8 |
9 | class _ISiteSigninHandler(metaclass=ABCMeta):
10 | """
11 | 实现站点签到的基类,所有站点签到类都需要继承此类,并实现match和signin方法
12 | 实现类放置到sitesignin目录下将会自动加载
13 | """
14 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
15 | site_url = ""
16 |
17 | @abstractmethod
18 | def match(self, url):
19 | """
20 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
21 | :param url: 站点Url
22 | :return: 是否匹配,如匹配则会调用该类的signin方法
23 | """
24 | return True if StringUtils.url_equal(url, self.site_url) else False
25 |
26 | @abstractmethod
27 | def signin(self, site_info: dict):
28 | """
29 | 执行签到操作
30 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
31 | :return: True|False,签到结果信息
32 | """
33 | pass
34 |
35 | @staticmethod
36 | def sign_in_result(html_res, regexs):
37 | """
38 | 判断是否签到成功
39 | """
40 | html_text = re.sub(r"#\d+", "", re.sub(r"\d+px", "", html_res))
41 | for regex in regexs:
42 | if re.search(str(regex), html_text):
43 | return True
44 | return False
45 |
46 | def info(self, msg):
47 | """
48 | 记录INFO日志
49 | :param msg: 日志信息
50 | """
51 | log.info(f"【Sites】{self.__class__.__name__} - {msg}")
52 |
53 | def warn(self, msg):
54 | """
55 | 记录WARN日志
56 | :param msg: 日志信息
57 | """
58 | log.warn(f"【Sites】{self.__class__.__name__} - {msg}")
59 |
60 | def error(self, msg):
61 | """
62 | 记录ERROR日志
63 | :param msg: 日志信息
64 | """
65 | log.error(f"【Sites】{self.__class__.__name__} - {msg}")
66 |
67 | def debug(self, msg):
68 | """
69 | 记录Debug日志
70 | :param msg: 日志信息
71 | """
72 | log.debug(f"【Sites】{self.__class__.__name__} - {msg}")
73 |
--------------------------------------------------------------------------------
/app/utils/time_utils.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | class TimeUtils:
4 | @staticmethod
5 | def time_difference(last_seen_str):
6 |
7 | try:
8 | if not last_seen_str:
9 | return ""
10 | if not isinstance(last_seen_str, str):
11 | return ""
12 | # 将时间字符串解析为 datetime 对象
13 | last_seen = datetime.strptime(last_seen_str, "%Y-%m-%d %H:%M:%S")
14 | current_time = datetime.now()
15 |
16 | # 计算时间差
17 | time_diff = current_time - last_seen
18 |
19 | # 获取天、小时和分钟
20 | days = time_diff.days
21 | hours, remainder = divmod(time_diff.seconds, 3600)
22 | minutes, seconds = divmod(remainder, 60)
23 |
24 | # 构建返回结果
25 | result_parts = []
26 | if days > 0:
27 | result_parts.append(f"{days}天")
28 | if hours > 0:
29 | result_parts.append(f"{hours}时")
30 | if minutes > 0 or (days == 0 and hours == 0): # 如果天和小时都为0,仍然显示分钟
31 | result_parts.append(f"{minutes}分")
32 |
33 | return ''.join(result_parts) + "前"
34 | except:
35 | return ''
36 |
37 | @staticmethod
38 | def less_than_days(date_str, target_days):
39 | """
40 | 小于三十天
41 | """
42 | try:
43 | if not date_str:
44 | return False
45 | if not isinstance(date_str, str):
46 | return False
47 | # 将时间字符串解析为 datetime 对象
48 | last_seen = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
49 | current_time = datetime.now()
50 |
51 | # 计算时间差
52 | time_diff = current_time - last_seen
53 |
54 | # 获取天、小时和分钟
55 | days = time_diff.days
56 |
57 | if days > target_days:
58 | return False
59 | else:
60 | return True
61 | except:
62 | return False
63 |
64 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/haidan.py:
--------------------------------------------------------------------------------
1 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
2 | from app.utils import StringUtils, RequestUtils
3 | from config import Config
4 |
5 |
6 | class HaiDan(_ISiteSigninHandler):
7 | """
8 | 海胆签到
9 | """
10 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
11 | site_url = "haidan.video"
12 |
13 | # 签到成功
14 | _succeed_regex = ['(?<=value=")已经打卡(?=")']
15 |
16 | @classmethod
17 | def match(cls, url):
18 | """
19 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
20 | :param url: 站点Url
21 | :return: 是否匹配,如匹配则会调用该类的signin方法
22 | """
23 | return True if StringUtils.url_equal(url, cls.site_url) else False
24 |
25 | def signin(self, site_info: dict):
26 | """
27 | 执行签到操作
28 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
29 | :return: 签到结果信息
30 | """
31 | site = site_info.get("name")
32 | site_cookie = site_info.get("cookie")
33 | ua = site_info.get("ua")
34 | proxy = Config().get_proxies() if site_info.get("proxy") else None
35 |
36 | # 签到
37 | sign_res = RequestUtils(cookies=site_cookie,
38 | headers=ua,
39 | proxies=proxy
40 | ).get_res(url="https://www.haidan.video/signin.php")
41 | if not sign_res or sign_res.status_code != 200:
42 | self.error(f"签到失败,请检查站点连通性")
43 | return False, f'【{site}】签到失败,请检查站点连通性'
44 |
45 | if "login.php" in sign_res.text:
46 | self.error(f"签到失败,cookie失效")
47 | return False, f'【{site}】签到失败,cookie失效'
48 |
49 | sign_status = self.sign_in_result(html_res=sign_res.text,
50 | regexs=self._succeed_regex)
51 | if sign_status:
52 | self.info(f"签到成功")
53 | return True, f'【{site}】签到成功'
54 |
55 | self.error(f"签到失败,签到接口返回 {sign_res.text}")
56 | return False, f'【{site}】签到失败'
57 |
--------------------------------------------------------------------------------
/app/message/client/serverchan.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import urlencode
2 |
3 | from app.message.client._base import _IMessageClient
4 | from app.utils import RequestUtils, ExceptionUtils
5 |
6 |
7 | class ServerChan(_IMessageClient):
8 | schema = "serverchan"
9 |
10 | _sckey = None
11 | _client_config = {}
12 |
13 | def __init__(self, config):
14 | self._client_config = config
15 | self.init_config()
16 |
17 | def init_config(self):
18 | if self._client_config:
19 | self._sckey = self._client_config.get('sckey')
20 |
21 | @classmethod
22 | def match(cls, ctype):
23 | return True if ctype == cls.schema else False
24 |
25 | def send_msg(self, title, text="", image="", url="", user_id=""):
26 | """
27 | 发送ServerChan消息
28 | :param title: 消息标题
29 | :param text: 消息内容
30 | :param image: 未使用
31 | :param url: 未使用
32 | :param user_id: 未使用
33 | """
34 | if not title and not text:
35 | return False, "标题和内容不能同时为空"
36 | if not self._sckey:
37 | return False, "参数未配置"
38 | try:
39 | sc_url = "https://sctapi.ftqq.com/%s.send?%s" % (self._sckey, urlencode({"title": title, "desp": text}))
40 | res = RequestUtils().get_res(sc_url)
41 | if res and res.status_code == 200:
42 | ret_json = res.json()
43 | errno = ret_json.get('code')
44 | error = ret_json.get('message')
45 | if errno == 0:
46 | return True, error
47 | else:
48 | return False, error
49 | elif res is not None:
50 | return False, f"错误码:{res.status_code},错误原因:{res.reason}"
51 | else:
52 | return False, "未获取到返回信息"
53 | except Exception as msg_e:
54 | ExceptionUtils.exception_traceback(msg_e)
55 | return False, str(msg_e)
56 |
57 | def send_list_msg(self, **kwargs):
58 | pass
59 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/hhanclub.py:
--------------------------------------------------------------------------------
1 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
2 | from app.utils import StringUtils, RequestUtils
3 | from config import Config
4 |
5 |
6 | class Hhanclub(_ISiteSigninHandler):
7 | """
8 | 海胆签到
9 | """
10 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
11 | site_url = "hhanclub.top"
12 |
13 | # 签到成功
14 | _succeed_regex = ["今日签到排名:(\\d+) / \\d+"]
15 |
16 | @classmethod
17 | def match(cls, url):
18 | """
19 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
20 | :param url: 站点Url
21 | :return: 是否匹配,如匹配则会调用该类的signin方法
22 | """
23 | return True if StringUtils.url_equal(url, cls.site_url) else False
24 |
25 | def signin(self, site_info: dict):
26 | """
27 | 执行签到操作
28 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
29 | :return: 签到结果信息
30 | """
31 | site = site_info.get("name")
32 | site_cookie = site_info.get("cookie")
33 | ua = site_info.get("ua")
34 | proxy = Config().get_proxies() if site_info.get("proxy") else None
35 |
36 | # 签到
37 | sign_res = RequestUtils(cookies=site_cookie,
38 | headers=ua,
39 | proxies=proxy
40 | ).get_res(url="https://hhanclub.top/attendance.php")
41 | if not sign_res or sign_res.status_code != 200:
42 | self.error(f"签到失败,请检查站点连通性")
43 | return False, f'【{site}】签到失败,请检查站点连通性'
44 |
45 | if "rule-tips" not in sign_res.text:
46 | self.error(f"签到失败,cookie失效")
47 | return False, f'【{site}】签到失败,cookie失效'
48 |
49 | sign_status = self.sign_in_result(html_res=sign_res.text,
50 | regexs=self._succeed_regex)
51 | if sign_status:
52 | self.info(f"签到成功")
53 | return True, f'【{site}】签到成功'
54 |
55 | self.error(f"签到失败,签到接口返回 {sign_res.text}")
56 | return False, f'【{site}】签到失败'
57 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/dedup/litefilter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on 2022/9/21 11:28 AM
4 | ---------
5 | @summary:
6 | ---------
7 | @author: Boris
8 | @email: boris_liu@foxmail.com
9 | """
10 | from typing import List, Union, Set
11 |
12 | from feapder.dedup.basefilter import BaseFilter
13 |
14 |
15 | class LiteFilter(BaseFilter):
16 | def __init__(self):
17 | self.datas: Set[str] = set()
18 |
19 | def add(
20 | self, keys: Union[List[str], str], *args, **kwargs
21 | ) -> Union[List[int], int]:
22 | """
23 |
24 | Args:
25 | keys: list / 单个值
26 | *args:
27 | **kwargs:
28 |
29 | Returns:
30 | list / 单个值 (如果数据已存在 返回 0 否则返回 1, 可以理解为是否添加成功)
31 | """
32 | if isinstance(keys, list):
33 | is_add = []
34 | for key in keys:
35 | if key not in self.datas:
36 | self.datas.add(key)
37 | is_add.append(1)
38 | else:
39 | is_add.append(0)
40 | else:
41 | if keys not in self.datas:
42 | is_add = 1
43 | self.datas.add(keys)
44 | else:
45 | is_add = 0
46 | return is_add
47 |
48 | def get(self, keys: Union[List[str], str]) -> Union[List[int], int]:
49 | """
50 | 检查数据是否存在
51 | Args:
52 | keys: list / 单个值
53 |
54 | Returns:
55 | list / 单个值 (如果数据已存在 返回 1 否则返回 0)
56 | """
57 | if isinstance(keys, list):
58 | temp_set = set()
59 | is_exist = []
60 | for key in keys:
61 | # 数据本身重复或者数据在去重库里
62 | if key in temp_set or key in self.datas:
63 | is_exist.append(1)
64 | else:
65 | is_exist.append(0)
66 | temp_set.add(key)
67 |
68 | return is_exist
69 | else:
70 | return int(keys in self.datas)
71 |
--------------------------------------------------------------------------------
/app/sites/siteuserinfo/nexus_rabbit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import json
3 |
4 | from lxml import etree
5 |
6 | from app.sites.siteuserinfo._base import SITE_BASE_ORDER
7 | from app.sites.siteuserinfo.nexus_php import NexusPhpSiteUserInfo
8 | from app.utils.exception_utils import ExceptionUtils
9 | from app.utils.types import SiteSchema
10 |
11 |
12 | class NexusRabbitSiteUserInfo(NexusPhpSiteUserInfo):
13 | schema = SiteSchema.NexusRabbit
14 | order = SITE_BASE_ORDER + 5
15 |
16 | @classmethod
17 | def match(cls, html_text):
18 | html = etree.HTML(html_text)
19 | if not html:
20 | return False
21 |
22 | printable_text = html.xpath("string(.)") if html else ""
23 | return 'Style by Rabbit' in printable_text
24 |
25 | def _parse_site_page(self, html_text):
26 | super()._parse_site_page(html_text)
27 | self._torrent_seeding_page = f"getusertorrentlistajax.php?page=1&limit=5000000&type=seeding&uid={self.userid}"
28 | self._torrent_seeding_headers = {"Accept": "application/json, text/javascript, */*; q=0.01"}
29 |
30 | def _parse_user_torrent_seeding_info(self, html_text, multi_page=False):
31 | """
32 | 做种相关信息
33 | :param html_text:
34 | :param multi_page: 是否多页数据
35 | :return: 下页地址
36 | """
37 |
38 | try:
39 | torrents = json.loads(html_text).get('data')
40 | except Exception as e:
41 | ExceptionUtils.exception_traceback(e)
42 | return
43 |
44 | page_seeding_size = 0
45 | page_seeding_info = []
46 |
47 | page_seeding = len(torrents)
48 | for torrent in torrents:
49 | seeders = int(torrent.get('seeders', 0))
50 | size = int(torrent.get('size', 0))
51 | page_seeding_size += int(torrent.get('size', 0))
52 |
53 | page_seeding_info.append([seeders, size])
54 |
55 | self.seeding += page_seeding
56 | self.seeding_size += page_seeding_size
57 | self.seeding_info.extend(page_seeding_info)
58 |
--------------------------------------------------------------------------------
/app/sites/site_limiter.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 |
4 | class SiteRateLimiter:
5 | def __init__(self, limit_interval: int, limit_count: int, limit_seconds: int):
6 | """
7 | 限制访问频率
8 | :param limit_interval: 单位时间(秒)
9 | :param limit_count: 单位时间内访问次数
10 | :param limit_seconds: 访问间隔(秒)
11 | """
12 | self.limit_count = limit_count
13 | self.limit_interval = limit_interval
14 | self.limit_seconds = limit_seconds
15 | self.last_visit_time = 0
16 | self.count = 0
17 |
18 | def check_rate_limit(self) -> (bool, str):
19 | """
20 | 检查是否超出访问频率控制
21 | :return: 超出返回True,否则返回False,超出时返回错误信息
22 | """
23 | current_time = time.time()
24 | # 防问间隔时间
25 | if self.limit_seconds:
26 | if current_time - self.last_visit_time < self.limit_seconds:
27 | return True, f"触发流控规则,访问间隔不得小于 {self.limit_seconds} 秒," \
28 | f"上次访问时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_visit_time))}"
29 | # 单位时间内访问次数
30 | if self.limit_interval and self.limit_count:
31 | if current_time - self.last_visit_time > self.limit_interval:
32 | # 计数清零
33 | self.count = 0
34 | if self.count >= self.limit_count:
35 | return True, f"触发流控规则,{self.limit_interval} 秒内访问次数不得超过 {self.limit_count} 次," \
36 | f"上次访问时间:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.last_visit_time))}"
37 | # 访问计数
38 | self.count += 1
39 | # 更新最后访问时间
40 | self.last_visit_time = current_time
41 | # 未触发流控
42 | return False, ""
43 |
44 |
45 | if __name__ == "__main__":
46 | # 限制 1 分钟内最多访问 10 次,单次访问间隔不得小于 10 秒
47 | site_rate_limit = SiteRateLimiter(10, 60, 10)
48 |
49 | # 模拟访问
50 | for i in range(12):
51 | if site_rate_limit.check_rate_limit():
52 | print("访问频率超限")
53 | else:
54 | print("访问成功")
55 | time.sleep(3)
56 |
--------------------------------------------------------------------------------
/app/media/tmdbv3api/objs/discover.py:
--------------------------------------------------------------------------------
1 | from app.media.tmdbv3api.tmdb import TMDb
2 |
3 | try:
4 | from urllib import urlencode
5 | except ImportError:
6 | from urllib.parse import urlencode
7 |
8 |
9 | class Discover(TMDb):
10 | _urls = {
11 | "movies": "/discover/movie",
12 | "tvs": "/discover/tv"
13 | }
14 |
15 | def discover_movies(self, params, page=1):
16 | """
17 | Discover movies by different types of data like average rating, number of votes, genres and certifications.
18 | :param params: dict
19 | :param page: int
20 | :return:
21 | """
22 | if not params:
23 | params = {}
24 | if page:
25 | params.update({"page": page})
26 | return self._get_obj(
27 | self._call(
28 | self._urls["movies"],
29 | urlencode(params)
30 | ),
31 | "results"
32 | )
33 |
34 | def discover_movies_pages(self, params):
35 | """
36 | Discover movies by different types of data like average rating, number of votes, genres and certifications.
37 | :param params: dict
38 | :return: total_pages
39 | """
40 | if not params:
41 | params = {}
42 | result = self._call(
43 | self._urls["movies"],
44 | urlencode(params)
45 | )
46 | return result.get("total_pages") or 0
47 |
48 | def discover_tv_shows(self, params, page=1):
49 | """
50 | Discover TV shows by different types of data like average rating, number of votes, genres,
51 | the network they aired on and air dates.
52 | :param params: dict
53 | :param page: int
54 | :return:
55 | """
56 | if not params:
57 | params = {}
58 | if page:
59 | params.update({"page": page})
60 | return self._get_obj(
61 | self._call(
62 | self._urls["tvs"],
63 | urlencode(params)
64 | ),
65 | "results"
66 | )
67 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/hdcity.py:
--------------------------------------------------------------------------------
1 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
2 | from app.utils import StringUtils, RequestUtils
3 | from config import Config
4 |
5 |
6 | class HDCity(_ISiteSigninHandler):
7 | """
8 | 城市签到
9 | """
10 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
11 | site_url = "hdcity.city"
12 |
13 | # 签到成功
14 | _success_text = '本次签到获得魅力'
15 | # 重复签到
16 | _repeat_text = '已签到'
17 |
18 | @classmethod
19 | def match(cls, url):
20 | """
21 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
22 | :param url: 站点Url
23 | :return: 是否匹配,如匹配则会调用该类的signin方法
24 | """
25 | return True if StringUtils.url_equal(url, cls.site_url) else False
26 |
27 | def signin(self, site_info: dict):
28 | """
29 | 执行签到操作
30 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
31 | :return: 签到结果信息
32 | """
33 | site = site_info.get("name")
34 | site_cookie = site_info.get("cookie")
35 | ua = site_info.get("ua")
36 | proxy = Config().get_proxies() if site_info.get("proxy") else None
37 |
38 | # 获取页面html
39 | html_res = RequestUtils(cookies=site_cookie,
40 | headers=ua,
41 | proxies=proxy
42 | ).get_res(url="https://hdcity.city/sign")
43 | if not html_res or html_res.status_code != 200:
44 | self.error(f"签到失败,请检查站点连通性")
45 | return False, f'【{site}】签到失败,请检查站点连通性'
46 |
47 | if "login" in html_res.text:
48 | self.error(f"签到失败,cookie失效")
49 | return False, f'【{site}】签到失败,cookie失效'
50 |
51 | # 判断是否已签到
52 | # '已连续签到278天,此次签到您获得了100魔力值奖励!'
53 | if self._success_text in html_res.text:
54 | self.info(f"签到成功")
55 | return True, f'【{site}】签到成功'
56 | if self._repeat_text in html_res.text:
57 | self.info(f"今日已签到")
58 | return True, f'【{site}】今日已签到'
59 | self.error(f"签到失败,签到接口返回 {html_res.text}")
60 | return False, f'【{site}】签到失败'
61 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/carpt.py:
--------------------------------------------------------------------------------
1 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
2 | from app.utils import StringUtils, RequestUtils
3 | from config import Config
4 |
5 |
6 | class CarPT(_ISiteSigninHandler):
7 | """
8 | 车站签到
9 | """
10 |
11 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
12 | site_url = "carpt.net"
13 |
14 | # 签到成功
15 | _success_text = "签到成功"
16 | _repeat_text = "请不要重复签到哦"
17 |
18 | @classmethod
19 | def match(cls, url):
20 | """
21 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
22 | :param url: 站点Url
23 | :return: 是否匹配,如匹配则会调用该类的signin方法
24 | """
25 | return True if StringUtils.url_equal(url, cls.site_url) else False
26 |
27 | def signin(self, site_info: dict):
28 | """
29 | 执行签到操作
30 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
31 | :return: 签到结果信息
32 | """
33 | site = site_info.get("name")
34 | site_cookie = site_info.get("cookie")
35 | ua = site_info.get("ua")
36 | proxy = Config().get_proxies() if site_info.get("proxy") else None
37 |
38 | # 获取页面html
39 | html_res = RequestUtils(cookies=site_cookie,
40 | headers=ua,
41 | proxies=proxy
42 | ).get_res(url="https://carpt.net/attendance.php")
43 | if not html_res or html_res.status_code != 200:
44 | self.error(f"签到失败,请检查站点连通性")
45 | return False, f'【{site}】签到失败,请检查站点连通性'
46 |
47 | if "login.php" in html_res.text:
48 | self.error(f"签到失败,cookie失效")
49 | return False, f'【{site}】签到失败,cookie失效'
50 |
51 | # 判断是否已签到
52 | # '已连续签到278天,此次签到您获得了100魔力值奖励!'
53 | if self._success_text in html_res.text:
54 | self.info(f"签到成功")
55 | return True, f'【{site}】签到成功'
56 | if self._repeat_text in html_res.text:
57 | self.info(f"今日已签到")
58 | return True, f'【{site}】今日已签到'
59 | self.error(f"签到失败,签到接口返回 {html_res.text}")
60 | return False, f'【{site}】签到失败'
61 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/hdtime.py:
--------------------------------------------------------------------------------
1 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
2 | from app.utils import StringUtils, RequestUtils
3 | from config import Config
4 |
5 |
6 | class HDTime(_ISiteSigninHandler):
7 | """
8 | 时光签到
9 | """
10 |
11 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
12 | site_url = "hdtime.org"
13 |
14 | # 签到成功
15 | _success_text = "签到成功"
16 | _repeat_text = "请不要重复签到哦"
17 |
18 | @classmethod
19 | def match(cls, url):
20 | """
21 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
22 | :param url: 站点Url
23 | :return: 是否匹配,如匹配则会调用该类的signin方法
24 | """
25 | return True if StringUtils.url_equal(url, cls.site_url) else False
26 |
27 | def signin(self, site_info: dict):
28 | """
29 | 执行签到操作
30 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
31 | :return: 签到结果信息
32 | """
33 | site = site_info.get("name")
34 | site_cookie = site_info.get("cookie")
35 | ua = site_info.get("ua")
36 | proxy = Config().get_proxies() if site_info.get("proxy") else None
37 |
38 | # 获取页面html
39 | html_res = RequestUtils(cookies=site_cookie,
40 | headers=ua,
41 | proxies=proxy
42 | ).get_res(url="https://hdtime.org/attendance.php")
43 | if not html_res or html_res.status_code != 200:
44 | self.error(f"签到失败,请检查站点连通性")
45 | return False, f'【{site}】签到失败,请检查站点连通性'
46 |
47 | if "login.php" in html_res.text:
48 | self.error(f"签到失败,cookie失效")
49 | return False, f'【{site}】签到失败,cookie失效'
50 |
51 | # 判断是否已签到
52 | # '已连续签到278天,此次签到您获得了100魔力值奖励!'
53 | if self._success_text in html_res.text:
54 | self.info(f"签到成功")
55 | return True, f'【{site}】签到成功'
56 | if self._repeat_text in html_res.text:
57 | self.info(f"今日已签到")
58 | return True, f'【{site}】今日已签到'
59 | self.error(f"签到失败,签到接口返回 {html_res.text}")
60 | return False, f'【{site}】签到失败'
61 |
--------------------------------------------------------------------------------
/app/plugins/modules/_autosignin/hdfans.py:
--------------------------------------------------------------------------------
1 | from app.plugins.modules._autosignin._base import _ISiteSigninHandler
2 | from app.utils import StringUtils, RequestUtils
3 | from config import Config
4 |
5 |
6 | class HDFans(_ISiteSigninHandler):
7 | """
8 | hdfans签到
9 | """
10 |
11 | # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url
12 | site_url = "hdfans.org"
13 |
14 | # 签到成功
15 | _success_text = "签到成功"
16 | _repeat_text = "请不要重复签到哦"
17 |
18 | @classmethod
19 | def match(cls, url):
20 | """
21 | 根据站点Url判断是否匹配当前站点签到类,大部分情况使用默认实现即可
22 | :param url: 站点Url
23 | :return: 是否匹配,如匹配则会调用该类的signin方法
24 | """
25 | return True if StringUtils.url_equal(url, cls.site_url) else False
26 |
27 | def signin(self, site_info: dict):
28 | """
29 | 执行签到操作
30 | :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息
31 | :return: 签到结果信息
32 | """
33 | site = site_info.get("name")
34 | site_cookie = site_info.get("cookie")
35 | ua = site_info.get("ua")
36 | proxy = Config().get_proxies() if site_info.get("proxy") else None
37 |
38 | # 获取页面html
39 | html_res = RequestUtils(cookies=site_cookie,
40 | headers=ua,
41 | proxies=proxy
42 | ).get_res(url="https://hdfans.org/attendance.php")
43 | if not html_res or html_res.status_code != 200:
44 | self.error(f"签到失败,请检查站点连通性")
45 | return False, f'【{site}】签到失败,请检查站点连通性'
46 |
47 | if "login.php" in html_res.text:
48 | self.error(f"签到失败,cookie失效")
49 | return False, f'【{site}】签到失败,cookie失效'
50 |
51 | # 判断是否已签到
52 | # '已连续签到278天,此次签到您获得了100魔力值奖励!'
53 | if self._success_text in html_res.text:
54 | self.info(f"签到成功")
55 | return True, f'【{site}】签到成功'
56 | if self._repeat_text in html_res.text:
57 | self.info(f"今日已签到")
58 | return True, f'【{site}】今日已签到'
59 | self.error(f"签到失败,签到接口返回 {html_res.text}")
60 | return False, f'【{site}】签到失败'
61 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/batch_spider_template.tmpl:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on {DATE}
4 | ---------
5 | @summary:
6 | ---------
7 | @author: {USER}
8 | """
9 |
10 | import feapder
11 | from feapder import ArgumentParser
12 |
13 |
14 | class ${spider_name}(feapder.BatchSpider):
15 | # 自定义数据库,若项目中有setting.py文件,此自定义可删除
16 | __custom_setting__ = dict(
17 | REDISDB_IP_PORTS="localhost:6379",
18 | REDISDB_USER_PASS="",
19 | REDISDB_DB=0,
20 | MYSQL_IP="localhost",
21 | MYSQL_PORT=3306,
22 | MYSQL_DB="",
23 | MYSQL_USER_NAME="",
24 | MYSQL_USER_PASS="",
25 | )
26 |
27 | def start_requests(self, task):
28 | yield feapder.Request("https://spidertools.cn")
29 |
30 | def parse(self, request, response):
31 | # 提取网站title
32 | print(response.xpath("//title/text()").extract_first())
33 | # 提取网站描述
34 | print(response.xpath("//meta[@name='description']/@content").extract_first())
35 | print("网站地址: ", response.url)
36 |
37 |
38 | if __name__ == "__main__":
39 | spider = ${spider_name}(
40 | redis_key="xxx:xxxx", # 分布式爬虫调度信息存储位置
41 | task_table="", # mysql中的任务表
42 | task_keys=["id", "xxx"], # 需要获取任务表里的字段名,可添加多个
43 | task_state="state", # mysql中任务状态字段
44 | batch_record_table="xxx_batch_record", # mysql中的批次记录表
45 | batch_name="xxx(周全)", # 批次名字
46 | batch_interval=7, # 批次周期 天为单位 若为小时 可写 1 / 24
47 | )
48 |
49 | parser = ArgumentParser(description="${spider_name}爬虫")
50 |
51 | parser.add_argument(
52 | "--start_master",
53 | action="store_true",
54 | help="添加任务",
55 | function=spider.start_monitor_task,
56 | )
57 | parser.add_argument(
58 | "--start_worker", action="store_true", help="启动爬虫", function=spider.start
59 | )
60 |
61 | parser.start()
62 |
63 | # 直接启动
64 | # spider.start() # 启动爬虫
65 | # spider.start_monitor_task() # 添加任务
66 |
67 | # 通过命令行启动
68 | # python ${file_name} --start_master # 添加任务
69 | # python ${file_name} --start_worker # 启动爬虫
70 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/dedup/README.md:
--------------------------------------------------------------------------------
1 | # Dedup
2 |
3 | Dedup是feapder大数据去重模块,内置3种去重机制,使用方式一致,可容纳的去重数据量与内存有关。不同于BloomFilter,去重受槽位数量影响,Dedup使用了弹性的去重机制,可容纳海量的数据去重。
4 |
5 |
6 | ## 去重方式
7 |
8 | ### 临时去重
9 |
10 | > 基于redis,支持批量,去重有时效性。去重一万条数据约0.26秒,一亿条数据占用内存约1.43G
11 |
12 | ```
13 | from feapder.dedup import Dedup
14 |
15 | data = {"xxx": 123, "xxxx": "xxxx"}
16 | datas = ["xxx", "bbb"]
17 |
18 | def test_ExpireFilter():
19 | dedup = Dedup(
20 | Dedup.ExpireFilter, expire_time=10, redis_url="redis://@localhost:6379/0"
21 | )
22 |
23 | # 逐条去重
24 | assert dedup.add(data) == 1
25 | assert dedup.get(data) == 1
26 |
27 | # 批量去重
28 | assert dedup.add(datas) == [1, 1]
29 | assert dedup.get(datas) == [1, 1]
30 | ```
31 |
32 |
33 | ### 内存去重
34 |
35 | > 基于内存,支持批量。去重一万条数据约0.5秒,一亿条数据占用内存约285MB
36 |
37 | ```
38 | from feapder.dedup import Dedup
39 |
40 | data = {"xxx": 123, "xxxx": "xxxx"}
41 | datas = ["xxx", "bbb"]
42 |
43 | def test_MemoryFilter():
44 | dedup = Dedup(Dedup.MemoryFilter) # 表名为test 历史数据3秒有效期
45 |
46 | # 逐条去重
47 | assert dedup.add(data) == 1
48 | assert dedup.get(data) == 1
49 |
50 | # 批量去重
51 | assert dedup.add(datas) == [1, 1]
52 | assert dedup.get(datas) == [1, 1]
53 | ```
54 |
55 | ### 永久去重
56 |
57 | > 基于redis,支持批量,永久去重。 去重一万条数据约3.5秒,一亿条数据占用内存约285MB
58 |
59 | from feapder.dedup import Dedup
60 |
61 | datas = {
62 | "xxx": xxx,
63 | "xxxx": "xxxx",
64 | }
65 |
66 | dedup = Dedup()
67 |
68 | print(dedup) #
69 | print(dedup.add(datas)) # 0 不存在
70 | print(dedup.get(datas)) # 1 存在
71 |
72 | ## 过滤数据
73 |
74 | Dedup可以通过如下方法,过滤掉已存在的数据
75 |
76 |
77 | ```python
78 | from feapder.dedup import Dedup
79 |
80 | def test_filter():
81 | dedup = Dedup(Dedup.BloomFilter, redis_url="redis://@localhost:6379/0")
82 |
83 | # 制造已存在数据
84 | datas = ["xxx", "bbb"]
85 | dedup.add(datas)
86 |
87 | # 过滤掉已存在数据 "xxx", "bbb"
88 | datas = ["xxx", "bbb", "ccc"]
89 | dedup.filter_exist_data(datas)
90 | assert datas == ["ccc"]
91 | ```
92 |
93 |
94 |
--------------------------------------------------------------------------------
/third_party/feapder/feapder/templates/project_template/main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on {DATE}
4 | ---------
5 | @summary: 爬虫入口
6 | ---------
7 | @author: {USER}
8 | """
9 |
10 | from feapder import ArgumentParser
11 |
12 | from spiders import *
13 |
14 | def crawl_xxx():
15 | """
16 | AirSpider爬虫
17 | """
18 | spider = xxx.XXXSpider()
19 | spider.start()
20 |
21 | def crawl_xxx():
22 | """
23 | Spider爬虫
24 | """
25 | spider = xxx.XXXSpider(redis_key="xxx:xxx")
26 | spider.start()
27 |
28 |
29 | def crawl_xxx(args):
30 | """
31 | BatchSpider爬虫
32 | """
33 | spider = xxx_spider.XXXSpider(
34 | task_table="", # mysql中的任务表
35 | batch_record_table="", # mysql中的批次记录表
36 | batch_name="xxx(周全)", # 批次名字
37 | batch_interval=7, # 批次时间 天为单位 若为小时 可写 1 / 24
38 | task_keys=["id", "xxx"], # 需要获取任务表里的字段名,可添加多个
39 | redis_key="xxx:xxxx", # redis中存放request等信息的根key
40 | task_state="state", # mysql中任务状态字段
41 | )
42 |
43 | if args == 1:
44 | spider.start_monitor_task()
45 | elif args == 2:
46 | spider.start()
47 | elif args == 3:
48 | spider.init_task()
49 |
50 |
51 | if __name__ == "__main__":
52 | parser = ArgumentParser(description="xxx爬虫")
53 |
54 | parser.add_argument(
55 | "--crawl_xxx", action="store_true", help="xxx爬虫", function=crawl_xxx
56 | )
57 | parser.add_argument(
58 | "--crawl_xxx", action="store_true", help="xxx爬虫", function=crawl_xxx
59 | )
60 | parser.add_argument(
61 | "--crawl_xxx",
62 | type=int,
63 | nargs=1,
64 | help="xxx爬虫",
65 | choices=[1, 2, 3],
66 | function=crawl_xxx,
67 | )
68 |
69 | parser.start()
70 |
71 | # main.py作为爬虫启动的统一入口,提供命令行的方式启动多个爬虫,若只有一个爬虫,可不编写main.py
72 | # 将上面的xxx修改为自己实际的爬虫名
73 | # 查看运行命令 python main.py --help
74 | # AirSpider与Spider爬虫运行方式 python main.py --crawl_xxx
75 | # BatchSpider运行方式
76 | # 1. 下发任务:python main.py --crawl_xxx 1
77 | # 2. 采集:python main.py --crawl_xxx 2
78 | # 3. 重置任务:python main.py --crawl_xxx 3
79 |
80 |
--------------------------------------------------------------------------------
/package/trayicon.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import webbrowser
4 |
5 | import wx
6 | import wx.adv
7 |
8 |
9 | class Balloon(wx.adv.TaskBarIcon):
10 | ICON = os.path.dirname(__file__).replace("package", "") + "nas-tools.ico"
11 |
12 | def __init__(self, homepage, log_path):
13 | wx.adv.TaskBarIcon.__init__(self)
14 | self.SetIcon(wx.Icon(self.ICON))
15 | self.Bind(wx.adv.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarLeftDClick)
16 | self.homepage = homepage
17 | self.log_path = log_path
18 |
19 | # Menu数据
20 | def setMenuItemData(self):
21 | return ("Log", self.Onlog), ("Close", self.OnClose)
22 |
23 | # 创建菜单
24 | def CreatePopupMenu(self):
25 | menu = wx.Menu()
26 | for itemName, itemHandler in self.setMenuItemData():
27 | if not itemName: # itemName为空就添加分隔符
28 | menu.AppendSeparator()
29 | continue
30 | menuItem = wx.MenuItem(None, wx.ID_ANY, text=itemName, kind=wx.ITEM_NORMAL) # 创建菜单项
31 | menu.Append(menuItem) # 将菜单项添加到菜单
32 | self.Bind(wx.EVT_MENU, itemHandler, menuItem)
33 | return menu
34 |
35 | def OnTaskBarLeftDClick(self, event):
36 | webbrowser.open(self.homepage)
37 |
38 | def Onlog(self, event):
39 | os.startfile(self.log_path)
40 |
41 | @staticmethod
42 | def OnClose(event):
43 | exe_name = os.path.basename(sys.executable)
44 | os.system('taskkill /F /IM ' + exe_name)
45 |
46 |
47 | class TrayIcon(wx.Frame):
48 | def __init__(self, homepage, log_path):
49 | app = wx.App()
50 | wx.Frame.__init__(self, None)
51 | self.taskBarIcon = Balloon(homepage, log_path)
52 | webbrowser.open(homepage)
53 | self.Hide()
54 | app.MainLoop()
55 |
56 |
57 | class NullWriter:
58 | softspace = 0
59 | encoding = 'UTF-8'
60 |
61 | def write(*args):
62 | pass
63 |
64 | def flush(*args):
65 | pass
66 |
67 | # Some packages are checking if stdout/stderr is available (e.g., youtube-dl). For details, see #1883.
68 | def isatty(self):
69 | return False
70 |
--------------------------------------------------------------------------------