mirror of
https://github.com/rachelos/we-mp-rss.git
synced 2026-09-01 04:57:12 +08:00
1.4.3 1.4.3
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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}
|
||||
#调试模式
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
@@ -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('不支持的通知类型')
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user