diff --git a/README.md b/README.md index b210ea6..25f3206 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,15 @@ pip install --no-cache-dir -r requirements.txt -i https://pypi.mirrors.ustc.edu. ``` ![](https://raw.githubusercontent.com/xingsu1021/pthelper/master/static/screenshot/update.png) +# v2.2.0 说明 + +```shell + 1、新增RSS订阅功能。 + 2、新增RSS订阅后直接发送TR下载,(QB未测试但已集成)。 + 3、此版本不支持在线升级使用。 +``` +![](https://raw.githubusercontent.com/xingsu1021/pthelper/master/static/screenshot/rss.png) + # v2.1.0 说明 ```shell diff --git a/apps/common/sites_sign.py b/apps/common/sites_sign.py index 2016d56..91dff73 100644 --- a/apps/common/sites_sign.py +++ b/apps/common/sites_sign.py @@ -1170,7 +1170,7 @@ def opencd(site_name, site_name_cn, site_url, site_cookie): logger.info(data_ocr) #验证码不足6未跳过 - if len(data_ocr) < 6 : + if len(data_ocr) != 6 : continue data = {'imagehash': imagehash, @@ -1246,7 +1246,7 @@ def hdsky(site_name, site_name_cn, site_url, site_cookie): ) try: - session = requests.session() + #session = requests.session() #验证码签到执行3次验证 for i in range(3): @@ -1256,7 +1256,7 @@ def hdsky(site_name, site_name_cn, site_url, site_cookie): #print('i--------->',i) return False, "%s(%s) 错误:连续3次验证码失败" % (site_name,site_name_cn) - response = session.post(sign_url, headers=headers, data=data, timeout=10) + response = requests.post(sign_url, headers=headers, data=data, timeout=10) logger.info(response.text) if response.status_code == 200: result = json.loads(response.text) @@ -1264,7 +1264,7 @@ def hdsky(site_name, site_name_cn, site_url, site_cookie): code = result['code'] #拼接验证码图片 hdsky_image_url = 'https://hdsky.me/image.php?action=regimage&imagehash=' + code - response = session.get(hdsky_image_url, headers=headers, timeout=10) + response = requests.get(hdsky_image_url, headers=headers, timeout=10) with open(image_code_name, "wb") as fp: fp.write(response.content) @@ -1277,14 +1277,14 @@ def hdsky(site_name, site_name_cn, site_url, site_cookie): logger.info(data_ocr) #验证码不足6未跳过 - if len(data_ocr) < 6 : + if len(data_ocr) != 6 : continue data = {'action': 'showup', 'imagehash': code, 'imagestring': data_ocr } - response = session.post("https://hdsky.me/showup.php", headers=headers, data=data, timeout=10) + response = requests.post("https://hdsky.me/showup.php", headers=headers, data=data, timeout=10) logger.info(response.text) try: result = json.loads(response.text) diff --git a/apps/common/utils.py b/apps/common/utils.py index dcc3dc7..7ae89e8 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -121,8 +121,6 @@ def timesince(dt, since='', default="just now"): _STRPTIME_LOCK = threading.Lock() -_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT" -_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z" def now_date(): """当前时间""" @@ -155,6 +153,7 @@ def http_to_unixtime(time_string): HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。 """ + _GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT" return to_unixtime(time_string, _GMT_FORMAT) def cst_to_unixtime(time_string): @@ -166,6 +165,7 @@ def cst_to_unixtime(time_string): def iso8601_to_unixtime(time_string): """把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。""" + _ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z" return to_unixtime(time_string, _ISO8601_FORMAT) def str2datetime(st, s_format='%Y-%m-%d %H:%M:%S'): @@ -249,10 +249,7 @@ def capacity_convert(size, expect='auto', rate=1000): return expect_size, expect def filesizeformat(value, baseMB=False): - """Format the value like a 'human-readable' file size (i.e. 13 KB, - 4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega, - giga etc.), if the second parameter is set to `True` the binary - prefixes are (mebi, gibi). + """将字节转换为 1.00KB 1.00Mb 1.00gb """ try: bytes = float(value) @@ -271,16 +268,16 @@ def filesizeformat(value, baseMB=False): if bytes < base: ret = '%d Bytes' % (bytes) elif bytes < base * base: - ret = '%d KB' % (bytes / base) + ret = '%.2f KB' % (bytes / base) elif bytes < base * base * base: - ret = '%d MB' % (bytes / (base * base)) + ret = '%.2f MB' % (bytes / (base * base)) elif bytes < base * base * base * base: - if bytes % (base * base * base) == 0: - ret = '%d GB' % (bytes / (base * base * base)) + if bytes % (base * base * base) >= 0: + ret = '%.2f GB' % (bytes / (base * base * base)) else: - ret = "%d MB" % (bytes / (base * base)) + ret = "%.2f MB" % (bytes / (base * base)) else: - ret = '%.1f TB' % (bytes / (base * base * base * base)) + ret = '%.2f TB' % (bytes / (base * base * base * base)) return ret diff --git a/apps/cron/crontabs.py b/apps/cron/crontabs.py index 23151d1..36618fe 100644 --- a/apps/cron/crontabs.py +++ b/apps/cron/crontabs.py @@ -6,12 +6,18 @@ from urllib import parse import os import logging from bs4 import BeautifulSoup +import feedparser +from thefuzz import process +from transmission_rpc import Client +import qbittorrentapi -from common.utils import send_email,send_telegram,send_iyuu, send_enwechat +from common.utils import send_email,send_telegram,send_iyuu, send_enwechat, parseUrl from common.sites_sign import signIngress from sites.models import SiteConfig, SiteInfo from .models import Job, Log from notify.models import NotifyConfig +from rss.models import Rule, SeedInfo + from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from django_apscheduler.jobstores import DjangoJobStore, register_job @@ -96,6 +102,9 @@ def my_scheduler(crontab_id=None, crontab_status=None, hour="*", minute="*", job elif jobtype_id == 1003: #重签 scheduler.add_job(re_fail_sign, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id]) + elif jobtype_id == 1002: + #RSS订阅 + scheduler.add_job(rss, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id]) else: #禁用状态,直接删除任务 @@ -117,6 +126,8 @@ def my_scheduler(crontab_id=None, crontab_status=None, hour="*", minute="*", job scheduler.add_job(sign, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id]) elif jobtype_id == 1003: scheduler.add_job(re_fail_sign, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id]) + elif jobtype_id == 1002: + scheduler.add_job(rss, 'cron', hour=hour, minute=minute, id=crontab_id, args=[crontab_id]) return True @@ -338,4 +349,224 @@ def send_msg(crontab_id = None, send_data = []): logger.info("-------------enwechat-------------------") logger.info(msg) + return + +#========================================================================================================== +def rss(crontab_id): + """ + 执行订阅 + 参数: + crontab_id:执行任务的ID + """ + + headers = { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36', + #'cookie': site_cookie, + } + + #获取任务ID + job_id = Job.objects.get(crontab_id=crontab_id) + ormdata_rule = Rule.objects.get(job_id = job_id.id) + #订阅地址 + rss_url = ormdata_rule.config_id.url + #站点信息 + siteinfo_id = ormdata_rule.config_id.siteinfo_id + site_name = siteinfo_id.siteconfig_name + site_name_cn = siteinfo_id.siteconfig_name_cn + #关键字 + keyword = [x for x in ormdata_rule.keyword.split(',')] + #码率 + rate = ormdata_rule.rate + + logger.info("-------------开始RSS订阅任务%s(%s)-------------------" % (site_name, site_name_cn)) + + d = feedparser.parse(rss_url, request_headers=headers) + #未获取返回状态 + if 'status' not in d: + logger.error('请求RSS地址失败') + return + + #匹配RSS过滤后发送 + send_data = [] + if d.status == 200: + #entries 电影内容 + for i in d.entries: + #标题 [Movie][Back to 1942 2012 V2 2160p WEB-DL H265 AAC-LeagueWEB][一九四二/温故1942/1942 | 类型: 剧情/历史/战争/灾难 主演: 张国立/张默/徐帆 [国语/中英双语硬字] | *4K* *V2高码*][16.35 GB] + seed_name = i.title + #种子类型 Movie + seed_type = i.tags[0].term + #种子页地址 https://lemonhd.org/details_movie.php?id=327311 + seed_link = i.link + #'links': [{'rel': 'alternate', 'type': 'text/html', 'href': 'https://lemonhd.org/details_tv.php?id=328435'}, + # {'length': '2543569030', 'type': 'application/x-bittorrent', 'href': 'https://lemonhd.org/download.php?id=328435&passkey=c1c6b3d5291bf330ffa9ad2872d52144', + # 'rel': 'enclosure'} + # ] + #种子下载地址 + seed_download_url = i.links[1].href + #种子大小 字节数 + seed_length = i.links[1].length + #发布时间 Wed, 07 Sep 2022 14:27:09 +0800 + seed_published_time = i.published + _TIME_FORMAT = "%a, %d %b %Y %H:%M:%S %z" + #将字符串转换成datetime在格式化成需要的 2022-09-07 09:26:22 + seed_published_time = datetime.datetime.strptime(seed_published_time, _TIME_FORMAT).strftime('%Y-%m-%d %H:%M:%S') + #种子ID afe783da83684e92f5ff6a4c42fd11da216b77a2 + seed_hash_id = i.id + + seedinfo = None + #确认无重复,则添加 + num = SeedInfo.objects.filter(seed_hash_id=seed_hash_id).filter(siteinfo_id=siteinfo_id).count() + if num == 0: + if keyword[0] == '': + + #未添加关键字则全部加入 + if rate == 'all': + #码率为所有 + seedinfo = SeedInfo.objects.create(seed_name = seed_name, + seed_type = seed_type, + seed_details_link = seed_link, + seed_donwload_link = seed_download_url, + seed_file_size = seed_length, + seed_hash_id = seed_hash_id, + seed_published_time = seed_published_time, + siteinfo_id = siteinfo_id, + rule_id = ormdata_rule + ) + else: + if rate in seed_name: + seedinfo = SeedInfo.objects.create(seed_name = seed_name, + seed_type = seed_type, + seed_details_link = seed_link, + seed_donwload_link = seed_download_url, + seed_file_size = seed_length, + seed_hash_id = seed_hash_id, + seed_published_time = seed_published_time, + siteinfo_id = siteinfo_id, + rule_id = ormdata_rule + ) + #发送下载器 + seed_download(seedinfo.id) + #将电影名加入发送 + send_data.append(seed_name) + else: + #通过匹配关键字添加 + _name, num=process.extractOne(seed_name, keyword) + if num >= 50: + #匹配度大于50添加 + if rate == 'all': + #码率为所有 + seedinfo = SeedInfo.objects.create(seed_name = seed_name, + seed_type = seed_type, + seed_details_link = seed_link, + seed_donwload_link = seed_download_url, + seed_file_size = seed_length, + seed_hash_id = seed_hash_id, + seed_published_time = seed_published_time, + siteinfo_id = siteinfo_id, + rule_id = ormdata_rule + ) + else: + if rate in seed_name: + seedinfo = SeedInfo.objects.create(seed_name = seed_name, + seed_type = seed_type, + seed_details_link = seed_link, + seed_donwload_link = seed_download_url, + seed_file_size = seed_length, + seed_hash_id = seed_hash_id, + seed_published_time = seed_published_time, + siteinfo_id = siteinfo_id, + rule_id = ormdata_rule + ) + #发送下载器 + seed_download(seedinfo.id) + #将电影名加入发送 + send_data.append(seed_name) + #发送通知 + send_msg(crontab_id,send_data) + + return + +def seed_download(seedinfo_id): + """ + 下载种子 + 参数 + seedinfo_id rss订阅后存入数据库的id + """ + + ormdata_seedinfo = SeedInfo.objects.get(id = seedinfo_id) + #下载连接 + seed_donwload_link = ormdata_seedinfo.seed_donwload_link + #下载器类型 + download_type = ormdata_seedinfo.rule_id.tools_id.typed + download_username = ormdata_seedinfo.rule_id.tools_id.username + download_password = ormdata_seedinfo.rule_id.tools_id.password + download_url = ormdata_seedinfo.rule_id.tools_id.url + #下载目录 + download_dirname = ormdata_seedinfo.rule_id.tools_id.dirname + #加入下载器后是否立刻下载 + is_paused = ormdata_seedinfo.rule_id.is_paused + + if download_dirname == "": + download_dirname = None + + url_data = parseUrl(download_url) + + #保存加入下载器后返回的ID + torrent_id = None + if download_type == 'tr': + try: + + c = Client(protocol= url_data['protocol'], + host=url_data['host'], + port=url_data['port'], + username=download_username, + password=download_password, + timeout = 10 + ) + + torrent = c.add_torrent(seed_donwload_link, + timeout = 10, + download_dir = download_dirname, + #paused = is_paused, #立刻下载,需要下载器开启默认下载才有效 + ) + #获取种子id + torrent_id = torrent.id + #如果启动则下载 + if is_paused: + c.start_torrent(torrent_id) + + ormdata_seedinfo.seed_torrent_id = torrent_id + ormdata_seedinfo.seed_status = True + ormdata_seedinfo.save() + + except Exception as e: + print(e) + + elif download_type == 'qb': + try: + + c = qbittorrentapi.Client( + host=url_data['host'], + port=url_data['port'], + username=download_username, + password=download_password, + ) + + torrent = c.torrents_add(seed_donwload_link, + save_path=download_dirname, + #is_paused=is_paused, #立刻下载 + ) + #获取种子id + torrent_id = torrent.id + #如果启动则下载 + if is_paused: + c.start_torrent(torrent_id) + + ormdata_seedinfo.seed_torrent_id = torrent_id + ormdata_seedinfo.seed_status = True + ormdata_seedinfo.save() + + except Exception as e: + print(e) + return \ No newline at end of file diff --git a/apps/cron/views.py b/apps/cron/views.py index 7d03159..1ec2659 100644 --- a/apps/cron/views.py +++ b/apps/cron/views.py @@ -545,14 +545,14 @@ class SignJobView(LoginRequiredMixin,TemplateView): name = "签到" #未配置消息通知不予通过 - if len(notifys) == 0: - response_data={"code":0,"msg":"请配置消息通知!"} - return JsonResponse(response_data) + #if len(notifys) == 0: + #response_data={"code":0,"msg":"请配置消息通知!"} + #return JsonResponse(response_data) data_count = Job.objects.filter(jobtype_id=1000).count() if data_count == 0: ormdata = Job.objects.create(name=name, - crontab_status=crontab_status, + crontab_status=int(crontab_status), hour=hour, minute=minute, jobtype_id=jobtype_id, @@ -563,7 +563,7 @@ class SignJobView(LoginRequiredMixin,TemplateView): ) else: ormdata = Job.objects.get(id=_id) - ormdata.crontab_status = crontab_status + ormdata.crontab_status = int(crontab_status) ormdata.hour = hour ormdata.minute = minute ormdata.crontab_time = crontab_time diff --git a/apps/download/__init__.py b/apps/download/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/download/admin.py b/apps/download/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/download/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/download/apps.py b/apps/download/apps.py new file mode 100644 index 0000000..c72d845 --- /dev/null +++ b/apps/download/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DownloadConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'download' diff --git a/apps/download/migrations/0001_initial.py b/apps/download/migrations/0001_initial.py new file mode 100644 index 0000000..1c8c39d --- /dev/null +++ b/apps/download/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1 on 2022-09-08 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Tools', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30, verbose_name='下载器名称')), + ('typed', models.CharField(max_length=30, verbose_name='下载器类型tr,qb')), + ('url', models.URLField(blank=True, verbose_name='下载器地址')), + ('username', models.CharField(max_length=50, verbose_name='下载器用户名')), + ('password', models.CharField(max_length=128, verbose_name='下载器密码')), + ('dirname', models.CharField(max_length=150, null=True, verbose_name='默认下载目录')), + ], + ), + ] diff --git a/apps/download/migrations/0002_alter_tools_name.py b/apps/download/migrations/0002_alter_tools_name.py new file mode 100644 index 0000000..8c3548c --- /dev/null +++ b/apps/download/migrations/0002_alter_tools_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1 on 2022-09-08 17:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('download', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='tools', + name='name', + field=models.CharField(max_length=30, unique=True, verbose_name='下载器名称'), + ), + ] diff --git a/apps/download/migrations/__init__.py b/apps/download/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/download/models.py b/apps/download/models.py new file mode 100644 index 0000000..4d7debe --- /dev/null +++ b/apps/download/models.py @@ -0,0 +1,12 @@ +from django.db import models + +# Create your models here. + +class Tools(models.Model): + """下载器""" + name = models.CharField('下载器名称', max_length=30, unique=True) + typed = models.CharField('下载器类型tr,qb', max_length=30) + url = models.URLField('下载器地址', blank=True) + username = models.CharField('下载器用户名', max_length=50) + password = models.CharField('下载器密码', max_length=128) + dirname = models.CharField('默认下载目录', max_length=150, null=True) \ No newline at end of file diff --git a/apps/download/templates/download/tools.html b/apps/download/templates/download/tools.html new file mode 100644 index 0000000..e5e72b6 --- /dev/null +++ b/apps/download/templates/download/tools.html @@ -0,0 +1,235 @@ + + + + +
+
+
+ +
+
+ +
+ +
+
+ + + + +
+
+
+ +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/apps/download/templates/download/toolsadd.html b/apps/download/templates/download/toolsadd.html new file mode 100644 index 0000000..3ae615f --- /dev/null +++ b/apps/download/templates/download/toolsadd.html @@ -0,0 +1,112 @@ + +
{% csrf_token %} + +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + +
+          + + +
+
+ + \ No newline at end of file diff --git a/apps/download/templates/download/toolsedit.html b/apps/download/templates/download/toolsedit.html new file mode 100644 index 0000000..847bfa0 --- /dev/null +++ b/apps/download/templates/download/toolsedit.html @@ -0,0 +1,140 @@ + + + +
{% csrf_token %} + + +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ +
+
+ +
+          + + +
+
+ + \ No newline at end of file diff --git a/apps/download/tests.py b/apps/download/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/download/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/download/urls/__init__.py b/apps/download/urls/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/download/urls/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/download/urls/views_urls.py b/apps/download/urls/views_urls.py new file mode 100644 index 0000000..038e50f --- /dev/null +++ b/apps/download/urls/views_urls.py @@ -0,0 +1,21 @@ +# coding:utf-8 +from django.urls import path +from .. import views, views_request + +app_name = 'download' + +urlpatterns = [ + # Resource asset path + + #下载器配置 + + path('tools', views.ToolsListView.as_view()), + path('tools/add', views.ToolsAddView.as_view()), + path('tools/edit', views.ToolsEditView.as_view()), + path('tools/del', views.ToolsDelView.as_view()), + path('tools/list', views_request.tools), + path('tools/select/list', views_request.select_tools), + path('tools/test', views_request.tools_test), + +] + diff --git a/apps/download/views.py b/apps/download/views.py new file mode 100644 index 0000000..79f5ebb --- /dev/null +++ b/apps/download/views.py @@ -0,0 +1,165 @@ +from django.shortcuts import render +from django.views.generic import TemplateView +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import JsonResponse + +from .models import Tools + +# Create your views here. + +class ToolsListView(LoginRequiredMixin,TemplateView): + """ + 显示 + """ + template_name = 'download/tools.html' + + def get(self, request, *args, **kwargs): + """ + + """ + return super(ToolsListView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + # 'data': self.ormdata_game + } + kwargs.update(context) + return super(ToolsListView, self).get_context_data(**kwargs) + + +class ToolsAddView(LoginRequiredMixin,TemplateView): + """ + 添加 + """ + template_name = 'download/toolsadd.html' + + #显示添加模板 + def get(self, request, *args, **kwargs): + """ + """ + return super(ToolsAddView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + } + kwargs.update(context) + return super(ToolsAddView, self).get_context_data(**kwargs) + + + #数据提交接收方法 + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + name = request.POST.get("name").strip() + url = request.POST.get("url").strip() + typed = request.POST.get("typed").strip() + username = request.POST.get("username").strip() + password = request.POST.get("password").strip() + dirname = request.POST.get("dirname").strip() + + + ormdata = Tools.objects.create(name=name, + url=url, + typed=typed, + username=username, + password=password, + dirname=dirname + ) + + ormdata.save() + + response_data={"code":1,"msg":"添加成功"} + + + return JsonResponse(response_data) + +class ToolsEditView(LoginRequiredMixin,TemplateView): + """ + 编辑域名 + """ + template_name = 'download/toolsedit.html' + + #显示添加模板 + def get(self, request, *args, **kwargs): + """ + 得到 + """ + + #记录ID + self._id = request.GET.get('id') + self.ormdata = Tools.objects.get(id=self._id) + + return super(ToolsEditView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + 'id': self._id, + 'url': self.ormdata.url, + 'name': self.ormdata.name, + 'typed': self.ormdata.typed, + 'username': self.ormdata.username, + 'password': self.ormdata.password, + 'dirname': self.ormdata.dirname + } + kwargs.update(context) + return super(ToolsEditView, self).get_context_data(**kwargs) + + #数据提交接收方法 + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + url = request.POST.get("url").strip() + name = request.POST.get("name").strip() + typed = request.POST.get("typed").strip() + username = request.POST.get("username").strip() + password = request.POST.get("password").strip() + dirname = request.POST.get("dirname").strip() + + _id = request.POST.get('id') + + ormdata = Tools.objects.get(id=_id) + + ormdata.url = url + ormdata.name=name + ormdata.typed = typed + ormdata.username = username + ormdata.password = password + ormdata.dirname = dirname + + ormdata.save() + + response_data={"code":1,"msg":"添加成功"} + + return JsonResponse(response_data) + +class ToolsDelView(LoginRequiredMixin,TemplateView): + """ + 删除 + """ + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + + #得到批量或者单个要删除的id + ids = request.POST.getlist("ids[]") + #print("ids====>",ids) + + Tools.objects.filter(id__in=ids).delete() + + #Site.objects.filter(id=i).delete() + + response_data={"code":1,"msg":"操作成功"} + + + return JsonResponse(response_data) + +#================== \ No newline at end of file diff --git a/apps/download/views_request.py b/apps/download/views_request.py new file mode 100644 index 0000000..01dadf6 --- /dev/null +++ b/apps/download/views_request.py @@ -0,0 +1,158 @@ +#coding=utf-8 +from django.http import JsonResponse, StreamingHttpResponse,HttpResponse +from django.db.models import Q +from django.contrib.auth.decorators import login_required +from transmission_rpc import Client +import qbittorrentapi + +from .models import Tools +from common.utils import parseUrl + +#直接使用检查是否管理员 +#from django.contrib.auth.decorators import user_passes_test +#@user_passes_test(lambda u: u.is_superuser) + +@login_required +def tools(request): + """ + 提供相应的数据 + """ + + #得到偏移量 + pageIndex= int(request.GET.get('page',1)) + #得到查询个数 + pageSize = int(request.GET.get('limit',10)) + + #搜索字段 + searchKey = request.GET.get('searchKey','') + searchValue = request.GET.get('searchValue','') + #去掉空格 + searchValue = searchValue.strip() + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + if searchValue == '': + + data['count'] = Tools.objects.count() + if pageIndex == 1: + ormdata = Tools.objects.order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = Tools.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + else: + + data['count'] = Tools.objects.filter(Q(name__icontains=searchValue)|Q(typed__icontains=searchValue)).count() + if pageIndex == 1: + ormdata = Tools.objects.filter(Q(name__icontains=searchValue)|Q(typed__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = Tools.objects.filter(Q(name__icontains=searchValue)|Q(typed__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + + for i in ormdata: + + data['data'].append({"id":i.id, + "name":i.name, + "typed":i.typed, + "username":i.username, + "url":i.url, + "password":i.password, + "dirname":i.dirname, + }) + #返回json串 + #return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json") + return JsonResponse(data) + +#================== +@login_required +def tools_test(request): + """ + 测试下载器配置连通性 + """ + + url = request.POST.get("url").strip() + typed = request.POST.get("typed").strip() + username = request.POST.get("username").strip() + password = request.POST.get("password").strip() + + url_data = parseUrl(url) + + if typed == 'tr': + try: + + c = Client(protocol= url_data['protocol'], + host=url_data['host'], + port=url_data['port'], + username=username, + password=password, + timeout = 10 + ) + + response_data={"code":1,"msg": "连接成功" } + except: + response_data={"code":0,"msg": "连接失败" } + elif typed == 'qb': + + c = qbittorrentapi.Client( + host=url_data['host'], + port=url_data['port'], + username=username, + password=password, + ) + + try: + c.auth_log_in() + response_data={"code":1,"msg": "连接成功" } + except: + response_data={"code":0,"msg": "连接失败" } + + return JsonResponse(response_data) + +#================== +@login_required +def select_tools(request): + """ + RSS规则 + xmSelect + """ + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + #获取所有记录 + data['count'] = Tools.objects.count() + + ormdata = Tools.objects.order_by(order_by).all() + + + for i in ormdata: + + data['data'].append({ + "id":i.id, + "name":i.name + }) + + return JsonResponse(data) \ No newline at end of file diff --git a/apps/initdata/initDatas.py b/apps/initdata/initDatas.py index 8e61001..a62ba39 100644 --- a/apps/initdata/initDatas.py +++ b/apps/initdata/initDatas.py @@ -1,16 +1,17 @@ from myauth.models import User from django.contrib.auth.hashers import make_password -from sites.models import SiteConfig +from sites.models import SiteConfig, SiteInfo from cron.models import JobType from notify.models import MailType +from rss.models import FilmCategory, FilmType def init_datas(): """ 初始化数据库 """ print('开始初始化用户数据库...') - + if User.objects.count() == 0: #添加管理员 User.objects.create(name='admin', @@ -24,7 +25,7 @@ def init_datas(): print('初始化管理员成功.') else: print("管理员已经存在,忽略...") - + print('开始初始化站点配置...') sites_data = [{'name':'hdfans','name_cn':'红豆饭','index_url':'https://hdfans.org','sign_type':'general'}, {'name':'1ptba','name_cn':'壹PT吧','index_url':'https://1ptba.com','sign_type':'general'}, @@ -78,7 +79,7 @@ def init_datas(): {'name':'wintersakura','name_cn':'冬樱','index_url':'https://wintersakura.net','sign_type':'general'}, {'name':'hdpt','name_cn':'明教','index_url':'https://hdpt.xyz','sign_type':'general'}, ] - + for site in sites_data: get_site = SiteConfig.objects.filter(name=site['name']).count() if get_site == 0: @@ -91,18 +92,25 @@ def init_datas(): #else: ##更新站点配置 #SiteConfig.objects.filter(name=site['name']).update(sign_type = site['sign_type']) - + #print('站点 [%s] 更新成功' % site['name']) ##print('站点 [%s] 已经存在,忽略...' % site['name']) - + + print('开始修复站点信息中文名称...') + ormdata_siteinfo = SiteInfo.objects.filter(siteconfig_name_cn='') + for siteinfo in ormdata_siteinfo: + siteconfig_name_cn = list(SiteConfig.objects.filter(name=siteinfo.siteconfig_name).values_list('name_cn',flat=True)) + SiteInfo.objects.filter(siteconfig_name=siteinfo.siteconfig_name).update(siteconfig_name_cn = siteconfig_name_cn[0]) + + print('开始初始化任务类型...') jobtypes_data = [{'name':'签到','type_id':1000}, {'name':'辅种','type_id':1001}, - {'name':'刷流','type_id':1002}, + {'name':'RSS订阅','type_id':1002}, {'name':'签到重试','type_id':1003}, ] - + for jobtype in jobtypes_data: get_jobtype = JobType.objects.filter(name=jobtype['name']).count() if get_jobtype == 0: @@ -111,14 +119,16 @@ def init_datas(): ) print('添加任务类型 [%s] 成功' % jobtype['name']) else: + #将原刷流更新为RSS订阅 + JobType.objects.filter(type_id=1002).update(name = 'RSS订阅') print('任务类型 [%s] 已经存在,忽略...' % jobtype['name']) - + print('开始初始化通知邮箱类型...') mailtypes_data = [{'name':'QQ邮箱','alias_name':'qq','smtp_server':'smtp.qq.com','smtp_port':465}, - {'name':'新浪邮箱(非vip)','alias_name':'sina','smtp_server':'smtp.sina.com','smtp_port':465}, - {'name':'网易163邮箱','alias_name':'163','smtp_server':'smtp.163.com','smtp_port':465}, + {'name':'新浪邮箱(非vip)','alias_name':'sina','smtp_server':'smtp.sina.com','smtp_port':465}, + {'name':'网易163邮箱','alias_name':'163','smtp_server':'smtp.163.com','smtp_port':465}, ] - + for mailtype in mailtypes_data: get_mailtype = MailType.objects.filter(name=mailtype['name']).count() if get_mailtype == 0: @@ -130,6 +140,62 @@ def init_datas(): print('添加邮箱类型 [%s] 成功' % mailtype['name']) else: print('邮箱类型 [%s] 已经存在,忽略...' % mailtype['name']) - + + filmcategorys_data = [{'name_en':'Movies','name_cn':'电影'}, + {'name_en':'TV Series','name_cn':'电视剧'}, + {'name_en':'Docs','name_cn':'纪录片'}, + {'name_en':'Animations','name_cn':'动画片'}, + {'name_en':'TV Shows','name_cn':'综艺'}, + {'name_en':'Sports','name_cn':'体育'}, + {'name_en':'MV','name_cn':'音乐视频'}, + {'name_en':'Music','name_cn':'音乐'}, + {'name_en':'Others','name_cn':'其他'}, + ] + for film_category in filmcategorys_data: + get_film_category = FilmCategory.objects.filter(name_en=film_category['name_en']).count() + if get_film_category == 0: + FilmCategory.objects.create(name_en = film_category['name_en'], + name_cn = film_category['name_cn'] + ) + print('添加影视类别 [%s] 成功' % film_category['name_en']) + else: + print('影视类别 [%s] 已经存在,忽略...' % film_category['name_en']) + + filmtype_data = [{'name':'喜剧'}, + {'name':'爱情'}, + {'name':'动作'}, + {'name':'科幻'}, + {'name':'动画'}, + {'name':'悬疑'}, + {'name':'犯罪'}, + {'name':'惊悚'}, + {'name':'冒险'}, + {'name':'音乐'}, + {'name':'历史'}, + {'name':'奇幻'}, + {'name':'恐怖'}, + {'name':'战争'}, + {'name':'传记'}, + {'name':'歌舞'}, + {'name':'武侠'}, + {'name':'情色'}, + {'name':'灾难'}, + {'name':'西部'}, + {'name':'纪录片'}, + {'name':'短片'}, + {'name':'真人秀'}, + {'name':'脱口秀'}, + {'name':'古装'}, + {'name':'家庭'}, + {'name':'剧情'}, + ] + + for film_type in filmtype_data: + get_film_type = FilmType.objects.filter(name=film_type['name']).count() + if get_film_type == 0: + FilmType.objects.create(name = film_type['name']) + print('添加影视类型 [%s] 成功' % film_type['name']) + else: + print('影视类型 [%s] 已经存在,忽略...' % film_type['name']) + print('初始化成功.') - \ No newline at end of file diff --git a/apps/notify/views_request.py b/apps/notify/views_request.py index 639a79a..bf10ece 100644 --- a/apps/notify/views_request.py +++ b/apps/notify/views_request.py @@ -23,37 +23,37 @@ def iyuutest(request): """ _id = request.POST.get('id','') - - if _id == '': + iyuu_key = request.POST.get('iyuu_key').strip() + #if _id == '': - response_data={"code":0,"msg":"请先配置IYUU令牌或刷新页面"} + #response_data={"code":0,"msg":"请先配置IYUU令牌或刷新页面"} + #else: + + #ormdata = NotifyConfig.objects.get(id=_id) + #iyuu_key = ormdata.iyuu_key + + + user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36' + + now = datetime.datetime.now() + time = now.strftime("%Y-%m-%d %H:%M:%S") + headers = { + 'user_agent': user_agent, + 'Content-type': 'application/x-www-form-urlencoded' + } + + now_time = parse.quote('
') + parse.unquote(time) + parse.quote('

') + send_txts = parse.quote('
[测试]

') + api = 'https://iyuu.cn/' + iyuu_key + '.send' + sen_url = api + '?text='+ parse.quote('测试提示') + '&desp=' + now_time + send_txts + response = requests.get(sen_url, headers=headers ,verify=False) + + response_msg = json.loads(response.text) + if response.status_code == 200 and response_msg['errcode'] == 0: + response_data={"code":1,"msg":"发送测试成功"} else: - - ormdata = NotifyConfig.objects.get(id=_id) - iyuu_key = ormdata.iyuu_key - - - user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36' - - now = datetime.datetime.now() - time = now.strftime("%Y-%m-%d %H:%M:%S") - headers = { - 'user_agent': user_agent, - 'Content-type': 'application/x-www-form-urlencoded' - } - - now_time = parse.quote('
') + parse.unquote(time) + parse.quote('

') - send_txts = parse.quote('
[测试]

') - api = 'https://iyuu.cn/' + iyuu_key + '.send' - sen_url = api + '?text='+ parse.quote('测试提示') + '&desp=' + now_time + send_txts - response = requests.get(sen_url, headers=headers ,verify=False) - - response_msg = json.loads(response.text) - if response.status_code == 200 and response_msg['errcode'] == 0: - response_data={"code":1,"msg":"发送测试成功"} - else: - response_data={"code":0,"msg":"发送测试失败,%s" % response_msg['errmsg'] } + response_data={"code":0,"msg":"发送测试失败,%s" % response_msg['errmsg'] } return JsonResponse(response_data) diff --git a/apps/rss/__init__.py b/apps/rss/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/rss/admin.py b/apps/rss/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/rss/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/rss/apps.py b/apps/rss/apps.py new file mode 100644 index 0000000..c81cea6 --- /dev/null +++ b/apps/rss/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class RSSConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'rss' diff --git a/apps/rss/migrations/0001_initial.py b/apps/rss/migrations/0001_initial.py new file mode 100644 index 0000000..487a73a --- /dev/null +++ b/apps/rss/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 4.1 on 2022-09-08 09:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('sites', '0014_alter_siteinfo_siteconfig_name_cn'), + ] + + operations = [ + migrations.CreateModel( + name='FilmCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name_en', models.CharField(max_length=20, verbose_name='英文名')), + ('name_cn', models.CharField(max_length=20, verbose_name='中文名')), + ], + ), + migrations.CreateModel( + name='FilmType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=20, verbose_name='名称')), + ], + ), + migrations.CreateModel( + name='Config', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.URLField(blank=True, verbose_name='RSS地址')), + ('refresh_time', models.IntegerField(verbose_name='刷新间隔分钟')), + ('siteinfo_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteinfo', to='sites.siteinfo')), + ], + ), + ] diff --git a/apps/rss/migrations/0002_rule.py b/apps/rss/migrations/0002_rule.py new file mode 100644 index 0000000..6a3f0ab --- /dev/null +++ b/apps/rss/migrations/0002_rule.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1 on 2022-09-08 17:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('download', '0002_alter_tools_name'), + ('rss', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Rule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, verbose_name='规则名称')), + ('keyword', models.CharField(max_length=100, null=True, verbose_name='关键字,使用逗号分隔')), + ('config_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='config', to='rss.config')), + ('tools_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tools', to='download.tools')), + ], + ), + ] diff --git a/apps/rss/migrations/0003_rule_status_alter_rule_name.py b/apps/rss/migrations/0003_rule_status_alter_rule_name.py new file mode 100644 index 0000000..ade1a63 --- /dev/null +++ b/apps/rss/migrations/0003_rule_status_alter_rule_name.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1 on 2022-09-08 19:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0002_rule'), + ] + + operations = [ + migrations.AddField( + model_name='rule', + name='status', + field=models.BooleanField(default=False, verbose_name='状态启用,禁用'), + ), + migrations.AlterField( + model_name='rule', + name='name', + field=models.CharField(max_length=50, unique=True, verbose_name='规则名称'), + ), + ] diff --git a/apps/rss/migrations/0004_remove_config_refresh_time_rule_refresh_time.py b/apps/rss/migrations/0004_remove_config_refresh_time_rule_refresh_time.py new file mode 100644 index 0000000..ebb0a91 --- /dev/null +++ b/apps/rss/migrations/0004_remove_config_refresh_time_rule_refresh_time.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1 on 2022-09-09 13:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0003_rule_status_alter_rule_name'), + ] + + operations = [ + migrations.RemoveField( + model_name='config', + name='refresh_time', + ), + migrations.AddField( + model_name='rule', + name='refresh_time', + field=models.IntegerField(default=5, verbose_name='刷新间隔分钟'), + ), + ] diff --git a/apps/rss/migrations/0005_rule_crontab_id.py b/apps/rss/migrations/0005_rule_crontab_id.py new file mode 100644 index 0000000..cd9da28 --- /dev/null +++ b/apps/rss/migrations/0005_rule_crontab_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1 on 2022-09-09 14:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0004_remove_config_refresh_time_rule_refresh_time'), + ] + + operations = [ + migrations.AddField( + model_name='rule', + name='crontab_id', + field=models.CharField(max_length=64, null=True, verbose_name='执行ID'), + ), + ] diff --git a/apps/rss/migrations/0006_remove_rule_crontab_id_rule_job_id.py b/apps/rss/migrations/0006_remove_rule_crontab_id_rule_job_id.py new file mode 100644 index 0000000..8f65f40 --- /dev/null +++ b/apps/rss/migrations/0006_remove_rule_crontab_id_rule_job_id.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1 on 2022-09-09 17:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cron', '0013_alter_job_sites'), + ('rss', '0005_rule_crontab_id'), + ] + + operations = [ + migrations.RemoveField( + model_name='rule', + name='crontab_id', + ), + migrations.AddField( + model_name='rule', + name='job_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='job', to='cron.job'), + ), + ] diff --git a/apps/rss/migrations/0007_seedinfo.py b/apps/rss/migrations/0007_seedinfo.py new file mode 100644 index 0000000..d8ddfcc --- /dev/null +++ b/apps/rss/migrations/0007_seedinfo.py @@ -0,0 +1,33 @@ +# Generated by Django 4.0.3 on 2022-09-10 10:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0014_alter_siteinfo_siteconfig_name_cn'), + ('rss', '0006_remove_rule_crontab_id_rule_job_id'), + ] + + operations = [ + migrations.CreateModel( + name='SeedInfo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('seed_name', models.CharField(max_length=500, verbose_name='种子名称')), + ('seed_type', models.CharField(max_length=50, verbose_name='种子类型')), + ('seed_details_link', models.URLField(blank=True, verbose_name='种子详情地址')), + ('seed_published_time', models.DateField(verbose_name='发布时间')), + ('seed_hash_id', models.CharField(max_length=50, verbose_name='种子hash值')), + ('seed_donwload_link', models.URLField(blank=True, verbose_name='种子下载地址')), + ('seed_file_size', models.IntegerField(verbose_name='种子文件大小')), + ('seed_status', models.BooleanField(default=False, verbose_name='状态:下载,未下载')), + ('photos', models.URLField(blank=True, verbose_name='海报')), + ('douban_link', models.URLField(blank=True, verbose_name='豆瓣电影地址')), + ('imdb_link', models.URLField(blank=True, verbose_name='IMDB电影地址')), + ('siteinfo_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.siteinfo')), + ], + ), + ] diff --git a/apps/rss/migrations/0008_alter_config_url.py b/apps/rss/migrations/0008_alter_config_url.py new file mode 100644 index 0000000..2d0cc92 --- /dev/null +++ b/apps/rss/migrations/0008_alter_config_url.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 11:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0007_seedinfo'), + ] + + operations = [ + migrations.AlterField( + model_name='config', + name='url', + field=models.URLField(blank=True, max_length=350, verbose_name='RSS地址'), + ), + ] diff --git a/apps/rss/migrations/0009_alter_seedinfo_seed_published_time.py b/apps/rss/migrations/0009_alter_seedinfo_seed_published_time.py new file mode 100644 index 0000000..750d5c0 --- /dev/null +++ b/apps/rss/migrations/0009_alter_seedinfo_seed_published_time.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0008_alter_config_url'), + ] + + operations = [ + migrations.AlterField( + model_name='seedinfo', + name='seed_published_time', + field=models.DateTimeField(verbose_name='发布时间'), + ), + ] diff --git a/apps/rss/migrations/0010_alter_seedinfo_seed_file_size.py b/apps/rss/migrations/0010_alter_seedinfo_seed_file_size.py new file mode 100644 index 0000000..2784dc9 --- /dev/null +++ b/apps/rss/migrations/0010_alter_seedinfo_seed_file_size.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 11:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0009_alter_seedinfo_seed_published_time'), + ] + + operations = [ + migrations.AlterField( + model_name='seedinfo', + name='seed_file_size', + field=models.BigIntegerField(verbose_name='种子文件大小'), + ), + ] diff --git a/apps/rss/migrations/0011_alter_seedinfo_unique_together.py b/apps/rss/migrations/0011_alter_seedinfo_unique_together.py new file mode 100644 index 0000000..1abb8b8 --- /dev/null +++ b/apps/rss/migrations/0011_alter_seedinfo_unique_together.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 12:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0014_alter_siteinfo_siteconfig_name_cn'), + ('rss', '0010_alter_seedinfo_seed_file_size'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='seedinfo', + unique_together={('seed_hash_id', 'siteinfo_id')}, + ), + ] diff --git a/apps/rss/migrations/0012_seedinfo_rule_id.py b/apps/rss/migrations/0012_seedinfo_rule_id.py new file mode 100644 index 0000000..88e2509 --- /dev/null +++ b/apps/rss/migrations/0012_seedinfo_rule_id.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.3 on 2022-09-10 15:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0011_alter_seedinfo_unique_together'), + ] + + operations = [ + migrations.AddField( + model_name='seedinfo', + name='rule_id', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rss.rule'), + ), + ] diff --git a/apps/rss/migrations/0013_seedinfo_seed_torrent_id.py b/apps/rss/migrations/0013_seedinfo_seed_torrent_id.py new file mode 100644 index 0000000..858b798 --- /dev/null +++ b/apps/rss/migrations/0013_seedinfo_seed_torrent_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 16:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0012_seedinfo_rule_id'), + ] + + operations = [ + migrations.AddField( + model_name='seedinfo', + name='seed_torrent_id', + field=models.IntegerField(null=True, verbose_name='在下载器的标识ID'), + ), + ] diff --git a/apps/rss/migrations/0014_rule_rate.py b/apps/rss/migrations/0014_rule_rate.py new file mode 100644 index 0000000..b8c526b --- /dev/null +++ b/apps/rss/migrations/0014_rule_rate.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 20:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0013_seedinfo_seed_torrent_id'), + ] + + operations = [ + migrations.AddField( + model_name='rule', + name='rate', + field=models.CharField(max_length=10, null=True, verbose_name='码率:空,1080,2060'), + ), + ] diff --git a/apps/rss/migrations/0015_alter_rule_rate.py b/apps/rss/migrations/0015_alter_rule_rate.py new file mode 100644 index 0000000..09353d4 --- /dev/null +++ b/apps/rss/migrations/0015_alter_rule_rate.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 20:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0014_rule_rate'), + ] + + operations = [ + migrations.AlterField( + model_name='rule', + name='rate', + field=models.CharField(default='all', max_length=10, verbose_name='码率:all,1080,2060'), + ), + ] diff --git a/apps/rss/migrations/0016_rule_is_paused.py b/apps/rss/migrations/0016_rule_is_paused.py new file mode 100644 index 0000000..df443ad --- /dev/null +++ b/apps/rss/migrations/0016_rule_is_paused.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2022-09-10 21:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rss', '0015_alter_rule_rate'), + ] + + operations = [ + migrations.AddField( + model_name='rule', + name='is_paused', + field=models.BooleanField(default=False, verbose_name='是否立刻下载'), + ), + ] diff --git a/apps/rss/migrations/__init__.py b/apps/rss/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/rss/models.py b/apps/rss/models.py new file mode 100644 index 0000000..edbef1e --- /dev/null +++ b/apps/rss/models.py @@ -0,0 +1,74 @@ +from django.db import models +from sites.models import SiteInfo +from download.models import Tools +from cron.models import Job +# Create your models here. + +class FilmCategory(models.Model): + """影视类别""" + name_en = models.CharField('英文名', max_length=20) + name_cn = models.CharField('中文名', max_length=20) + +class FilmType(models.Model): + """影视类型""" + name = models.CharField('名称', max_length=20) + +#class FilmActor(models.Model): + #"""影视演员""" + #name = models.CharField('名称', max_length=50) + #name_en = models.CharField('英文名称', max_length=100) + #constellation = models.CharField('星座', max_length=10) + #gender = models.CharField('性别', max_length=2) + #birth = models.DateField('出生日期') + #birthplace = models.CharField('出生地', max_length=50) + #job = models.CharField('职业', max_length=100) + #description = models.TextField('简介') + #avatar = models.URLField('头像', blank=True) + +class Config(models.Model): + """RSS配置""" + url = models.URLField('RSS地址', blank=True, max_length=350) + #site_name = models.CharField('网站名简称,英文', max_length=50, unique=True) + #site_name_cn = models.CharField('网站名简称,中文', max_length=50, unique=True) + #on_update 和 on_delete 后面可以跟的词语有四个 + #1. no action 表示 不做任何操作, + #2. set null 表示在外键表中将相应字段设置为null + #3. set default 表示设置为默认值 + #4. cascade 表示级联操作,就是说,如果主键表中被参考字段更新,外键表中也更新,主键表中的记录被删除,外键表中改行也相应删除 + siteinfo_id = models.ForeignKey(SiteInfo, on_delete=models.CASCADE, related_name='siteinfo') + +class Rule(models.Model): + """RSS规则""" + name = models.CharField('规则名称', max_length=50, unique=True) + config_id = models.ForeignKey(Config, on_delete=models.CASCADE, related_name='config') + keyword = models.CharField('关键字,使用逗号分隔', max_length=100, null=True) + tools_id = models.ForeignKey(Tools, on_delete=models.CASCADE, related_name='tools') + status = models.BooleanField("状态启用,禁用",default=False) + refresh_time = models.IntegerField('刷新间隔分钟', default=5) + rate = models.CharField('码率:all,1080,2060', max_length=10, default='all') + is_paused = models.BooleanField("是否立刻下载",default=False) + + job_id = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='job', null=True) + +class SeedInfo(models.Model): + """种子信息""" + seed_name = models.CharField('种子名称', max_length=500) + seed_type = models.CharField('种子类型', max_length=50) + seed_details_link = models.URLField('种子详情地址', blank=True) + seed_published_time = models.DateTimeField('发布时间') + seed_hash_id = models.CharField('种子hash值', max_length=50) + seed_donwload_link = models.URLField('种子下载地址', blank=True) + seed_file_size = models.BigIntegerField('种子文件大小') + seed_status = models.BooleanField('状态:下载,未下载', default=False) + seed_torrent_id = models.IntegerField("在下载器的标识ID", null=True) + + photos = models.URLField('海报', blank=True) + douban_link = models.URLField('豆瓣电影地址', blank=True) + imdb_link = models.URLField('IMDB电影地址', blank=True) + + siteinfo_id = models.ForeignKey(SiteInfo, on_delete=models.CASCADE) + rule_id = models.ForeignKey(Rule, on_delete=models.CASCADE, null=True) + + class Meta: + unique_together=("seed_hash_id", "siteinfo_id") + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssconfig.html b/apps/rss/templates/rss/rssconfig.html new file mode 100644 index 0000000..21f231c --- /dev/null +++ b/apps/rss/templates/rss/rssconfig.html @@ -0,0 +1,203 @@ + +
+
+
+ +
+
+ +
+ +
+
+ + + + + 此处仅配置不运行,需定制规则启动 +
+
+
+ +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssconfigadd.html b/apps/rss/templates/rss/rssconfigadd.html new file mode 100644 index 0000000..c291fe0 --- /dev/null +++ b/apps/rss/templates/rss/rssconfigadd.html @@ -0,0 +1,87 @@ + +
{% csrf_token %} + +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ + +
+
+ + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssconfigedit.html b/apps/rss/templates/rss/rssconfigedit.html new file mode 100644 index 0000000..a285808 --- /dev/null +++ b/apps/rss/templates/rss/rssconfigedit.html @@ -0,0 +1,93 @@ + +
{% csrf_token %} + + +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ + +
+
+ + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssrule.html b/apps/rss/templates/rss/rssrule.html new file mode 100644 index 0000000..dca43e3 --- /dev/null +++ b/apps/rss/templates/rss/rssrule.html @@ -0,0 +1,241 @@ + +
+
+
+ +
+
+ +
+ +
+
+ + + + +
+
+
+ +
+ +
+
+
+ + + + + +{% verbatim myblock %} + + +{% endverbatim myblock %} + + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssruleadd.html b/apps/rss/templates/rss/rssruleadd.html new file mode 100644 index 0000000..5166190 --- /dev/null +++ b/apps/rss/templates/rss/rssruleadd.html @@ -0,0 +1,207 @@ + +
{% csrf_token %} + +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+ +   分钟 +
+
+ +
+ +
+ + +
+
+ +
+ +
+ +
+
+ +
+ + +
+ {{ notifys|safe }} +
+
+ +
+ + +
+
+ + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssruleedit.html b/apps/rss/templates/rss/rssruleedit.html new file mode 100644 index 0000000..adb3b04 --- /dev/null +++ b/apps/rss/templates/rss/rssruleedit.html @@ -0,0 +1,222 @@ + +
{% csrf_token %} + + + +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +   分钟 +
+
+ +
+ +
+ {% if is_paused %} + + + {% else %} + + + {% endif %} +
+
+ +
+ +
+
+
+
+ +
+ +
+ {% if status == 1 %} + + {% else %} + + {% endif %} +
+
+ +
+ + +
+ {{ notifys|safe }} +
+
+ +
+ + +
+
+ + \ No newline at end of file diff --git a/apps/rss/templates/rss/rssseedinfo.html b/apps/rss/templates/rss/rssseedinfo.html new file mode 100644 index 0000000..c293d60 --- /dev/null +++ b/apps/rss/templates/rss/rssseedinfo.html @@ -0,0 +1,217 @@ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+
+
+ +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/apps/rss/tests.py b/apps/rss/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/rss/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/rss/urls/__init__.py b/apps/rss/urls/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/rss/urls/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/rss/urls/views_urls.py b/apps/rss/urls/views_urls.py new file mode 100644 index 0000000..5360de3 --- /dev/null +++ b/apps/rss/urls/views_urls.py @@ -0,0 +1,32 @@ +# coding:utf-8 +from django.urls import path +from .. import views, views_request + +app_name = 'rss' + +urlpatterns = [ + # Resource asset path + + #RSS配置 + path('config', views.RssConfigListView.as_view()), + path('config/add', views.RssConfigAddView.as_view()), + path('config/edit', views.RssConfigEditView.as_view()), + path('config/del', views.RssConfigDelView.as_view()), + path('config/list', views_request.config), + path('config/select/list', views_request.select_rssconfig), + + #RSS规则 + path('rule', views.RssRuleListView.as_view()), + path('rule/add', views.RssRuleAddView.as_view()), + path('rule/edit', views.RssRuleEditView.as_view()), + path('rule/del', views.RssRuleDelView.as_view()), + path('rule/list', views_request.rule), + path('rule/checkbox/setstatus', views_request.checkbox_setRuleStatus), + + #RSS种子详情 + path('seedinfo', views.RssSeedInfoListView.as_view()), + path('seedinfo/del', views.RssSeedInfoDelView.as_view()), + path('seedinfo/list', views_request.seedinfo), + path('seedinfo/donwload', views_request.seed_download), +] + diff --git a/apps/rss/views.py b/apps/rss/views.py new file mode 100644 index 0000000..a44eb1b --- /dev/null +++ b/apps/rss/views.py @@ -0,0 +1,455 @@ +from django.shortcuts import render +from django.views.generic import TemplateView +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import JsonResponse +from django.db import transaction +import uuid + +from .models import Config, Rule, SeedInfo +from sites.models import SiteInfo +from download.models import Tools +from cron.crontabs import my_scheduler +from notify.models import NotifyConfig +from cron.models import Job + +# Create your views here. + +class RssConfigListView(LoginRequiredMixin,TemplateView): + """ + 显示 + """ + template_name = 'rss/rssconfig.html' + + def get(self, request, *args, **kwargs): + """ + + """ + return super(RssConfigListView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + # 'data': self.ormdata_game + } + kwargs.update(context) + return super(RssConfigListView, self).get_context_data(**kwargs) + + +class RssConfigAddView(LoginRequiredMixin,TemplateView): + """ + 添加 + """ + template_name = 'rss/rssconfigadd.html' + + #显示添加模板 + def get(self, request, *args, **kwargs): + """ + """ + return super(RssConfigAddView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + } + kwargs.update(context) + return super(RssConfigAddView, self).get_context_data(**kwargs) + + + #数据提交接收方法 + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + siteinfo_id = request.POST.get("siteinfo_id").strip() + url = request.POST.get("url").strip() + + ormdata_siteinfo = SiteInfo.objects.get(id=int(siteinfo_id)) + + ormdata = Config.objects.create(url=url, + siteinfo_id=ormdata_siteinfo + ) + + ormdata.save() + + response_data={"code":1,"msg":"添加成功"} + + + return JsonResponse(response_data) + +class RssConfigEditView(LoginRequiredMixin,TemplateView): + """ + 编辑域名 + """ + template_name = 'rss/rssconfigedit.html' + + #显示添加模板 + def get(self, request, *args, **kwargs): + """ + 得到 + """ + + #记录ID + self._id = request.GET.get('id') + self.ormdata = Config.objects.get(id=self._id) + + return super(RssConfigEditView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + 'id': self._id, + 'url': self.ormdata.url, + 'siteinfo_id': self.ormdata.siteinfo_id.id + } + kwargs.update(context) + return super(RssConfigEditView, self).get_context_data(**kwargs) + + #数据提交接收方法 + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + url = request.POST.get("url").strip() + siteinfo_id = request.POST.get("siteinfo_id").strip() + + _id = request.POST.get('id') + + ormdata_siteinfo = SiteInfo.objects.get(id=int(siteinfo_id)) + + ormdata = Config.objects.get(id=_id) + + ormdata.url = url + ormdata.siteinfo_id=ormdata_siteinfo + + ormdata.save() + + response_data={"code":1,"msg":"添加成功"} + + return JsonResponse(response_data) + +class RssConfigDelView(LoginRequiredMixin,TemplateView): + """ + 删除 + """ + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + + #得到批量或者单个要删除的id + ids = request.POST.getlist("ids[]") + #print("ids====>",ids) + + Config.objects.filter(id__in=ids).delete() + + #Site.objects.filter(id=i).delete() + + response_data={"code":1,"msg":"操作成功"} + + + return JsonResponse(response_data) + +#================== +class RssRuleListView(LoginRequiredMixin,TemplateView): + """ + 显示 + """ + template_name = 'rss/rssrule.html' + + def get(self, request, *args, **kwargs): + """ + + """ + return super(RssRuleListView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + # 'data': self.ormdata_game + } + kwargs.update(context) + return super(RssRuleListView, self).get_context_data(**kwargs) + + +class RssRuleAddView(LoginRequiredMixin,TemplateView): + """ + 添加 + """ + template_name = 'rss/rssruleadd.html' + + #显示添加模板 + def get(self, request, *args, **kwargs): + """ + """ + return super(RssRuleAddView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + html_notifys = [] + notify_type = {'iyuu':'IYUU','telegram':'Telegram','email':'邮箱','enwechat':'企业微信'} + + #获取通知配置 + notifys_count = NotifyConfig.objects.count() + if notifys_count == 0: + html_notifys.append('请先配置通知') + else: + for d in NotifyConfig.objects.all(): + html_notifys.append('') + + context = { + 'notifys':"".join(html_notifys), + } + kwargs.update(context) + return super(RssRuleAddView, self).get_context_data(**kwargs) + + #事务回滚 + @transaction.atomic + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + name = request.POST.get("name").strip() + config_id = request.POST.get("config_id").strip() + keyword = request.POST.get("keyword").strip() + tools_id = request.POST.get("tools_id").strip() + status = request.POST.get("status") + refresh_time = request.POST.get("refresh_time",'') + notifys = request.POST.getlist("notifys[]") + rate = request.POST.get("rate").strip() + is_paused = request.POST.get("rate") + + ormdata_config = Config.objects.get(id=int(config_id)) + ormdata_tools = Tools.objects.get(id=int(tools_id)) + #任务ID号 + crontab_id = str(uuid.uuid1()) + + if status == "on": + status = 1 + else: + status = 0 + + if refresh_time == '': + refresh_time = 5 + + #创建任务 + #每几分运行 + crontab_time = '*/%s * * * *' % refresh_time + + ormdata_job = Job.objects.create(name=name, + jobtype_id=1002, + crontab_time_type = "minute", + crontab_status = status, + minute = refresh_time, + notifys = ",".join(notifys), + crontab_time = crontab_time, + crontab_id = crontab_id, + + ) + ormdata_job.save() + + #创建订阅规则 + ormdata_rule = Rule.objects.create(name=name, + config_id=ormdata_config, + tools_id=ormdata_tools, + keyword=keyword, + status = status, + refresh_time = int(refresh_time), + job_id = ormdata_job, + rate = rate, + is_paused = int(is_paused) + ) + + ormdata_rule.save() + + + #开始进行订阅操作 + minute = '*/%s' % refresh_time + my_scheduler(crontab_id, status, minute=minute, jobtype_id=1002) + + response_data={"code":1,"msg":"添加成功"} + + + return JsonResponse(response_data) + +class RssRuleEditView(LoginRequiredMixin,TemplateView): + """ + 编辑域名 + """ + template_name = 'rss/rssruleedit.html' + + #显示添加模板 + def get(self, request, *args, **kwargs): + """ + 得到 + """ + + #记录ID + self._id = request.GET.get('id') + self.ormdata = Rule.objects.get(id=self._id) + + return super(RssRuleEditView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + html_notifys = [] + notify_type = {'iyuu':'IYUU','telegram':'Telegram','email':'邮箱','enwechat':'企业微信'} + + ormdata_job = Job.objects.get(id=self.ormdata.job_id.id) + + for d in NotifyConfig.objects.all(): + if d.name in ormdata_job.notifys: + html_notifys.append('') + else: + html_notifys.append('') + + context = { + 'id': self._id, + 'name': self.ormdata.name, + 'config_id': self.ormdata.config_id.id, + 'tools_id': self.ormdata.tools_id.id, + 'keyword': self.ormdata.keyword, + 'status': self.ormdata.status, + 'refresh_time': self.ormdata.refresh_time, + 'notifys':"".join(html_notifys), + 'crontab_id': ormdata_job.crontab_id, + 'rate': self.ormdata.rate, + 'is_paused':self.ormdata.is_paused + + } + kwargs.update(context) + return super(RssRuleEditView, self).get_context_data(**kwargs) + + #数据提交接收方法 + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + name = request.POST.get("name").strip() + config_id = request.POST.get("config_id").strip() + keyword = request.POST.get("keyword").strip() + tools_id = request.POST.get("tools_id").strip() + status = request.POST.get("status") + refresh_time = request.POST.get("refresh_time",'') + crontab_id = request.POST.get("crontab_id") + rate = request.POST.get("rate") + is_paused = request.POST.get("is_paused") + notifys = request.POST.getlist("notifys[]") + + _id = request.POST.get('id') + + ormdata_config = Config.objects.get(id=int(config_id)) + ormdata_tools = Tools.objects.get(id=int(tools_id)) + + if status == "on": + status = 1 + else: + status = 0 + + if refresh_time == '': + refresh_time = 5 + + ormdata = Rule.objects.get(id=_id) + + ormdata.name = name + ormdata.config_id=ormdata_config + ormdata.tools_id = ormdata_tools + ormdata.keyword = keyword + ormdata.status = status + ormdata.refresh_time = refresh_time + ormdata.rate = rate + ormdata.is_paused = int(is_paused) + + ormdata.save() + + crontab_time = '*/%s * * * *' % refresh_time + Job.objects.filter(crontab_id=crontab_id).update(notifys=",".join(notifys), + crontab_time = crontab_time, + minute = refresh_time + ) + + minute = '*/%s' % refresh_time + my_scheduler(crontab_id = crontab_id, crontab_status = status, minute=minute, jobtype_id=1002 ) + + response_data={"code":1,"msg":"添加成功"} + + return JsonResponse(response_data) + +class RssRuleDelView(LoginRequiredMixin,TemplateView): + """ + 删除 + """ + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + + #得到批量或者单个要删除的id + ids = request.POST.getlist("ids[]") + #print("ids====>",ids) + + #Rule.objects.filter(id__in=ids).delete() + + for i in ids: + _id = Rule.objects.get(id=i) + #删除任务 + Job.objects.filter(id=_id.job_id.id).delete() + #删除记录 + Rule.objects.filter(id=i).delete() + #删除计划任务 + my_scheduler(crontab_id = _id.job_id.crontab_id, action="del") + + response_data={"code":1,"msg":"操作成功"} + + + return JsonResponse(response_data) + +#================== +class RssSeedInfoListView(LoginRequiredMixin,TemplateView): + """ + 显示 + """ + template_name = 'rss/rssseedinfo.html' + + def get(self, request, *args, **kwargs): + """ + + """ + return super(RssSeedInfoListView, self).get(request, *args, **kwargs) + + #显示编辑模板 + def get_context_data(self, **kwargs): + + context = { + # 'data': self.ormdata_game + } + kwargs.update(context) + return super(RssSeedInfoListView, self).get_context_data(**kwargs) + + +class RssSeedInfoDelView(LoginRequiredMixin,TemplateView): + """ + 删除 + """ + def post(self, request, *args, **kwargs): + """ + 数据提交 + """ + + #得到批量或者单个要删除的id + ids = request.POST.getlist("ids[]") + #print("ids====>",ids) + + SeedInfo.objects.filter(id__in=ids).delete() + + response_data={"code":1,"msg":"操作成功"} + + + return JsonResponse(response_data) + +#================== \ No newline at end of file diff --git a/apps/rss/views_request.py b/apps/rss/views_request.py new file mode 100644 index 0000000..2fb02ff --- /dev/null +++ b/apps/rss/views_request.py @@ -0,0 +1,339 @@ +#coding=utf-8 +from django.http import JsonResponse, StreamingHttpResponse,HttpResponse +from django.db.models import Q +from django.contrib.auth.decorators import login_required +from transmission_rpc import Client +import qbittorrentapi + +from .models import Config, Rule, SeedInfo +from cron.crontabs import my_scheduler +from common.utils import filesizeformat, parseUrl + +#直接使用检查是否管理员 +#from django.contrib.auth.decorators import user_passes_test +#@user_passes_test(lambda u: u.is_superuser) + +@login_required +def config(request): + """ + 提供相应的数据 + """ + + #得到偏移量 + pageIndex= int(request.GET.get('page',1)) + #得到查询个数 + pageSize = int(request.GET.get('limit',10)) + + #搜索字段 + searchKey = request.GET.get('searchKey','') + searchValue = request.GET.get('searchValue','') + #去掉空格 + searchValue = searchValue.strip() + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + if searchValue == '': + + data['count'] = Config.objects.count() + if pageIndex == 1: + ormdata = Config.objects.order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = Config.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + else: + + data['count'] = Config.objects.filter(Q(name__icontains=searchValue)|Q(name_cn__icontains=searchValue)).count() + if pageIndex == 1: + ormdata = Config.objects.filter(Q(name__icontains=searchValue)|Q(name_cn__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = Config.objects.filter(Q(name__icontains=searchValue)|Q(name_cn__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + + for i in ormdata: + + data['data'].append({"id":i.id, + "site_name":i.siteinfo_id.siteconfig_name + '(' + i.siteinfo_id.siteconfig_name_cn + ')', + "url":i.url, + }) + #返回json串 + #return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json") + return JsonResponse(data) + +#=================================================================================================================== +@login_required +def rule(request): + """ + 提供相应的数据 + """ + + #得到偏移量 + pageIndex= int(request.GET.get('page',1)) + #得到查询个数 + pageSize = int(request.GET.get('limit',10)) + + #搜索字段 + searchKey = request.GET.get('searchKey','') + searchValue = request.GET.get('searchValue','') + #去掉空格 + searchValue = searchValue.strip() + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + if searchValue == '': + + data['count'] = Rule.objects.count() + if pageIndex == 1: + ormdata = Rule.objects.order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = Rule.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + else: + + data['count'] = Rule.objects.filter(Q(name__icontains=searchValue)).count() + if pageIndex == 1: + ormdata = Rule.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = Rule.objects.filter(Q(name__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + + for i in ormdata: + + data['data'].append({"id":i.id, + #"config_id":i.config_id.siteinfo_id.siteconfig_name_cn, + "config_id":i.config_id.siteinfo_id.siteconfig_name + '(' + i.config_id.siteinfo_id.siteconfig_name_cn + ')', + "tools_id":i.tools_id.name, + "name":i.name, + "keyword":i.keyword, + "status":i.status, + "refresh_time":i.refresh_time, + "rate":i.rate, + "is_paused":i.is_paused + }) + #返回json串 + #return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json") + return JsonResponse(data) + +#=================================================================================================================== +@login_required +def seedinfo(request): + """ + 提供相应的数据 + """ + + #得到偏移量 + pageIndex= int(request.GET.get('page',1)) + #得到查询个数 + pageSize = int(request.GET.get('limit',10)) + + #搜索字段 + searchKey = request.GET.get('searchKey','') + searchValue = request.GET.get('searchValue','') + #去掉空格 + searchValue = searchValue.strip() + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + if searchValue == '': + + data['count'] = SeedInfo.objects.count() + if pageIndex == 1: + ormdata = SeedInfo.objects.order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = SeedInfo.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + else: + + data['count'] = SeedInfo.objects.filter(Q(seed_name__icontains=searchValue)).count() + if pageIndex == 1: + ormdata = SeedInfo.objects.filter(Q(seed_name__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit + else: + ormdata = SeedInfo.objects.filter(Q(seed_name__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + + for i in ormdata: + + data['data'].append({"id":i.id, + "siteinfo_id":i.siteinfo_id.siteconfig_name + '(' + i.siteinfo_id.siteconfig_name_cn + ')', + "seed_name":i.seed_name, + "seed_type":i.seed_type, + "seed_published_time":i.seed_published_time, + "seed_file_size":filesizeformat(i.seed_file_size), + "seed_status":i.seed_status, + "seed_details_link":i.seed_details_link, + }) + #返回json串 + #return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json") + return JsonResponse(data) +#=================================================================================================================== +@login_required +def checkbox_setRuleStatus(request): + """ + 设置规则启用,禁用 + """ + + _id = request.GET.get('id') + value = request.GET.get('value') + tag = request.GET.get('tag') + + ormdata_rule = Rule.objects.get(id=_id) + + if tag == 'status': + #修改状态 + Rule.objects.filter(id=_id).update(status=int(value)) + if int(value) == 1: + #激活任务 + minute = '*/%s' % ormdata_rule.refresh_time + my_scheduler(crontab_id = ormdata_rule.job_id.crontab_id, crontab_status = True, minute=minute, jobtype_id=1002 ) + else: + #禁用任务 + my_scheduler(crontab_id = ormdata_rule.job_id.crontab_id, action="del" ) + + data = {'code':1,'msg':'操作成功'} + else: + data = {'code':0,'msg':'未知操作'} + + #返回json串 + #return HttpResponse(json.dumps(data,ensure_ascii = False), "application/json") + return JsonResponse(data) + +#================== +@login_required +def select_rssconfig(request): + """ + RSS订阅 + xmSelect + """ + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + #获取数据标志 1 返回所有 其它返回未配置站点 + #flag = request.GET.get('flag', 0) + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + #获取所有记录 + data['count'] = Config.objects.count() + + ormdata = Config.objects.order_by(order_by).all() + + for i in ormdata: + data['data'].append({ + "id":i.id, + "name":i.siteinfo_id.siteconfig_name + "(" + i.siteinfo_id.siteconfig_name_cn + ")" + }) + + return JsonResponse(data) + + +#================== +@login_required +def seed_download(request): + """ + 下载种子 + """ + #种子ID + seed_id = request.POST.get("id") + + ormdata_seedinfo = SeedInfo.objects.get(id = seed_id) + #下载连接 + seed_donwload_link = ormdata_seedinfo.seed_donwload_link + #下载器类型 + download_type = ormdata_seedinfo.rule_id.tools_id.typed + download_username = ormdata_seedinfo.rule_id.tools_id.username + download_password = ormdata_seedinfo.rule_id.tools_id.password + download_url = ormdata_seedinfo.rule_id.tools_id.url + #下载目录 + download_dirname = ormdata_seedinfo.rule_id.tools_id.dirname + + if download_dirname == "": + download_dirname = None + + url_data = parseUrl(download_url) + + if download_type == 'tr': + try: + + c = Client(protocol= url_data['protocol'], + host=url_data['host'], + port=url_data['port'], + username=download_username, + password=download_password, + timeout = 10 + ) + + torrent = c.add_torrent(seed_donwload_link, + timeout = 10, + download_dir = download_dirname + + ) + #获取种子id + torrent_id = torrent.id + + ormdata_seedinfo.seed_torrent_id = torrent_id + ormdata_seedinfo.seed_status = True + ormdata_seedinfo.save() + + response_data={"code":1,"msg": "添加成功" } + except Exception as e: + print(e) + response_data={"code":0,"msg": "添加失败" } + elif download_type == 'qb': + + c = qbittorrentapi.Client( + host=url_data['host'], + port=url_data['port'], + username=download_username, + password=download_password, + ) + + try: + c.auth_log_in() + response_data={"code":1,"msg": "添加成功" } + except: + response_data={"code":0,"msg": "添加失败" } + + return JsonResponse(response_data) \ No newline at end of file diff --git a/apps/sites/templates/sites/siteinfoadd.html b/apps/sites/templates/sites/siteinfoadd.html index 85aac1e..90703ce 100644 --- a/apps/sites/templates/sites/siteinfoadd.html +++ b/apps/sites/templates/sites/siteinfoadd.html @@ -61,7 +61,7 @@ /* 获取所有记录类型 */ var loadIndex = layer.load(2); - admin.req('sites/siteconfigname2siteinfo/list', function (res) { + admin.req('sites/siteconfigname/list', function (res) { layer.close(loadIndex); if (0 === res.code) { $.each(res.data, function (index, item) { diff --git a/apps/sites/templates/sites/siteinfoedit.html b/apps/sites/templates/sites/siteinfoedit.html index ea6081c..81e5441 100644 --- a/apps/sites/templates/sites/siteinfoedit.html +++ b/apps/sites/templates/sites/siteinfoedit.html @@ -60,7 +60,7 @@ /* 获取所有记录类型 */ var loadIndex = layer.load(2); - admin.req('sites/siteconfigname/list', function (res) { + admin.req('sites/siteconfigname/list?flag=1', function (res) { layer.close(loadIndex); if (0 === res.code) { $.each(res.data, function (index, item) { diff --git a/apps/sites/urls/views_urls.py b/apps/sites/urls/views_urls.py index a029a99..a27656d 100644 --- a/apps/sites/urls/views_urls.py +++ b/apps/sites/urls/views_urls.py @@ -14,7 +14,6 @@ urlpatterns = [ path('siteconfig/del', views.SiteConfigDelView.as_view()), path('siteconfig/list', views_request.siteconfig), path('siteconfigname/list', views_request.siteconfigname), - path('siteconfigname2siteinfo/list', views_request.siteconfigname2siteinfo), path('siteconfig/export', views_request.siteconfigExport), path('siteconfig/import', views_request.siteconfigImport), #站点升级配置 @@ -31,6 +30,7 @@ urlpatterns = [ path('siteinfo/list', views_request.siteinfo), path('siteinfo/export', views_request.siteinfoExport), path('siteinfo/import', views_request.siteinfoImport), + path('siteinfo/select/list', views_request.select_siteinfo), ] diff --git a/apps/sites/views.py b/apps/sites/views.py index a5712d7..bf2cf8f 100644 --- a/apps/sites/views.py +++ b/apps/sites/views.py @@ -370,9 +370,12 @@ class SiteInfoAddView(LoginRequiredMixin,TemplateView): cookie = request.POST.get("cookie").strip() passkey = request.POST.get("passkey").strip() + siteconfig_name_cn = list(SiteConfig.objects.filter(name=siteconfig_name).values_list('name_cn',flat=True)) + ormdata = SiteInfo.objects.create(siteconfig_name=siteconfig_name, cookie=cookie, passkey=passkey, + siteconfig_name_cn = siteconfig_name_cn[0] ) ormdata.save() @@ -423,12 +426,14 @@ class SiteInfoEditView(LoginRequiredMixin,TemplateView): _id = request.POST.get('id') + siteconfig_name_cn = list(SiteConfig.objects.filter(name=siteconfig_name).values_list('name_cn',flat=True)) + ormdata = SiteInfo.objects.get(id=_id) ormdata.siteconfig_name = siteconfig_name ormdata.cookie = cookie ormdata.passkey = passkey - + ormdata.siteconfig_name_cn = siteconfig_name_cn[0] ormdata.save() diff --git a/apps/sites/views_request.py b/apps/sites/views_request.py index 84698ad..ab147da 100644 --- a/apps/sites/views_request.py +++ b/apps/sites/views_request.py @@ -4,6 +4,7 @@ from django.db.models import Q from django.contrib.auth.decorators import login_required from .models import SiteConfig, SiteRank, SiteInfo +from rss.models import Config import simplejson as json import io from urllib import parse @@ -178,19 +179,19 @@ def siteinfo(request): ormdata = SiteInfo.objects.order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] else: - data['count'] = SiteInfo.objects.filter(Q(siteconfig_name__icontains=searchValue)).count() + data['count'] = SiteInfo.objects.filter(Q(siteconfig_name__icontains=searchValue)|Q(siteconfig_name_cn__icontains=searchValue)).count() if pageIndex == 1: - ormdata = SiteInfo.objects.filter(Q(siteconfig_name__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit + ormdata = SiteInfo.objects.filter(Q(siteconfig_name__icontains=searchValue)|Q(siteconfig_name_cn__icontains=searchValue)).order_by(order_by)[:pageSize] #offset:limit else: - ormdata = SiteInfo.objects.filter(Q(siteconfig_name__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] + ormdata = SiteInfo.objects.filter(Q(siteconfig_name__icontains=searchValue)|Q(siteconfig_name_cn__icontains=searchValue)).order_by(order_by)[pageSize*(pageIndex-1):pageIndex * pageSize] for i in ormdata: - siteconfig_ormdata = SiteConfig.objects.get(name=i.siteconfig_name) + #siteconfig_ormdata = SiteConfig.objects.get(name=i.siteconfig_name) data['data'].append({"id":i.id, "siteconfig_name":i.siteconfig_name, - 'siteconfig_name_cn':siteconfig_ormdata.name_cn, + 'siteconfig_name_cn':i.siteconfig_name_cn, "cookie":i.cookie, "passkey":i.passkey, }) @@ -392,6 +393,7 @@ def siteinfoImport(request): return JsonResponse(response_data) + #================== @login_required def siteconfigname(request): @@ -405,6 +407,9 @@ def siteconfigname(request): data['msg'] = "" data['data'] = [] + #获取数据标志 1 返回所有 其它返回未配置站点 + flag = request.GET.get('flag', 0) + #得到排序字段 sort = request.GET.get('sort','name') #得到排序规则 @@ -419,47 +424,12 @@ def siteconfigname(request): data['count'] = SiteConfig.objects.count() ormdata = SiteConfig.objects.order_by(order_by).all() - - - for i in ormdata: - - data['data'].append({ - "name":i.name, - "name_en_cn":i.name + "(" + i.name_cn + ")" - }) - - return JsonResponse(data) - -#================== -@login_required -def siteconfigname2siteinfo(request): - """ - 网站集合,去掉已经创建的站点名称 - xmSelect - """ - - data = {} - data['code'] = 0 - data['msg'] = "" - data['data'] = [] - - #得到排序字段 - sort = request.GET.get('sort','name') - #得到排序规则 - order_by_type = request.GET.get('order','') - if order_by_type == 'asc': - order_by = sort + if int(flag) == 0: + #获取siteinfo中配置的站点名 + ormdata_siteinfo = list(SiteInfo.objects.values_list('siteconfig_name', flat=True)) else: - order_by = '-' + sort - - #获取所有记录 - data['count'] = SiteConfig.objects.count() - - ormdata = SiteConfig.objects.order_by(order_by).all() - - #获取siteinfo中配置的站点名 - ormdata_siteinfo = list(SiteInfo.objects.values_list('siteconfig_name', flat=True)) + ormdata_siteinfo = [] for i in ormdata: #忽略已经配置的站点 @@ -469,4 +439,52 @@ def siteconfigname2siteinfo(request): "name_en_cn":i.name + "(" + i.name_cn + ")" }) + return JsonResponse(data) + + +#================== +@login_required +def select_siteinfo(request): + """ + RSS订阅 + xmSelect + """ + + data = {} + data['code'] = 0 + data['msg'] = "" + data['data'] = [] + + #获取数据标志 1 返回所有 其它返回未配置站点 + flag = request.GET.get('flag', 0) + + #得到排序字段 + sort = request.GET.get('sort','id') + #得到排序规则 + order_by_type = request.GET.get('order','') + + if order_by_type == 'asc': + order_by = sort + else: + order_by = '-' + sort + + #获取所有记录 + data['count'] = SiteInfo.objects.count() + + ormdata = SiteInfo.objects.order_by(order_by).all() + + if int(flag) == 0: + #获取RSS配置的站点ID + ormdata_config = list(Config.objects.values_list('siteinfo_id', flat=True)) + else: + ormdata_config = [] + + for i in ormdata: + #只返回未配置rss的站点 + if i.id not in ormdata_config: + data['data'].append({ + "id":i.id, + "name":i.siteconfig_name + "(" + i.siteconfig_name_cn + ")" + }) + return JsonResponse(data) \ No newline at end of file diff --git a/paddleocr/captcha_rec_model/inference.pdiparams b/paddleocr/captcha_rec_model/inference.pdiparams index 53bf535..426efec 100644 Binary files a/paddleocr/captcha_rec_model/inference.pdiparams and b/paddleocr/captcha_rec_model/inference.pdiparams differ diff --git a/paddleocr/captcha_rec_model/inference.pdmodel b/paddleocr/captcha_rec_model/inference.pdmodel index 9dadd74..763d147 100644 Binary files a/paddleocr/captcha_rec_model/inference.pdmodel and b/paddleocr/captcha_rec_model/inference.pdmodel differ diff --git a/requirements.txt b/requirements.txt index c04f110..27290d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,8 @@ thefuzz #thefuzz[speedup] #paddlepaddle paddleocr +transmission-rpc +qbittorrent-api #本地测试使用 #PyMySQL >= 1.0.2 #使用后台运行 diff --git a/static/json/menus.json b/static/json/menus.json index cc4b18b..2dae95d 100644 --- a/static/json/menus.json +++ b/static/json/menus.json @@ -49,6 +49,17 @@ } ] }, + { + "title": "下载工具管理", + "icon": "layui-icon layui-icon-set", + "path": "javascript:;", + "children": [ + { + "title": "下载器配置", + "path": "#/download/tools" + } + ] + }, { "title": "消息推送配置", "icon": "layui-icon layui-icon-set", @@ -72,6 +83,25 @@ } ] }, + { + "title": "RSS订阅管理", + "icon": "layui-icon layui-icon-set", + "path": "javascript:;", + "children": [ + { + "title": "RSS配置", + "path": "#/rss/config" + }, + { + "title": "RSS规则", + "path": "#/rss/rule" + }, + { + "title": "RSS种子详情", + "path": "#/rss/seedinfo" + } + ] + }, { "title": "任务管理", "icon": "layui-icon layui-icon-set", diff --git a/static/screenshot/rss.png b/static/screenshot/rss.png new file mode 100644 index 0000000..1003786 Binary files /dev/null and b/static/screenshot/rss.png differ