1.4.3 1.4.3

This commit is contained in:
t@123654
2025-07-15 23:40:36 +08:00
parent f42ca342c2
commit befdca66c4
19 changed files with 180 additions and 672 deletions
+2 -1
View File
@@ -48,4 +48,5 @@ uvicorn==0.33.0
webdriver-manager==4.0.2
websocket-client==1.8.0
wsproto==1.2.0
colorama==0.4.6
colorama==0.4.6
markdownify
+6 -3
View File
@@ -7,14 +7,14 @@
快速运行
```
docker run -d --name we-mp-rss -p 8001:8001 ghcr.io/rachelos/we-mp-rss:latest
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data ghcr.io/rachelos/we-mp-rss:latest
```
http://<您的ip>:8001/ 即可开启
# 官方镜像和代理镜像
```
docker run -d --name we-mp-rss -p 8001:8001 rachelos/we-mp-rss:latest
docker run -d --name we-mp-rss -p 8001:8001 docker.1ms.run/rachelos/we-mp-rss:latest
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data rachelos/we-mp-rss:latest
docker run -d --name we-mp-rss -p 8001:8001 -v ./data:/app/data docker.1ms.run/rachelos/we-mp-rss:latest docker.1ms.run/rachelos/we-mp-rss:latest
```
<br/>
@@ -148,6 +148,7 @@ API服务启动后,访问以下地址查看文档:
## ⚙️ 环境变量
[更多环境变量配置请查看`config.example.yaml`文件](config.example.yaml)
| 变量名 | 说明 | 默认值 |
| ------------------------ | ---------------------------------------------------------------------------- | --------------------------- |
| `DB` | **必填** 数据库地址 例如: mysql+pymysql://<用户名>:<密码>@<数据库IP>/<数据库名> | sqlite:///data/db.db |
@@ -156,6 +157,7 @@ API服务启动后,访问以下地址查看文档:
| `DINGDING_WEBHOOK` | 钉钉机器人Webhook地址 | - |
| `WECHAT_WEBHOOK` | 微信机器人Webhook地址 | - |
| `FEISHU_WEBHOOK` | 飞书机器人Webhook地址 | - |
| `CUSTOM_WEBHOOK` | 自定义Webhook地址 | - |
| `USER_AGENT` | 用户代理字符串 | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 |
| `PORT` | API服务端口 | 8001 |
| `DEBUG` | 调试模式 | False |
@@ -176,6 +178,7 @@ API服务启动后,访问以下地址查看文档:
| `GATHER.MODEL` | 采集模式(web模式可采集发布链接,api模式可采集临时链接) | web |
| `GATHER.CONTENT_AUTO_CHECK` | 是否自动检查未采集文章内容 | False |
| `GATHER.CONTENT_AUTO_INTERVAL` | 自动检查未采集文章内容的时间间隔(分钟) | 59 |
| `WEBHOOK.CONTENT_FORMAT` | 文章内容的发送格式(默认使用html格式,可选text、markdown) | html |
| `SAFE_HIDE_CONFIG` | 需要隐藏的配置信息(逗号分隔) | db,secret,token,notice.wechat,notice.feishu,notice.dingding |
| `LOG_FILE` | 日志文件路径,默认为空字符串,表示不输出到文件。如果要输出到文件,可以指定一个路径如:/var/log/we-mp-rss.log | - |
| `LOG_LEVEL` | 日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL) | INFO |
+6 -1
View File
@@ -15,9 +15,11 @@ server:
db: ${DB:-sqlite:///data/db.db}
#通知
notice:
#通知方式,可选dingding、wechat、feishu、custom
dingding: "${DINGDING_WEBHOOK}"
wechat: "${WECHAT_WEBHOOK}"
feishu: "${FEISHU_WEBHOOK}"
custom: "${CUSTOM_WEBHOOK}"
secret: ${SECRET_KEY:-we-mp-rss}
user_agent: ${USER_AGENT:-Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36/WeRss}
@@ -25,7 +27,10 @@ user_agent: ${USER_AGENT:-Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/
#定时任务执行每篇稿件间隔时间 单位秒 默认10s 允许值 1-60秒之间
interval: ${SPAN_INTERVAL:- 10}
webhook:
#文章内容的发送格式(默认使用html格式,可选text、markdown)
content_format: ${WEBHOOK.CONTENT_FORMAT:-html}
#API服务端口
port: ${PORT:-8001}
#调试模式
-642
View File
@@ -1,642 +0,0 @@
import re
from typing import Any, Dict, List, Union
# """
# 模板引擎使用示例
# 基础用法:
# 1. 简单变量替换: {{variable}}
# 2. 条件判断: {% if condition %}...{% endif %}
# 3. 循环结构: {% for item in items %}...{% endfor %}
# """
class TemplateParser:
"""A lightweight template engine supporting variables, conditions and loops."""
def __init__(self, template: str):
"""Initialize the template parser with a template string."""
self.template = template
self.compiled = None
self.custom_functions = {}
def register_function(self, name: str, func: callable) -> None:
"""
Register a custom function to be available in template expressions.
Args:
name: The name to use in templates
func: The function to register
"""
self.custom_functions[name] = func
def register_functions(self, functions: Dict[str, callable]) -> None:
"""
Register multiple custom functions at once.
Args:
functions: Dictionary of function names to functions
"""
self.custom_functions.update(functions)
def compile_template(self) -> None:
"""Compile the template into an intermediate representation."""
# Split template into static parts and control blocks
pattern = re.compile(
r'(\{\%.*?\%\})|' # control blocks {% ... %}
r'(\{\{.*?\}\})' # variables {{ ... }}
)
self.compiled = pattern.split(self.template)
def render(self, context: Dict[str, Any]) -> str:
"""
Render the template with the given context.
Args:
context: A dictionary containing variables for template rendering
Returns:
The rendered template as a string
"""
# Security check: validate context keys
for key in context.keys():
if not isinstance(key, str) or not key.isidentifier():
raise ValueError(f"Invalid context key: {key}. Keys must be valid Python identifiers")
if self.compiled is None:
print("Compiling template...")
self.compile_template()
output = []
i = 0
while i < len(self.compiled):
part = self.compiled[i]
if part is None:
i += 1
continue
# Handle variables {{ var }} and nested {{ var.attr }} and eval expressions
if part.startswith('{{') and part.endswith('}}'):
# print(f"\nProcessing variable part: {part}")
var_expr = part[2:-2].strip()
# print(f"Extracted expression: {var_expr}")
# Check if this is an eval expression (starts with =)
if var_expr.startswith('='):
try:
# Evaluate the expression (after =)
expr = var_expr[1:]
if not self._is_safe_expression(expr):
raise ValueError("Potentially dangerous expression detected")
# Create safe evaluation environment
safe_globals = self._get_safe_globals()
eval_globals = {**safe_globals, **self.custom_functions}
result = eval(expr, eval_globals, context)
output.append(str(result))
except Exception as e:
output.append(f'[Error: {str(e)}]')
elif '.' in var_expr:
# print(f"DEBUG - Processing nested variable: {var_expr}") # Debug
# Handle nested attribute access
parts = var_expr.split('.')
current = context.get(parts[0], {})
for part_name in parts[1:]:
if isinstance(current, dict):
current = current.get(part_name, '')
else:
current = getattr(current, part_name, '')
if current is None:
current = ''
break
output.append(str(current))
else:
# Simple variable access
output.append(str(context.get(var_expr, '')))
i += 1
# Handle control blocks {% ... %}
elif part.startswith('{%') and part.endswith('%}'):
block = part[2:-2].strip()
# Handle if condition
if block.startswith('if '):
condition = block[3:].strip()
result, updated_context = self._evaluate_condition(condition, context)
# Merge all variables except special ones and functions
for k, v in updated_context.items():
if not k.startswith('__') and k not in self.custom_functions:
# Only update context if the key doesn't exist or was modified
if k not in context or context[k] != v:
context[k] = v
# Ensure final_price is available in context if it was calculated
if 'final_price' in updated_context:
context['final_price'] = updated_context['final_price']
# Find matching endif using helper method
endif_idx = self._skip_control_block(i, 'if', 'endif')
if endif_idx == len(self.compiled):
i += 1
continue
# Find else if exists
else_idx = -1
for j in range(i+1, endif_idx):
part = self.compiled[j]
if isinstance(part, str) and part.strip() in ('{% else %}', 'else'):
else_idx = j
break
# print(f"DEBUG - Control block boundaries: else={else_idx}, endif={endif_idx}")
# Process the appropriate block
if result:
# Process if block (from current position to else or endif)
end_idx = else_idx if else_idx != -1 else endif_idx
if_content = self.compiled[i+1:end_idx]
# print(f"DEBUG - Processing if block from {i+1} to {end_idx}")
if_parser = TemplateParser('')
if_parser.compiled = if_content
rendered = if_parser.render(context)
output.append(rendered)
elif else_idx != -1:
# Process else block
else_content = self.compiled[else_idx+1:endif_idx]
# print(f"DEBUG - Processing else block from {else_idx+1} to {endif_idx}")
else_parser = TemplateParser('')
else_parser.compiled = else_content
rendered = else_parser.render(context)
output.append(rendered)
# Skip to after endif
i = endif_idx + 1
# Handle for loop
elif block.startswith('for ') and ' in ' in block:
loop_var, iterable = self._parse_for_block(block)
items = self._get_iterable(iterable, context)
# Collect loop content
loop_content = []
j = i + 1
while j < len(self.compiled):
inner_part = self.compiled[j]
if (isinstance(inner_part, str) and
inner_part.startswith('{% endfor %}')):
break
loop_content.append(str(inner_part) if inner_part else '')
j += 1
# Render loop
# print(f"DEBUG - For loop items: {items}") # Debug
loop_output = []
total_items = len(items)
for item_idx, item in enumerate(items):
loop_context = context.copy()
loop_context[loop_var] = item
# Add loop variable with iteration info
loop_context['loop'] = {
'index': item_idx + 1,
'index0': item_idx,
'first': item_idx == 0,
'last': item_idx == total_items - 1,
'length': total_items
}
# Render loop content with current item
item_output = []
j = 0
while j < len(loop_content):
part = loop_content[j]
if part is None:
j += 1
continue
# Handle if conditions inside for loop
if (isinstance(part, str) and
part.startswith('{% if ') and
part.endswith('%}')):
condition = part[6:-2].strip()
result, _ = self._evaluate_condition(condition, loop_context)
# Find matching endif
endif_idx = j + 1
nested_depth = 1
while endif_idx < len(loop_content):
inner_part = loop_content[endif_idx]
if (isinstance(inner_part, str) and
inner_part.startswith('{% if ') and
inner_part.endswith('%}')):
nested_depth += 1
elif (isinstance(inner_part, str) and
inner_part.startswith('{% endif %}')):
nested_depth -= 1
if nested_depth == 0:
break
endif_idx += 1
# Process if block if condition is true
if result:
if_content = loop_content[j+1:endif_idx]
rendered = self._render_parts(if_content, loop_context)
item_output.append(rendered)
# Skip to after endif
j = endif_idx + 1
# Handle variable references
elif isinstance(part, str) and part.startswith('{{') and part.endswith('}}'):
var_expr = part[2:-2].strip()
# print(f"DEBUG - Evaluating variable: {var_expr}") # Debug
if var_expr.startswith('='):
# Handle eval expressions
try:
expr = var_expr[1:]
if not self._is_safe_expression(expr):
raise ValueError("Potentially dangerous expression detected")
safe_globals = self._get_safe_globals()
eval_globals = {**safe_globals, **self.custom_functions}
result = eval(expr, eval_globals, loop_context)
value = str(result)
except Exception as e:
value = f'[Error: {str(e)}]'
elif '.' in var_expr:
# Handle nested attributes
parts = var_expr.split('.')
current = loop_context.get(parts[0], {})
for part_name in parts[1:]:
if isinstance(current, dict):
current = current.get(part_name, '')
else:
current = getattr(current, part_name, '')
if current is None:
current = ''
break
value = str(current)
else:
# Handle simple variable
value = str(loop_context.get(var_expr, ''))
# print(f"DEBUG - Variable value: {value}") # Debug
item_output.append(value)
j += 1
else:
# Handle literal text (preserve whitespace and newlines)
item_output.append(str(part))
j += 1
rendered_item = ''.join(item_output)
# print(f"DEBUG - Rendered item {item_idx}:\n{repr(rendered_item)}") # Debug
loop_output.append(rendered_item)
if loop_output:
# Join all loop items with newlines and add to output
loop_result = '\n'.join(loop_output)
output.append(loop_result)
else:
# print("DEBUG - No loop output generated")
pass
# Skip to end of loop
i = j + 1
# Handle endif/endfor
elif block in ('endif', 'endfor'):
i += 1
else:
i += 1
# Static text
else:
output.append(str(part) if part else '')
i += 1
# Clean up the output by removing excessive newlines
result = ''.join(output)
return self._clean_output(result)
def _get_safe_globals(self) -> Dict[str, Any]:
"""Return a dictionary of safe builtins for eval/exec."""
safe_builtins = {
'None': None,
'True': True,
'False': False,
'bool': bool,
'int': int,
'float': float,
'str': str,
'list': list,
'dict': dict,
'tuple': tuple,
'len': len,
'sum': sum,
'min': min,
'max': max,
'abs': abs,
'round': round
}
return safe_builtins
def _is_safe_expression(self, expr: str) -> bool:
"""Check if an expression contains potentially dangerous operations."""
forbidden = [
'import', 'open', 'exec', 'eval', 'system', 'subprocess',
'__import__', 'getattr', 'setattr', 'delattr', 'compile',
'globals', 'locals', 'vars', 'dir', 'help', 'reload',
'input', 'file', 'execfile', 'reload', 'exit', 'quit'
]
expr_lower = expr.lower()
return not any(keyword in expr_lower for keyword in forbidden)
def _evaluate_condition(self, condition: str, context: Dict[str, Any]) -> tuple:
"""
Evaluate a condition expression or code block in the given context.
Returns (result, updated_context) where updated_context contains any new variables
created during evaluation.
"""
try:
if not self._is_safe_expression(condition):
raise ValueError(f"Potentially dangerous expression: {condition}")
# Special handling for loop variables
if 'loop.' in condition:
# Handle not conditions
has_not = 'not ' in condition
loop_var = condition.split('loop.')[-1].strip()
if has_not:
loop_var = loop_var.replace('not ', '').strip()
loop_info = context.get('loop', {})
result = False
if loop_var == 'last':
result = loop_info.get('last', False)
elif loop_var == 'first':
result = loop_info.get('first', False)
elif loop_var == 'index':
result = bool(loop_info.get('index', 0))
elif loop_var == 'index0':
result = bool(loop_info.get('index0', 0))
# Invert result if 'not' was present
return (not result if has_not else result), context
# Create safe evaluation environment
safe_globals = self._get_safe_globals()
eval_globals = {**safe_globals, **self.custom_functions}
# Make a copy of context to avoid modifying the original
local_vars = context.copy()
# Handle multi-line code blocks
if '\n' in condition.strip():
# Compile and execute the code block in restricted environment
code = compile(condition, '<string>', 'exec')
exec(code, eval_globals, local_vars)
# The last expression's value should be in __result__
result = bool(local_vars.get('__result__', False))
# Return result and updated context (excluding special vars)
updated_context = {k: v for k, v in local_vars.items()
if not k.startswith('__') and k not in self.custom_functions}
# Debug output
print(f"DEBUG - Condition evaluation result: {result}")
print(f"DEBUG - Local vars after execution: {local_vars.keys()}")
print(f"DEBUG - Updated context to return: {updated_context.keys()}")
# Ensure all calculated variables are included
for k, v in local_vars.items():
if (not k.startswith('__') and
k not in self.custom_functions and
k not in updated_context):
updated_context[k] = v
print(f"DEBUG - Added {k} to context: {v}")
return result, updated_context
# Handle function calls with = prefix
if condition.startswith('='):
result = bool(eval(condition[1:], eval_globals, local_vars))
return result, local_vars
# Handle nested attribute access (e.g. user.is_admin)
if '.' in condition:
parts = condition.split('.')
current = local_vars.get(parts[0], {})
for part in parts[1:]:
if isinstance(current, dict):
current = current.get(part, None)
else:
current = getattr(current, part, None)
if current is None:
return False, local_vars
# Handle empty collections
if isinstance(current, (list, dict, set)) and not current:
return False, local_vars
return bool(current), local_vars
# Handle direct variable reference
if condition in local_vars:
value = local_vars[condition]
if isinstance(value, (list, dict, set)):
return len(value) > 0, local_vars
return bool(value), local_vars
# Evaluate other expressions
result = bool(eval(condition, eval_globals, local_vars))
return result, local_vars
except Exception:
return False, context
def _skip_control_block(self, start_idx: int, start_tag: str, end_tag: str) -> int:
"""Skip a control block until matching end tag is found."""
if start_idx >= len(self.compiled):
return len(self.compiled)
depth = 1
i = start_idx + 1
# print(f"DEBUG - Searching for {end_tag} starting from {start_idx}")
while i < len(self.compiled):
part = self.compiled[i]
if isinstance(part, str) and part.startswith('{%') and part.endswith('%}'):
block = part[2:-2].strip()
# print(f"DEBUG - Token {i}: {block} (depth={depth})")
# Handle nested blocks
if block.startswith('if ') or block.startswith('for '):
depth += 1
# print(f"DEBUG - Found nested block, depth increased to {depth}")
elif block == end_tag:
depth -= 1
# print(f"DEBUG - Found {end_tag}, depth decreased to {depth}")
if depth == 0:
# print(f"DEBUG - Found matching {end_tag} at {i}")
return i
elif block == 'else' and depth == 1:
# print(f"DEBUG - Found else at {i}")
# Don't decrease depth for else blocks
pass
elif block in ['endif', 'endfor'] and depth > 1:
depth -= 1
# print(f"DEBUG - Found closing tag in nested block, depth decreased to {depth}")
i += 1
# print(f"DEBUG - Error: Reached end without finding matching {end_tag} (current depth: {depth})")
# print(f"DEBUG - Last processed block: {self.compiled[i-1] if i > 0 else 'None'}")
return len(self.compiled)
def _clean_output(self, output: str) -> str:
"""Clean up the final output by removing excessive newlines and whitespace."""
lines = output.split('\n')
cleaned = []
prev_line_empty = False
for line in lines:
stripped = line.strip()
# Skip empty lines between list items
if not stripped and cleaned and cleaned[-1].strip().startswith('-'):
continue
# Skip consecutive empty lines
if not stripped and prev_line_empty:
continue
cleaned.append(line)
prev_line_empty = not stripped
# Ensure exactly one newline at end
return '\n'.join(cleaned).strip()
def _parse_for_block(self, block: str) -> tuple:
"""Parse a for block into loop variable and iterable parts."""
parts = block[4:].split(' in ', 1)
return parts[0].strip(), parts[1].strip()
def _get_iterable(self, iterable: str, context: Dict[str, Any]) -> List[Any]:
"""Get an iterable from context or evaluate expression."""
if iterable in context:
return context[iterable]
try:
if not self._is_safe_expression(iterable):
raise ValueError("Potentially dangerous expression detected")
safe_globals = self._get_safe_globals()
return eval(iterable, safe_globals, context)
except Exception:
return []
def _render_parts(self, parts: List[Union[str, None]], context: Dict[str, Any]) -> str:
"""Render a list of template parts with the given context."""
temp_parser = TemplateParser('')
temp_parser.compiled = parts
return temp_parser.render(context)
# Example usage
if __name__ == '__main__':
template = """
<html>
<body>
<h1>Hello {{ name }}!</h1>
{% if show_details %}
<div class="details">
<p>Your details:</p>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</body>
</html>
"""
context = {
'name': 'World',
'show_details': True,
'items': ['Item 1', 'Item 2', 'Item 3']
}
parser = TemplateParser(template)
result = parser.render(context)
print(result)
# 示例代码 - 自定义函数功能
print("\n=== 自定义函数示例 ===")
# 创建使用自定义函数的模板
func_template = """
{{= greet(name) }}
{{= calculate(10, 20) }}
{{= format_date(now) }}
{% if
# 多行代码块示例
user = context.get('user')
premium = user.get('membership') == 'premium'
active = user.get('is_active', False)
__result__ = premium and active
%}
<p>Welcome premium user {{ user.name }}!</p>
{% else %}
<p>Welcome standard user {{ user.name }}!</p>
{% endif %}
{% if
# 带计算的代码块示例
total = calculate(10, 20)
discount = 0.2 if user.get('membership') == 'premium' else 0.1
final_price = total * (1 - discount)
__result__ = final_price > 15
%}
<p>Special discount applied! Final price: {{ final_price }}</p>
{% endif %}
"""
# 定义自定义函数
def greet(name):
return f"Hello, {name}!"
def calculate(x, y):
return x + y
def format_date(dt):
return dt.strftime("%Y-%m-%d")
def is_premium_user(user):
return user.get('membership') == 'premium'
# 创建解析器并注册函数
func_parser = TemplateParser(func_template)
func_parser.register_function('greet', greet)
func_parser.register_function('calculate', calculate)
func_parser.register_function('format_date', format_date)
func_parser.register_function('is_premium_user', is_premium_user)
# 准备上下文
from datetime import datetime
func_context = {
'name': 'Function User',
'now': datetime.now(),
'user': {
'name': 'test_user',
'membership': 'premium' # 测试 premium 用户
}
}
# 渲染并打印结果
func_result = func_parser.render(func_context)
print(func_result)
+1 -1
View File
@@ -1,4 +1,4 @@
from .base import Base,Column,String,Integer,DateTime,Text,MEDIUMTEXT
from .base import Base,Column,String,Integer,DateTime,Text
class Article(Base):
__tablename__ = 'articles'
id = Column(String(255), primary_key=True)
+6 -2
View File
@@ -1,9 +1,13 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, Column, Integer, String, DateTime,Date,ForeignKey,Boolean,Text,Enum,Table
from sqlalchemy import create_engine, Column, Integer, String, DateTime,Date,ForeignKey,Boolean,Enum,Table
from sqlalchemy import inspect
from sqlalchemy.dialects.mysql import MEDIUMTEXT,LONGTEXT,LONGBLOB
from sqlalchemy.exc import SQLAlchemyError
from core.config import cfg
if cfg.get("db","sqlite").startswith("sqlite"):
from sqlalchemy import Text
else:
from sqlalchemy.dialects.mysql import MEDIUMTEXT as Text
class DataStatus():
DELETED:int = 1000
+7 -1
View File
@@ -1,6 +1,7 @@
from .wechat import send_wechat_message
from .dingtalk import send_dingtalk_message
from .feishu import send_feishu_message
from .custom import send_custom_message
def notice( webhook_url, title, text,notice_type: str=None):
"""
@@ -19,8 +20,11 @@ def notice( webhook_url, title, text,notice_type: str=None):
notice_type = 'wechat'
elif 'oapi.dingtalk.com' in webhook_url:
notice_type = 'dingtalk'
elif 'open.feishu.cn' in webhook_url:
# 兼容企业本地化部署的飞书,如open.feishu.xxxx.com
elif 'open.feishu.' in webhook_url:
notice_type = 'feishu'
else:
notice_type = 'custom'
if notice_type == 'wechat':
send_wechat_message(webhook_url, title, text)
@@ -28,5 +32,7 @@ def notice( webhook_url, title, text,notice_type: str=None):
send_dingtalk_message(webhook_url, title, text)
elif notice_type == 'feishu':
send_feishu_message(webhook_url, title, text)
elif notice_type == 'custom':
send_custom_message(webhook_url, title, text)
else:
print('不支持的通知类型')
+27
View File
@@ -0,0 +1,27 @@
import requests
import json
def send_custom_message(webhook_url, title, text):
"""
发送微信消息
参数:
- webhook_url: 自定义Webhook地址
- title: 消息标题
- text: 消息内容
"""
headers = {'Content-Type': 'application/json'}
data = {
"title": title,
"content": text
}
try:
response = requests.post(
url=webhook_url,
headers=headers,
data=json.dumps(data)
)
print(response.text)
except Exception as e:
print('自定义webhook通知发送失败', e)
+20
View File
@@ -1,4 +1,5 @@
import threading
import random
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from typing import Callable, Any, Optional
@@ -82,6 +83,25 @@ class TaskScheduler:
logger.error(error_msg)
raise ValueError(error_msg)
# 处理随机时间范围
def parse_random_field(field: str, field_name: str):
if '~' in field:
try:
min_val, max_val = map(int, field.split('~'))
if min_val > max_val:
raise ValueError(f"Invalid {field_name} range: {field}")
return lambda: str(random.randint(min_val, max_val))
except ValueError as e:
raise ValueError(f"Invalid {field_name} format: {field}") from e
return field
second = parse_random_field(second, 'second')
minute = parse_random_field(minute, 'minute')
hour = parse_random_field(hour, 'hour')
day = parse_random_field(day, 'day')
month = parse_random_field(month, 'month')
day_of_week = parse_random_field(day_of_week, 'day_of_week')
trigger = CronTrigger(
second=second,
minute=minute,
+3 -2
View File
@@ -1,4 +1,3 @@
from core.config import cfg
from driver.success import Success
import time
@@ -14,7 +13,9 @@ def sys_notice(text:str="",title:str=""):
wechat_webhook = cfg.get('notice')['wechat']
if len(wechat_webhook)>0:
notice(wechat_webhook, title, markdown_text)
custom_webhook = cfg.get('notice')['custom']
if len(custom_webhook)>0:
notice(custom_webhook, title, markdown_text)
from driver.wx import WX_API
def send_wx_code(title:str="",url:str=""):
if cfg.get("server.send_code",False):
+91 -10
View File
@@ -7,6 +7,9 @@ from dataclasses import dataclass
from core.lax import TemplateParser
from datetime import datetime
from core.log import logger
from core.config import cfg
from bs4 import BeautifulSoup
import re
@dataclass
class MessageWebHook:
task: MessageTask
@@ -61,21 +64,99 @@ def call_webhook(hook: MessageWebHook) -> str:
ValueError: 当webhook调用失败时抛出
"""
template = hook.task.message_template if hook.task.message_template else """{
"articles": [
{% for article in articles %}
{{article}}{% if not loop.last %},{% endif %}{% endfor %}]
{% endfor %}
]
}"""
parser = TemplateParser(template)
"feed": {
"id": "{{ feed.id }}",
"name": "{{ feed.mp_name }}"
},
"articles": [
{% if articles %}
{% for article in articles %}
{
"id": "{{ article.id }}",
"mp_id": "{{ article.mp_id }}",
"title": "{{ article.title }}",
"pic_url": "{{ article.pic_url }}",
"url": "{{ article.url }}",
"description": "{{ article.description }}",
"publish_time": "{{ article.publish_time }}"
}{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %}
],
"task": {
"id": "{{ task.id }}",
"name": "{{ task.name }}"
},
"now": "{{ now }}"
}
"""
# 检查template是否需要content
template_needs_content = "content" in template.lower()
# 根据content_format处理内容
content_format = cfg.get("webhook.content_format", "html")
logger.info(f'Content将以{content_format}格式发送')
processed_articles = []
for article in hook.articles:
if isinstance(article, dict) and "content" in article and article["content"]:
processed_article = article.copy()
# 只有template需要content时才进行格式转换
if template_needs_content:
if content_format == 'text':
# 去除HTML标签,保留纯文本
soup = BeautifulSoup(processed_article['content'], 'html.parser')
text = soup.get_text().strip()
processed_article['content'] = re.sub(r'\n\s*\n', '\n\n', text)
elif content_format == 'markdown':
from markdownify import markdownify
# 转换HTML到Markdown
processed_article['content'] = markdownify(
processed_article['content'],
heading_style="ATX",
bullets='-*+',
code_language='python'
)
# 替换多个连续换行符为单个换行符
processed_article['content'] = re.sub(r'\n\s*\n', '\n\n', processed_article['content'])
processed_articles.append(processed_article)
else:
processed_articles.append(article)
data = {
"feed": hook.feed,
"articles": hook.articles,
"articles": processed_articles,
"task": hook.task,
'now': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
"now": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
payload = parser.render(data)
# 预处理content字段
import json
def process_content(content):
if content is None:
return ""
# 进行JSON转义处理引号
json_escaped = json.dumps(content, ensure_ascii=False)
# 去掉外层引号避免重复
return json_escaped[1:-1]
# 处理articles中的content字段,进行JSON转义
if "articles" in data:
for i, article in enumerate(data["articles"]):
if isinstance(article, dict):
if "content" in article:
data["articles"][i]["content"] = process_content(article["content"])
elif hasattr(article, "content"):
setattr(data["articles"][i], "content", process_content(getattr(article, "content")))
parser = TemplateParser(template)
payload = parser.render(data)
# logger.info(payload)
# 检查web_hook_url是否为空
if not hook.task.web_hook_url:
logger.error("web_hook_url为空")
+1 -1
View File
@@ -48,4 +48,4 @@ uvicorn==0.33.0
webdriver-manager==4.0.2
websocket-client==1.8.0
wsproto==1.2.0
markdownify
@@ -1 +1 @@
import{h as w,_ as D,d as E,r as I,o as N,c as p,w as u,a as s,b as m,e as t,f as o,t as l,g as S}from"./index.cdda9ff7.js";const M=async()=>await w.get("/wx/sys/info");const P=E({__name:"SysInfo",setup(F){const n=I({os:{name:"",version:"",release:""},python_version:"",uptime:0,system:{node:"",machine:"",processor:""},api_version:"/api/v1/wx",core_version:"",latest_version:"",need_update:!0,wx:{token:"",expiry_time:""},queue:{is_running:!1,pending_tasks:0}}),f=d=>{const e=Math.floor(d/86400),r=Math.floor(d%86400/3600),a=Math.floor(d%3600/60);return`${e}\u5929 ${r}\u5C0F\u65F6 ${a}\u5206\u949F`},C=()=>{window.open("https://github.com/rachelos/we-mp-rss","_blank")};return N(async()=>{n.value=await M()}),(d,e)=>{const r=s("desktop-outlined"),a=s("a-descriptions-item"),_=s("code-outlined"),v=s("clock-circle-outlined"),b=s("deployment-unit-outlined"),i=s("api-outlined"),y=s("appstore-outlined"),c=s("cloud-download-outlined"),B=s("a-button"),x=s("a-descriptions"),g=s("a-card"),k=s("a-page-header");return m(),p(k,{title:"\u7CFB\u7EDF\u4FE1\u606F","sub-title":`\u7248\u672C: ${n.value.version}`},{default:u(()=>[t(g,{bordered:!1,class:"sys-info-card"},{default:u(()=>[t(x,{bordered:"",column:{xs:1,sm:1,md:1,lg:2}},{default:u(()=>[t(a,{label:"\u64CD\u4F5C\u7CFB\u7EDF"},{label:u(()=>[t(r),e[0]||(e[0]=o(" \u64CD\u4F5C\u7CFB\u7EDF "))]),default:u(()=>[o(" "+l(n.value.os.name),1)]),_:1}),t(a,{label:"\u7CFB\u7EDF\u7248\u672C"},{label:u(()=>[t(_),e[1]||(e[1]=o(" \u7CFB\u7EDF\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.os.version)+" ("+l(n.value.os.release)+") ",1)]),_:1}),t(a,{label:"Python\u7248\u672C"},{label:u(()=>[t(_),e[2]||(e[2]=o(" Python\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.python_version),1)]),_:1}),t(a,{label:"\u8FD0\u884C\u65F6\u95F4"},{label:u(()=>[t(v),e[3]||(e[3]=o(" \u8FD0\u884C\u65F6\u95F4 "))]),default:u(()=>[o(" "+l(f(n.value.uptime)),1)]),_:1}),t(a,{label:"\u7CFB\u7EDF\u67B6\u6784"},{label:u(()=>[t(b),e[4]||(e[4]=o(" \u7CFB\u7EDF\u67B6\u6784 "))]),default:u(()=>[o(" "+l(n.value.system.node)+" / "+l(n.value.system.machine)+" ("+l(n.value.system.processor)+") ",1)]),_:1}),t(a,{label:"TOKEN"},{label:u(()=>[t(i),e[5]||(e[5]=o(" TOKEN "))]),default:u(()=>[o(" "+l(n.value.wx.token),1)]),_:1}),t(a,{label:"\u8FC7\u671F\u65F6\u95F4"},{label:u(()=>[t(i),e[6]||(e[6]=o(" \u8FC7\u671F\u65F6\u95F4 "))]),default:u(()=>[o(" "+l(n.value.wx.expiry_time),1)]),_:1}),t(a,{label:"API\u7248\u672C"},{label:u(()=>[t(i),e[7]||(e[7]=o(" API\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.api_version),1)]),_:1}),t(a,{label:"\u961F\u5217\u72B6\u6001"},{label:u(()=>[t(i),e[8]||(e[8]=o(" \u961F\u5217\u72B6\u6001 "))]),default:u(()=>[o(" "+l(n.value.queue.is_running||!1),1)]),_:1}),t(a,{label:"\u961F\u5217\u6570\u91CF"},{label:u(()=>[t(i),e[9]||(e[9]=o(" \u6302\u8D77\u961F\u5217\u6570\u91CF "))]),default:u(()=>[o(" "+l(n.value.queue.pending_tasks||0),1)]),_:1}),t(a,{label:"\u6838\u5FC3\u7248\u672C"},{label:u(()=>[t(y),e[10]||(e[10]=o(" \u6838\u5FC3\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.core_version),1)]),_:1}),t(a,{label:"\u6700\u65B0\u7248\u672C"},{label:u(()=>[t(c),e[11]||(e[11]=o(" \u6700\u65B0\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.latest_version)+" ",1),n.value.need_update?(m(),p(B,{key:0,type:"text",size:"small",style:{"margin-left":"8px"},onClick:C},{default:u(()=>e[12]||(e[12]=[o("\u7ACB\u5373\u66F4\u65B0")])),_:1,__:[12]})):S("",!0)]),_:1})]),_:1})]),_:1})]),_:1},8,["sub-title"])}}});var q=D(P,[["__scopeId","data-v-69f02b08"]]);export{q as default};
import{h as w,_ as D,d as E,r as I,o as N,c as p,w as u,a as s,b as m,e as t,f as o,t as l,g as S}from"./index.1d777651.js";const M=async()=>await w.get("/wx/sys/info");const P=E({__name:"SysInfo",setup(F){const n=I({os:{name:"",version:"",release:""},python_version:"",uptime:0,system:{node:"",machine:"",processor:""},api_version:"/api/v1/wx",core_version:"",latest_version:"",need_update:!0,wx:{token:"",expiry_time:""},queue:{is_running:!1,pending_tasks:0}}),f=d=>{const e=Math.floor(d/86400),r=Math.floor(d%86400/3600),a=Math.floor(d%3600/60);return`${e}\u5929 ${r}\u5C0F\u65F6 ${a}\u5206\u949F`},C=()=>{window.open("https://github.com/rachelos/we-mp-rss","_blank")};return N(async()=>{n.value=await M()}),(d,e)=>{const r=s("desktop-outlined"),a=s("a-descriptions-item"),_=s("code-outlined"),v=s("clock-circle-outlined"),b=s("deployment-unit-outlined"),i=s("api-outlined"),y=s("appstore-outlined"),c=s("cloud-download-outlined"),B=s("a-button"),x=s("a-descriptions"),g=s("a-card"),k=s("a-page-header");return m(),p(k,{title:"\u7CFB\u7EDF\u4FE1\u606F","sub-title":`\u7248\u672C: ${n.value.version}`},{default:u(()=>[t(g,{bordered:!1,class:"sys-info-card"},{default:u(()=>[t(x,{bordered:"",column:{xs:1,sm:1,md:1,lg:2}},{default:u(()=>[t(a,{label:"\u64CD\u4F5C\u7CFB\u7EDF"},{label:u(()=>[t(r),e[0]||(e[0]=o(" \u64CD\u4F5C\u7CFB\u7EDF "))]),default:u(()=>[o(" "+l(n.value.os.name),1)]),_:1}),t(a,{label:"\u7CFB\u7EDF\u7248\u672C"},{label:u(()=>[t(_),e[1]||(e[1]=o(" \u7CFB\u7EDF\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.os.version)+" ("+l(n.value.os.release)+") ",1)]),_:1}),t(a,{label:"Python\u7248\u672C"},{label:u(()=>[t(_),e[2]||(e[2]=o(" Python\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.python_version),1)]),_:1}),t(a,{label:"\u8FD0\u884C\u65F6\u95F4"},{label:u(()=>[t(v),e[3]||(e[3]=o(" \u8FD0\u884C\u65F6\u95F4 "))]),default:u(()=>[o(" "+l(f(n.value.uptime)),1)]),_:1}),t(a,{label:"\u7CFB\u7EDF\u67B6\u6784"},{label:u(()=>[t(b),e[4]||(e[4]=o(" \u7CFB\u7EDF\u67B6\u6784 "))]),default:u(()=>[o(" "+l(n.value.system.node)+" / "+l(n.value.system.machine)+" ("+l(n.value.system.processor)+") ",1)]),_:1}),t(a,{label:"TOKEN"},{label:u(()=>[t(i),e[5]||(e[5]=o(" TOKEN "))]),default:u(()=>[o(" "+l(n.value.wx.token),1)]),_:1}),t(a,{label:"\u8FC7\u671F\u65F6\u95F4"},{label:u(()=>[t(i),e[6]||(e[6]=o(" \u8FC7\u671F\u65F6\u95F4 "))]),default:u(()=>[o(" "+l(n.value.wx.expiry_time),1)]),_:1}),t(a,{label:"API\u7248\u672C"},{label:u(()=>[t(i),e[7]||(e[7]=o(" API\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.api_version),1)]),_:1}),t(a,{label:"\u961F\u5217\u72B6\u6001"},{label:u(()=>[t(i),e[8]||(e[8]=o(" \u961F\u5217\u72B6\u6001 "))]),default:u(()=>[o(" "+l(n.value.queue.is_running||!1),1)]),_:1}),t(a,{label:"\u961F\u5217\u6570\u91CF"},{label:u(()=>[t(i),e[9]||(e[9]=o(" \u6302\u8D77\u961F\u5217\u6570\u91CF "))]),default:u(()=>[o(" "+l(n.value.queue.pending_tasks||0),1)]),_:1}),t(a,{label:"\u6838\u5FC3\u7248\u672C"},{label:u(()=>[t(y),e[10]||(e[10]=o(" \u6838\u5FC3\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.core_version),1)]),_:1}),t(a,{label:"\u6700\u65B0\u7248\u672C"},{label:u(()=>[t(c),e[11]||(e[11]=o(" \u6700\u65B0\u7248\u672C "))]),default:u(()=>[o(" "+l(n.value.latest_version)+" ",1),n.value.need_update?(m(),p(B,{key:0,type:"text",size:"small",style:{"margin-left":"8px"},onClick:C},{default:u(()=>e[12]||(e[12]=[o("\u7ACB\u5373\u66F4\u65B0")])),_:1,__:[12]})):S("",!0)]),_:1})]),_:1})]),_:1})]),_:1},8,["sub-title"])}}});var q=D(P,[["__scopeId","data-v-69f02b08"]]);export{q as default};
@@ -1 +1 @@
import{_ as k,d as A,u as M,j as U,r,o as R,i as L,e as u,k as v,w as t,M as d,a as l,b as N,f as B}from"./index.cdda9ff7.js";import{g as S,u as $,c as h}from"./tagManagement.5290c414.js";const j={class:"tag-form"},q=A({__name:"TagForm",setup(z){const i=M(),m=U(),s=r(!1),_=r(!1),p=r(!1),a=r({name:"",cover:null,intro:null,status:1}),g={name:[{required:!0,message:"\u8BF7\u8F93\u5165\u6807\u7B7E\u540D\u79F0"}]},F=async c=>{try{_.value=!0;const e=await S(c);a.value=e.data}catch{d.error("\u83B7\u53D6\u6807\u7B7E\u8BE6\u60C5\u5931\u8D25")}finally{_.value=!1}},E=async()=>{try{p.value=!0,s.value?(await $(i.params.id,a.value),d.success("\u66F4\u65B0\u6210\u529F")):(await h(a.value),d.success("\u521B\u5EFA\u6210\u529F")),m.push("/tags")}catch{d.error(s.value?"\u66F4\u65B0\u5931\u8D25":"\u521B\u5EFA\u5931\u8D25")}finally{p.value=!1}};return R(()=>{i.params.id&&(s.value=!0,F(i.params.id))}),(c,e)=>{const y=l("a-page-header"),b=l("a-input"),n=l("a-form-item"),D=l("a-upload"),V=l("a-textarea"),w=l("a-switch"),f=l("a-button"),x=l("a-space"),C=l("a-form"),T=l("a-card");return N(),L("div",j,[u(y,{title:s.value?"\u7F16\u8F91\u6807\u7B7E":"\u6DFB\u52A0\u6807\u7B7E",subtitle:"\u6807\u7B7E\u4FE1\u606F",onBack:e[0]||(e[0]=o=>v(m).go(-1))},null,8,["title"]),u(T,{loading:_.value},{default:t(()=>[u(C,{model:a.value,rules:g,layout:"vertical",onSubmit:E},{default:t(()=>[u(n,{label:"\u6807\u7B7E\u540D\u79F0",field:"name"},{default:t(()=>[u(b,{modelValue:a.value.name,"onUpdate:modelValue":e[1]||(e[1]=o=>a.value.name=o),placeholder:"\u8BF7\u8F93\u5165\u6807\u7B7E\u540D\u79F0"},null,8,["modelValue"])]),_:1}),u(n,{label:"\u5C01\u9762\u56FE",field:"cover"},{default:t(()=>[u(D,{"file-list":a.value.cover,"onUpdate:fileList":e[2]||(e[2]=o=>a.value.cover=o),action:"/api/upload",limit:1,"list-type":"picture-card"},null,8,["file-list"])]),_:1}),u(n,{label:"\u7B80\u4ECB",field:"intro"},{default:t(()=>[u(V,{modelValue:a.value.intro,"onUpdate:modelValue":e[3]||(e[3]=o=>a.value.intro=o),placeholder:"\u8BF7\u8F93\u5165\u6807\u7B7E\u7B80\u4ECB","auto-size":{minRows:3}},null,8,["modelValue"])]),_:1}),u(n,{label:"\u72B6\u6001",field:"status"},{default:t(()=>[u(w,{modelValue:a.value.status,"onUpdate:modelValue":e[4]||(e[4]=o=>a.value.status=o),"checked-value":1,"unchecked-value":0},null,8,["modelValue"])]),_:1}),u(n,null,{default:t(()=>[u(x,null,{default:t(()=>[u(f,{type:"primary","html-type":"submit",loading:p.value},{default:t(()=>e[6]||(e[6]=[B(" \u63D0\u4EA4 ")])),_:1,__:[6]},8,["loading"]),u(f,{onClick:e[5]||(e[5]=o=>v(m).go(-1))},{default:t(()=>e[7]||(e[7]=[B("\u53D6\u6D88")])),_:1,__:[7]})]),_:1})]),_:1})]),_:1},8,["model"])]),_:1},8,["loading"])])}}});var H=k(q,[["__scopeId","data-v-73b591e8"]]);export{H as default};
import{_ as k,d as A,u as M,j as U,r,o as R,i as L,e as u,k as v,w as t,M as d,a as l,b as N,f as B}from"./index.1d777651.js";import{g as S,u as $,c as h}from"./tagManagement.712f4daf.js";const j={class:"tag-form"},q=A({__name:"TagForm",setup(z){const i=M(),m=U(),s=r(!1),_=r(!1),p=r(!1),a=r({name:"",cover:null,intro:null,status:1}),g={name:[{required:!0,message:"\u8BF7\u8F93\u5165\u6807\u7B7E\u540D\u79F0"}]},F=async c=>{try{_.value=!0;const e=await S(c);a.value=e.data}catch{d.error("\u83B7\u53D6\u6807\u7B7E\u8BE6\u60C5\u5931\u8D25")}finally{_.value=!1}},E=async()=>{try{p.value=!0,s.value?(await $(i.params.id,a.value),d.success("\u66F4\u65B0\u6210\u529F")):(await h(a.value),d.success("\u521B\u5EFA\u6210\u529F")),m.push("/tags")}catch{d.error(s.value?"\u66F4\u65B0\u5931\u8D25":"\u521B\u5EFA\u5931\u8D25")}finally{p.value=!1}};return R(()=>{i.params.id&&(s.value=!0,F(i.params.id))}),(c,e)=>{const y=l("a-page-header"),b=l("a-input"),n=l("a-form-item"),D=l("a-upload"),V=l("a-textarea"),w=l("a-switch"),f=l("a-button"),x=l("a-space"),C=l("a-form"),T=l("a-card");return N(),L("div",j,[u(y,{title:s.value?"\u7F16\u8F91\u6807\u7B7E":"\u6DFB\u52A0\u6807\u7B7E",subtitle:"\u6807\u7B7E\u4FE1\u606F",onBack:e[0]||(e[0]=o=>v(m).go(-1))},null,8,["title"]),u(T,{loading:_.value},{default:t(()=>[u(C,{model:a.value,rules:g,layout:"vertical",onSubmit:E},{default:t(()=>[u(n,{label:"\u6807\u7B7E\u540D\u79F0",field:"name"},{default:t(()=>[u(b,{modelValue:a.value.name,"onUpdate:modelValue":e[1]||(e[1]=o=>a.value.name=o),placeholder:"\u8BF7\u8F93\u5165\u6807\u7B7E\u540D\u79F0"},null,8,["modelValue"])]),_:1}),u(n,{label:"\u5C01\u9762\u56FE",field:"cover"},{default:t(()=>[u(D,{"file-list":a.value.cover,"onUpdate:fileList":e[2]||(e[2]=o=>a.value.cover=o),action:"/api/upload",limit:1,"list-type":"picture-card"},null,8,["file-list"])]),_:1}),u(n,{label:"\u7B80\u4ECB",field:"intro"},{default:t(()=>[u(V,{modelValue:a.value.intro,"onUpdate:modelValue":e[3]||(e[3]=o=>a.value.intro=o),placeholder:"\u8BF7\u8F93\u5165\u6807\u7B7E\u7B80\u4ECB","auto-size":{minRows:3}},null,8,["modelValue"])]),_:1}),u(n,{label:"\u72B6\u6001",field:"status"},{default:t(()=>[u(w,{modelValue:a.value.status,"onUpdate:modelValue":e[4]||(e[4]=o=>a.value.status=o),"checked-value":1,"unchecked-value":0},null,8,["modelValue"])]),_:1}),u(n,null,{default:t(()=>[u(x,null,{default:t(()=>[u(f,{type:"primary","html-type":"submit",loading:p.value},{default:t(()=>e[6]||(e[6]=[B(" \u63D0\u4EA4 ")])),_:1,__:[6]},8,["loading"]),u(f,{onClick:e[5]||(e[5]=o=>v(m).go(-1))},{default:t(()=>e[7]||(e[7]=[B("\u53D6\u6D88")])),_:1,__:[7]})]),_:1})]),_:1})]),_:1},8,["model"])]),_:1},8,["loading"])])}}});var H=k(q,[["__scopeId","data-v-73b591e8"]]);export{H as default};
@@ -1 +1 @@
import{l as D,d as T}from"./tagManagement.5290c414.js";import{_ as A,d as $,r as c,o as w,i as z,e,w as a,M as p,a as n,b as g,f as r,c as v}from"./index.cdda9ff7.js";const L={class:"tag-list"},M=$({__name:"TagList",setup(S){const _=c(!1),m=c([]),o=c({current:1,pageSize:10,total:0}),i=async()=>{try{_.value=!0;const u=await D({skip:(o.value.current-1)*o.value.pageSize,limit:o.value.pageSize});m.value=u.data,o.value.total=u.total||0}catch{p.error("\u83B7\u53D6\u6807\u7B7E\u5217\u8868\u5931\u8D25")}finally{_.value=!1}},B=async u=>{try{await T(u),p.success("\u5220\u9664\u6210\u529F"),i()}catch{p.error("\u5220\u9664\u5931\u8D25")}},y=u=>{o.value.current=u,i()};return w(()=>{i()}),(u,t)=>{const d=n("a-button"),F=n("a-page-header"),s=n("a-table-column"),f=n("a-tag"),k=n("a-popconfirm"),x=n("a-space"),E=n("a-table"),C=n("a-card");return g(),z("div",L,[e(F,{title:"\u6807\u7B7E\u7BA1\u7406",subtitle:"\u7BA1\u7406\u6587\u7AE0\u6807\u7B7E"},{extra:a(()=>[e(d,{type:"primary",onClick:t[0]||(t[0]=l=>u.$router.push("/tags/add"))},{default:a(()=>t[1]||(t[1]=[r(" \u6DFB\u52A0\u6807\u7B7E ")])),_:1,__:[1]})]),_:1}),e(C,null,{default:a(()=>[e(E,{loading:_.value,data:m.value,pagination:o.value,onPageChange:y},{columns:a(()=>[e(s,{title:"ID","data-index":"id"}),e(s,{title:"\u6807\u7B7E\u540D\u79F0","data-index":"name"}),e(s,{title:"\u72B6\u6001","data-index":"status"},{cell:a(({record:l})=>[l.status===1?(g(),v(f,{key:0,color:"green"},{default:a(()=>t[2]||(t[2]=[r("\u542F\u7528")])),_:1,__:[2]})):(g(),v(f,{key:1,color:"red"},{default:a(()=>t[3]||(t[3]=[r("\u7981\u7528")])),_:1,__:[3]}))]),_:1}),e(s,{title:"\u521B\u5EFA\u65F6\u95F4","data-index":"created_at"}),e(s,{title:"\u64CD\u4F5C"},{cell:a(({record:l})=>[e(x,null,{default:a(()=>[e(d,{type:"text",onClick:b=>u.$router.push(`/tags/edit/${l.id}`)},{default:a(()=>t[4]||(t[4]=[r(" \u7F16\u8F91 ")])),_:2,__:[4]},1032,["onClick"]),e(k,{content:"\u786E\u8BA4\u5220\u9664\u8BE5\u6807\u7B7E\uFF1F",onOk:b=>B(l.id)},{default:a(()=>[e(d,{type:"text",status:"danger"},{default:a(()=>t[5]||(t[5]=[r("\u5220\u9664")])),_:1,__:[5]})]),_:2},1032,["onOk"])]),_:2},1024)]),_:1})]),_:1},8,["loading","data","pagination"])]),_:1})])}}});var N=A(M,[["__scopeId","data-v-45832f9f"]]);export{N as default};
import{l as D,d as T}from"./tagManagement.712f4daf.js";import{_ as A,d as $,r as c,o as w,i as z,e,w as a,M as p,a as n,b as g,f as r,c as v}from"./index.1d777651.js";const L={class:"tag-list"},M=$({__name:"TagList",setup(S){const _=c(!1),m=c([]),o=c({current:1,pageSize:10,total:0}),i=async()=>{try{_.value=!0;const u=await D({skip:(o.value.current-1)*o.value.pageSize,limit:o.value.pageSize});m.value=u.data,o.value.total=u.total||0}catch{p.error("\u83B7\u53D6\u6807\u7B7E\u5217\u8868\u5931\u8D25")}finally{_.value=!1}},B=async u=>{try{await T(u),p.success("\u5220\u9664\u6210\u529F"),i()}catch{p.error("\u5220\u9664\u5931\u8D25")}},y=u=>{o.value.current=u,i()};return w(()=>{i()}),(u,t)=>{const d=n("a-button"),F=n("a-page-header"),s=n("a-table-column"),f=n("a-tag"),k=n("a-popconfirm"),x=n("a-space"),E=n("a-table"),C=n("a-card");return g(),z("div",L,[e(F,{title:"\u6807\u7B7E\u7BA1\u7406",subtitle:"\u7BA1\u7406\u6587\u7AE0\u6807\u7B7E"},{extra:a(()=>[e(d,{type:"primary",onClick:t[0]||(t[0]=l=>u.$router.push("/tags/add"))},{default:a(()=>t[1]||(t[1]=[r(" \u6DFB\u52A0\u6807\u7B7E ")])),_:1,__:[1]})]),_:1}),e(C,null,{default:a(()=>[e(E,{loading:_.value,data:m.value,pagination:o.value,onPageChange:y},{columns:a(()=>[e(s,{title:"ID","data-index":"id"}),e(s,{title:"\u6807\u7B7E\u540D\u79F0","data-index":"name"}),e(s,{title:"\u72B6\u6001","data-index":"status"},{cell:a(({record:l})=>[l.status===1?(g(),v(f,{key:0,color:"green"},{default:a(()=>t[2]||(t[2]=[r("\u542F\u7528")])),_:1,__:[2]})):(g(),v(f,{key:1,color:"red"},{default:a(()=>t[3]||(t[3]=[r("\u7981\u7528")])),_:1,__:[3]}))]),_:1}),e(s,{title:"\u521B\u5EFA\u65F6\u95F4","data-index":"created_at"}),e(s,{title:"\u64CD\u4F5C"},{cell:a(({record:l})=>[e(x,null,{default:a(()=>[e(d,{type:"text",onClick:b=>u.$router.push(`/tags/edit/${l.id}`)},{default:a(()=>t[4]||(t[4]=[r(" \u7F16\u8F91 ")])),_:2,__:[4]},1032,["onClick"]),e(k,{content:"\u786E\u8BA4\u5220\u9664\u8BE5\u6807\u7B7E\uFF1F",onOk:b=>B(l.id)},{default:a(()=>[e(d,{type:"text",status:"danger"},{default:a(()=>t[5]||(t[5]=[r("\u5220\u9664")])),_:1,__:[5]})]),_:2},1032,["onOk"])]),_:2},1024)]),_:1})]),_:1},8,["loading","data","pagination"])]),_:1})])}}});var N=A(M,[["__scopeId","data-v-45832f9f"]]);export{N as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{h as e}from"./index.cdda9ff7.js";const o=t=>e.get("/wx/tags",{params:{offset:(t==null?void 0:t.offset)||0,limit:(t==null?void 0:t.limit)||100}}),n=t=>e.get(`/wx/tags/${t}`),u=t=>e.post("/wx/tags",t),c=(t,g)=>e.put(`/wx/tags/${t}`,g),i=t=>e.delete(`/wx/tags/${t}`);export{u as c,i as d,n as g,o as l,c as u};
import{h as e}from"./index.1d777651.js";const o=t=>e.get("/wx/tags",{params:{offset:(t==null?void 0:t.offset)||0,limit:(t==null?void 0:t.limit)||100}}),n=t=>e.get(`/wx/tags/${t}`),u=t=>e.post("/wx/tags",t),c=(t,g)=>e.put(`/wx/tags/${t}`,g),i=t=>e.delete(`/wx/tags/${t}`);export{u as c,i as d,n as g,o as l,c as u};
+2 -2
View File
@@ -10,8 +10,8 @@
<meta name="author" content="Rachel" />
<script src="https://hm.baidu.com/hm.js?975de8724ac02eb7e6d2357bb95c067d"></script>
<title>WeRss微信公众号订阅助手</title>
<script type="module" crossorigin src="/assets/index.cdda9ff7.js"></script>
<link rel="stylesheet" href="/assets/index.73255e32.css">
<script type="module" crossorigin src="/assets/index.1d777651.js"></script>
<link rel="stylesheet" href="/assets/index.e02de4de.css">
</head>
<body>
+3 -1
View File
@@ -15,7 +15,9 @@
<a-link href="/api/docs" target="_blank" style="margin-right: 20px;">Docs</a-link>
<a-link href="https://gitee.com/rachel_os/we-mp-rss" target="_blank" style="margin-right: 20px;">Gitee</a-link>
<a-link href="https://github.com/rachelos/we-mp-rss" target="_blank" style="margin-right: 20px;">GitHub</a-link>
<a-tooltip content="GitHub或者Google账户注册登录,获得首月5美元奖励。注册180+天的GitHub账户还可以解锁每月5美元的额度赠送。" position="bottom">
<a-link href="https://console.run.claw.cloud/signin?link=FJ0VXS42W2P9" target="_blank" style="margin-right: 20px;">ClawCloud</a-link>
</a-tooltip>
<a-tooltip content="如果您需要部署此项目,建议采用腾讯云服务器,您懂得" position="bottom">
<a-link href="https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=f8ce741e7b24cd68141ab2115122ea94&from=console" target="_blank" style="margin-right: 20px;">云部署</a-link>
</a-tooltip>