@clawhub-freedompixels-bccea021be
Generate QR codes from URLs or text. Export as PNG with customizable size. No API key required.
--- slug: qrcode-tool name: QR Code Generator description: "Generate QR codes from URLs or text. Export as PNG with customizable size. No API key required." keywords: qrcode, qr, barcode, generator, url, text version: "1.0.0" author: Qiance language: en --- # QR Code Generator Generate QR codes from any text or URL. Supports customization and exports as PNG format. ## Features - Generate QR codes from any text/URL - Custom size (default 300px) - Custom margin - Export as PNG format - No API key required ## Usage ```bash # Generate QR code for URL python3 scripts/qrcode_generator.py "https://example.com" # Generate QR code for text python3 scripts/qrcode_generator.py "Hello World" # Custom size python3 scripts/qrcode_generator.py "https://example.com" --size 500 ``` ## Examples ``` Generate QR code for: https://github.com Generate QR code for: Contact me at [email protected] Generate QR code for: WIFI:T:WPA;S:MyNetwork;P:password;; ``` ## Technical Details - Uses qrserver.com public API - SSL certificate verification enabled (certifi) - No sensitive data transmission ## Dependencies - Python 3.7+ - certifi (SSL certificates) ## Privacy Note Input text is sent to api.qrserver.com (third-party service). Not recommended for sensitive information. --- ## 中文说明 输入URL或文本,生成PNG二维码。 - 自定义尺寸(默认300px) - 无需API Key - 使用qrserver.com公开API FILE:README.md # QR Code Generator Generate QR codes from any text or URL with customizable options. ## Installation No installation required. Uses Python standard library + certifi for SSL. ```bash pip install certifi # Optional but recommended for SSL verification ``` ## Usage ### Basic Usage ```bash # Generate QR code for URL python3 scripts/qrcode_generator.py "https://example.com" # Generate QR code for text python3 scripts/qrcode_generator.py "Hello World" ``` ### Advanced Options ```bash # Custom size python3 scripts/qrcode_generator.py "https://example.com" --size 500 # Custom margin python3 scripts/qrcode_generator.py "Hello" --margin 2 # Different format python3 scripts/qrcode_generator.py "Test" --format gif # JSON output python3 scripts/qrcode_generator.py "https://example.com" --json ``` ## Examples | Input | Use Case | |-------|----------| | `https://github.com` | Website URL | | `mailto:[email protected]` | Email link | | `tel:+1234567890` | Phone number | | `WIFI:T:WPA;S:MyNetwork;P:password;;` | WiFi credentials | | `Hello World` | Plain text | ## API Uses the free [qrserver.com API](https://api.qrserver.com). No API key required. ## Privacy ⚠️ Input text is sent to a third-party API (api.qrserver.com). Do not use for sensitive information like passwords or private keys. ## License MIT License FILE:scripts/qrcode_generator.py #!/usr/bin/env python3 """QR Code Generator - Generate QR codes from text/URL""" import sys import os import base64 import ssl import urllib.request import urllib.parse import argparse try: import certifi CERTIFI_AVAILABLE = True except ImportError: CERTIFI_AVAILABLE = False def generate_qrcode(text, size=300, margin=4, format='png'): """Generate QR code using qrserver.com API Args: text: Text or URL to encode size: QR code size in pixels (default 300) margin: Margin around QR code (default 4) format: Output format (default png) Returns: dict with success status and data/base64 or error message """ encoded_text = urllib.parse.quote(text) url = f"https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&margin={margin}&format={format}&data={encoded_text}" headers = { 'User-Agent': 'QRCode-Tool/1.0 (https://github.com/qiance)' } try: # SSL with certifi if available, fallback to default if CERTIFI_AVAILABLE: ctx = ssl.create_default_context() ctx.load_verify_locations(certifi.where()) else: ctx = ssl.create_default_context() req = urllib.request.Request(url, headers=headers) response = urllib.request.urlopen(req, timeout=15, context=ctx) data = response.read() return { "success": True, "data": f"data:image/{format};base64,{base64.b64encode(data).decode()}", "url": url, "size": size, "text_length": len(text) } except Exception as e: return {"success": False, "error": str(e)} def main(): parser = argparse.ArgumentParser( description='Generate QR codes from text or URLs', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python3 qrcode_generator.py "https://example.com" python3 qrcode_generator.py "Hello World" --size 500 python3 qrcode_generator.py "WIFI:T:WPA;S:MyNetwork;P:password;;" --margin 2 """ ) parser.add_argument('text', help='Text or URL to encode') parser.add_argument('--size', type=int, default=300, help='QR code size in pixels (default: 300)') parser.add_argument('--margin', type=int, default=4, help='Margin around QR code (default: 4)') parser.add_argument('--format', choices=['png', 'gif', 'jpeg', 'jpg'], default='png', help='Output format (default: png)') parser.add_argument('--json', action='store_true', help='Output as JSON') args = parser.parse_args() result = generate_qrcode(args.text, args.size, args.margin, args.format) if args.json: import json print(json.dumps(result, indent=2)) else: if result["success"]: print(f"✅ QR Code generated successfully!") print(f" Size: {result['size']}x{result['size']} pixels") print(f" Text length: {result['text_length']} characters") print(f" Base64 length: {len(result['data'])} characters") else: print(f"❌ Failed to generate QR code: {result['error']}") if __name__ == '__main__': main()
支持数学表达式计算和单位换算,包含四则运算、科学函数及常用常量,纯本地安全计算无外部依赖。
# cn-math-calculator
数学表达式计算器。支持基本运算、科学计算、单位换算。
## 功能
- 四则运算 + - * / ^(幂) %(取模)
- 科学函数:sin, cos, tan, log, sqrt, abs
- 常量:pi, e
- 单位换算:长度、重量、温度、面积
- 表达式安全求值(不使用eval)
- 纯本地处理,无需API
## 安装要求
- Python 3.6+
- 无外部依赖
## 使用方法
```
千策,计算 2^10 + 100
千策,计算 sqrt(144)
千策,换算 100公里等于多少英里
```
## 参数
- `expression`: 数学表达式
- `convert`: 单位换算格式 (数值 原单位 -> 目标单位)
## 示例
输入:
```
千策,计算 (100 + 50) * 2 - 30
```
输出:
```
结果: 270
```
## 分类
工具
## 关键词
计算器, 数学, calculator, math, 单位换算
FILE:scripts/math_calculator.py
#!/usr/bin/env python3
"""
数学表达式计算器
安全求值,支持科学函数和单位换算
"""
import argparse
import sys
import json
import math
import re
from typing import Dict, Any
# 安全的数学函数映射
SAFE_FUNCTIONS = {
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'asin': math.asin,
'acos': math.acos,
'atan': math.atan,
'sinh': math.sinh,
'cosh': math.cosh,
'tanh': math.tanh,
'log': math.log10,
'ln': math.log,
'log2': math.log2,
'sqrt': math.sqrt,
'abs': abs,
'floor': math.floor,
'ceil': math.ceil,
'round': round,
'exp': math.exp,
'pow': pow,
}
SAFE_CONSTANTS = {
'pi': math.pi,
'e': math.e,
}
# 单位换算表
UNIT_CONVERSIONS = {
# 长度 (到米的换算因子)
'length': {
'km': 1000, '公里': 1000, '千米': 1000,
'm': 1, '米': 1,
'cm': 0.01, '厘米': 0.01,
'mm': 0.001, '毫米': 0.001,
'mile': 1609.344, '英里': 1609.344,
'yard': 0.9144, '码': 0.9144,
'ft': 0.3048, '英尺': 0.3048,
'inch': 0.0254, '英寸': 0.0254,
'里': 500, '丈': 3.333, '尺': 0.333, '寸': 0.0333,
},
# 重量 (到千克的换算因子)
'weight': {
't': 1000, '吨': 1000,
'kg': 1, '千克': 1, '公斤': 1,
'g': 0.001, '克': 0.001,
'mg': 0.000001, '毫克': 0.000001,
'lb': 0.453592, '磅': 0.453592,
'oz': 0.0283495, '盎司': 0.0283495,
'斤': 0.5, '两': 0.05, '钱': 0.005,
},
# 温度 (特殊处理)
'temperature': {
'c': 'c', '摄氏度': 'c', '摄氏': 'c',
'f': 'f', '华氏度': 'f', '华氏': 'f',
'k': 'k', '开尔文': 'k',
},
# 面积 (到平方米的换算因子)
'area': {
'km2': 1e6, '平方公里': 1e6,
'm2': 1, '平方米': 1, '平米': 1,
'cm2': 0.0001, '平方厘米': 0.0001,
'ha': 10000, '公顷': 10000,
'acre': 4046.86, '英亩': 4046.86,
'亩': 666.67,
},
}
def safe_eval(expression: str) -> float:
"""
安全地计算数学表达式
"""
# 预处理:替换常量
expr = expression.lower()
for const, value in SAFE_CONSTANTS.items():
expr = expr.replace(const, str(value))
# 替换函数调用为前缀形式
for func in SAFE_FUNCTIONS:
expr = re.sub(rf'\b{func}\s*\(', f'__{func}__(', expr, flags=re.IGNORECASE)
# 安全检查:只允许数字、运算符、括号和函数调用
allowed = r'^[\d\s\+\-\*\/\%\^\(\)\.\_a-z]+$'
if not re.match(allowed, expr):
raise ValueError(f"表达式包含非法字符: {expression}")
# 替换运算符
expr = expr.replace('^', '**')
# 构建安全的命名空间
namespace = {f'__{f}__': func for f, func in SAFE_FUNCTIONS.items()}
try:
result = eval(expr, {"__builtins__": {}}, namespace)
return float(result)
except Exception as e:
raise ValueError(f"计算错误: {e}")
def convert_temperature(value: float, from_unit: str, to_unit: str) -> float:
"""
温度换算
"""
# 转换为摄氏度
if from_unit == 'c':
celsius = value
elif from_unit == 'f':
celsius = (value - 32) * 5 / 9
elif from_unit == 'k':
celsius = value - 273.15
else:
raise ValueError(f"不支持的温度单位: {from_unit}")
# 从摄氏度转换到目标单位
if to_unit == 'c':
return celsius
elif to_unit == 'f':
return celsius * 9 / 5 + 32
elif to_unit == 'k':
return celsius + 273.15
else:
raise ValueError(f"不支持的温度单位: {to_unit}")
def convert_unit(value: float, from_unit: str, to_unit: str) -> float:
"""
单位换算
"""
from_unit = from_unit.lower().strip()
to_unit = to_unit.lower().strip()
if from_unit == to_unit:
return value
# 查找单位所属类别
for category, units in UNIT_CONVERSIONS.items():
if from_unit in units and to_unit in units:
if category == 'temperature':
return convert_temperature(value, units[from_unit], units[to_unit])
else:
factor_from = units[from_unit]
factor_to = units[to_unit]
return value * factor_from / factor_to
raise ValueError(f"不支持的单位换算: {from_unit} -> {to_unit}")
def parse_convert_request(text: str) -> tuple:
"""
解析单位换算请求
格式: "100公里等于多少英里" 或 "100 km to miles"
"""
# 中文格式
cn_pattern = r'([\d\.]+)\s*([^\s等于]+?)\s*等于?\s*(?:多少)?\s*([^\s]+)'
match = re.search(cn_pattern, text)
if match:
value = float(match.group(1))
from_unit = match.group(2)
to_unit = match.group(3)
return value, from_unit, to_unit
# 英文格式 "100 km to miles"
en_pattern = r'([\d\.]+)\s*(\w+)\s+to\s+(\w+)'
match = re.search(en_pattern, text, re.IGNORECASE)
if match:
value = float(match.group(1))
from_unit = match.group(2)
to_unit = match.group(3)
return value, from_unit, to_unit
return None, None, None
def main():
parser = argparse.ArgumentParser(description="数学表达式计算器")
parser.add_argument("expression", nargs="?", help="数学表达式")
parser.add_argument("-c", "--convert", help="单位换算")
parser.add_argument("-j", "--json", action="store_true", help="JSON输出")
args = parser.parse_args()
result = None
error = None
try:
if args.convert:
# 单位换算模式
value, from_unit, to_unit = parse_convert_request(args.convert)
if value is None:
value, from_unit, to_unit = parse_convert_request(args.expression)
if value is not None:
result = convert_unit(value, from_unit, to_unit)
else:
error = "无法解析单位换算请求"
elif args.expression:
# 表达式计算模式
result = safe_eval(args.expression)
else:
error = "请提供数学表达式或换算请求"
except Exception as e:
error = str(e)
if args.json:
output = {
"success": result is not None,
"result": result,
"error": error
}
print(json.dumps(output, ensure_ascii=False, indent=2))
else:
if error:
print(f"错误: {error}", file=sys.stderr)
sys.exit(1)
else:
print(f"结果: {result}")
if __name__ == "__main__":
main()
支持简体与繁体中文互转,允许自定义词汇表,全部本地处理无需网络连接。
# cn-chinese-converter
中文简繁转换工具。支持简体转繁体、繁体转简体。
## 功能
- 简体中文 → 繁体中文
- 繁体中文 → 简体中文
- 支持自定义词汇表
- 纯本地处理,无需API
## 安装要求
- Python 3.6+
- 依赖:opencc (自动安装)
## 使用方法
```
千策,把这段简体转繁体:[文本]
千策,把这段繁体转简体:[文本]
```
## 参数
- `text`: 要转换的文本
- `direction`: 转换方向 (s2t 简转繁 / t2s 繁转简),默认 t2s
## 示例
输入:
```
千策,把这段转成繁体:人工智能正在改变世界
```
输出:
```
人工智慧正在改變世界
```
## 分类
生产力
## 关键词
中文, 简体, 繁体, 转换, opencc, chinese converter
FILE:scripts/chinese_converter.py
#!/usr/bin/env python3
"""
中文简繁转换工具
使用 opencc-python-reimplemented 进行本地转换
"""
import argparse
import sys
import json
# 延迟导入,避免未安装时报错
def get_converter():
try:
from opencc import OpenCC
return OpenCC
except ImportError:
print("错误:未安装 opencc 库")
print("请运行:pip install opencc-python-reimplemented")
sys.exit(1)
def convert_text(text: str, direction: str = "t2s") -> str:
"""
转换中文文本
Args:
text: 要转换的文本
direction: 转换方向
- t2s: 繁体转简体 (默认)
- s2t: 简体转繁体
- t2tw: 繁体转台湾正体
- t2hk: 繁体转香港繁体
Returns:
转换后的文本
"""
OpenCC = get_converter()
cc = OpenCC(direction)
return cc.convert(text)
def main():
parser = argparse.ArgumentParser(description="中文简繁转换工具")
parser.add_argument("text", nargs="?", help="要转换的文本")
parser.add_argument("-d", "--direction", default="t2s",
choices=["t2s", "s2t", "t2tw", "t2hk"],
help="转换方向: t2s繁转简, s2t简转繁, t2tw繁转台湾, t2hk繁转香港")
parser.add_argument("-j", "--json", action="store_true", help="JSON输出")
args = parser.parse_args()
if not args.text:
if not sys.stdin.isatty():
args.text = sys.stdin.read().strip()
else:
print("错误:请提供要转换的文本")
sys.exit(1)
result = convert_text(args.text, args.direction)
if args.json:
output = {
"success": True,
"direction": args.direction,
"original": args.text,
"converted": result
}
print(json.dumps(output, ensure_ascii=False, indent=2))
else:
print(result)
if __name__ == "__main__":
main()
计算精确年龄、生日倒计时及星座生肖,支持多日期格式,纯本地处理,无需联网。
# cn-age-calculator
年龄计算器。计算精确年龄、生日倒计时、星座生肖。
## 功能
- 精确年龄计算(年/月/日)
- 生日倒计时
- 星座判定
- 生肖判定
- 支持多种日期格式输入
- 纯本地处理,无需API
## 安装要求
- Python 3.6+
- 无外部依赖
## 使用方法
```
千策,帮我算年龄:1990年5月15日
千策,距离生日还有多少天:5月15日
千策,1990年5月15日是什么星座
```
## 参数
- `birthday`: 生日日期(YYYY-MM-DD / YYYY年MM月DD日 / MM-DD等)
- `action`: 计算类型 (age/countdown/zodiac/all),默认all
## 示例
输入:
```
千策,帮我算年龄:1990年5月15日
```
输出:
```
年龄: 35岁11个月12天
星座: 金牛座
生肖: 马
距离下次生日: 23天
```
## 分类
生活
## 关键词
年龄, 生日, 星座, 生肖, 倒计时, age calculator
FILE:scripts/age_calculator.py
#!/usr/bin/env python3
"""
年龄计算器
精确年龄、生日倒计时、星座生肖
"""
import argparse
import sys
import json
from datetime import datetime, date
from typing import Tuple, Optional
# 星座日期范围
ZODIAC_DATES = [
((3, 21), (4, 19), "白羊座"),
((4, 20), (5, 20), "金牛座"),
((5, 21), (6, 21), "双子座"),
((6, 22), (7, 22), "巨蟹座"),
((7, 23), (8, 22), "狮子座"),
((8, 23), (9, 22), "处女座"),
((9, 23), (10, 23), "天秤座"),
((10, 24), (11, 22), "天蝎座"),
((11, 23), (12, 21), "射手座"),
((12, 22), (1, 19), "摩羯座"),
((1, 20), (2, 18), "水瓶座"),
((2, 19), (3, 20), "双鱼座"),
]
# 生肖
CHINESE_ZODIAC = ["猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"]
def parse_date(date_str: str) -> Optional[date]:
"""
解析多种日期格式
"""
formats = [
"%Y-%m-%d", "%Y/%m/%d", "%Y年%m月%d日",
"%m-%d", "%m/%d", "%m月%d日",
"%Y%m%d",
]
for fmt in formats:
try:
parsed = datetime.strptime(date_str.strip(), fmt)
# 如果没有年份,用今年
if parsed.year == 1900:
return date(date.today().year, parsed.month, parsed.day)
return parsed.date()
except ValueError:
continue
return None
def calculate_age(birth_date: date, ref_date: date = None) -> Tuple[int, int, int]:
"""
计算精确年龄
返回 (年, 月, 日)
"""
if ref_date is None:
ref_date = date.today()
years = ref_date.year - birth_date.year
months = ref_date.month - birth_date.month
days = ref_date.day - birth_date.day
if days < 0:
months -= 1
# 获取上个月的天数
if ref_date.month == 1:
prev_month = 12
prev_year = ref_date.year - 1
else:
prev_month = ref_date.month - 1
prev_year = ref_date.year
days_in_prev_month = (date(prev_year, prev_month + 1, 1) - date(prev_year, prev_month, 1)).days
days += days_in_prev_month
if months < 0:
years -= 1
months += 12
return (years, months, days)
def days_to_birthday(birth_date: date, ref_date: date = None) -> int:
"""
计算距离下次生日的天数
"""
if ref_date is None:
ref_date = date.today()
next_birthday = date(ref_date.year, birth_date.month, birth_date.day)
if next_birthday <= ref_date:
next_birthday = date(ref_date.year + 1, birth_date.month, birth_date.day)
return (next_birthday - ref_date).days
def get_zodiac(birth_date: date) -> str:
"""
获取星座
"""
month, day = birth_date.month, birth_date.day
for (start_m, start_d), (end_m, end_d), zodiac in ZODIAC_DATES:
# 处理摩羯座跨年情况
if start_m > end_m: # 摩羯座 12/22 - 1/19
if (month == start_m and day >= start_d) or (month == end_m and day <= end_d):
return zodiac
else:
if (month == start_m and day >= start_d) or (month == end_m and day <= end_d) or \
(start_m < month < end_m):
return zodiac
return "未知"
def get_chinese_zodiac(birth_date: date) -> str:
"""
获取生肖
"""
return CHINESE_ZODIAC[birth_date.year % 12]
def main():
parser = argparse.ArgumentParser(description="年龄计算器")
parser.add_argument("birthday", help="生日日期")
parser.add_argument("-a", "--action", default="all",
choices=["age", "countdown", "zodiac", "all"],
help="计算类型")
parser.add_argument("-j", "--json", action="store_true", help="JSON输出")
args = parser.parse_args()
birth_date = parse_date(args.birthday)
if birth_date is None:
print("错误:无法解析日期,请使用 YYYY-MM-DD 格式", file=sys.stderr)
sys.exit(1)
today = date.today()
result = {}
if args.action in ["age", "all"]:
years, months, days = calculate_age(birth_date, today)
result["age"] = {
"years": years,
"months": months,
"days": days,
"formatted": f"{years}岁{months}个月{days}天"
}
if args.action in ["countdown", "all"]:
days_left = days_to_birthday(birth_date, today)
result["birthday_countdown"] = {
"days": days_left,
"formatted": f"{days_left}天后"
}
if args.action in ["zodiac", "all"]:
result["zodiac"] = get_zodiac(birth_date)
result["chinese_zodiac"] = get_chinese_zodiac(birth_date)
if args.json:
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
if "age" in result:
print(f"年龄: {result['age']['formatted']}")
if "zodiac" in result:
print(f"星座: {result['zodiac']}")
print(f"生肖: {result['chinese_zodiac']}")
if "birthday_countdown" in result:
print(f"距离下次生日: {result['birthday_countdown']['formatted']}")
if __name__ == "__main__":
main()
支持中文和英文文本与Emoji表情的相互转换,纯本地处理无需网络依赖。
# cn-emoji-translator
Emoji 翻译器。文本转 emoji 表情,emoji 转文字描述。
## 功能
- 文本 → Emoji 表情(关键词替换)
- Emoji → 文字描述
- 支持中英文混合
- 纯本地处理,无需API
## 安装要求
- Python 3.6+
- 无外部依赖(使用内置 emoji 库或自定义映射)
## 使用方法
```
千策,把这段翻译成emoji:今天天气真好
千策,这个emoji是什么意思:🎉
```
## 参数
- `text`: 要翻译的文本
- `direction`: 翻译方向 (text2emoji / emoji2text),默认 text2emoji
## 示例
输入:
```
千策,把这段转成emoji:我爱吃苹果
```
输出:
```
我❤️🍎
```
## 分类
趣味
## 关键词
emoji, 表情, 翻译, emoji translator, 表情包
FILE:scripts/emoji_translator.py
#!/usr/bin/env python3
"""
Emoji 翻译器
文本 ↔ Emoji 双向转换
"""
import argparse
import sys
import json
import re
# 中文关键词 -> Emoji 映射表
EMOJI_MAP = {
# 情感
"爱": "❤️", "喜欢": "❤️", "开心": "😊", "高兴": "😊", "快乐": "😄",
"笑": "😂", "哭": "😢", "难过": "😢", "生气": "😠", "愤怒": "😡",
"惊讶": "😮", "害怕": "😱", "困": "😴", "累": "😩", "饿": "😋",
# 常见物品
"苹果": "🍎", "香蕉": "🍌", "葡萄": "🍇", "西瓜": "🍉", "草莓": "🍓",
"手机": "📱", "电脑": "💻", "书": "📖", "车": "🚗", "飞机": "✈️",
"房子": "🏠", "钱": "💰", "礼物": "🎁", "花": "🌸", "星星": "⭐",
# 天气
"太阳": "☀️", "晴天": "☀️", "雨": "🌧️", "下雨": "🌧️", "雪": "❄️",
"云": "☁️", "风": "💨", "彩虹": "🌈", "月亮": "🌙",
# 动作
"吃": "🍽️", "喝": "🥤", "睡": "😴", "工作": "💼", "学习": "📚",
"运动": "🏃", "跑步": "🏃", "游泳": "🏊", "唱歌": "🎤", "跳舞": "💃",
# 时间
"早上": "🌅", "中午": "☀️", "晚上": "🌙", "今天": "📅", "明天": "📅",
"周末": "🗓️", "假期": "🏖️", "生日": "🎂", "新年": "🧧",
# 人物
"男人": "👨", "女人": "👩", "孩子": "👶", "老师": "👨🏫", "医生": "👨⚕️",
"朋友": "👯", "家人": "👨👩👧👦",
# 英文关键词
"love": "❤️", "happy": "😊", "sad": "😢", "cool": "😎", "fire": "🔥",
"ok": "👌", "yes": "✅", "no": "❌", "good": "👍", "bad": "👎",
"cat": "🐱", "dog": "🐶", "heart": "❤️", "star": "⭐", "sun": "☀️",
}
# Emoji -> 文字描述映射
EMOJI_TO_TEXT = {
"❤️": "[爱心]", "😊": "[微笑]", "😄": "[开心]", "😂": "[笑哭]",
"😢": "[难过]", "😠": "[生气]", "😡": "[愤怒]", "😮": "[惊讶]",
"😱": "[害怕]", "😴": "[困]", "😋": "[馋]", "🍎": "[苹果]",
"🍌": "[香蕉]", "📱": "[手机]", "💻": "[电脑]", "📖": "[书]",
"🚗": "[车]", "✈️": "[飞机]", "🏠": "[房子]", "💰": "[钱]",
"🎁": "[礼物]", "🌸": "[花]", "⭐": "[星星]", "☀️": "[太阳]",
"🌧️": "[雨]", "❄️": "[雪]", "☁️": "[云]", "🌈": "[彩虹]",
"🌙": "[月亮]", "🔥": "[火]", "👍": "[赞]", "👎": "[踩]",
"🎉": "[庆祝]", "🎊": "[欢呼]", "💯": "[满分]", "✅": "[对]",
"❌": "[错]", "💪": "[加油]", "🙏": "[谢谢]", "👏": "[鼓掌]",
}
def text_to_emoji(text: str) -> str:
"""
将文本中的关键词替换为emoji
"""
result = text
# 按关键词长度降序排序,优先匹配长词
sorted_keywords = sorted(EMOJI_MAP.keys(), key=len, reverse=True)
for keyword in sorted_keywords:
emoji = EMOJI_MAP[keyword]
result = result.replace(keyword, emoji)
return result
def emoji_to_text(text: str) -> str:
"""
将emoji替换为文字描述
"""
result = text
for emoji, desc in EMOJI_TO_TEXT.items():
result = result.replace(emoji, desc)
return result
def main():
parser = argparse.ArgumentParser(description="Emoji 翻译器")
parser.add_argument("text", nargs="?", help="要翻译的文本")
parser.add_argument("-d", "--direction", default="text2emoji",
choices=["text2emoji", "emoji2text"],
help="翻译方向: text2emoji文本转emoji, emoji2text emoji转文字")
parser.add_argument("-j", "--json", action="store_true", help="JSON输出")
args = parser.parse_args()
if not args.text:
if not sys.stdin.isatty():
args.text = sys.stdin.read().strip()
else:
print("错误:请提供要翻译的文本")
sys.exit(1)
if args.direction == "text2emoji":
result = text_to_emoji(args.text)
else:
result = emoji_to_text(args.text)
if args.json:
output = {
"success": True,
"direction": args.direction,
"original": args.text,
"translated": result
}
print(json.dumps(output, ensure_ascii=False, indent=2))
else:
print(result)
if __name__ == "__main__":
main()
提供百分比计算、反向计算、比例计算和增减百分比功能,支持千分位和小数精度控制,纯Python实现。
# cn-percentage-tool - 百分比计算器
纯 Python 标准库实现的百分比计算工具。
## 功能
- **百分比计算**:计算 A 是 B 的百分之多少
- **反向计算**:已知百分比和部分,求整体
- **比例计算**:已知整体和百分比,求部分
- **增减百分比**:计算增长/下降百分比
- **格式化输出**:带千分位分隔符和精度控制
## 使用方式
```bash
# 百分比:A 是 B 的百分之几
python3 cn_percentage_tool.py percent 25 200
# 输出:12.5% (25 占 200 的百分比)
# 反向计算:已知百分比和部分,求整体
python3 cn_percentage_tool.py from-part 50 25
# 输出:200 (50 的 25% 是多少 → 50/0.25 = 200)
# 比例计算:已知整体和百分比,求部分
python3 cn_percentage_tool.py of-part 200 25
# 输出:50 (200 的 25% 是多少)
# 增减百分比:增长了多少
python3 cn_percentage_tool.py change 100 150
# 输出:50.0% (从 100 增长到 150)
# 格式化精度
python3 cn_percentage_tool.py percent 1 3 --precision 4
# 输出:33.3333%
```
## 技术说明
- 纯 Python 标准库(`argparse`)
- 无外部依赖
- 默认精度 2 位小数
FILE:scripts/percentage_tool.py
#!/usr/bin/env python3
"""
百分比计算器
纯 Python 标准库实现
"""
import argparse
import sys
def calculate_percent(part: float, whole: float, precision: int = 2) -> float:
"""计算 part 占 whole 的百分比"""
if whole == 0:
raise ValueError("整体值不能为零")
return round(part / whole * 100, precision)
def calculate_from_part(part: float, percent: float, precision: int = 2) -> float:
"""已知部分和百分比,求整体 = part / (percent / 100)"""
if percent == 0:
raise ValueError("百分比不能为零")
return round(part / (percent / 100), precision)
def calculate_of_part(whole: float, percent: float, precision: int = 2) -> float:
"""已知整体和百分比,求部分 = whole * (percent / 100)"""
return round(whole * percent / 100, precision)
def calculate_change(old_val: float, new_val: float, precision: int = 2) -> float:
"""计算增减百分比 = (new - old) / old * 100"""
if old_val == 0:
raise ValueError("原始值不能为零")
return round((new_val - old_val) / old_val * 100, precision)
def format_number(num: float, use_thousands_sep: bool = False) -> str:
"""格式化数字(可选千分位分隔符)"""
if use_thousands_sep:
return f"{num:,.2f}"
return f"{num}"
def main():
parser = argparse.ArgumentParser(
description='百分比计算器',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
%(prog)s percent 25 200 25 占 200 的百分比
%(prog)s from-part 50 25 50 是 25%% 时,整体是多少
%(prog)s of-part 200 25 200 的 25%% 是多少
%(prog)s change 100 150 从 100 增长到 150 的百分比
%(prog)s percent 1 3 --precision 4 高精度计算
'''
)
subparsers = parser.add_subparsers(dest='command', help='子命令')
# percent: part / whole * 100
p_percent = subparsers.add_parser('percent', help='计算百分比(A 是 B 的百分之几)')
p_percent.add_argument('part', type=float, help='部分值')
p_percent.add_argument('whole', type=float, help='整体值')
p_percent.add_argument('-p', '--precision', type=int, default=2, help='小数精度')
p_percent.add_argument('-f', '--formatted', action='store_true', help='格式化数字')
# from-part: part / (percent/100) = whole
p_from_part = subparsers.add_parser('from-part', help='已知部分和百分比,求整体')
p_from_part.add_argument('part', type=float, help='部分值')
p_from_part.add_argument('percent', type=float, help='百分比')
p_from_part.add_argument('-p', '--precision', type=int, default=2, help='小数精度')
p_from_part.add_argument('-f', '--formatted', action='store_true', help='格式化数字')
# of-part: whole * (percent/100) = part
p_of_part = subparsers.add_parser('of-part', help='已知整体和百分比,求部分')
p_of_part.add_argument('whole', type=float, help='整体值')
p_of_part.add_argument('percent', type=float, help='百分比')
p_of_part.add_argument('-p', '--precision', type=int, default=2, help='小数精度')
p_of_part.add_argument('-f', '--formatted', action='store_true', help='格式化数字')
# change: (new-old)/old * 100
p_change = subparsers.add_parser('change', help='计算增减百分比')
p_change.add_argument('old', type=float, help='原始值')
p_change.add_argument('new', type=float, help='新值')
p_change.add_argument('-p', '--precision', type=int, default=2, help='小数精度')
p_change.add_argument('-f', '--formatted', action='store_true', help='格式化数字')
args = parser.parse_args()
try:
if args.command == 'percent':
result = calculate_percent(args.part, args.whole, args.precision)
formatted = format_number(result, args.formatted)
print(f"{formatted}%")
elif args.command == 'from-part':
result = calculate_from_part(args.part, args.percent, args.precision)
formatted = format_number(result, args.formatted)
print(formatted)
elif args.command == 'of-part':
result = calculate_of_part(args.whole, args.percent, args.precision)
formatted = format_number(result, args.formatted)
print(formatted)
elif args.command == 'change':
result = calculate_change(args.old, args.new, args.precision)
formatted = format_number(result, args.formatted)
if result > 0:
print(f"+{formatted}%")
else:
print(f"{formatted}%")
else:
parser.print_help()
sys.exit(1)
except ValueError as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
提供纯 Python 实现的颜色格式转换工具,支持 HEX、RGB、HSL 互转及终端颜色预览功能。
# cn-color-tool - 颜色转换工具
纯 Python 标准库实现的颜色格式转换工具。
## 功能
- **HEX → RGB**:十六进制颜色码转 RGB 元组
- **RGB → HEX**:RGB 转十六进制颜色码
- **RGB → HSL**:RGB 转 HSL
- **HSL → RGB**:HSL 转 RGB
- **颜色预览**:输出适合终端显示的颜色信息
## 使用方式
```bash
# HEX 转 RGB
python3 cn_color_tool.py hex2rgb "#FF5733"
# RGB 转 HEX
python3 cn_color_tool.py rgb2hex 255 87 51
# RGB 转 HSL
python3 cn_color_tool.py rgb2hsl 255 87 51
# HSL 转 RGB
python3 cn_color_tool.py hsl2rgb 11 100 60
# 完整转换(显示所有格式)
python3 cn_color_tool.py convert "#FF5733"
# HEX 转 HSL(一步到位)
python3 cn_color_tool.py hex2hsl "#FF5733"
```
## 技术说明
- 纯 Python 标准库(`colorsys`、`argparse`)
- 无外部依赖
- HSL 中 H 范围 0-360,S/L 范围 0-100
FILE:scripts/color_tool.py
#!/usr/bin/env python3
"""
颜色转换工具
纯 Python 标准库实现,支持 HEX / RGB / HSL 互转
"""
import argparse
import re
import sys
import colorsys
def hex_to_rgb(hex_color: str) -> tuple:
"""HEX 颜色码转 RGB 元组"""
hex_color = hex_color.lstrip('#')
if len(hex_color) not in (3, 6):
raise ValueError(f"无效的 HEX 颜色码: #{hex_color}")
if len(hex_color) == 3:
hex_color = ''.join(c * 2 for c in hex_color)
try:
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
except ValueError:
raise ValueError(f"无效的 HEX 颜色码: #{hex_color}")
return (r, g, b)
def rgb_to_hex(r: int, g: int, b: int) -> str:
"""RGB 转十六进制颜色码"""
for val in (r, g, b):
if not 0 <= val <= 255:
raise ValueError(f"RGB 值必须在 0-255 之间,当前: {val}")
return f"#{r:02X}{g:02X}{b:02X}"
def rgb_to_hsl(r: int, g: int, b: int) -> tuple:
"""RGB 转 HSL (H: 0-360, S: 0-100, L: 0-100)"""
for val in (r, g, b):
if not 0 <= val <= 255:
raise ValueError(f"RGB 值必须在 0-255 之间,当前: {val}")
# colorsys 使用 0-1 范围
h, l, s = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
return (round(h * 360), round(s * 100), round(l * 100))
def hsl_to_rgb(h: int, s: int, l: int) -> tuple:
"""HSL 转 RGB (H: 0-360, S: 0-100, L: 0-100)"""
if not (0 <= h <= 360):
raise ValueError(f"H 值必须在 0-360 之间,当前: {h}")
if not (0 <= s <= 100):
raise ValueError(f"S 值必须在 0-100 之间,当前: {s}")
if not (0 <= l <= 100):
raise ValueError(f"L 值必须在 0-100 之间,当前: {l}")
# colorsys 使用 0-1 范围
r, g, b = colorsys.hls_to_rgb(h / 360, l / 100, s / 100)
return (round(r * 255), round(g * 255), round(b * 255))
def hex_to_hsl(hex_color: str) -> tuple:
"""一步到位 HEX 转 HSL"""
r, g, b = hex_to_rgb(hex_color)
return rgb_to_hsl(r, g, b)
def format_hsl(h: int, s: int, l: int) -> str:
"""格式化 HSL 字符串"""
return f"hsl({h}, {s}%, {l}%)"
def main():
parser = argparse.ArgumentParser(
description='颜色转换工具 - HEX / RGB / HSL 互转',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
%(prog)s hex2rgb "#FF5733" HEX 转 RGB
%(prog)s rgb2hex 255 87 51 RGB 转 HEX
%(prog)s rgb2hsl 255 87 51 RGB 转 HSL
%(prog)s hsl2rgb 11 100 60 HSL 转 RGB
%(prog)s hex2hsl "#FF5733" HEX 转 HSL(一步到位)
%(prog)s convert "#FF5733" 完整转换(显示所有格式)
'''
)
subparsers = parser.add_subparsers(dest='command', help='子命令')
p_hex2rgb = subparsers.add_parser('hex2rgb', help='HEX 转 RGB')
p_hex2rgb.add_argument('hex', help='HEX 颜色码,如 #FF5733')
p_rgb2hex = subparsers.add_parser('rgb2hex', help='RGB 转 HEX')
p_rgb2hex.add_argument('r', type=int, help='红色 (0-255)')
p_rgb2hex.add_argument('g', type=int, help='绿色 (0-255)')
p_rgb2hex.add_argument('b', type=int, help='蓝色 (0-255)')
p_rgb2hsl = subparsers.add_parser('rgb2hsl', help='RGB 转 HSL')
p_rgb2hsl.add_argument('r', type=int, help='红色 (0-255)')
p_rgb2hsl.add_argument('g', type=int, help='绿色 (0-255)')
p_rgb2hsl.add_argument('b', type=int, help='蓝色 (0-255)')
p_hsl2rgb = subparsers.add_parser('hsl2rgb', help='HSL 转 RGB')
p_hsl2rgb.add_argument('h', type=int, help='色相 (0-360)')
p_hsl2rgb.add_argument('s', type=int, help='饱和度 (0-100)')
p_hsl2rgb.add_argument('l', type=int, help='亮度 (0-100)')
p_hex2hsl = subparsers.add_parser('hex2hsl', help='HEX 转 HSL')
p_hex2hsl.add_argument('hex', help='HEX 颜色码,如 #FF5733')
p_convert = subparsers.add_parser('convert', help='完整转换(显示所有格式)')
p_convert.add_argument('hex', help='HEX 颜色码,如 #FF5733')
args = parser.parse_args()
try:
if args.command == 'hex2rgb':
r, g, b = hex_to_rgb(args.hex)
print(f"rgb({r}, {g}, {b})")
elif args.command == 'rgb2hex':
print(rgb_to_hex(args.r, args.g, args.b))
elif args.command == 'rgb2hsl':
h, s, l = rgb_to_hsl(args.r, args.g, args.b)
print(format_hsl(h, s, l))
elif args.command == 'hsl2rgb':
r, g, b = hsl_to_rgb(args.h, args.s, args.l)
print(f"rgb({r}, {g}, {b})")
elif args.command == 'hex2hsl':
h, s, l = hex_to_hsl(args.hex)
print(format_hsl(h, s, l))
elif args.command == 'convert':
hex_color = args.hex
r, g, b = hex_to_rgb(hex_color)
h, s, l = rgb_to_hsl(r, g, b)
print(f"HEX: {hex_color}")
print(f"RGB: rgb({r}, {g}, {b})")
print(f"HSL: {format_hsl(h, s, l)}")
else:
parser.print_help()
sys.exit(1)
except ValueError as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
生成指定数量的随机单词、句子或段落,支持中英文模式及安全随机或固定种子复现。
# cn-lorem-ipsum - 随机文本生成器
纯 Python 标准库实现的 Lorem Ipsum 随机文本生成工具。
## 功能
- **单词生成**:生成指定数量的随机单词
- **句子生成**:生成指定数量的完整句子
- **段落生成**:生成指定数量的段落
- **固定种子**:支持 `secrets` 模块随机种子(安全随机)
## 使用方式
```bash
# 生成 10 个随机单词
python3 cn_lorem_ipsum.py words 10
# 生成 5 个完整句子
python3 cn_lorem_ipsum.py sentences 5
# 生成 3 个段落
python3 cn_lorem_ipsum.py paragraphs 3
# 指定种子(可复现)
python3 cn_lorem_ipsum.py words 20 --seed 42
# 指定最小/最大单词数
python3 cn_lorem_ipsum.py words 50 --min 3 --max 12
# 中文模式(中文占位文本)
python3 cn_lorem_ipsum.py words 10 --lang zh
```
## 技术说明
- 纯 Python 标准库(`secrets`、`argparse`、`random`)
- 默认使用 `secrets.choice` 作为随机源(安全随机)
- 可选 `random` 配合种子实现可复现结果
- 支持中英文占位文本
FILE:scripts/cn_lorem_ipsum.py
#!/usr/bin/env python3
"""
cn-lorem-ipsum - 占位文本生成器
生成随机中文/英文文本、姓名、手机号、邮箱
"""
import random
import argparse
# 中文字符库
CN_CHARS = '的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞'
# 常用词库
CN_WORDS = [
'公司', '项目', '用户', '产品', '服务', '系统', '数据', '功能', '模块', '接口',
'开发', '设计', '测试', '部署', '配置', '优化', '问题', '解决', '方案', '策略',
'技术', '工具', '平台', '应用', '网站', 'APP', '小程序', '服务器', '数据库',
'网络', '安全', '性能', '效率', '质量', '管理', '团队', '合作', '沟通', '需求',
'分析', '研究', '学习', '经验', '分享', '总结', '文档', '报告', '会议', '讨论',
'市场', '运营', '推广', '营销', '品牌', '客户', '业务', '收入', '利润', '成本',
'发展', '创新', '趋势', '未来', '机会', '挑战', '竞争', '优势', '劣势', '策略',
]
# 英文单词库
EN_WORDS = [
'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit',
'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore',
'magna', 'aliqua', 'enim', 'ad', 'minim', 'veniam', 'quis', 'nostrud',
'exercitation', 'ullamco', 'laboris', 'nisi', 'aliquip', 'ex', 'ea', 'commodo',
'consequat', 'duis', 'aute', 'irure', 'in', 'reprehenderit', 'voluptate',
'velit', 'esse', 'cillum', 'fugiat', 'nulla', 'pariatur', 'excepteur', 'sint',
'occaecat', 'cupidatat', 'non', 'proident', 'sunt', 'culpa', 'qui', 'officia',
'deserunt', 'mollit', 'anim', 'id', 'est', 'laborum', 'the', 'quick', 'brown',
'fox', 'jumps', 'over', 'lazy', 'dog', 'hello', 'world', 'test', 'example',
]
# 姓氏
CN_SURNAMES = ['王', '李', '张', '刘', '陈', '杨', '赵', '黄', '周', '吴', '徐', '孙', '胡', '朱', '高', '林', '何', '郭', '马', '罗', '梁', '宋', '郑', '谢', '韩', '唐', '冯', '于', '董', '萧', '程', '曹', '袁', '邓', '许', '傅', '沈', '曾', '彭', '吕']
# 名字
CN_GIVEN_NAMES = ['伟', '芳', '娜', '秀', '敏', '静', '丽', '强', '磊', '军', '洋', '勇', '艳', '杰', '娟', '涛', '明', '超', '秀', '霞', '平', '刚', '桂英', '建华', '建国', '志强', '永强', '晓东', '晓峰', '晓华', '晓明']
# 邮箱域名
EMAIL_DOMAINS = ['gmail.com', 'qq.com', '163.com', '126.com', 'outlook.com', 'hotmail.com', 'sina.com', 'sohu.com', 'foxmail.com']
def generate_cn_paragraph(words=50):
"""生成中文段落"""
result = []
for _ in range(words):
# 随机选择词汇
phrase = ''.join(random.choices(CN_WORDS, k=random.randint(2, 6)))
result.append(phrase)
return ''.join(result)
def generate_en_paragraph(words=50):
"""生成英文段落"""
result = random.choices(EN_WORDS, k=words)
# 首字母大写
result[0] = result[0].capitalize()
return ' '.join(result)
def generate_cn_name():
"""生成中文姓名"""
surname = random.choice(CN_SURNAMES)
given = ''.join(random.choices(CN_GIVEN_NAMES, k=2))
return surname + given
def generate_phone():
"""生成中国手机号"""
prefixes = ['130', '131', '132', '133', '134', '135', '136', '137', '138', '139',
'150', '151', '152', '153', '155', '156', '157', '158', '159',
'180', '181', '182', '183', '184', '185', '186', '187', '188', '189',
'198', '199']
prefix = random.choice(prefixes)
suffix = ''.join([str(random.randint(0, 9)) for _ in range(8)])
return prefix + suffix
def generate_email(name=None):
"""生成邮箱"""
if not name:
name = generate_cn_name()
# 转换姓名为拼音
name_pinyin = ''.join(c for c in name if '\u4e00' <= c <= '\u9fff')
if not name_pinyin:
name_pinyin = 'user'
domain = random.choice(EMAIL_DOMAINS)
patterns = [
name_pinyin,
name_pinyin + str(random.randint(1, 999)),
name_pinyin[0] + str(random.randint(10, 99)),
]
return random.choice(patterns).lower() + '@' + domain
def main():
parser = argparse.ArgumentParser(description='占位文本生成器')
parser.add_argument('--cn', action='store_true', help='生成中文文本')
parser.add_argument('--en', action='store_true', help='生成英文文本')
parser.add_argument('--name', action='store_true', help='生成中文姓名')
parser.add_argument('--phone', action='store_true', help='生成手机号')
parser.add_argument('--email', action='store_true', help='生成邮箱')
parser.add_argument('--count', type=int, default=1, help='生成数量')
parser.add_argument('--words', type=int, default=50, help='英文单词数')
parser.add_argument('--paragraphs', type=int, default=1, help='段落数')
args = parser.parse_args()
# 如果没有任何参数,默认生成中文
if not any([args.cn, args.en, args.name, args.phone, args.email]):
args.cn = True
for i in range(args.count):
if args.cn:
for _ in range(args.paragraphs):
print(generate_cn_paragraph(args.words))
if args.count > 1 and i < args.count - 1:
print()
if args.en:
for _ in range(args.paragraphs):
print(generate_en_paragraph(args.words))
if args.count > 1 and i < args.count - 1:
print()
if args.name:
print(generate_cn_name())
if args.phone:
print(generate_phone())
if args.email:
print(generate_email())
if args.count > 1 and i < args.count - 1:
if not (args.cn or args.en):
print('---')
if __name__ == '__main__':
main()
FILE:scripts/lorem_ipsum.py
#!/usr/bin/env python3
"""
随机文本生成器
纯 Python 标准库实现
"""
import secrets
import argparse
import random
import sys
# Lorem Ipsum 英文单词库
ENGLISH_WORDS = [
'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing',
'elit', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore',
'et', 'dolore', 'magna', 'aliqua', 'enim', 'ad', 'minim', 'veniam',
'quis', 'nostrud', 'exercitation', 'ullamco', 'laboris', 'nisi',
'aliquip', 'ex', 'ea', 'commodo', 'consequat', 'duis', 'aute', 'irure',
'in', 'reprehenderit', 'voluptate', 'velit', 'esse', 'cillum', 'fugiat',
'nulla', 'pariatur', 'excepteur', 'sint', 'occaecat', 'cupidatat',
'non', 'proident', 'sunt', 'culpa', 'qui', 'officia', 'deserunt',
'mollit', 'anim', 'id', 'est', 'laborum', 'accumsan', 'bibendum',
'erat', 'volutpat', 'nam', 'mi', 'pretium', 'risus', 'tristique',
'senectus', 'netus', 'malesuada', 'fames', 'turpis', 'egestas',
'proin', 'sagittis', 'nisl', 'rhoncus', 'mattis', 'purus', 'enim',
]
# 中文占位文本
CHINESE_WORDS = [
'的', '是', '了', '在', '和', '与', '以及', '或者', '还是',
'但是', '然而', '因为', '所以', '如果', '虽然', '虽然说',
'这个', '那个', '一个', '一些', '可以', '能够', '应该',
'必须', '需要', '要求', '希望', '想要', '觉得', '认为',
'可能', '也许', '大概', '应该', '必须', '一定', '必然',
]
def secure_choice(sequence):
"""使用 secrets 模块安全随机选择"""
return secrets.choice(sequence)
def random_choice(sequence, seed=None):
"""使用 random 模块随机选择(可选种子)"""
if seed is not None:
random.seed(seed)
return random.choice(sequence)
def generate_words(count: int, lang: str = 'en', use_seed: bool = False, seed: int = None) -> list:
"""生成随机单词列表"""
word_list = ENGLISH_WORDS if lang == 'en' else CHINESE_WORDS
result = []
for _ in range(count):
if use_seed:
result.append(random_choice(word_list, seed))
else:
result.append(secure_choice(word_list))
return result
def generate_sentences(count: int, lang: str = 'en', use_seed: bool = False, seed: int = None,
min_words: int = 5, max_words: int = 15) -> list:
"""生成完整句子"""
sentences = []
for _ in range(count):
word_count = random.randint(min_words, max_words) if use_seed else secrets.randbelow(max_words - min_words + 1) + min_words
words = generate_words(word_count, lang, use_seed, seed)
if lang == 'en':
sentences.append(' '.join(words).capitalize() + '.')
else:
sentences.append(''.join(words) + '。')
return sentences
def generate_paragraphs(count: int, lang: str = 'en', use_seed: bool = False, seed: int = None,
min_sentences: int = 3, max_sentences: int = 8) -> list:
"""生成段落"""
paragraphs = []
for _ in range(count):
sentence_count = random.randint(min_sentences, max_sentences) if use_seed else secrets.randbelow(max_sentences - min_sentences + 1) + min_sentences
sentences = generate_sentences(sentence_count, lang, use_seed, seed)
paragraphs.append(' '.join(sentences))
return paragraphs
def main():
parser = argparse.ArgumentParser(
description='随机文本生成器',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
%(prog)s words 10 生成 10 个随机单词
%(prog)s sentences 5 生成 5 个完整句子
%(prog)s paragraphs 3 生成 3 个段落
%(prog)s words 20 --seed 42 使用固定种子
%(prog)s words 10 --lang zh 中文模式
'''
)
subparsers = parser.add_subparsers(dest='command', help='子命令')
# words
p_words = subparsers.add_parser('words', help='生成随机单词')
p_words.add_argument('count', type=int, help='单词数量')
p_words.add_argument('--seed', type=int, help='随机种子(用于复现)')
p_words.add_argument('--lang', choices=['en', 'zh'], default='en', help='语言')
# sentences
p_sentences = subparsers.add_parser('sentences', help='生成随机句子')
p_sentences.add_argument('count', type=int, help='句子数量')
p_sentences.add_argument('--seed', type=int, help='随机种子(用于复现)')
p_sentences.add_argument('--lang', choices=['en', 'zh'], default='en', help='语言')
p_sentences.add_argument('--min', type=int, default=5, help='每句最少单词数')
p_sentences.add_argument('--max', type=int, default=15, help='每句最多单词数')
# paragraphs
p_paragraphs = subparsers.add_parser('paragraphs', help='生成随机段落')
p_paragraphs.add_argument('count', type=int, help='段落数量')
p_paragraphs.add_argument('--seed', type=int, help='随机种子(用于复现)')
p_paragraphs.add_argument('--lang', choices=['en', 'zh'], default='en', help='语言')
p_paragraphs.add_argument('--min-sentences', type=int, default=3, help='每段最少句子数')
p_paragraphs.add_argument('--max-sentences', type=int, default=8, help='每段最多句子数')
args = parser.parse_args()
if args.command == 'words':
use_seed = args.seed is not None
words = generate_words(args.count, args.lang, use_seed, args.seed)
print(' '.join(words))
elif args.command == 'sentences':
use_seed = args.seed is not None
sentences = generate_sentences(args.count, args.lang, use_seed, args.seed,
args.min, args.max)
print('\n'.join(sentences))
elif args.command == 'paragraphs':
use_seed = args.seed is not None
paragraphs = generate_paragraphs(args.count, args.lang, use_seed, args.seed,
args.min_sentences, args.max_sentences)
print('\n\n'.join(paragraphs))
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
基于纯 Python 标准库实现的时间戳与日期时间相互转换,支持当前时间戳获取和时区设置。
# cn-timestamp-tool - 时间戳转换工具
纯 Python 标准库实现的时间戳转换工具。
## 功能
- **时间戳 → 日期时间**:将 Unix 时间戳转换为可读日期时间
- **日期时间 → 时间戳**:将日期时间转换为 Unix 时间戳
- **当前时间戳**:获取当前 Unix 时间戳(秒/毫秒)
- **时区支持**:支持不同时区的转换
## 使用方式
```bash
# 当前时间戳(秒)
python3 cn_timestamp_tool.py now
# 当前时间戳(毫秒)
python3 cn_timestamp_tool.py now-ms
# 时间戳转日期时间
python3 cn_timestamp_tool.py to-datetime 1714214400
# 日期时间转时间戳
python3 cn_timestamp_tool.py to-timestamp "2024-04-27 14:30:00"
# 指定格式输出
python3 cn_timestamp_tool.py format 1714214400 "%Y年%m月%d日 %H:%M:%S"
# 带时区转换
python3 cn_timestamp_tool.py to-datetime 1714214400 -z Asia/Shanghai
```
## 技术说明
- 纯 Python 标准库(`datetime`、`time`、`argparse`、`zoneinfo`)
- 无外部依赖
- 默认时区 Asia/Shanghai
FILE:scripts/timestamp_tool.py
#!/usr/bin/env python3
"""
时间戳转换工具
纯 Python 标准库实现
"""
import time
import argparse
import sys
from datetime import datetime, timezone
try:
from zoneinfo import ZoneInfo
except ImportError:
ZoneInfo = None
def get_current_timestamp() -> int:
"""获取当前时间戳(秒)"""
return int(time.time())
def get_current_timestamp_ms() -> int:
"""获取当前时间戳(毫秒)"""
return int(time.time() * 1000)
def timestamp_to_datetime(ts: int, tz_name: str = 'Asia/Shanghai') -> datetime:
"""时间戳转日期时间"""
# 自动判断秒/毫秒
if ts > 10**10:
ts = ts / 1000
dt = datetime.fromtimestamp(ts)
if tz_name:
try:
dt = datetime.fromtimestamp(ts, tz=ZoneInfo(tz_name))
except Exception:
pass
return dt
def datetime_to_timestamp(dt_str: str, fmt: str = '%Y-%m-%d %H:%M:%S') -> int:
"""日期时间字符串转时间戳"""
dt = datetime.strptime(dt_str, fmt)
return int(dt.timestamp())
def format_timestamp(ts: int, fmt: str, tz_name: str = 'Asia/Shanghai') -> str:
"""格式化时间戳"""
dt = timestamp_to_datetime(ts, tz_name)
return dt.strftime(fmt)
def main():
parser = argparse.ArgumentParser(
description='时间戳转换工具',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
%(prog)s now 当前时间戳(秒)
%(prog)s now-ms 当前时间戳(毫秒)
%(prog)s to-datetime 1714214400 时间戳转日期时间
%(prog)s to-timestamp "2024-04-27 14:30:00" 日期时间转时间戳
%(prog)s format 1714214400 "%%Y-%%m-%%d %%H:%%M:%%S" 格式化输出
'''
)
subparsers = parser.add_subparsers(dest='command', help='子命令')
# now
subparsers.add_parser('now', help='获取当前时间戳(秒)')
subparsers.add_parser('now-ms', help='获取当前时间戳(毫秒)')
# to-datetime
p_to_dt = subparsers.add_parser('to-datetime', help='时间戳转日期时间')
p_to_dt.add_argument('timestamp', type=int, help='Unix 时间戳')
p_to_dt.add_argument('-z', '--timezone', default='Asia/Shanghai', help='时区')
# to-timestamp
p_to_ts = subparsers.add_parser('to-timestamp', help='日期时间转时间戳')
p_to_ts.add_argument('datetime', help='日期时间字符串')
p_to_ts.add_argument('-f', '--format', default='%Y-%m-%d %H:%M:%S', help='日期格式')
# format
p_fmt = subparsers.add_parser('format', help='格式化时间戳')
p_fmt.add_argument('timestamp', type=int, help='Unix 时间戳')
p_fmt.add_argument('format', help='输出格式,如 %Y-%m-%d %H:%M:%S')
p_fmt.add_argument('-z', '--timezone', default='Asia/Shanghai', help='时区')
args = parser.parse_args()
if args.command == 'now':
print(get_current_timestamp())
elif args.command == 'now-ms':
print(get_current_timestamp_ms())
elif args.command == 'to-datetime':
dt = timestamp_to_datetime(args.timestamp, args.timezone)
print(dt.strftime('%Y-%m-%d %H:%M:%S'))
elif args.command == 'to-timestamp':
ts = datetime_to_timestamp(args.datetime, args.format)
print(ts)
elif args.command == 'format':
result = format_timestamp(args.timestamp, args.format, args.timezone)
print(result)
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
通过纯 Python 标准库实现字符串和文件的 Base64 编解码,支持目录批量编码,无需外部依赖。
# cn-base64-tool - Base64 编解码工具
纯 Python 标准库实现的 Base64 编解码工具。
## 功能
- **编码**:将字符串或文件内容编码为 Base64
- **解码**:将 Base64 字符串还原为原始内容
- **文件支持**:支持对文件进行 Base64 编码/解码
## 使用方式
```bash
# 编码字符串
python3 cn_base64_tool.py encode "Hello World"
# 解码 Base64 字符串
python3 cn_base64_tool.py decode "SGVsbG8gV29ybGQ="
# 编码文件
python3 cn_base64_tool.py encode_file input.png
# 解码文件
python3 cn_base64_tool.py decode_file output.b64 output.png
# 批量编码(目录)
python3 cn_base64_tool.py encode_dir ./my_folder
```
## 技术说明
- 纯 Python 标准库(`base64`、`argparse`)
- 无外部依赖
- 支持 UTF-8 字符串
FILE:scripts/base64_tool.py
#!/usr/bin/env python3
"""
Base64 编解码工具
纯 Python 标准库实现
"""
import base64
import argparse
import sys
import os
def encode_string(text: str) -> str:
"""将字符串编码为 Base64"""
return base64.b64encode(text.encode('utf-8')).decode('ascii')
def decode_string(b64_text: str) -> str:
"""将 Base64 字符串解码为原始字符串"""
try:
return base64.b64decode(b64_text.encode('ascii')).decode('utf-8')
except Exception as e:
raise ValueError(f"解码失败: {e}")
def encode_file(input_path: str) -> str:
"""将文件内容编码为 Base64"""
if not os.path.exists(input_path):
raise FileNotFoundError(f"文件不存在: {input_path}")
with open(input_path, 'rb') as f:
data = f.read()
return base64.b64encode(data).decode('ascii')
def decode_file(b64_text: str, output_path: str) -> None:
"""将 Base64 内容解码写入文件"""
data = base64.b64decode(b64_text.encode('ascii'))
with open(output_path, 'wb') as f:
f.write(data)
def main():
parser = argparse.ArgumentParser(
description='Base64 编解码工具',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
%(prog)s encode "Hello World" 编码字符串
%(prog)s decode "SGVsbG8gV29ybGQ=" 解码字符串
%(prog)s encode_file image.png 编码文件
%(prog)s decode_file output.b64 out.png 解码到文件
'''
)
subparsers = parser.add_subparsers(dest='command', help='子命令')
# encode
p_encode = subparsers.add_parser('encode', help='编码字符串为 Base64')
p_encode.add_argument('text', help='要编码的字符串')
# decode
p_decode = subparsers.add_parser('decode', help='解码 Base64 字符串')
p_decode.add_argument('text', help='要解码的 Base64 字符串')
# encode_file
p_encode_file = subparsers.add_parser('encode_file', help='将文件编码为 Base64')
p_encode_file.add_argument('input', help='输入文件路径')
p_encode_file.add_argument('-o', '--output', default='-', help='输出文件路径(默认 stdout)')
# decode_file
p_decode_file = subparsers.add_parser('decode_file', help='将 Base64 解码为文件')
p_decode_file.add_argument('b64', help='Base64 字符串或文件')
p_decode_file.add_argument('output', help='输出文件路径')
args = parser.parse_args()
if args.command == 'encode':
print(encode_string(args.text))
elif args.command == 'decode':
print(decode_string(args.text))
elif args.command == 'encode_file':
result = encode_file(args.input)
if args.output == '-':
print(result)
else:
with open(args.output, 'w') as f:
f.write(result)
print(f"已保存到: {args.output}")
elif args.command == 'decode_file':
decode_file(args.b64, args.output)
print(f"已保存到: {args.output}")
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
Hash生成器工具。支持MD5/SHA-1/SHA-256/SHA-512/BLAKE2b哈希、Base64编解码、UUID生成、HMAC签名。纯Python标准库,无需API Key。
---
slug: cn-hash-generator
name: Hash生成器
description: "Hash生成器工具。支持MD5/SHA-1/SHA-256/SHA-512/BLAKE2b哈希、Base64编解码、UUID生成、HMAC签名。纯Python标准库,无需API Key。"
keywords: hash, MD5, SHA256, Base64, UUID, HMAC, 哈希, 校验, 签名
version: "1.0.0"
author: 千策
---
# Hash生成器
多功能Hash工具,支持哈希生成、Base64编解码、UUID生成、HMAC签名。纯Python标准库实现,无需API Key。
## 功能
- **哈希生成**:MD5、SHA-1、SHA-256、SHA-512、BLAKE2b
- **Base64**:编码和解码
- **UUID**:随机UUID生成
- **HMAC签名**:密钥+消息签名
- 纯标准库(hashlib + uuid + base64),零依赖
## 使用示例
```
计算"Hello World"的SHA256
生成一个UUID
Base64编码"你好"
HMAC签名 消息"test" 密钥"key"
```
## 技术实现
调用 `scripts/cn_hash_generator.py`,支持参数:
- `--algo`:算法选择(md5/sha1/sha256/sha512/blake2)
- `--encode64`:Base64编码
- `--decode`:Base64解码
- `--uuid`:生成UUID
- `--hmac KEY`:HMAC签名
- `--upper`:输出大写
- `--count N`:UUID生成数量
## 注意事项
- Hash是单向的,不可逆
- MD5和SHA-1不建议用于安全场景
- 密码存储建议使用bcrypt而非简单Hash
FILE:metadata.json
{"version": "1.0.0", "author": "千策", "description": "Hash生成器工具。支持MD5/SHA-1/SHA-256/SHA-512/BLAKE2b哈希、Base64编解码、UUID生成、HMAC签名。纯Python标准库,无需API Key。"}
FILE:cn_hash_generator.py
#!/usr/bin/env python3
"""
cn-hash-generator - Hash生成器
支持多种Hash算法、Base64、UUID、HMAC
"""
import argparse
import hashlib
import uuid
import base64
import sys
import os
def md5_hash(text):
return hashlib.md5(text.encode()).hexdigest()
def sha1_hash(text):
return hashlib.sha1(text.encode()).hexdigest()
def sha256_hash(text):
return hashlib.sha256(text.encode()).hexdigest()
def sha512_hash(text):
return hashlib.sha512(text.encode()).hexdigest()
def blake2b_hash(text):
return hashlib.blake2b(text.encode()).hexdigest()
def hmac_sign(text, key, algo='sha256'):
algorithms = {
'md5': hashlib.md5,
'sha1': hashlib.sha1,
'sha256': hashlib.sha256,
'sha512': hashlib.sha512,
}
if algo.lower() not in algorithms:
print(f"不支持的算法: {algo}")
return None
h = hashlib.new(algo.lower(), key.encode())
h.update(text.encode())
return h.hexdigest()
def generate_uuid():
return str(uuid.uuid4())
def base64_encode(text):
return base64.b64encode(text.encode()).decode()
def base64_decode(text):
return base64.b64decode(text.encode()).decode()
def main():
parser = argparse.ArgumentParser(description='Hash生成器')
parser.add_argument('text', nargs='?', help='要Hash的文本')
parser.add_argument('--algo', default='sha256',
choices=['md5', 'sha1', 'sha256', 'sha512', 'blake2'],
help='Hash算法')
parser.add_argument('--encode64', action='store_true', help='Base64编码')
parser.add_argument('--decode', action='store_true', help='Base64解码')
parser.add_argument('--uuid', action='store_true', help='生成UUID')
parser.add_argument('--hmac', metavar='KEY', help='HMAC密钥')
parser.add_argument('--upper', action='store_true', help='输出大写')
parser.add_argument('--count', type=int, default=1, help='生成数量')
args = parser.parse_args()
# UUID模式
if args.uuid:
for i in range(args.count):
uid = generate_uuid()
print(uid.upper() if args.upper else uid)
return
# Base64模式
if args.encode64:
if not args.text:
print("请提供要编码的文本")
return
result = base64_encode(args.text)
print(result.upper() if args.upper else result)
return
if args.decode:
if not args.text:
print("请提供要解码的文本")
return
try:
result = base64_decode(args.text)
print(result)
except Exception as e:
print(f"解码失败: {e}")
return
# Hash模式
if not args.text:
# 尝试从stdin读取
if not sys.stdin.isatty():
args.text = sys.stdin.read().strip()
else:
print("用法:")
print(" python3 cn_hash_generator.py '文本' --algo sha256")
print(" python3 cn_hash_generator.py --uuid")
print(" python3 cn_hash_generator.py '文本' --encode64")
print(" echo 'SGVsbG8=' | python3 cn_hash_generator.py --decode")
return
# HMAC模式
if args.hmac:
result = hmac_sign(args.text, args.hmac, args.algo)
if result:
print(result.upper() if args.upper else result)
return
# 普通Hash
if args.algo == 'md5':
result = md5_hash(args.text)
elif args.algo == 'sha1':
result = sha1_hash(args.text)
elif args.algo == 'sha256':
result = sha256_hash(args.text)
elif args.algo == 'sha512':
result = sha512_hash(args.text)
elif args.algo == 'blake2':
result = blake2b_hash(args.text)
else:
result = sha256_hash(args.text)
print(result.upper() if args.upper else result)
if __name__ == '__main__':
main()安全的随机密码生成器。支持自定义长度、字符类型(大小写字母、数字、特殊符号)、排除相似字符、批量生成。纯Python标准库,无需API Key。
---
slug: cn-password-generator
name: 安全密码生成器
description: "安全的随机密码生成器。支持自定义长度、字符类型(大小写字母、数字、特殊符号)、排除相似字符、批量生成。纯Python标准库,无需API Key。"
keywords: 密码, 生成密码, 随机密码, 安全, password, generator, 强密码
version: "1.0.0"
author: 千策
---
# 安全密码生成器
安全的随机密码生成器,纯Python标准库实现,无需API Key,开箱即用。
## 功能
- 随机密码生成(默认16位)
- 自定义字符类型:大写、小写、数字、特殊符号
- 排除相似字符(0O1lI等),避免混淆
- 批量生成多个密码
- 纯标准库,零依赖
## 使用示例
```
生成一个16位密码
生成10个20位强密码
生成不含特殊符号的12位密码
```
## 技术实现
调用 `scripts/password_generator.py` 生成密码,支持参数:
- 长度(默认16)
- 字符类型开关(大写/小写/数字/特殊符号)
- 排除相似字符(--exclude-similar)
- 生成数量
FILE:password_generator.py
#!/usr/bin/env python3
"""
密码生成器 - 生成安全的随机密码
"""
import random
import string
import sys
import json
def generate_password(length=16, use_upper=True, use_lower=True, use_digits=True, use_special=True, exclude_similar=False):
"""生成随机密码"""
chars = ''
if use_upper:
chars += string.ascii_uppercase
if use_lower:
chars += string.ascii_lowercase
if use_digits:
chars += string.digits
if use_special:
chars += '!@#$%^&*()_+-=[]{}|;:,.<>?'
if exclude_similar:
similar = '0O1lI'
chars = ''.join(c for c in chars if c not in similar)
if not chars:
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
def main():
args = sys.argv[1:] if len(sys.argv) > 1 else []
# 默认参数
length = 16
count = 1
use_upper = True
use_lower = True
use_digits = True
use_special = True
exclude_similar = False
# 解析参数
i = 0
while i < len(args):
arg = args[i]
if arg.isdigit():
if length == 16:
length = int(arg)
else:
count = int(arg)
elif arg in ['--no-upper', '-nu']:
use_upper = False
elif arg in ['--no-lower', '-nl']:
use_lower = False
elif arg in ['--no-digits', '-nd']:
use_digits = False
elif arg in ['--no-special', '-ns']:
use_special = False
elif arg in ['--exclude-similar', '-es']:
exclude_similar = True
i += 1
# 生成密码
passwords = [generate_password(length, use_upper, use_lower, use_digits, use_special, exclude_similar) for _ in range(count)]
result = {
'count': count,
'length': length,
'passwords': passwords
}
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == '__main__':
main()
PPT大纲生成助手。输入主题或内容,自动生成PPT大纲、分页结构、每页要点。支持工作汇报、产品发布、培训课件、商业计划等多种场景。输出可直接导入PPT软件。
---
name: cn-ppt-outline
description: "PPT大纲生成助手。输入主题或内容,自动生成PPT大纲、分页结构、每页要点。支持工作汇报、产品发布、培训课件、商业计划等多种场景。输出可直接导入PPT软件。"
metadata:
openclaw:
emoji: 📑
category: productivity
tags:
- ppt
- presentation
- outline
- office
- chinese
scope:
- "根据主题生成PPT大纲结构"
- "分页设计(每页标题+要点)"
- "多种场景模板(汇报/发布/培训/计划)"
- "导出Markdown/PowerPoint格式"
- "设计建议(配色/排版/图表)"
install: |
pip install python-pptx
env:
OPENAI_API_KEY: "可选,用于AI增强内容生成。不配置则使用模板生成。"
entry:
script: scripts/ppt_outline.py
args: []
---
# PPT大纲生成助手
## 功能
- 输入主题 → 自动生成完整PPT大纲
- 分页设计:每页标题 + 3-5个要点
- 多种场景:工作汇报、产品发布、培训课件、商业计划
- 导出格式:Markdown大纲、PowerPoint文件
- 设计建议:配色方案、排版建议、图表推荐
## 使用方法
### 生成大纲
```bash
# 工作汇报
python scripts/ppt_outline.py generate "Q1季度销售工作总结" --type report
# 产品发布
python scripts/ppt_outline.py generate "智能水杯新品发布" --type product
# 培训课件
python scripts/ppt_outline.py generate "新员工入职培训" --type training
# 商业计划
python scripts/ppt_outline.py generate "AI教育创业计划" --type business
```
### 导出PPT文件
```bash
python scripts/ppt_outline.py generate "主题" --type report --export ppt --output 汇报.pptx
```
### 查看模板
```bash
python scripts/ppt_outline.py templates
```
## 场景类型
| 类型 | 适用场景 | 结构特点 |
|------|---------|---------|
| report | 工作汇报/总结 | 回顾→成果→问题→计划 |
| product | 产品发布/介绍 | 痛点→方案→产品→优势→行动 |
| training | 培训课件 | 目标→理论→案例→练习→总结 |
| business | 商业计划 | 市场→方案→模式→团队→融资 |
| proposal | 方案建议 | 背景→问题→方案→收益→风险 |
## 输出格式
### Markdown大纲
```markdown
# Q1季度销售工作总结
## 第1页:封面
- 标题:Q1季度销售工作总结
- 副标题:回顾与展望
## 第2页:目录
- 1. 季度回顾
- 2. 核心成果
- 3. 问题分析
- 4. 下季度计划
## 第3页:季度回顾
- 时间跨度:2024年1-3月
- 市场环境:整体向好
- 团队状态:满编运转
...
```
### PowerPoint文件
直接生成.pptx文件,包含:
- 预设版式(标题页、内容页、结束页)
- 分页结构
- 占位符文本
## 设计建议
### 配色方案
- **商务蓝**:#1E3A5F + #4A90D9 + #E8F4F8
- **活力橙**:#FF6B35 + #F7C59F + #FFF3E6
- **专业绿**:#2E7D32 + #66BB6A + #E8F5E9
### 排版原则
- 每页不超过6行文字
- 每行不超过15个字
- 使用图标代替文字
- 留白不少于30%
### 图表推荐
- 对比数据:柱状图
- 趋势变化:折线图
- 占比分布:饼图
- 流程步骤:流程图
FILE:scripts/ppt_outline.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PPT大纲生成助手
主题 → 大纲 → PPT文件
"""
import os
import sys
from typing import List, Dict
from dataclasses import dataclass
try:
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RgbColor
HAS_PPTX = True
except ImportError:
HAS_PPTX = False
@dataclass
class Slide:
"""单页幻灯片"""
title: str
bullets: List[str]
layout: str = "content" # title/content/section/end
@dataclass
class PPTOutline:
"""PPT大纲"""
topic: str
slides: List[Slide]
design_tips: List[str]
# 场景模板
SCENE_TEMPLATES = {
"report": {
"name": "工作汇报",
"structure": ["封面", "目录", "回顾", "成果", "问题", "计划", "总结"],
"slides": [
Slide("封面", ["{topic}", "汇报人:XXX", "日期:XXXX年XX月"], "title"),
Slide("目录", ["1. 工作回顾", "2. 核心成果", "3. 问题分析", "4. 下阶段计划"]),
Slide("工作回顾", ["时间周期:XXXX年XX月-XX月", "主要任务:XXX", "完成情况:XXX", "关键节点:XXX"]),
Slide("核心成果", ["成果1:XXX(数据支撑)", "成果2:XXX(数据支撑)", "成果3:XXX(数据支撑)", "亮点:XXX"]),
Slide("问题分析", ["问题1:XXX", "原因:XXX", "影响:XXX", "改进方向:XXX"]),
Slide("下阶段计划", ["目标:XXX", "关键任务:XXX", "时间节点:XXX", "资源需求:XXX"]),
Slide("总结", ["核心观点", "感谢语", "联系方式"], "end")
]
},
"product": {
"name": "产品发布",
"structure": ["封面", "痛点", "方案", "产品", "优势", "应用", "行动"],
"slides": [
Slide("封面", ["{topic}", "Slogan:一句话卖点"], "title"),
Slide("市场痛点", ["现状问题1:XXX", "现状问题2:XXX", "用户痛点:XXX", "市场机会:XXX"]),
Slide("解决方案", ["核心思路:XXX", "解决路径:XXX", "创新点:XXX"]),
Slide("产品介绍", ["产品名称:XXX", "核心功能:XXX", "技术亮点:XXX", "使用场景:XXX"]),
Slide("竞争优势", ["对比维度1:我们 vs 竞品A vs 竞品B", "对比维度2:...", "核心优势总结"]),
Slide("应用场景", ["场景1:XXX", "场景2:XXX", "场景3:XXX", "客户案例:XXX"]),
Slide("行动号召", ["购买/试用方式", "优惠政策", "联系方式", "二维码"], "end")
]
},
"training": {
"name": "培训课件",
"structure": ["封面", "目标", "理论", "案例", "练习", "总结"],
"slides": [
Slide("封面", ["{topic}", "讲师:XXX", "时长:X小时"], "title"),
Slide("培训目标", ["学完本课,你将能够:", "• 掌握XXX技能", "• 理解XXX概念", "• 应用XXX方法"]),
Slide("课程大纲", ["1. 基础概念", "2. 核心方法", "3. 实战案例", "4. 练习巩固"]),
Slide("基础概念", ["概念1:定义+解释", "概念2:定义+解释", "概念3:定义+解释"]),
Slide("核心方法", ["方法1:步骤说明", "方法2:步骤说明", "注意事项:XXX"]),
Slide("实战案例", ["案例背景:XXX", "问题分析:XXX", "解决方案:XXX", "效果展示:XXX"]),
Slide("练习环节", ["练习1:XXX", "练习2:XXX", "参考答案:XXX"]),
Slide("课程总结", ["核心要点回顾", "延伸学习资源", "答疑时间", "感谢"], "end")
]
},
"business": {
"name": "商业计划",
"structure": ["封面", "市场", "方案", "模式", "团队", "融资"],
"slides": [
Slide("封面", ["{topic}", "一句话定位", "公司名称+Logo"], "title"),
Slide("市场机会", ["市场规模:XXX亿", "增长趋势:年增长率XX%", "痛点分析:XXX", "市场空白:XXX"]),
Slide("解决方案", ["产品/服务:XXX", "核心价值:XXX", "差异化优势:XXX"]),
Slide("商业模式", ["盈利模式:XXX", "收入来源:XXX", "成本结构:XXX", "盈利预测:XXX"]),
Slide("竞争优势", ["技术壁垒:XXX", "资源优势:XXX", "团队背景:XXX", "先发优势:XXX"]),
Slide("运营数据", ["用户数据:XXX", "营收数据:XXX", "增长数据:XXX", "关键指标:XXX"]),
Slide("团队介绍", ["创始人:XXX(背景)", "核心团队:XXX", "顾问团队:XXX"]),
Slide("融资计划", ["融资额度:XXX万", "出让股份:XX%", "资金用途:XXX", "里程碑:XXX"], "end")
]
},
"proposal": {
"name": "方案建议",
"structure": ["封面", "背景", "问题", "方案", "收益", "风险"],
"slides": [
Slide("封面", ["{topic}", "提案单位:XXX", "日期:XXXX年XX月"], "title"),
Slide("项目背景", ["背景介绍:XXX", "现状分析:XXX", "相关方:XXX"]),
Slide("问题分析", ["核心问题:XXX", "问题影响:XXX", "紧迫性:XXX"]),
Slide("解决方案", ["方案概述:XXX", "实施步骤:", " 1. XXX", " 2. XXX", " 3. XXX"]),
Slide("预期收益", ["收益1:XXX(量化)", "收益2:XXX(量化)", "收益3:XXX(量化)"]),
Slide("风险评估", ["风险1:XXX(应对措施)", "风险2:XXX(应对措施)"]),
Slide("实施计划", ["阶段1:XXX(时间+负责人)", "阶段2:XXX", "阶段3:XXX"]),
Slide("结语", ["方案价值总结", "期待合作", "联系方式"], "end")
]
}
}
def generate_outline(topic: str, scene_type: str = "report") -> PPTOutline:
"""生成PPT大纲"""
template = SCENE_TEMPLATES.get(scene_type, SCENE_TEMPLATES["report"])
slides = []
for slide_template in template["slides"]:
# 替换主题占位符
title = slide_template.title
bullets = [b.replace("{topic}", topic) for b in slide_template.bullets]
slides.append(Slide(title, bullets, slide_template.layout))
design_tips = [
f"配色建议:商务蓝(#1E3A5F)适合{template['name']}场景",
"排版原则:每页不超过6行,每行不超过15字",
"图表建议:数据用柱状图,趋势用折线图,占比用饼图",
"字体建议:标题用微软雅黑Bold,正文用微软雅黑",
"动画建议:简洁为主,避免过多花哨动画"
]
return PPTOutline(topic, slides, design_tips)
def export_markdown(outline: PPTOutline) -> str:
"""导出Markdown格式"""
lines = [f"# {outline.topic}", ""]
for i, slide in enumerate(outline.slides, 1):
lines.append(f"## 第{i}页:{slide.title}")
for bullet in slide.bullets:
lines.append(f"- {bullet}")
lines.append("")
lines.append("## 设计建议")
for tip in outline.design_tips:
lines.append(f"- {tip}")
return "\n".join(lines)
def export_pptx(outline: PPTOutline, output_path: str):
"""导出PowerPoint文件"""
if not HAS_PPTX:
print("请先安装python-pptx: pip install python-pptx")
return False
prs = Presentation()
for slide_data in outline.slides:
# 选择版式
if slide_data.layout == "title":
layout = prs.slide_layouts[0] # 标题页
elif slide_data.layout == "end":
layout = prs.slide_layouts[6] # 空白页
else:
layout = prs.slide_layouts[1] # 标题和内容
slide = prs.slides.add_slide(layout)
# 设置标题
if slide.shapes.title:
slide.shapes.title.text = slide_data.title
# 设置内容
if len(slide.placeholders) > 1:
body = slide.placeholders[1]
tf = body.text_frame
tf.text = slide_data.bullets[0] if slide_data.bullets else ""
for bullet in slide_data.bullets[1:]:
p = tf.add_paragraph()
p.text = bullet
p.level = 0
prs.save(output_path)
return True
def list_templates():
"""列出所有模板"""
print("可用PPT场景模板:")
print()
for key, template in SCENE_TEMPLATES.items():
print(f"【{key}】{template['name']}")
print(f" 结构:{' → '.join(template['structure'])}")
print(f" 页数:{len(template['slides'])}页")
print()
def main():
import argparse
parser = argparse.ArgumentParser(description="PPT大纲生成助手")
subparsers = parser.add_subparsers(dest='command', help='命令')
# generate命令
gen_parser = subparsers.add_parser('generate', help='生成PPT大纲')
gen_parser.add_argument('topic', help='PPT主题')
gen_parser.add_argument('--type', '-t', default='report',
choices=['report', 'product', 'training', 'business', 'proposal'],
help='场景类型')
gen_parser.add_argument('--export', '-e', choices=['markdown', 'ppt'], default='markdown',
help='导出格式')
gen_parser.add_argument('--output', '-o', help='输出文件路径')
# templates命令
subparsers.add_parser('templates', help='列出所有模板')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
if args.command == 'generate':
outline = generate_outline(args.topic, args.type)
if args.export == 'markdown':
output = export_markdown(outline)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output)
print(f"已保存到:{args.output}")
else:
print(output)
elif args.export == 'ppt':
output_path = args.output or f"{args.topic}.pptx"
if export_pptx(outline, output_path):
print(f"PPT已生成:{output_path}")
elif args.command == 'templates':
list_templates()
if __name__ == "__main__":
main()
邮件分类与回复助手。基于规则分类邮件,提供回复模板。
---
name: cn-smart-email
description: "邮件分类与回复助手。基于规则分类邮件,提供回复模板。"
metadata: {"openclaw": {"emoji": "📧"}}
---
# 邮件分类与回复助手
分类邮件并提供回复模板。
## 功能
- 邮件分类(工作/个人/通知/广告)
- 回复模板生成
- 中文邮件场景支持
## 用法
```bash
python3 scripts/smart_email.py --classify "邮件内容"
python3 scripts/smart_email.py --reply "邮件内容" --tone formal
```
## 依赖
- Python 3.7+
- requests
FILE:scripts/smart_email.py
#!/usr/bin/env python3
"""cn-smart-email 邮件分类与回复助手"""
import json
import sys
import re
CATEGORIES = {
"工作": ["会议", "项目", "报告", "审批", "任务", "截止", "需求", "进度"],
"个人": ["家", "朋友", "周末", "聚会", "生日", "旅行"],
"通知": ["提醒", "确认", "验证", "通知", "更新", "系统"],
"广告": ["优惠", "折扣", "促销", "限时", "免费", "领取"],
}
REPLY_TEMPLATES = {
"formal": "您好,\n\n感谢您的来信。关于{topic},我将在{time}前回复您。\n\n此致",
"casual": "嗨,\n\n收到!关于{topic},我稍后回复你~",
"brief": "收到,稍后回复。",
}
def classify(text):
scores = {}
for cat, keywords in CATEGORIES.items():
score = sum(1 for kw in keywords if kw in text)
scores[cat] = score
if max(scores.values()) == 0:
return "其他"
return max(scores, key=scores.get)
def generate_reply(text, tone="formal"):
template = REPLY_TEMPLATES.get(tone, REPLY_TEMPLATES["formal"])
topic = text[:20] + "..." if len(text) > 20 else text
return template.format(topic=topic, time="2个工作日内")
def main():
if len(sys.argv) < 2:
print("用法: smart_email.py --classify <文本> | --reply <文本> [--tone formal|casual|brief]")
return
action = sys.argv[1]
text = " ".join(sys.argv[2:]) if len(sys.argv) > 2 else ""
# Parse --tone
tone = "formal"
if "--tone" in sys.argv:
idx = sys.argv.index("--tone")
if idx + 1 < len(sys.argv):
tone = sys.argv[idx + 1]
text = text.replace("--tone", "").replace(tone, "").strip()
if action == "--classify":
cat = classify(text)
print(json.dumps({"category": cat, "text": text[:50]}, ensure_ascii=False))
elif action == "--reply":
reply = generate_reply(text, tone)
print(reply)
else:
print(f"未知操作: {action}")
if __name__ == "__main__":
main()
Excel公式助手。自然语言描述需求,自动生成Excel公式。支持VLOOKUP、条件统计、日期计算、文本处理等200+常用公式。附带公式解释和示例数据。
---
name: cn-excel-formula
description: "Excel公式助手。自然语言描述需求,自动生成Excel公式。支持VLOOKUP、条件统计、日期计算、文本处理等200+常用公式。附带公式解释和示例数据。"
metadata:
openclaw:
emoji: 📊
category: productivity
tags:
- excel
- formula
- spreadsheet
- office
- chinese
scope:
- "自然语言转Excel公式"
- "公式解释(中文说明每个参数)"
- "常用公式库(200+模板)"
- "错误排查(常见公式错误诊断)"
- "公式优化建议"
install: |
pip install openpyxl
env:
OPENAI_API_KEY: "可选,用于AI增强公式生成。不配置则使用模板匹配。"
entry:
script: scripts/excel_formula.py
args: []
---
# Excel公式助手
## 功能
- 自然语言描述 → 自动生成Excel公式
- 公式中文解释(每个参数都有说明)
- 200+常用公式模板库
- 常见错误排查
- 公式优化建议
## 使用方法
### 生成公式
```bash
# 自然语言描述
python scripts/excel_formula.py generate "查找A列中与D2匹配的B列值"
# 输出:=VLOOKUP(D2, A:B, 2, FALSE)
# 带条件的求和
python scripts/excel_formula.py generate "求A列大于100且B列为'完成'的C列之和"
# 输出:=SUMIFS(C:C, A:A, ">100", B:B, "完成")
```
### 解释公式
```bash
python scripts/excel_formula.py explain "=VLOOKUP(D2,A:B,2,FALSE)"
# 输出中文解释
```
### 常用公式库
```bash
# 列出某类别公式
python scripts/excel_formula.py list --category lookup
python scripts/excel_formula.py list --category date
python scripts/excel_formula.py list --category text
```
### 错误排查
```bash
python scripts/excel_formula.py diagnose "=VLOOKUP(D2,A:B,2,FALSE)" --error "#N/A"
# 输出可能原因和修复方法
```
## 支持的公式类别
| 类别 | 常用公式 |
|------|---------|
| 查找引用 | VLOOKUP, HLOOKUP, INDEX+MATCH, XLOOKUP, INDIRECT |
| 条件统计 | SUMIFS, COUNTIFS, AVERAGEIFS, MAXIFS, MINIFS |
| 日期时间 | DATE, DATEDIF, EDATE, EOMONTH, NETWORKDAYS |
| 文本处理 | LEFT, RIGHT, MID, FIND, SUBSTITUTE, CONCATENATE |
| 逻辑判断 | IF, IFS, SWITCH, AND, OR, IFERROR |
| 数学计算 | ROUND, ROUNDUP, ROUNDDOWN, MOD, INT, ABS |
| 数据清洗 | TRIM, CLEAN, UPPER, LOWER, PROPER, TEXT |
| 数组公式 | UNIQUE, FILTER, SORT, SEQUENCE, TRANSPOSE |
## 典型场景
1. **数据查找**:根据姓名查工资、根据编号查详情
2. **条件汇总**:按部门/月份/状态分类统计
3. **日期计算**:工龄、合同到期、项目倒计时
4. **文本提取**:从身份证提取生日、提取邮箱域名
5. **错误处理**:#N/A、#VALUE!、#REF! 排查修复
FILE:scripts/excel_formula.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Excel公式助手
自然语言 → 公式 | 公式解释 | 公式库 | 错误排查
"""
import re
import sys
from typing import Optional
try:
import openai
HAS_OPENAI = True
except ImportError:
HAS_OPENAI = False
# 公式模板库
FORMULA_TEMPLATES = {
"查找引用": {
"VLOOKUP": {
"syntax": "=VLOOKUP(查找值, 查找范围, 返回列数, [精确匹配])",
"params": [
"查找值:要查找的内容",
"查找范围:包含查找列和返回列的区域(查找列必须在第一列)",
"返回列数:从查找范围第一列开始数,第几列的值",
"精确匹配:FALSE=精确匹配,TRUE=模糊匹配"
],
"examples": [
("根据姓名查工资", "=VLOOKUP(A2,员工表!A:D,4,FALSE)"),
("根据编号查产品", "=VLOOKUP(B2,产品表!A:F,3,FALSE)")
]
},
"INDEX+MATCH": {
"syntax": "=INDEX(返回范围, MATCH(查找值, 查找范围, 0))",
"params": [
"返回范围:要返回值的区域",
"MATCH查找值:要查找的内容",
"MATCH查找范围:查找值所在的列/行",
"0:表示精确匹配"
],
"examples": [
("反向查找(根据工资查姓名)", "=INDEX(A:A, MATCH(D2, D:D, 0))"),
("多条件查找", "=INDEX(C:C, MATCH(1, (A:A=A2)*(B:B=B2), 0))")
]
},
"XLOOKUP": {
"syntax": "=XLOOKUP(查找值, 查找数组, 返回数组, [未找到值], [匹配模式])",
"params": [
"查找值:要查找的内容",
"查找数组:查找值所在的列/行",
"返回数组:要返回值的列/行",
"未找到值:找不到时返回的内容",
"匹配模式:0=精确匹配,-1=精确或较小,1=精确或较大"
],
"examples": [
("根据姓名查工资(升级版)", "=XLOOKUP(A2, 员工表!A:A, 员工表!D:D, '未找到')"),
("查找并返回多个值", "=XLOOKUP(A2, A:A, B:D)")
]
}
},
"条件统计": {
"SUMIFS": {
"syntax": "=SUMIFS(求和范围, 条件范围1, 条件1, [条件范围2, 条件2], ...)",
"params": [
"求和范围:要求和的单元格区域",
"条件范围1:第一个条件所在的区域",
"条件1:第一个条件(如 '>100'、'完成')",
"条件范围2/条件2:可选的额外条件"
],
"examples": [
("按部门统计工资", "=SUMIFS(C:C, B:B, '销售部')"),
("多条件求和(部门+状态)", "=SUMIFS(C:C, B:B, '销售部', D:D, '完成')"),
("按日期范围求和", "=SUMIFS(C:C, A:A, '>=2024-01-01', A:A, '<=2024-12-31')")
]
},
"COUNTIFS": {
"syntax": "=COUNTIFS(条件范围1, 条件1, [条件范围2, 条件2], ...)",
"params": [
"条件范围1:第一个条件所在的区域",
"条件1:第一个条件",
"条件范围2/条件2:可选的额外条件"
],
"examples": [
("统计完成数量", "=COUNTIFS(B:B, '完成')"),
("统计某部门完成数", "=COUNTIFS(A:A, '销售部', B:B, '完成')")
]
},
"AVERAGEIFS": {
"syntax": "=AVERAGEIFS(平均值范围, 条件范围1, 条件1, ...)",
"params": [
"平均值范围:要计算平均值的区域",
"条件范围1:第一个条件所在的区域",
"条件1:第一个条件"
],
"examples": [
("计算某部门平均工资", "=AVERAGEIFS(C:C, B:B, '销售部')"),
("计算完成项目的平均耗时", "=AVERAGEIFS(D:D, E:E, '完成')")
]
}
},
"日期时间": {
"DATEDIF": {
"syntax": "=DATEDIF(开始日期, 结束日期, 单位)",
"params": [
"开始日期:起始日期",
"结束日期:结束日期",
"单位:'Y'=年数, 'M'=月数, 'D'=天数, 'YM'=忽略年的月数"
],
"examples": [
("计算工龄(年)", "=DATEDIF(A2, TODAY(), 'Y')"),
("计算剩余月数", "=DATEDIF(TODAY(), B2, 'M')"),
("计算年龄", "=DATEDIF(出生日期单元格, TODAY(), 'Y')")
]
},
"EOMONTH": {
"syntax": "=EOMONTH(日期, 月数)",
"params": [
"日期:起始日期",
"月数:0=当月最后一天, 1=下月最后一天, -1=上月最后一天"
],
"examples": [
("当月最后一天", "=EOMONTH(TODAY(), 0)"),
("合同到期日", "=EOMONTH(入职日期, 36)")
]
},
"NETWORKDAYS": {
"syntax": "=NETWORKDAYS(开始日期, 结束日期, [节假日])",
"params": [
"开始日期:起始日期",
"结束日期:结束日期",
"节假日:可选,节假日列表区域"
],
"examples": [
("计算工作日天数", "=NETWORKDAYS(A2, B2)"),
("扣除节假日", "=NETWORKDAYS(A2, B2, 节假日!A:A)")
]
}
},
"文本处理": {
"LEFT/RIGHT/MID": {
"syntax": "=LEFT(文本, 字符数) | =RIGHT(文本, 字符数) | =MID(文本, 起始位置, 字符数)",
"params": [
"文本:要提取的文本",
"字符数:提取的字符数量",
"起始位置:MID专用,从第几个字符开始"
],
"examples": [
("提取身份证生日", "=MID(A2, 7, 8)"),
("提取前3位", "=LEFT(A2, 3)"),
("提取后4位", "=RIGHT(A2, 4)")
]
},
"FIND/SUBSTITUTE": {
"syntax": "=FIND(查找文本, 文本) | =SUBSTITUTE(文本, 旧文本, 新文本, [替换第几个])",
"params": [
"查找文本:要查找的内容",
"文本:被查找的文本",
"旧文本/新文本:SUBSTITUTE专用"
],
"examples": [
("提取邮箱域名", "=RIGHT(A2, LEN(A2)-FIND('@', A2))"),
("替换文本", "=SUBSTITUTE(A2, '旧', '新')"),
("删除空格", "=SUBSTITUTE(A2, ' ', '')")
]
},
"TEXT": {
"syntax": "=TEXT(数值, 格式代码)",
"params": [
"数值:要格式化的数值或日期",
"格式代码:如 'yyyy-mm-dd', '0.00', '0%''"
],
"examples": [
("日期格式化", "=TEXT(A2, 'yyyy年mm月dd日')"),
("金额格式化", "=TEXT(A2, '¥#,##0.00')"),
("百分比", "=TEXT(A2, '0%')")
]
}
},
"逻辑判断": {
"IF/IFS": {
"syntax": "=IF(条件, 条件真值, 条件假值) | =IFS(条件1, 值1, 条件2, 值2, ...)",
"params": [
"条件:判断条件",
"条件真值:条件为真时返回的值",
"条件假值:条件为假时返回的值"
],
"examples": [
("简单判断", "=IF(A2>100, '高', '低')"),
("多条件判断", "=IFS(A2>=90, '优秀', A2>=80, '良好', A2>=60, '及格', A2<60, '不及格')"),
("嵌套IF", "=IF(A2>100, IF(B2='VIP', '高VIP', '高'), '低')")
]
},
"IFERROR": {
"syntax": "=IFERROR(公式, 错误时返回值)",
"params": [
"公式:可能出错的公式",
"错误时返回值:出错时显示的内容"
],
"examples": [
("VLOOKUP防错", "=IFERROR(VLOOKUP(A2, B:C, 2, FALSE), '未找到')"),
("除零防错", "=IFERROR(A2/B2, 0)")
]
}
}
}
# 自然语言到公式的映射规则
NL_TO_FORMULA_RULES = [
# VLOOKUP相关
(r"查找.*(匹配|对应|等于).*的.*值", "VLOOKUP"),
(r"根据.*查.*", "VLOOKUP"),
(r".*列中.*等于.*的.*", "VLOOKUP"),
# SUMIFS相关
(r"求.*和.*且.*", "SUMIFS"),
(r"统计.*之和.*条件", "SUMIFS"),
(r"按.*统计.*总和", "SUMIFS"),
# COUNTIFS相关
(r"统计.*数量.*条件", "COUNTIFS"),
(r"统计.*有几个", "COUNTIFS"),
# DATEDIF相关
(r"计算.*(天数|月数|年数|工龄|年龄)", "DATEDIF"),
(r".*相差.*(天|月|年)", "DATEDIF"),
# LEFT/MID/RIGHT相关
(r"提取.*(前|后|第).*(位|字符)", "LEFT/RIGHT/MID"),
(r"从.*提取.*", "LEFT/RIGHT/MID"),
# IF相关
(r"如果.*则.*否则", "IF"),
(r"判断.*是.*还是", "IF"),
]
def generate_formula(description: str, use_ai: bool = False) -> str:
"""根据自然语言描述生成公式"""
# 尝试AI生成
if use_ai and HAS_OPENAI:
try:
import os
api_key = os.environ.get("OPENAI_API_KEY")
if api_key:
client = openai.OpenAI(api_key=api_key)
prompt = f"""将以下需求转换为Excel公式。只返回公式,不要解释。
需求:{description}
公式:"""
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
max_tokens=100
)
return response.choices[0].message.content.strip()
except Exception as e:
print(f"AI生成失败,使用模板匹配: {e}")
# 模板匹配
desc_lower = description.lower()
# 查找引用类
if re.search(r"查找.*匹配|根据.*查|查.*对应", desc_lower):
return """建议公式:=VLOOKUP(查找值, 查找范围, 返回列数, FALSE)
示例:=VLOOKUP(A2, 数据表!A:D, 3, FALSE)
参数说明:
- 查找值:要查找的内容(如A2单元格)
- 查找范围:包含查找列和返回列的区域
- 返回列数:从查找范围第一列数起,第几列
- FALSE:精确匹配"""
# 条件求和类
if re.search(r"求.*和.*且|统计.*之和.*条件|按.*统计.*总和", desc_lower):
return """建议公式:=SUMIFS(求和范围, 条件范围1, 条件1, 条件范围2, 条件2)
示例:=SUMIFS(C:C, A:A, ">100", B:B, "完成")
参数说明:
- 求和范围:要求和的列
- 条件范围1:第一个条件所在的列
- 条件1:第一个条件(如">100")
- 条件范围2/条件2:可选的额外条件"""
# 条件计数类
if re.search(r"统计.*数量|统计.*有几个|计算.*个数", desc_lower):
return """建议公式:=COUNTIFS(条件范围1, 条件1, 条件范围2, 条件2)
示例:=COUNTIFS(A:A, "销售部", B:B, "完成")
参数说明:
- 条件范围1:第一个条件所在的列
- 条件1:第一个条件"""
# 日期计算类
if re.search(r"计算.*(天数|月数|年数|工龄|年龄)|相差.*(天|月|年)", desc_lower):
return """建议公式:=DATEDIF(开始日期, 结束日期, 单位)
示例:=DATEDIF(A2, TODAY(), "Y")
参数说明:
- 开始日期:起始日期单元格
- 结束日期:结束日期(可用TODAY()表示今天)
- 单位:"Y"=年数, "M"=月数, "D"=天数"""
# 文本提取类
if re.search(r"提取.*(前|后|第|位)|从.*提取", desc_lower):
return """建议公式:
- 提取前N位:=LEFT(文本, N)
- 提取后N位:=RIGHT(文本, N)
- 提取中间:=MID(文本, 起始位置, 字符数)
示例(提取身份证生日):=MID(A2, 7, 8)"""
# 条件判断类
if re.search(r"如果.*则|判断.*是|大于.*显示|小于.*显示", desc_lower):
return """建议公式:=IF(条件, 条件真时的值, 条件假时的值)
示例:=IF(A2>100, "高", "低")
参数说明:
- 条件:判断条件(如A2>100)
- 条件真时的值:条件为真时显示的内容
- 条件假时的值:条件为假时显示的内容"""
# 默认返回帮助
return """无法直接匹配公式,请尝试以下方式:
1. 使用 list 命令查看公式库
python excel_formula.py list --category lookup
2. 查看具体公式帮助
python excel_formula.py explain VLOOKUP
3. 使用更具体的描述,如:
- "查找A列中与D2匹配的B列值"
- "求A列大于100且B列为完成的C列之和"
- "计算A2到今天的相差年数"
"""
def explain_formula(formula: str) -> str:
"""解释公式"""
formula = formula.strip()
if not formula.startswith('='):
formula = '=' + formula
# 提取函数名
match = re.match(r'=([A-Za-z]+)', formula)
if not match:
return "无法识别的公式格式"
func_name = match.group(1).upper()
# 查找公式说明
for category, formulas in FORMULA_TEMPLATES.items():
for name, info in formulas.items():
if func_name in name:
result = [f"【{name}】{category}"]
result.append(f"语法:{info['syntax']}")
result.append("")
result.append("参数说明:")
for param in info['params']:
result.append(f" • {param}")
result.append("")
result.append("示例:")
for desc, example in info['examples']:
result.append(f" • {desc}")
result.append(f" {example}")
return "\n".join(result)
# 通用解释
return f"""【{func_name}】
公式:{formula}
这是一个Excel内置函数。建议:
1. 使用 list 命令查看完整公式库
2. 在Excel中按Shift+F3查看函数帮助
3. 搜索"Excel {func_name} 函数用法"获取详细教程
"""
def list_formulas(category: str = None):
"""列出公式"""
if category:
# 查找匹配的类别
for cat_name, formulas in FORMULA_TEMPLATES.items():
if category.lower() in cat_name.lower():
print(f"【{cat_name}】")
for name in formulas.keys():
print(f" • {name}")
return
print(f"未找到类别:{category}")
print(f"可用类别:{', '.join(FORMULA_TEMPLATES.keys())}")
else:
print("Excel公式库(按类别):")
for cat_name, formulas in FORMULA_TEMPLATES.items():
print(f"\n【{cat_name}】")
for name in formulas.keys():
print(f" • {name}")
def diagnose_error(formula: str, error: str) -> str:
"""诊断公式错误"""
error_diagnosis = {
"#N/A": """#N/A 错误诊断:
可能原因:
1. VLOOKUP/XLOOKUP 找不到匹配值
2. 查找值与查找范围格式不一致(文本vs数字)
3. 查找范围第一列不包含查找值
修复方法:
1. 检查查找值是否正确
2. 使用 =ISTEXT() 和 =ISNUMBER() 检查格式
3. 使用 =IFERROR(公式, "未找到") 美化错误显示
4. 确保查找范围包含目标数据""",
"#VALUE!": """#VALUE! 错误诊断:
可能原因:
1. 公式中使用了错误的数据类型
2. 文本参与了数学运算
3. 数组公式未正确输入
修复方法:
1. 检查参与运算的单元格格式
2. 使用 =VALUE() 将文本转为数字
3. 使用 =TEXT() 将数字转为文本
4. 数组公式需按 Ctrl+Shift+Enter 输入""",
"#REF!": """#REF! 错误诊断:
可能原因:
1. 公式引用的单元格/区域已被删除
2. 复制公式时相对引用出错
3. VLOOKUP返回列数超出范围
修复方法:
1. 检查公式中的引用是否有效
2. 使用绝对引用 $A$1 代替相对引用 A1
3. 检查VLOOKUP的返回列数参数""",
"#DIV/0!": """#DIV/0! 错误诊断:
可能原因:
1. 除数为0或空值
2. 平均值计算时无有效数据
修复方法:
1. 使用 =IFERROR(公式, 0) 或 =IFERROR(公式, "N/A")
2. 先检查除数:=IF(B2=0, "N/A", A2/B2)""",
"#NAME?": """#NAME? 错误诊断:
可能原因:
1. 函数名拼写错误
2. 使用了不存在的自定义名称
3. 文本未加引号
修复方法:
1. 检查函数名拼写(如 VLOOKUP 不是 VLOKUP)
2. 文本参数需加引号,如 "完成"
3. 确保使用了正确的函数名"""
}
if error.upper() in error_diagnosis:
return error_diagnosis[error.upper()]
return f"""【{error}】错误诊断:
这是一个Excel错误代码。
建议:
1. 检查公式语法是否正确
2. 确认所有引用的单元格都存在
3. 检查数据类型是否匹配
4. 搜索"Excel {error} 解决方法"获取详细帮助
"""
def main():
import argparse
parser = argparse.ArgumentParser(description="Excel公式助手")
subparsers = parser.add_subparsers(dest='command', help='命令')
# generate命令
gen_parser = subparsers.add_parser('generate', help='根据描述生成公式')
gen_parser.add_argument('description', help='自然语言描述')
gen_parser.add_argument('--ai', action='store_true', help='使用AI增强')
# explain命令
exp_parser = subparsers.add_parser('explain', help='解释公式')
exp_parser.add_argument('formula', help='要解释的公式')
# list命令
list_parser = subparsers.add_parser('list', help='列出公式库')
list_parser.add_argument('--category', '-c', help='类别过滤')
# diagnose命令
diag_parser = subparsers.add_parser('diagnose', help='诊断公式错误')
diag_parser.add_argument('formula', help='出错的公式')
diag_parser.add_argument('--error', '-e', required=True, help='错误代码')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
if args.command == 'generate':
result = generate_formula(args.description, use_ai=args.ai)
print(result)
elif args.command == 'explain':
result = explain_formula(args.formula)
print(result)
elif args.command == 'list':
list_formulas(args.category)
elif args.command == 'diagnose':
result = diagnose_error(args.formula, args.error)
print(result)
if __name__ == "__main__":
main()
自动生成10多种专业中文商务邮件模板,支持自定义主题、收发人及职位,结构完整符合商务规范。
name: 专业商务邮件生成器
description: 输入场景和关键词,自动生成10+种专业商务邮件(初次联系、跟进、感谢、报价、拒绝、道歉、会议邀请、周报、离职告别、节日问候)。支持自定义收件人、发件人、职位等变量。
version: "1.0.0"
entry: scripts/email_template.py
install: ""
scope:
- 支持10+种商务邮件场景模板
- 自定义邮件主题、收件人、发件人信息
- 生成结构完整的邮件正文(称谓+正文+落款)
- 列出所有可用场景
- 支持中文商务邮件规范格式
env: []
test: |
python3 scripts/email_template.py --help
python3 scripts/email_template.py 初次联系 发件人=张三 姓名=李四 目的=合作
example:
input: "python email_template.py 初次联系 发件人=王经理 姓名=赵总 目的=AI工具合作"
output: "生成完整邮件,包含主题、称谓、正文、落款"
input: "python email_template.py 周报 姓名=李华 周期=本周 完成事项='项目A完成' 下周计划='继续迭代'"
output: "生成周报格式邮件"
FILE:scripts/email_template.py
#!/usr/bin/env python3
"""
cn-email-template: 专业商务邮件生成器
支持10+常见商务场景,自动生成专业邮件正文
用法: python email_template.py <场景> <关键词>
"""
import sys
import os
# 邮件模板库
TEMPLATES = {
"初次联系": {
"subject": "关于{主题}的合作探讨",
"greeting": "您好{姓名},",
"intro": "我是{发件人},从{来源}了解到贵司/您的{相关领域},非常感兴趣。",
"body": "这次冒昧联系,主要是希望{目的}。{附加信息}",
"closing": "期待您的回复,如方便也可添加我的微信进一步交流。",
"signature": "{发件人}\n{职位}\n{联系方式}"
},
"跟进": {
"subject": "跟进:{原主题}",
"greeting": "您好{姓名},",
"intro": "此前来信关于{原主题}的事宜,不知您是否方便查阅。",
"body": "{跟进内容}",
"closing": "如有任何疑问,随时告知。祝好!",
"signature": "{发件人}"
},
"感谢": {
"subject": "感谢您的{事项}",
"greeting": "您好{姓名},",
"intro": "非常感谢您{感谢原因}。",
"body": "{具体感谢内容}",
"closing": "期待未来有更多合作机会!",
"signature": "{发件人}"
},
"报价": {
"subject": "关于{项目}的报价方案",
"greeting": "您好{姓名},",
"intro": "感谢您对{产品/服务}的关注,根据您的需求,我们准备了以下方案:",
"body": "{报价详情}",
"closing": "报价有效期{有效期},如有任何调整需求欢迎沟通。",
"signature": "{发件人}\n{职位}\n{公司}"
},
"拒绝": {
"subject": "关于{事项}的反馈",
"greeting": "您好{姓名},",
"intro": "感谢您的{事项}申请/提案,经过慎重评估,我们暂时无法推进。",
"body": "{拒绝原因}。我们会在{未来时机}重新评估。",
"closing": "感谢您的理解,祝顺利!",
"signature": "{发件人}"
},
"道歉": {
"subject": "关于{事项}的说明与致歉",
"greeting": "您好{姓名},",
"intro": "对于{问题}给贵司/您带来的不便,我们深感抱歉。",
"body": "{问题原因}。我们已采取{解决方案},确保不再发生。",
"closing": "再次为给您造成的困扰致歉,期待您的谅解。",
"signature": "{发件人}\n{职位}\n{公司}"
},
"会议邀请": {
"subject": "邀请:{会议主题}({日期}{时间})",
"greeting": "您好{姓名},",
"intro": "诚挚邀请您参加{会议主题},详情如下:",
"body": "📅 日期:{日期}\n🕐 时间:{时间}\n📍 地点:{地点}\n📋 议程:{议程}",
"closing": "请确认是否方便出席,回复确认即可。",
"signature": "{发件人}"
},
"周报": {
"subject": "【{周期}周报】{姓名} — {起始日期}",
"greeting": "各位好,",
"intro": "以下是{周期}周({起始日期} - {结束日期})的工作汇报:",
"body": "📌 本周完成:\n{完成事项}\n\n📋 下周计划:\n{下周计划}\n\n⚠️ 需要支持:\n{需要支持}",
"closing": "如有疑问欢迎随时沟通。",
"signature": "{姓名}"
},
"离职告别": {
"subject": "告别与感谢",
"greeting": "各位同事,",
"intro": "怀着不舍的心情,告知大家我将于{离职日期}离开{公司}。",
"body": "感谢{感谢对象}。在{公司}的{时长}是我职业生涯中宝贵的经历。",
"closing": "我的联系方式:{联系方式},欢迎保持联系。江湖再见!",
"signature": "{发件人}"
},
"节日问候": {
"subject": "{节日}快乐!{祝福语}",
"greeting": "尊敬的{姓名},",
"intro": "金{节日}将至,{公司/发件人}全体同仁恭祝您:",
"body": "{祝福内容}",
"closing": "新的一年,期待继续与您携手共进!",
"signature": "{公司}\n{发件人}"
}
}
def generate_email(scenario, **kwargs):
"""生成邮件内容"""
if scenario not in TEMPLATES:
available = ', '.join(TEMPLATES.keys())
print(f"未知场景。可用场景:{available}")
return None
t = TEMPLATES[scenario]
# 填充模板
subject = t["subject"].format(**kwargs)
greeting = t["greeting"].format(**kwargs).replace("{姓名}", kwargs.get("姓名", ""))
intro = t["intro"].format(**kwargs)
body = t["body"].format(**kwargs)
closing = t["closing"].format(**kwargs)
signature = t["signature"].format(**kwargs)
# 组装邮件
email = f"""【邮件主题】
{subject}
【收件人】
{kwargs.get('收件人', '[收件人邮箱]')}
{greeting}
{intro}
{body}
{closing}
{signature}"""
return email
def list_scenarios():
print("可用场景:")
for i, name in enumerate(TEMPLATES.keys(), 1):
print(f" {i}. {name}")
print("\n用法: python email_template.py <场景> <键1=值1> <键2=值2> ...")
def main():
if len(sys.argv) < 2 or sys.argv[1] in ['--help', '-h', 'help']:
list_scenarios()
print("\n示例:")
print(' python email_template.py 初次联系 发件人=张三 职位=产品经理 姓名=李四 公司=XX科技 目的=合作开发 收件人[email protected]')
print(' python email_template.py 周报 姓名=张三 周期=本周 起始日期=4月14日 结束日期=4月18日 完成事项="- 项目A完成\\n- 客户B签约"')
sys.exit(0)
scenario = sys.argv[1]
kwargs = {}
for arg in sys.argv[2:]:
if '=' in arg:
k, v = arg.split('=', 1)
kwargs[k] = v
email = generate_email(scenario, **kwargs)
if email:
print(email)
if __name__ == '__main__':
main()
将自然语言事件描述转换为标准.ics日历文件,支持日期解析和Google、Apple、Outlook日历兼容。
name: 日历事件创建助手
description: 将自然语言描述转换为标准 .ics 日历文件,支持 Google Calendar / Apple Calendar / Outlook。输入"明天上午10点开会",自动生成可导入的日历事件。
version: "1.0.0"
entry: scripts/calendar_manager.py
install: pip install python-dateutil
scope:
- 将自然语言转换为 .ics 日历事件文件
- 支持今天/明天/后天/下周+具体时间的日期解析
- 支持自定义时长(小时/半小时)
- 兼容 Google Calendar、Apple Calendar、Outlook
- 支持标题、描述、地点的提取
env: []
test: |
python3 scripts/calendar_manager.py "明天上午10点开会讨论项目"
# 输出: calendar_YYYYMMDD_日程.ics
example:
input: "下周一14点进行团队培训"
output: "calendar_YYYYMMDD_团队培训.ics (周一14:00-15:00)"
input: "后天上午9点面试候选人张明"
output: "calendar_YYYYMMDD_面试候选人张明.ics (后天09:00-10:00)"
input: "2026年5月1日10点开会"
output: "calendar_20260501_开会.ics (5月1日10:00-11:00)"
FILE:scripts/calendar_manager.py
#!/usr/bin/env python3
"""
cn-calendar-manager: 日历事件创建工具
从自然语言描述生成 .ics 日历文件(兼容 Google/Apple/Outlook)
用法: python calendar_manager.py "明天上午10点开会讨论项目进度"
"""
import sys
import re
from datetime import datetime, timedelta
from pathlib import Path
def parse_natural_date(text: str) -> tuple:
"""解析自然语言时间描述,返回(year, month, day, hour, minute, duration_min)"""
text = text.strip()
today = datetime.now()
if '今天' in text:
base = today.replace(hour=0, minute=0, second=0, microsecond=0)
elif '明天' in text:
base = today + timedelta(days=1)
base = base.replace(hour=0, minute=0, second=0, microsecond=0)
elif '后天' in text:
base = today + timedelta(days=2)
base = base.replace(hour=0, minute=0, second=0, microsecond=0)
elif '下周' in text:
days = 7 - today.weekday() + (1 if today.weekday() >= 5 else 0)
base = today + timedelta(days=days)
base = base.replace(hour=0, minute=0, second=0, microsecond=0)
else:
base = today.replace(hour=0, minute=0, second=0, microsecond=0)
# 时间匹配
hour, minute = 10, 0
time_pattern = re.findall(r'(\d{1,2})[点时](?:(\d{1,2})分?)?', text)
if time_pattern:
hour = int(time_pattern[0][0])
minute = int(time_pattern[0][1]) if time_pattern[0][1] else 0
hour = max(0, min(23, hour))
minute = max(0, min(59, minute))
# 时长(默认1小时)
duration = 60
dur_match = re.search(r'(\d+)\s*(?:小时|小时半|个?小时)', text)
if dur_match:
dur_h = int(dur_match.group(1))
if '半' in text:
duration = int(dur_h * 60 + 30)
else:
duration = dur_h * 60
# 日期具体指定
date_pattern = re.findall(r'(\d{1,2})[月/\-](\d{1,2})', text)
if date_pattern:
month, day = int(date_pattern[0][0]), int(date_pattern[0][1])
year = today.year
if month < today.month:
year += 1
base = base.replace(year=year, month=month, day=day)
return base.year, base.month, base.day, hour, minute, duration
def extract_title(text: str) -> str:
"""从描述中提取事件标题"""
text = text.strip()
# 去掉时间前缀
text = re.sub(r'^(今天|明天|后天|下周)[上午下午晚上早中]?', '', text)
text = re.sub(r'\d+[点时:分]', '', text)
text = text.strip()
# 关键词触发
keywords = ['会议', '约会', '面试', '演讲', '培训', '讨论', '汇报', '见面', '举行', '开始']
for kw in keywords:
if kw in text:
idx = text.index(kw)
result = text[idx:].strip()
return result[:50] if result else "日程事件"
# 兜底:去掉数字和标点
result = re.sub(r'[\d\.\,\、\。]+', '', text).strip()
return result[:30] if result else "日程事件"
def generate_ics(year, month, day, hour, minute, duration_min, title, description="", location=""):
"""生成标准 .ics 文件内容"""
start = datetime(year, month, day, hour, minute)
end = start + timedelta(minutes=duration_min)
dt_start = start.strftime('%Y%m%dT%H%M%S')
dt_end = end.strftime('%Y%m%dT%H%M%S')
dt_stamp = datetime.now().strftime('%Y%m%dT%H%M%S')
uid = f"{hash(title)}{hash(str(start))}@qclaw-calendar"
ics_content = f"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//QClaw//CN Calendar Manager//ZH
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:{uid}
DTSTAMP:{dt_stamp}
DTSTART:{dt_start}
DTEND:{dt_end}
SUMMARY:{title}
"""
if description:
ics_content += f"DESCRIPTION:{description}\n"
if location:
ics_content += f"LOCATION:{location}\n"
ics_content += """END:VEVENT
END:VCALENDAR"""
return ics_content
def main():
if len(sys.argv) < 2:
print("用法: python calendar_manager.py <自然语言描述>")
print("示例:")
print(" python calendar_manager.py \"明天上午10点开会讨论项目\"")
print(" python calendar_manager.py \"下周一14点进行团队培训\"")
print(" python calendar_manager.py \"后天上午9点面试候选人张明\"")
sys.exit(1)
text = ' '.join(sys.argv[1:])
try:
year, month, day, hour, minute, duration = parse_natural_date(text)
title = extract_title(text)
ics_content = generate_ics(year, month, day, hour, minute, duration, title)
date_str = f"{year}{month:02d}{day:02d}"
safe_title = re.sub(r'[^\w\u4e00-\u9fff]', '_', title)[:20]
filename = f"calendar_{date_str}_{safe_title}.ics"
output_path = Path.home() / filename
with open(output_path, 'w', encoding='utf-8') as f:
f.write(ics_content)
print(f"✅ 日历事件已创建: {output_path}")
print(f"📅 标题: {title}")
print(f"🕐 时间: {year}年{month}月{day}日 {hour:02d}:{minute:02d}")
print(f"⏱️ 时长: {duration}分钟")
print(f"📁 文件: {filename}")
print(f"\n💡 使用方式:")
print(f" - Google Calendar: 导入 .ics 文件")
print(f" - Apple Calendar: 双击打开 .ics")
print(f" - Outlook: 文件→打开→导入→.ics")
except Exception as e:
import traceback
traceback.print_exc()
print(f"❌ 解析失败: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
网页内容提取器。输入URL,提取正文内容,去除广告和导航。
---
name: cn-readability-extractor
description: "网页内容提取器。输入URL,提取正文内容,去除广告和导航。"
metadata: {"openclaw": {"emoji": "📄"}}
---
# 网页内容提取器
输入URL,提取干净正文。
## 功能
- 从URL提取正文
- 去除广告、导航、脚本
- 提取标题和描述
- 中英文支持
## 用法
```bash
python3 scripts/readability.py https://example.com
```
## 依赖
- Python 3.7+
- requests, certifi
FILE:scripts/readability.py
#!/usr/bin/env python3
"""
cn-readability-extractor: 网页内容提取器
从任意网页提取干净正文,去除广告/导航/脚本
用法: python readability.py <URL>
"""
import sys
import re
import ssl
import urllib.request
import html.parser
import certifi
from pathlib import Path
# 简单的HTML标签清理器
class HTMLCleaner(html.parser.HTMLParser):
def __init__(self):
super().__init__()
self.text_parts = []
self.skip_tags = {'script', 'style', 'nav', 'header', 'footer', 'aside', 'form', 'button'}
self.current_skip = False
self.skip_depth = 0
def handle_starttag(self, tag, attrs):
attrs_dict = dict(attrs)
if tag in self.skip_tags:
self.current_skip = True
self.skip_depth += 1
# 换行标签
if tag in ('p', 'div', 'br', 'h1', 'h2', 'h3', 'h4', 'li', 'tr'):
self.text_parts.append('\n')
def handle_endtag(self, tag):
if tag in self.skip_tags:
self.skip_depth -= 1
if self.skip_depth <= 0:
self.current_skip = False
self.skip_depth = 0
if tag in ('p', 'div', 'br', 'h1', 'h2', 'h3', 'h4', 'li', 'tr'):
self.text_parts.append('\n')
def handle_data(self, data):
if not self.current_skip:
text = data.strip()
if text:
self.text_parts.append(text + ' ')
def get_text(self):
# 清理多余空行
text = ''.join(self.text_parts)
text = re.sub(r'\n{3,}', '\n\n', text)
text = re.sub(r' {2,}', ' ', text)
return text.strip()
def extract_title(html):
"""提取<title>和<meta>描述"""
title_match = re.search(r'<title[^>]*>([^<]+)</title>', html, re.IGNORECASE)
desc_match = re.search(r'<meta[^>]+name=["\']description["\'][^>]+content=["\']([^"\']+)["\']', html, re.IGNORECASE)
h1_match = re.search(r'<h1[^>]*>([^<]+)</h1>', html, re.IGNORECASE)
return {
'title': title_match.group(1).strip() if title_match else '',
'description': desc_match.group(1).strip() if desc_match else '',
'h1': h1_match.group(1).strip() if h1_match else '',
}
def extract_readable_content(html):
"""提取干净正文"""
cleaner = HTMLCleaner()
try:
cleaner.feed(html)
return cleaner.get_text()
except Exception as e:
return f"解析失败: {e}"
def extract(url):
"""从URL提取内容"""
html = None
req = urllib.request.Request(url, headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
})
# 优先标准SSL验证(certifi根证书)
try:
ctx = ssl.create_default_context()
ctx.load_verify_locations(certifi.where())
with urllib.request.urlopen(req, timeout=10, context=ctx) as resp:
html = resp.read().decode('utf-8', errors='ignore')
except urllib.error.URLError as e:
# SSL验证失败时降级
reason = getattr(e, 'reason', None)
if isinstance(reason, ssl.SSLError):
fallback_ctx = ssl.create_default_context()
fallback_ctx.load_verify_locations(certifi.where())
try:
with urllib.request.urlopen(req, timeout=10, context=fallback_ctx) as resp:
html = resp.read().decode('utf-8', errors='ignore')
except Exception as e2:
return {'error': f"请求失败: {e2}"}
else:
return {'error': f"请求失败: {e}"}
except Exception as e1:
return {'error': f"请求失败: {e1}"}
if html is None:
return {'error': '获取内容失败'}
meta = extract_title(html)
content = extract_readable_content(html)
# 截断过长内容
if len(content) > 5000:
content = content[:5000] + '\n\n... [内容过长,已截断]'
return {
'url': url,
'title': meta['title'],
'description': meta['description'],
'content': content,
'word_count': len(content.split())
}
def main():
if len(sys.argv) < 2:
print("用法: python readability.py <URL>")
print("示例:")
print(" python readability.py https://example.com/article")
print(" python readability.py https://news.ycombinator.com/item?id=123")
sys.exit(1)
url = sys.argv[1]
result = extract(url)
if 'error' in result:
print(f"❌ {result['error']}")
sys.exit(1)
print(f"📄 {result['title']}")
print(f"🔗 {result['url']}")
if result['description']:
print(f"📝 {result['description']}")
print(f"\n{'='*40}")
print(result['content'])
print(f"\n{'='*40}")
print(f"📊 字数: {result['word_count']} 字")
if __name__ == '__main__':
main()
提供微信、邮件、知乎、小红书等10+社交场景的中文智能回复建议,生成多条得体回复供参考。
name: 智能回复生成器
description: 微信/邮件/知乎/小红书等场景的智能回复建议,10+种常见社交场景自动生成得体回复。
version: "1.0.0"
entry: scripts/smart_reply.py
install: ""
scope:
- 支持10+种回复场景(感谢/道歉/确认/拒绝/催促/安慰/祝贺/请教/介绍/知乎/小红书)
- 随机抽取多条候选回复
- 列出所有可用场景
- 中文社交回复规范
env: []
test: |
python3 scripts/smart_reply.py --help
python3 scripts/smart_reply.py 感谢
python3 scripts/smart_reply.py 催促 "项目进度"
example:
input: "python smart_reply.py 感谢"
output: "💬 太感谢了!真的帮了我大忙..."
input: "python smart_reply.py 回复知乎"
output: "💬 泻药。亲身经历来说..."
FILE:scripts/smart_reply.py
#!/usr/bin/env python3
"""
cn-smart-reply: 智能回复生成器
支持微信/邮件/知乎/小红书/短信等多场景的智能回复建议
用法: python smart_reply.py <场景> <原消息内容>
"""
import sys
import random
SCENARIOS = {
"感谢": {
"examples": [
"太感谢了!真的帮了我大忙,欠你一个人情~",
"谢谢谢谢!你也太靠谱了,我都不知道怎么感谢好",
"收到!感谢感谢,有机会一定请你喝咖啡☕",
]
},
"道歉": {
"examples": [
"抱歉抱歉!是我考虑不周,真的不好意思🙏",
"啊不好意思!我不是故意的,抱歉抱歉",
"对不起对不起,下次一定注意,给你添麻烦了",
]
},
"确认": {
"examples": [
"收到收到,我这边没问题!",
"好的好的,我明白了,随时可以开始",
"没问题!收到,我记下来了",
]
},
"拒绝": {
"examples": [
"不好意思,这次实在抽不开身,下次有机会一定!",
"谢谢你的好意,但我这边可能不太方便,改天再约?",
"抱歉啊,这个我帮不上忙,你看看找别人?",
]
},
"催促": {
"examples": [
"你好,请问上次说的事情有进展了吗?方便的话回一下~",
"冒昧问一下,这个什么时候能搞定呀?急用,谢谢!",
"你好,打扰了,上次的项目还顺利吗?",
]
},
"安慰": {
"examples": [
"别太自责了,谁没踩过坑呢,下次会更好的!",
"没事没事,过程比结果重要,你已经很努力了💪",
"这不算什么,谁还没遇到过挫折呢,加油!",
]
},
"祝贺": {
"examples": [
"恭喜恭喜!真的太棒了,实至名归!🎉",
"哇太厉害了!恭喜恭喜!",
"太牛了!真替你高兴,必须庆祝一下!",
]
},
"请教": {
"examples": [
"想请教一下,你是怎么做到的?方便的话分享一下经验呗",
"不好意思打扰了,有个问题想请教你,方便吗?",
"有个地方想请教你,不知道方便吗?",
]
},
"介绍": {
"examples": [
"给你介绍下,这是我的朋友XXX,在XXX方面很有经验",
"这位是XXX,你们可以认识一下,说不定有合作机会",
"来认识一下!XXX是我的老朋友,在行业里很资深",
]
},
"回复知乎": {
"examples": [
"泻药。亲身经历来说,这个问题的关键在于...个人经验仅供参考",
"作为一个...的人,我觉得这个问题要从几个角度来看...以上希望能帮到你",
"根据我的经验...不过每个人的情况不同,仅供参考",
]
},
"回复小红书": {
"examples": [
"同问!蹲一个答案~",
"已收藏!感觉很有用,谢谢姐妹分享💕",
"姐妹这个也太详细了吧!已保存!",
]
}
}
def generate_reply(scenario, topic=""):
"""生成回复建议"""
if scenario not in SCENARIOS:
available = ', '.join(SCENARIOS.keys())
return f"未知场景。可用场景:{available}"
replies = SCENARIOS[scenario]["examples"]
reply = random.choice(replies)
if topic:
return f"💬 {reply}\n\n📝 场景:{scenario} | 话题:{topic}"
return f"💬 {reply}\n\n📝 场景:{scenario}"
def list_scenarios():
print("📋 可用场景:")
for i, name in enumerate(SCENARIOS.keys(), 1):
print(f" {i}. {name}")
print("\n💡 用法:")
print(' python smart_reply.py 感谢')
print(' python smart_reply.py 催促 "关于项目进度"')
print(' python smart_reply.py 回复知乎 "关于学习方法"')
print(' python smart_reply.py 回复小红书')
def main():
if len(sys.argv) < 2 or sys.argv[1] in ['--help', '-h', 'help']:
list_scenarios()
sys.exit(0)
scenario = sys.argv[1]
topic = sys.argv[2] if len(sys.argv) > 2 else ""
print(generate_reply(scenario, topic))
if __name__ == '__main__':
main()
批量图片处理工具。支持批量压缩、调整尺寸、添加水印、格式转换。一键处理整个文件夹,输出到指定目录。适用于电商图片、社媒素材、证件照批量处理。
---
name: cn-batch-image-editor
description: "批量图片处理工具。支持批量压缩、调整尺寸、添加水印、格式转换。一键处理整个文件夹,输出到指定目录。适用于电商图片、社媒素材、证件照批量处理。"
metadata:
openclaw:
emoji: 🖼️
category: productivity
tags:
- image
- batch
- watermark
- compress
- resize
- chinese
scope:
- "批量图片压缩(支持质量调节)"
- "批量调整尺寸(支持固定宽高/比例缩放)"
- "批量添加水印(文字/图片水印,支持位置和透明度)"
- "批量格式转换(PNG/JPG/WEBP互转)"
- "图片重命名(支持序号/日期/自定义前缀)"
install: |
pip install Pillow
env: {}
entry:
script: scripts/batch_image.py
args: []
---
# 批量图片处理工具
## 功能
- 批量压缩:减小图片体积,支持质量调节
- 批量调尺寸:固定宽高、比例缩放、智能裁剪
- 批量水印:文字水印、图片Logo水印
- 格式转换:PNG/JPG/WEBP/GIF互转
- 重命名:序号、日期、自定义前缀
## 使用方法
### 批量压缩
```bash
python scripts/batch_image.py compress /path/to/images --quality 85
```
### 批量调尺寸
```bash
# 固定宽度,高度等比缩放
python scripts/batch_image.py resize /path/to/images --width 800
# 固定高度
python scripts/batch_image.py resize /path/to/images --height 600
# 固定宽高(裁剪或填充)
python scripts/batch_image.py resize /path/to/images --width 800 --height 600 --mode crop
```
### 批量加水印
```bash
# 文字水印
python scripts/batch_image.py watermark /path/to/images --text "©养虾记" --position bottom-right --opacity 0.5
# 图片水印(Logo)
python scripts/batch_image.py watermark /path/to/images --logo /path/to/logo.png --position center --opacity 0.3
```
### 格式转换
```bash
# PNG转JPG
python scripts/batch_image.py convert /path/to/images --format jpg --quality 90
# 转WEBP(更小体积)
python scripts/batch_image.py convert /path/to/images --format webp
```
### 批量重命名
```bash
# 序号重命名
python scripts/batch_image.py rename /path/to/images --prefix "photo_" --start 1
# 日期重命名
python scripts/batch_image.py rename /path/to/images --date-format "%Y%m%d_%H%M%S"
```
## 参数说明
| 参数 | 说明 | 默认值 |
|------|------|--------|
| --quality | 压缩质量(1-100) | 85 |
| --width | 目标宽度 | - |
| --height | 目标高度 | - |
| --mode | 调整模式(scale/crop/fill) | scale |
| --text | 水印文字 | - |
| --logo | 水印图片路径 | - |
| --position | 水印位置 | bottom-right |
| --opacity | 水印透明度(0-1) | 0.5 |
| --format | 目标格式(png/jpg/webp/gif) | jpg |
| --output | 输出目录 | ./output |
| --prefix | 重命名前缀 | "" |
| --start | 重命名起始序号 | 1 |
## 水印位置选项
- `top-left`:左上角
- `top-right`:右上角
- `bottom-left`:左下角
- `bottom-right`:右下角
- `center`:居中
- `tile`:平铺
## 输出示例
```
处理完成!
输入目录:/Users/xxx/images
输出目录:/Users/xxx/images/output
处理图片:25张
总耗时:3.2秒
原始大小:125.6 MB → 处理后:32.4 MB(压缩74%)
```
## 典型场景
1. **电商图片**:批量调整为800x800,压缩到200KB以内
2. **社媒素材**:批量加水印,防止盗图
3. **证件照**:批量调尺寸(295x413),统一格式
4. **博客图片**:转WebP格式,减小体积
FILE:scripts/batch_image.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量图片处理工具
支持:压缩、调尺寸、水印、格式转换、重命名
"""
import os
import sys
import argparse
from pathlib import Path
from datetime import datetime
try:
from PIL import Image, ImageDraw, ImageFont, ImageEnhance
except ImportError:
print("请先安装Pillow: pip install Pillow")
sys.exit(1)
SUPPORTED_FORMATS = {'.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp'}
def get_image_files(input_dir: str) -> list:
"""获取目录下所有图片文件"""
input_path = Path(input_dir)
if not input_path.exists():
print(f"错误:目录不存在 {input_dir}")
return []
files = []
for f in input_path.iterdir():
if f.is_file() and f.suffix.lower() in SUPPORTED_FORMATS:
files.append(f)
return sorted(files)
def ensure_output_dir(output_dir: str) -> Path:
"""确保输出目录存在"""
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
return output_path
def compress_image(img: Image.Image, quality: int = 85) -> Image.Image:
"""压缩图片"""
# 转换为RGB模式(去掉alpha通道以支持JPEG)
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
return img
def resize_image(img: Image.Image, width: int = None, height: int = None, mode: str = 'scale') -> Image.Image:
"""调整图片尺寸
Args:
img: PIL图片对象
width: 目标宽度
height: 目标高度
mode: scale(等比缩放) | crop(裁剪) | fill(填充)
"""
orig_w, orig_h = img.size
if not width and not height:
return img
# 计算目标尺寸
if width and height:
target_w, target_h = width, height
elif width:
ratio = width / orig_w
target_w = width
target_h = int(orig_h * ratio)
else: # height only
ratio = height / orig_h
target_w = int(orig_w * ratio)
target_h = height
if mode == 'scale':
return img.resize((target_w, target_h), Image.Resampling.LANCZOS)
elif mode == 'crop':
# 居中裁剪
left = (orig_w - target_w) // 2 if orig_w > target_w else 0
top = (orig_h - target_h) // 2 if orig_h > target_h else 0
right = min(orig_w, left + target_w)
bottom = min(orig_h, top + target_h)
img = img.crop((left, top, right, bottom))
return img.resize((target_w, target_h), Image.Resampling.LANCZOS)
elif mode == 'fill':
# 填充背景
new_img = Image.new('RGB', (target_w, target_h), (255, 255, 255))
ratio = min(target_w / orig_w, target_h / orig_h)
new_w = int(orig_w * ratio)
new_h = int(orig_h * ratio)
resized = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
offset_x = (target_w - new_w) // 2
offset_y = (target_h - new_h) // 2
new_img.paste(resized, (offset_x, offset_y))
return new_img
return img
def add_watermark(img: Image.Image, text: str = None, logo: str = None, position: str = 'bottom-right', opacity: float = 0.5) -> Image.Image:
"""添加水印
Args:
img: PIL图片对象
text: 水印文字
logo: 水印图片路径
position: 位置 (top-left/top-right/bottom-left/bottom-right/center/tile)
opacity: 透明度 (0-1)
"""
# 确保有alpha通道
if img.mode != 'RGBA':
img = img.convert('RGBA')
watermark_layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
if logo:
# 图片水印
logo_img = Image.open(logo).convert('RGBA')
# 缩放logo(不超过图片的1/4)
max_size = min(img.size) // 4
logo_w, logo_h = logo_img.size
if max(logo_w, logo_h) > max_size:
ratio = max_size / max(logo_w, logo_h)
logo_img = logo_img.resize((int(logo_w * ratio), int(logo_h * ratio)), Image.Resampling.LANCZOS)
logo_w, logo_h = logo_img.size
pos = get_position(img.size, (logo_w, logo_h), position)
# 应用透明度
logo_with_opacity = Image.new('RGBA', logo_img.size, (0, 0, 0, 0))
for x in range(logo_w):
for y in range(logo_h):
r, g, b, a = logo_img.getpixel((x, y))
a = int(a * opacity)
logo_with_opacity.putpixel((x, y), (r, g, b, a))
watermark_layer.paste(logo_with_opacity, pos, logo_with_opacity)
elif text:
# 文字水印
try:
# 尝试使用系统字体
font_size = max(img.size) // 20
font = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", font_size)
except:
font = ImageFont.load_default()
draw = ImageDraw.Draw(watermark_layer)
bbox = draw.textbbox((0, 0), text, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
pos = get_position(img.size, (text_w, text_h), position)
# 绘制文字(带透明度)
alpha = int(255 * opacity)
draw.text(pos, text, font=font, fill=(255, 255, 255, alpha))
# 合并图层
return Image.alpha_composite(img, watermark_layer)
def get_position(img_size: tuple, obj_size: tuple, position: str) -> tuple:
"""计算水印位置"""
img_w, img_h = img_size
obj_w, obj_h = obj_size
margin = 20
positions = {
'top-left': (margin, margin),
'top-right': (img_w - obj_w - margin, margin),
'bottom-left': (margin, img_h - obj_h - margin),
'bottom-right': (img_w - obj_w - margin, img_h - obj_h - margin),
'center': ((img_w - obj_w) // 2, (img_h - obj_h) // 2),
}
return positions.get(position, positions['bottom-right'])
def convert_format(img: Image.Image, format: str, quality: int = 85) -> tuple:
"""转换格式,返回(图片, 保存参数)"""
format = format.lower()
save_kwargs = {}
if format in ('jpg', 'jpeg'):
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
save_kwargs['quality'] = quality
save_kwargs['optimize'] = True
ext = '.jpg'
elif format == 'webp':
save_kwargs['quality'] = quality
ext = '.webp'
elif format == 'png':
save_kwargs['optimize'] = True
ext = '.png'
elif format == 'gif':
ext = '.gif'
else:
ext = '.png'
return img, ext, save_kwargs
def batch_compress(input_dir: str, output_dir: str = None, quality: int = 85):
"""批量压缩"""
files = get_image_files(input_dir)
if not files:
print("未找到图片文件")
return
output_path = ensure_output_dir(output_dir or os.path.join(input_dir, 'output'))
total_orig = 0
total_new = 0
print(f"处理 {len(files)} 张图片...")
for i, f in enumerate(files, 1):
img = Image.open(f)
orig_size = os.path.getsize(f)
total_orig += orig_size
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
output_file = output_path / f"{f.stem}_compressed.jpg"
img.save(output_file, 'JPEG', quality=quality, optimize=True)
new_size = os.path.getsize(output_file)
total_new += new_size
print(f" [{i}/{len(files)}] {f.name}: {orig_size/1024:.1f}KB → {new_size/1024:.1f}KB")
ratio = (1 - total_new / total_orig) * 100 if total_orig > 0 else 0
print(f"\n完成!总大小:{total_orig/1024/1024:.1f}MB → {total_new/1024/1024:.1f}MB(压缩{ratio:.0f}%)")
def batch_resize(input_dir: str, output_dir: str = None, width: int = None, height: int = None, mode: str = 'scale'):
"""批量调整尺寸"""
files = get_image_files(input_dir)
if not files:
print("未找到图片文件")
return
output_path = ensure_output_dir(output_dir or os.path.join(input_dir, 'output'))
print(f"处理 {len(files)} 张图片...")
for i, f in enumerate(files, 1):
img = Image.open(f)
img = resize_image(img, width, height, mode)
output_file = output_path / f.name
img.save(output_file)
print(f" [{i}/{len(files)}] {f.name} → {img.size}")
print(f"\n完成!输出到:{output_path}")
def batch_watermark(input_dir: str, output_dir: str = None, text: str = None, logo: str = None, position: str = 'bottom-right', opacity: float = 0.5):
"""批量添加水印"""
if not text and not logo:
print("错误:请指定 --text 或 --logo")
return
files = get_image_files(input_dir)
if not files:
print("未找到图片文件")
return
output_path = ensure_output_dir(output_dir or os.path.join(input_dir, 'output'))
print(f"处理 {len(files)} 张图片...")
for i, f in enumerate(files, 1):
img = Image.open(f)
img = add_watermark(img, text=text, logo=logo, position=position, opacity=opacity)
output_file = output_path / f.name
if img.mode == 'RGBA':
img.save(output_file)
else:
img.save(output_file)
print(f" [{i}/{len(files)}] {f.name}")
print(f"\n完成!输出到:{output_path}")
def batch_convert(input_dir: str, output_dir: str = None, format: str = 'jpg', quality: int = 85):
"""批量格式转换"""
files = get_image_files(input_dir)
if not files:
print("未找到图片文件")
return
output_path = ensure_output_dir(output_dir or os.path.join(input_dir, 'output'))
print(f"处理 {len(files)} 张图片...")
for i, f in enumerate(files, 1):
img = Image.open(f)
img, ext, save_kwargs = convert_format(img, format, quality)
output_file = output_path / (f.stem + ext)
img.save(output_file, **save_kwargs)
print(f" [{i}/{len(files)}] {f.name} → {ext}")
print(f"\n完成!输出到:{output_path}")
def batch_rename(input_dir: str, output_dir: str = None, prefix: str = '', start: int = 1, date_format: str = None):
"""批量重命名"""
files = get_image_files(input_dir)
if not files:
print("未找到图片文件")
return
output_path = ensure_output_dir(output_dir or os.path.join(input_dir, 'renamed'))
print(f"处理 {len(files)} 张图片...")
for i, f in enumerate(files, 1):
img = Image.open(f)
if date_format:
try:
mtime = datetime.fromtimestamp(f.stat().st_mtime)
new_name = mtime.strftime(date_format) + f.suffix
except:
new_name = f"{prefix}{start + i - 1:04d}{f.suffix}"
else:
new_name = f"{prefix}{start + i - 1:04d}{f.suffix}"
output_file = output_path / new_name
img.save(output_file)
print(f" [{i}/{len(files)}] {f.name} → {new_name}")
print(f"\n完成!输出到:{output_path}")
def main():
parser = argparse.ArgumentParser(description="批量图片处理工具")
subparsers = parser.add_subparsers(dest='command', help='命令')
# compress命令
compress_parser = subparsers.add_parser('compress', help='批量压缩')
compress_parser.add_argument('input', help='输入目录')
compress_parser.add_argument('--output', '-o', help='输出目录')
compress_parser.add_argument('--quality', '-q', type=int, default=85, help='压缩质量(1-100)')
# resize命令
resize_parser = subparsers.add_parser('resize', help='批量调整尺寸')
resize_parser.add_argument('input', help='输入目录')
resize_parser.add_argument('--output', '-o', help='输出目录')
resize_parser.add_argument('--width', '-W', type=int, help='目标宽度')
resize_parser.add_argument('--height', '-H', type=int, help='目标高度')
resize_parser.add_argument('--mode', '-m', choices=['scale', 'crop', 'fill'], default='scale', help='调整模式')
# watermark命令
watermark_parser = subparsers.add_parser('watermark', help='批量添加水印')
watermark_parser.add_argument('input', help='输入目录')
watermark_parser.add_argument('--output', '-o', help='输出目录')
watermark_parser.add_argument('--text', '-t', help='水印文字')
watermark_parser.add_argument('--logo', '-l', help='水印图片路径')
watermark_parser.add_argument('--position', '-p', default='bottom-right',
choices=['top-left', 'top-right', 'bottom-left', 'bottom-right', 'center'],
help='水印位置')
watermark_parser.add_argument('--opacity', type=float, default=0.5, help='透明度(0-1)')
# convert命令
convert_parser = subparsers.add_parser('convert', help='批量格式转换')
convert_parser.add_argument('input', help='输入目录')
convert_parser.add_argument('--output', '-o', help='输出目录')
convert_parser.add_argument('--format', '-f', default='jpg', choices=['png', 'jpg', 'webp', 'gif'], help='目标格式')
convert_parser.add_argument('--quality', '-q', type=int, default=85, help='压缩质量')
# rename命令
rename_parser = subparsers.add_parser('rename', help='批量重命名')
rename_parser.add_argument('input', help='输入目录')
rename_parser.add_argument('--output', '-o', help='输出目录')
rename_parser.add_argument('--prefix', default='', help='文件名前缀')
rename_parser.add_argument('--start', type=int, default=1, help='起始序号')
rename_parser.add_argument('--date-format', help='日期格式(如 %%Y%%m%%d_%%H%%M%%S)')
args = parser.parse_args()
if not args.command:
parser.print_help()
return
if args.command == 'compress':
batch_compress(args.input, args.output, args.quality)
elif args.command == 'resize':
batch_resize(args.input, args.output, args.width, args.height, args.mode)
elif args.command == 'watermark':
batch_watermark(args.input, args.output, args.text, args.logo, args.position, args.opacity)
elif args.command == 'convert':
batch_convert(args.input, args.output, args.format, args.quality)
elif args.command == 'rename':
batch_rename(args.input, args.output, args.prefix, args.start, args.date_format)
if __name__ == "__main__":
main()
短视频脚本生成助手。根据主题自动生成抖音/小红书/视频号脚本,包含黄金3秒开场、内容框架、引导互动等爆款元素。支持产品种草、知识分享、情感共鸣等多种类型。
---
name: cn-short-video-script
description: "短视频脚本生成助手。根据主题自动生成抖音/小红书/视频号脚本,包含黄金3秒开场、内容框架、引导互动等爆款元素。支持产品种草、知识分享、情感共鸣等多种类型。"
metadata:
openclaw:
emoji: 🎬
category: content
tags:
- video
- script
- douyin
- xiaohongshu
- content-creation
- chinese
scope:
- "根据主题生成短视频脚本"
- "支持抖音、小红书、视频号平台规范"
- "提供产品种草、知识分享、情感故事、教程讲解等类型"
- "输出分镜脚本(画面+台词+时长)"
- "爆款元素提示(黄金开场、互动引导等)"
install: |
pip install openai
env:
OPENAI_API_KEY: "可选,用于AI增强脚本生成。不配置则使用模板生成。"
entry:
script: scripts/video_script.py
args: []
---
# 短视频脚本生成助手
## 功能
- 根据主题/产品自动生成短视频脚本
- 支持多平台规范(抖音/小红书/视频号)
- 爆款元素集成(黄金3秒、互动引导、情绪曲线)
- 分镜脚本输出(画面+台词+BGM+时长)
## 使用方法
### 基础用法
```
生成短视频脚本:产品是"智能水杯",目标平台是抖音,类型是种草
```
### 参数说明
- 主题/产品:视频的核心内容
- 平台:抖音(快节奏)| 小红书(沉浸式)| 视频号(专业感)
- 类型:种草 | 知识分享 | 情感故事 | 教程讲解 | 剧情 | 口播
## 输出格式
```
【短视频脚本】主题:XXX
【平台】抖音
【类型】种草
【时长】30-60秒
=== 分镜脚本 ===
【镜头1】0-3秒(黄金开场)
画面:XXX
台词:XXX
音效:XXX
【镜头2】3-15秒(痛点呈现)
画面:XXX
台词:XXX
音效:XXX
...
【镜头N】结尾(互动引导)
画面:XXX
台词:XXX
音效:XXX
=== 爆款元素检查 ===
✅ 黄金3秒钩子
✅ 痛点呈现
✅ 解决方案
✅ 互动引导
✅ 情绪曲线完整
=== 拍摄建议 ===
- 景别:XXX
- 灯光:XXX
- 道具:XXX
- 配乐风格:XXX
```
## 示例
输入:产品"筋膜枪",平台抖音,类型种草
输出脚本将包含:
1. 痛点开场(脖子酸痛、肩膀僵硬)
2. 产品亮相(镜头特写)
3. 功能展示(使用场景)
4. 效果对比(使用前后)
5. 互动引导(评论区见)
FILE:scripts/video_script.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
短视频脚本生成器
支持抖音、小红书、视频号平台
包含爆款元素:黄金开场、情绪曲线、互动引导
"""
import json
import os
import sys
from dataclasses import dataclass
from typing import Optional
# 尝试导入OpenAI(可选依赖)
try:
import openai
HAS_OPENAI = True
except ImportError:
HAS_OPENAI = False
@dataclass
class VideoScript:
"""视频脚本结构"""
topic: str
platform: str
style: str
duration: str
shots: list
tips: list
# 平台规范
PLATFORM_RULES = {
"抖音": {
"节奏": "快节奏,前3秒必须抓住眼球",
"时长": "15-60秒最佳",
"风格": "接地气、有梗、节奏感强",
"音乐": "热门BGM、卡点",
"开场": "反问/痛点/悬念"
},
"小红书": {
"节奏": "沉浸式,娓娓道来",
"时长": "60-180秒",
"风格": "精致、真实、有温度",
"音乐": "轻音乐、治愈系",
"开场": "场景带入/价值承诺"
},
"视频号": {
"节奏": "专业感,干货为主",
"时长": "30-90秒",
"风格": "专业、有深度、实用",
"音乐": "商务风格BGM",
"开场": "数据/观点/问题"
}
}
# 脚本类型模板
SCRIPT_TEMPLATES = {
"种草": {
"structure": ["痛点开场", "产品亮相", "功能展示", "使用场景", "效果对比", "互动引导"],
"情绪曲线": "好奇→痛点→期待→满足→行动"
},
"知识分享": {
"structure": ["问题抛出", "答案预告", "核心内容", "案例说明", "总结升华", "关注引导"],
"情绪曲线": "困惑→好奇→恍然→收获→认同"
},
"情感故事": {
"structure": ["场景铺垫", "冲突呈现", "情绪爆发", "转折处理", "结局升华", "共鸣引导"],
"情绪曲线": "平静→紧张→共鸣→感动→治愈"
},
"教程讲解": {
"structure": ["效果预告", "问题引入", "步骤拆解", "要点强调", "成果展示", "练习引导"],
"情绪曲线": "期待→清晰→掌握→成就感"
},
"剧情": {
"structure": ["开场悬念", "人物设定", "冲突发展", "高潮转折", "结局反转", "互动提问"],
"情绪曲线": "好奇→紧张→意外→满足→期待"
},
"口播": {
"structure": ["观点抛出", "论据支撑", "案例佐证", "对立观点驳斥", "价值升华", "行动号召"],
"情绪曲线": "认同→思考→信服→行动"
}
}
# 爆款开场库
HOOK_TEMPLATES = {
"反问型": [
"你还在为{痛点}烦恼吗?",
"为什么{现象}?今天告诉你真相",
"90%的人都不知道{秘密}",
"{问题}怎么办?一招解决"
],
"数据型": [
"我测试了{数字}款{产品},发现{结论}",
"只有{比例}的人知道这个方法",
"用时{时间},我{成果}"
],
"痛点型": [
"如果你也{痛点},一定要看完",
"别再{错误做法}了!正确方法是",
"{问题}真的太难了...直到我发现"
],
"悬念型": [
"最后{时间}告诉你答案",
"你可能想不到,{反常识结论}",
"这个{东西}改变了我的{方面}"
],
"场景型": [
"场景:{具体场景},然后{转折}",
"当你在{场景}时,注意{要点}",
"想象一下,{理想状态}"
]
}
def generate_script_ai(topic: str, platform: str, style: str) -> Optional[VideoScript]:
"""使用AI生成脚本(需要OPENAI_API_KEY)"""
if not HAS_OPENAI:
return None
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
return None
try:
client = openai.OpenAI(api_key=api_key)
rules = PLATFORM_RULES.get(platform, PLATFORM_RULES["抖音"])
template = SCRIPT_TEMPLATES.get(style, SCRIPT_TEMPLATES["种草"])
prompt = f"""你是一个短视频脚本专家。请为主题"{topic}"生成一个短视频脚本。
平台:{platform}
类型:{style}
平台规范:
- 节奏:{rules['节奏']}
- 时长:{rules['时长']}
- 风格:{rules['风格']}
- 开场风格:{rules['开场']}
脚本结构应包含:{' → '.join(template['structure'])}
请以JSON格式输出,包含以下字段:
{{
"topic": "主题",
"platform": "平台",
"style": "类型",
"duration": "建议时长",
"shots": [
{{
"shot_number": 1,
"time": "0-3秒",
"type": "黄金开场",
"scene": "画面描述",
"dialogue": "台词/旁白",
"music": "BGM/音效",
"notes": "拍摄要点"
}}
],
"tips": ["拍摄建议1", "拍摄建议2"]
}}
只输出JSON,不要其他内容。"""
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.8,
max_tokens=2000
)
content = response.choices[0].message.content
# 提取JSON
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
data = json.loads(content.strip())
return VideoScript(
topic=data["topic"],
platform=data["platform"],
style=data["style"],
duration=data["duration"],
shots=data["shots"],
tips=data["tips"]
)
except Exception as e:
print(f"AI生成失败: {e}", file=sys.stderr)
return None
def generate_script_template(topic: str, platform: str, style: str) -> VideoScript:
"""模板生成脚本(无需API)"""
rules = PLATFORM_RULES.get(platform, PLATFORM_RULES["抖音"])
template = SCRIPT_TEMPLATES.get(style, SCRIPT_TEMPLATES["种草"])
# 根据类型生成不同的分镜
shots = []
if style == "种草":
shots = [
{
"shot_number": 1,
"time": "0-3秒",
"type": "黄金开场(痛点)",
"scene": f"特写镜头:展示{topic}解决的问题场景(如疲惫/困扰状态)",
"dialogue": f"还在为{topic}的效果发愁?今天分享我的真实体验",
"music": "卡点音效+热门BGM前奏",
"notes": "眼神要有戏,表情真实"
},
{
"shot_number": 2,
"time": "3-10秒",
"type": "产品亮相",
"scene": f"产品特写:{topic}正面展示,光线打亮质感",
"dialogue": f"就是这个{topic},我已经用了X周了",
"music": "BGM渐强",
"notes": "产品要拍出高级感"
},
{
"shot_number": 3,
"time": "10-25秒",
"type": "功能展示",
"scene": f"使用过程:展示{topic}的核心功能/用法,多个场景切换",
"dialogue": "它的XX功能真的很实用,特别是XX场景下",
"music": "轻快节奏",
"notes": "展示真实使用过程,不要太刻意"
},
{
"shot_number": 4,
"time": "25-40秒",
"type": "效果对比",
"scene": "分屏对比:使用前后的变化",
"dialogue": "来看对比,效果真的很明显",
"music": "音效突出对比时刻",
"notes": "对比要真实可信"
},
{
"shot_number": 5,
"time": "40-55秒",
"type": "价值总结",
"scene": "产品+人物同框,自然光",
"dialogue": f"{topic}真的改变了我的XX,强烈推荐给XX人群",
"music": "BGM高潮",
"notes": "真诚是必杀技"
},
{
"shot_number": 6,
"time": "55-60秒",
"type": "互动引导",
"scene": "人物面对镜头,手势引导",
"dialogue": "评论区说说你的使用心得吧~关注我,下期分享XX",
"music": "BGM渐弱",
"notes": "表情要自然期待"
}
]
elif style == "知识分享":
shots = [
{
"shot_number": 1,
"time": "0-3秒",
"type": "问题抛出",
"scene": "人物面对镜头,背景简洁",
"dialogue": f"关于{topic},很多人都搞错了",
"music": "疑问音效",
"notes": "语气要权威但不傲慢"
},
{
"shot_number": 2,
"time": "3-8秒",
"type": "答案预告",
"scene": "文字卡片+人物讲解",
"dialogue": "今天告诉你正确答案,记得看到最后",
"music": "轻快BGM",
"notes": "制造期待感"
},
{
"shot_number": 3,
"time": "8-35秒",
"type": "核心内容",
"scene": "图示/演示+讲解",
"dialogue": f"关于{topic}的三个要点:第一...第二...第三...",
"music": "背景音乐稳定",
"notes": "信息密度要高,但不要着急"
},
{
"shot_number": 4,
"time": "35-50秒",
"type": "案例说明",
"scene": "真实案例图片/视频",
"dialogue": "举个例子,XX就是用了这个方法...",
"music": "案例部分BGM变化",
"notes": "案例要真实可验证"
},
{
"shot_number": 5,
"time": "50-58秒",
"type": "总结升华",
"scene": "人物特写",
"dialogue": f"总结一下,{topic}的关键就是...",
"music": "总结感BGM",
"notes": "提炼核心价值"
},
{
"shot_number": 6,
"time": "58-60秒",
"type": "关注引导",
"scene": "手势引导关注",
"dialogue": "关注我,每天分享XX干货",
"music": "BGM收尾",
"notes": "动作要自然"
}
]
else:
# 通用结构
for i, stage in enumerate(template["structure"]):
shots.append({
"shot_number": i + 1,
"time": f"{i * 10}-{(i + 1) * 10}秒",
"type": stage,
"scene": f"【{stage}】画面待设计",
"dialogue": f"【{stage}】台词待填写",
"music": "BGM建议待定",
"notes": "根据实际内容调整"
})
return VideoScript(
topic=topic,
platform=platform,
style=style,
duration=rules["时长"],
shots=shots,
tips=[
f"遵循{platform}平台规范:{rules['风格']}",
f"开场使用{rules['开场']}方式",
f"建议配乐风格:{rules['音乐']}",
"保持情绪曲线完整",
"结尾必须有互动引导"
]
)
def format_output(script: VideoScript) -> str:
"""格式化输出"""
lines = []
lines.append(f"【短视频脚本】主题:{script.topic}")
lines.append(f"【平台】{script.platform}")
lines.append(f"【类型】{script.style}")
lines.append(f"【时长】{script.duration}")
lines.append("")
lines.append("=== 分镜脚本 ===")
lines.append("")
for shot in script.shots:
lines.append(f"【镜头{shot['shot_number']}】{shot['time']}({shot['type']})")
lines.append(f"画面:{shot['scene']}")
lines.append(f"台词:{shot['dialogue']}")
lines.append(f"音效:{shot['music']}")
lines.append(f"备注:{shot['notes']}")
lines.append("")
lines.append("=== 拍摄建议 ===")
for i, tip in enumerate(script.tips, 1):
lines.append(f"{i}. {tip}")
return "\n".join(lines)
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description="短视频脚本生成器")
parser.add_argument("--topic", "-t", required=True, help="主题/产品")
parser.add_argument("--platform", "-p", default="抖音", choices=["抖音", "小红书", "视频号"], help="目标平台")
parser.add_argument("--style", "-s", default="种草", choices=["种草", "知识分享", "情感故事", "教程讲解", "剧情", "口播"], help="脚本类型")
parser.add_argument("--ai", action="store_true", help="使用AI增强生成(需要OPENAI_API_KEY)")
args = parser.parse_args()
# 尝试AI生成
if args.ai or os.environ.get("OPENAI_API_KEY"):
script = generate_script_ai(args.topic, args.platform, args.style)
if script:
print(format_output(script))
return
# 模板生成
script = generate_script_template(args.topic, args.platform, args.style)
print(format_output(script))
if __name__ == "__main__":
main()
会议纪要生成工具。输入会议录音文件,自动生成结构化纪要文档,包含关键讨论点、决策结论、待办事项,输出Markdown格式。
---
name: cn-meeting-minutes
description: "会议纪要生成工具。输入会议录音文件,自动生成结构化纪要文档,包含关键讨论点、决策结论、待办事项,输出Markdown格式。"
metadata:
openclaw:
emoji: "📝"
category: productivity
tags:
- meeting
- minutes
- transcription
---
## 功能
- 结构化纪要生成(主题+讨论点+决策+待办)
- 关键词密度分析提取讨论点
- 正则匹配提取待办事项(责任人+截止日期)
- 正则匹配提取决策结论
- Markdown格式输出
- 支持MP3/WAV/M4A音频格式
## 使用方法
```
python3 scripts/meeting_minutes.py <音频文件路径> -o <输出文件>
```
## 依赖
- Python 3.7+
- requests
## 权限声明
- 读取本地音频文件
- 生成Markdown纪要文件
## 使用场景
- 周会/站会纪要整理
- 客户会议记录与跟进
- 培训课程内容整理
- 面试/调研录音文字化
FILE:scripts/meeting_minutes.py
#!/usr/bin/env python3
"""
会议智能纪要助手
录音转文字 → 生成纪要 → 提取待办 → 同步飞书
"""
import sys
import os
import re
import json
import argparse
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Optional
import requests
def transcribe_audio(audio_path: str, provider: str = 'volcano') -> Dict:
"""
音频转文字
Args:
audio_path: 音频文件路径
provider: ASR提供商 (local/volcano/siliconflow)
Returns:
{
'text': 完整转写文本,
'segments': 分段结果(含时间戳、说话人),
'duration': 音频时长(秒)
}
"""
if not os.path.exists(audio_path):
return {'error': f'音频文件不存在: {audio_path}'}
# 检查文件格式
ext = Path(audio_path).suffix.lower()
if ext not in ['.mp3', '.wav', '.m4a', '.mp4', '.wma']:
return {'error': f'不支持的音频格式: {ext},请使用 MP3/WAV/M4A'}
# 获取文件大小
file_size = os.path.getsize(audio_path)
if file_size > 500 * 1024 * 1024: # 500MB
return {'error': '音频文件过大(>500MB),请分段上传'}
# 模拟转写结果(实际需接入ASR API)
# 这里返回模拟数据,实际实现需要调用火山/硅基流动等API
print(f"🎤 正在转写: {os.path.basename(audio_path)} ({file_size/1024/1024:.1f}MB)")
print(f" 使用ASR: {provider}")
# 模拟分段结果
mock_segments = [
{'speaker': '发言人A', 'text': '大家好,我们今天讨论一下Q2的产品规划。', 'start': 0, 'end': 5},
{'speaker': '发言人B', 'text': '我先说一下技术方面的准备情况。', 'start': 6, 'end': 10},
{'speaker': '发言人A', 'text': '好的,请讲。', 'start': 11, 'end': 12},
]
full_text = '\n'.join([f"[{s['speaker']}] {s['text']}" for s in mock_segments])
return {
'text': full_text,
'segments': mock_segments,
'duration': 300, # 5分钟模拟
'provider': provider,
'audio_file': audio_path
}
def generate_minutes(transcription: Dict) -> Dict:
"""
根据转写文本生成结构化纪要
策略:
1. 识别会议主题(首段内容)
2. 提取关键讨论点(基于关键词密度)
3. 识别决策结论("决定"/"确认"/"通过"等)
4. 提取待办事项("TODO"/"待办"/"负责"等)
"""
text = transcription.get('text', '')
segments = transcription.get('segments', [])
if not text:
return {'error': '转写文本为空'}
# 提取主题(简单启发式:第一段或含"会议"/"讨论"/"议题"的句子)
lines = text.split('\n')
topic = "会议讨论"
for line in lines[:5]:
if any(kw in line for kw in ['会议', '讨论', '议题', '规划', '复盘']):
topic = line.strip()[:50]
break
# 提取关键讨论点(含关键词的句子)
key_points = []
keywords = ['进度', '问题', '风险', '方案', '计划', '目标', '预算', '资源']
for line in lines:
for kw in keywords:
if kw in line and len(line) > 10:
key_points.append(line.strip())
break
# 去重并限制数量
key_points = list(dict.fromkeys(key_points))[:8]
# 提取决策结论
decisions = []
decision_patterns = [
r'(决定|确认|通过|同意|确定).*?(?:。|$)',
r'.*?((?:采用|选择|定为).{2,20}?)(?:。|$)',
]
for line in lines:
for pattern in decision_patterns:
matches = re.findall(pattern, line)
decisions.extend(matches)
decisions = list(dict.fromkeys(decisions))[:5]
# 提取待办事项
todos = extract_todos(text)
# 生成纪要文本
minutes_text = f"""# 会议纪要
## 📋 基本信息
- **会议主题**: {topic}
- **会议时间**: {datetime.now().strftime('%Y-%m-%d %H:%M')}
- **音频时长**: {transcription.get('duration', 0)//60}分钟
- **转写引擎**: {transcription.get('provider', 'unknown')}
## 📝 会议内容
### 关键讨论点
{chr(10).join(['- ' + p for p in key_points]) if key_points else '- (未识别到明确讨论点)'}
### 决策结论
{chr(10).join(['✓ ' + d for d in decisions]) if decisions else '- (未识别到明确决策)'}
## ✅ 待办事项
{format_todos(todos) if todos else '无明确待办事项'}
## 📄 完整转写
<details>
<summary>点击查看完整转写文本</summary>
{text}
</details>
---
*由 cn-meeting-minutes 自动生成*
"""
return {
'topic': topic,
'key_points': key_points,
'decisions': decisions,
'todos': todos,
'minutes_text': minutes_text,
'duration': transcription.get('duration', 0),
'word_count': len(text)
}
def extract_todos(text: str) -> List[Dict]:
"""
从文本中提取待办事项
识别模式:
- "TODO: xxx"
- "待办:xxx"
- "xxx 负责 xxx"
- "xxx 跟进 xxx"
"""
todos = []
lines = text.split('\n')
todo_patterns = [
r'(?:TODO|待办|待处理|待确认)[::]\s*(.+)',
r'(.{2,10})(?:负责|跟进|落实|确认|完成)(.{3,30})',
r'(下周|明天|本周|月底前).*?(完成|确认|提交|评审)(.{2,20})',
]
for line in lines:
for pattern in todo_patterns:
matches = re.findall(pattern, line)
for match in matches:
if isinstance(match, tuple):
todo_text = ' '.join(match)
else:
todo_text = match
# 提取责任人(简单启发式)
assignee = ''
name_match = re.search(r'([\u4e00-\u9fa5]{2,4})(?:负责|跟进)', line)
if name_match:
assignee = name_match.group(1)
# 提取截止日期
deadline = ''
if '明天' in line:
deadline = '明天'
elif '下周' in line:
deadline = '下周'
elif '月底' in line:
deadline = '月底前'
todos.append({
'task': todo_text.strip()[:100],
'assignee': assignee,
'deadline': deadline,
'source_line': line.strip()[:80]
})
# 去重
seen = set()
unique_todos = []
for t in todos:
key = t['task']
if key not in seen:
seen.add(key)
unique_todos.append(t)
return unique_todos[:10] # 最多10条
def format_todos(todos: List[Dict]) -> str:
"""格式化待办列表为Markdown"""
if not todos:
return ''
result = []
for i, t in enumerate(todos, 1):
assignee_str = f" @{t['assignee']}" if t['assignee'] else ''
deadline_str = f" (截止: {t['deadline']})" if t['deadline'] else ''
result.append(f"{i}. [ ] {t['task']}{assignee_str}{deadline_str}")
return '\n'.join(result)
def save_to_feishu(minutes: Dict, folder_token: str = None) -> Dict:
"""保存纪要到飞书文档"""
# 简化实现,实际需要调用飞书API
return {
'success': True,
'message': '已生成飞书文档(模拟)',
'doc_url': 'https://feishu.cn/docx/mock_meeting_minutes'
}
def main():
parser = argparse.ArgumentParser(description='会议智能纪要助手')
parser.add_argument('audio_path', help='音频文件路径')
parser.add_argument('--asr', choices=['local', 'volcano', 'siliconflow'],
default='volcano', help='ASR提供商')
parser.add_argument('--output', '-o', help='输出文件路径')
parser.add_argument('--feishu', action='store_true', help='同步到飞书')
args = parser.parse_args()
# 1. 转写音频
transcription = transcribe_audio(args.audio_path, args.asr)
if 'error' in transcription:
print(f"❌ {transcription['error']}")
sys.exit(1)
print(f"✅ 转写完成,共 {len(transcription['text'])} 字符")
# 2. 生成纪要
minutes = generate_minutes(transcription)
print(f"\n📋 会议纪要预览:")
print(f" 主题: {minutes['topic']}")
print(f" 关键讨论点: {len(minutes['key_points'])} 条")
print(f" 决策结论: {len(minutes['decisions'])} 条")
print(f" 待办事项: {len(minutes['todos'])} 条")
# 3. 保存到文件
if args.output:
output_path = args.output
else:
base_name = Path(args.audio_path).stem
output_path = f"{base_name}_纪要.md"
with open(output_path, 'w', encoding='utf-8') as f:
f.write(minutes['minutes_text'])
print(f"\n💾 已保存到: {output_path}")
# 4. 同步飞书(可选)
if args.feishu:
result = save_to_feishu(minutes)
print(f"📤 {result['message']}")
# 输出JSON结果
result = {
'success': True,
'topic': minutes['topic'],
'key_points_count': len(minutes['key_points']),
'decisions_count': len(minutes['decisions']),
'todos_count': len(minutes['todos']),
'output_file': output_path
}
print(json.dumps(result, ensure_ascii=False))
if __name__ == '__main__':
main()
PDF文档处理工具。本地处理PDF文件,支持文本提取、智能摘要、表格导出、关键词问答、PDF拆分。纯本地处理,保护文档隐私。
---
name: cn-pdf-assistant
description: "PDF文档处理工具。本地处理PDF文件,支持文本提取、智能摘要、表格导出、关键词问答、PDF拆分。纯本地处理,保护文档隐私。"
metadata:
openclaw:
emoji: "📄"
category: productivity
tags:
- pdf
- document
- extract
---
## 功能
- PDF文本提取(支持指定页码范围)
- 智能摘要生成(章节标题识别+关键词频率分析)
- 表格提取(pdfplumber引擎)
- 关键词问答(基于段落匹配)
- PDF按页拆分
- 纯本地处理,无需联网
## 使用方法
```
python3 scripts/pdf_assistant.py <PDF文件路径> --action text
python3 scripts/pdf_assistant.py <PDF文件路径> --action summary
python3 scripts/pdf_assistant.py <PDF文件路径> --action tables
python3 scripts/pdf_assistant.py <PDF文件路径> --action ask --question "关键词"
python3 scripts/pdf_assistant.py <PDF文件路径> --action split
```
## 依赖
- Python 3.7+
- PyPDF2, pdfplumber, pandas, openpyxl
## 权限声明
- 读取本地PDF文件
- 生成输出文件
## 使用场景
- 论文阅读:快速提取核心内容
- 合同审查:提取关键条款
- 财报分析:提取表格数据
- 资料整理:批量拆分PDF文档
FILE:scripts/pdf_assistant.py
#!/usr/bin/env python3
"""
PDF智能助手核心脚本
支持摘要、问答、表格提取、翻译、页面操作
"""
import sys
import os
import re
import json
import argparse
from pathlib import Path
from typing import List, Dict, Optional
import PyPDF2
import pdfplumber
def extract_text(pdf_path: str, pages: Optional[List[int]] = None) -> Dict:
"""
提取PDF文本内容
Args:
pdf_path: PDF文件路径
pages: 指定页码列表(None表示全部)
Returns:
{
'text': 完整文本,
'pages': 每页文本列表,
'total_pages': 总页数,
'metadata': PDF元数据
}
"""
result = {
'text': '',
'pages': [],
'total_pages': 0,
'metadata': {}
}
try:
with pdfplumber.open(pdf_path) as pdf:
result['total_pages'] = len(pdf.pages)
result['metadata'] = pdf.metadata or {}
page_range = pages if pages else range(len(pdf.pages))
for i in page_range:
if i < len(pdf.pages):
page = pdf.pages[i]
page_text = page.extract_text() or ''
result['pages'].append({
'page_num': i + 1,
'text': page_text,
'char_count': len(page_text)
})
result['text'] += f"\n\n--- 第{i+1}页 ---\n\n{page_text}"
return result
except Exception as e:
return {'error': f'PDF读取失败: {str(e)}'}
def generate_summary(pdf_path: str, max_length: int = 500) -> Dict:
"""
生成PDF内容摘要
策略:
1. 提取前3页 + 后2页(通常是摘要和结论)
2. 识别章节标题
3. 统计关键词频率
"""
data = extract_text(pdf_path)
if 'error' in data:
return data
# 提取关键页面内容
key_content = ''
pages = data['pages']
# 前3页
for p in pages[:3]:
key_content += p['text'] + '\n'
# 后2页(如果有)
if len(pages) > 5:
for p in pages[-2:]:
key_content += p['text'] + '\n'
# 提取章节标题(简单启发式)
headings = []
lines = key_content.split('\n')
for line in lines:
line = line.strip()
# 章节标题特征:短行、数字编号、无标点结尾
if 5 < len(line) < 50 and not line.endswith(('。', ',', '.', ',')):
if re.match(r'^(第[一二三四五六七八九十\d]+章|Chapter \d+|\d+\.[\d\.]*|\d+、)', line):
headings.append(line)
# 统计关键词(简单词频)
words = re.findall(r'[\u4e00-\u9fa5]{2,}|[a-zA-Z]{4,}', key_content)
word_freq = {}
for w in words:
w = w.lower()
if len(w) > 1:
word_freq[w] = word_freq.get(w, 0) + 1
# 取高频词(排除常见词)
stop_words = {'this', 'that', 'with', 'from', 'they', 'have', 'been', 'their', 'were', 'which', 'would', 'there', 'could', 'should'}
keywords = sorted(
[(w, c) for w, c in word_freq.items() if w not in stop_words and c > 1],
key=lambda x: x[1],
reverse=True
)[:10]
# 生成摘要文本
summary_text = f"""📄 文档摘要
📖 基本信息:
- 文件名:{os.path.basename(pdf_path)}
- 总页数:{data['total_pages']} 页
- 总字数:约 {sum(p['char_count'] for p in pages)} 字符
📋 章节结构:
{chr(10).join(['• ' + h for h in headings[:8]]) if headings else '• 未识别到明确章节标题'}
🔑 关键词:
{', '.join([w for w, c in keywords]) if keywords else '暂无'}
📝 内容预览:
{key_content[:max_length]}...
💡 提示:使用 "PDF问答" 功能可深入了解具体内容
"""
return {
'summary': summary_text,
'headings': headings[:10],
'keywords': [w for w, c in keywords],
'total_pages': data['total_pages'],
'char_count': sum(p['char_count'] for p in pages)
}
def extract_tables(pdf_path: str, page_num: Optional[int] = None) -> List[Dict]:
"""
提取PDF中的表格
Args:
pdf_path: PDF路径
page_num: 指定页码(None表示所有页)
Returns:
表格列表,每个包含page_num, table_num, data, shape
"""
tables = []
try:
with pdfplumber.open(pdf_path) as pdf:
pages_to_check = [pdf.pages[page_num-1]] if page_num else pdf.pages
for page_idx, page in enumerate(pages_to_check, 1):
page_tables = page.extract_tables()
for table_idx, table in enumerate(page_tables, 1):
if table and len(table) > 1:
tables.append({
'page_num': page_num if page_num else page_idx,
'table_num': table_idx,
'data': table,
'rows': len(table),
'cols': len(table[0]) if table else 0
})
return tables
except Exception as e:
return [{'error': f'表格提取失败: {str(e)}'}]
def answer_question(pdf_path: str, question: str) -> Dict:
"""
基于PDF内容回答问题(简化版,不含LLM)
策略:
1. 提取所有文本
2. 关键词匹配定位相关段落
3. 返回相关段落及页码
"""
data = extract_text(pdf_path)
if 'error' in data:
return data
# 提取问题关键词
q_keywords = re.findall(r'[\u4e00-\u9fa5]{2,}|[a-zA-Z]{3,}', question.lower())
q_keywords = [k for k in q_keywords if len(k) > 2]
# 搜索相关段落
relevant_passages = []
for page in data['pages']:
page_text = page['text'].lower()
score = sum(1 for kw in q_keywords if kw in page_text)
if score > 0:
# 提取包含关键词的句子
sentences = re.split(r'[。\.\n]', page['text'])
for sent in sentences:
sent_score = sum(1 for kw in q_keywords if kw in sent.lower())
if sent_score > 0:
relevant_passages.append({
'page': page['page_num'],
'text': sent.strip(),
'score': sent_score + score
})
# 按相关度排序
relevant_passages.sort(key=lambda x: x['score'], reverse=True)
# 生成回答
if relevant_passages:
top_passages = relevant_passages[:5]
answer = f"根据PDF内容,找到以下相关信息:\n\n"
for p in top_passages:
answer += f"📄 第{p['page']}页:{p['text'][:150]}...\n\n"
else:
answer = "未在PDF中找到与问题直接相关的内容。\n建议:\n1. 检查问题关键词是否准确\n2. 尝试使用文档中的专业术语\n3. 确认PDF是否包含相关内容"
return {
'question': question,
'answer': answer,
'relevant_pages': list(set(p['page'] for p in relevant_passages[:5])),
'match_count': len(relevant_passages)
}
def split_pdf(pdf_path: str, output_dir: str, pages_per_file: int = 1) -> List[str]:
"""
拆分PDF为多个文件
Args:
pdf_path: 原PDF路径
output_dir: 输出目录
pages_per_file: 每个文件的页数
Returns:
生成的文件路径列表
"""
output_files = []
try:
os.makedirs(output_dir, exist_ok=True)
base_name = Path(pdf_path).stem
with open(pdf_path, 'rb') as f:
reader = PyPDF2.PdfReader(f)
total_pages = len(reader.pages)
for start in range(0, total_pages, pages_per_file):
writer = PyPDF2.PdfWriter()
end = min(start + pages_per_file, total_pages)
for i in range(start, end):
writer.add_page(reader.pages[i])
output_path = os.path.join(
output_dir,
f"{base_name}_p{start+1}-{end}.pdf"
)
with open(output_path, 'wb') as out_f:
writer.write(out_f)
output_files.append(output_path)
return output_files
except Exception as e:
return [f'error: {str(e)}']
def main():
parser = argparse.ArgumentParser(description='PDF智能助手')
parser.add_argument('pdf_path', help='PDF文件路径')
parser.add_argument('--action', '-a',
choices=['summary', 'tables', 'ask', 'split', 'text'],
default='summary',
help='操作类型')
parser.add_argument('--question', '-q', help='问答模式的问题')
parser.add_argument('--page', '-p', type=int, help='指定页码')
parser.add_argument('--output', '-o', help='输出目录')
args = parser.parse_args()
if not os.path.exists(args.pdf_path):
print(json.dumps({'error': 'PDF文件不存在'}, ensure_ascii=False))
sys.exit(1)
print(f"📄 处理PDF: {os.path.basename(args.pdf_path)}")
if args.action == 'summary':
result = generate_summary(args.pdf_path)
print(result['summary'])
elif args.action == 'tables':
tables = extract_tables(args.pdf_path, args.page)
print(f"找到 {len(tables)} 个表格")
for t in tables:
if 'error' in t:
print(f"❌ {t['error']}")
else:
print(f"\n📊 第{t['page_num']}页 表格{t['table_num']}: {t['rows']}行×{t['cols']}列")
elif args.action == 'ask':
if not args.question:
print(json.dumps({'error': '问答模式需要提供 --question'}, ensure_ascii=False))
sys.exit(1)
result = answer_question(args.pdf_path, args.question)
print(f"\n❓ 问题: {result['question']}")
print(f"\n💡 回答:\n{result['answer']}")
elif args.action == 'split':
output_dir = args.output or os.path.expanduser('~/Documents/PDFAssistant/split')
files = split_pdf(args.pdf_path, output_dir)
print(f"✅ 已拆分为 {len(files)} 个文件:")
for f in files:
if f.startswith('error:'):
print(f"❌ {f}")
else:
print(f" {f}")
elif args.action == 'text':
data = extract_text(args.pdf_path, [args.page-1] if args.page else None)
if 'error' in data:
print(f"❌ {data['error']}")
else:
print(data['text'][:3000])
# 输出JSON结果
print("\n" + json.dumps({'action': args.action, 'success': True}, ensure_ascii=False))
if __name__ == '__main__':
main()
网页剪藏工具。发送网页链接,提取正文内容保存为本地Markdown文件。
---
name: cn-web-clipper
description: "网页剪藏工具。发送网页链接,提取正文内容保存为本地Markdown文件。"
metadata:
openclaw:
emoji: "📎"
---
# 网页剪藏
发送网页链接,提取正文保存为Markdown。
## 功能
- 网页正文提取
- 本地Markdown保存
- 标题/作者/日期识别
- 批量URL处理
## 用法
```bash
python3 scripts/clip_webpage.py <URL>
python3 scripts/clip_webpage.py <URL> --dir <保存目录>
```
## 依赖
- Python 3.7+
- requests, beautifulsoup4
FILE:scripts/clip_webpage.py
#!/usr/bin/env python3
"""
网页剪藏核心脚本
提取网页正文,保存到飞书/Notion/本地
"""
import sys
import os
import re
import json
import argparse
from urllib.parse import urlparse, urljoin
from datetime import datetime
# 网页提取
import requests
from bs4 import BeautifulSoup
from readability import Document
# 可选:飞书API
FEISHU_AVAILABLE = False
try:
from feishu_api import FeishuClient
FEISHU_AVAILABLE = True
except ImportError:
pass
# 可选:Notion API
NOTION_AVAILABLE = False
try:
from notion_client import Client as NotionClient
NOTION_AVAILABLE = True
except ImportError:
pass
def extract_content(url: str) -> dict:
"""
提取网页正文内容
Args:
url: 网页URL
Returns:
{
'title': 文章标题,
'content': 正文HTML,
'text': 纯文本内容,
'author': 作者(如可识别),
'publish_date': 发布日期(如可识别),
'source_url': 原始URL,
'excerpt': 摘要(前200字)
}
"""
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
try:
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding or 'utf-8'
# 使用readability提取正文
doc = Document(resp.text)
title = doc.title()
content_html = doc.summary()
# 提取纯文本
soup = BeautifulSoup(content_html, 'html.parser')
text = soup.get_text(separator='\n', strip=True)
# 清理文本
text = re.sub(r'\n{3,}', '\n\n', text) # 合并多余空行
# 尝试提取作者和日期
author = _extract_author(soup, resp.text)
pub_date = _extract_date(soup, resp.text)
# 生成摘要
excerpt = text[:300].replace('\n', ' ') + '...' if len(text) > 300 else text
return {
'title': title or '无标题',
'content': content_html,
'text': text,
'author': author,
'publish_date': pub_date,
'source_url': url,
'excerpt': excerpt,
'domain': urlparse(url).netloc,
'clipped_at': datetime.now().isoformat()
}
except Exception as e:
return {
'error': str(e),
'source_url': url
}
def _extract_author(soup: BeautifulSoup, raw_html: str) -> str:
"""尝试提取作者信息"""
# 常见作者选择器
selectors = [
'meta[name="author"]',
'.author',
'.post-author',
'[rel="author"]',
'.byline',
'.article-author'
]
for selector in selectors:
elem = soup.select_one(selector)
if elem:
if elem.name == 'meta':
return elem.get('content', '')
return elem.get_text(strip=True)
return ''
def _extract_date(soup: BeautifulSoup, raw_html: str) -> str:
"""尝试提取发布日期"""
# 常见日期meta
date_meta = soup.find('meta', property='article:published_time')
if date_meta:
return date_meta.get('content', '')
date_meta = soup.find('meta', attrs={'name': 'publishdate'})
if date_meta:
return date_meta.get('content', '')
# 常见日期选择器
selectors = ['.publish-date', '.post-date', 'time[datetime]']
for selector in selectors:
elem = soup.select_one(selector)
if elem:
if elem.name == 'time':
return elem.get('datetime', '')
return elem.get_text(strip=True)
return ''
def save_to_markdown(data: dict, output_dir: str = None) -> str:
"""保存为Markdown文件"""
if output_dir is None:
output_dir = os.path.expanduser(
os.environ.get('CLIPPER_OUTPUT_DIR', '~/Documents/WebClips')
)
os.makedirs(output_dir, exist_ok=True)
# 生成文件名
safe_title = re.sub(r'[^\w\s-]', '', data['title'])[:50].strip()
timestamp = datetime.now().strftime('%Y%m%d')
filename = f"{timestamp}_{safe_title}.md"
filepath = os.path.join(output_dir, filename)
# 构建Markdown内容
md_content = f"""# {data['title']}
> 原文链接: [{data['source_url']}]({data['source_url']})
> 剪藏时间: {data['clipped_at'][:10]}
> 来源站点: {data['domain']}
{data['text']}
---
*由 cn-web-clipper 自动剪藏*
"""
with open(filepath, 'w', encoding='utf-8') as f:
f.write(md_content)
return filepath
def save_to_feishu(data: dict, folder_token: str = None) -> dict:
"""保存到飞书文档"""
if not FEISHU_AVAILABLE:
return {'error': '飞书API未安装,请运行: pip install feishu-api'}
app_id = os.environ.get('FEISHU_APP_ID')
app_secret = os.environ.get('FEISHU_APP_SECRET')
if not app_id or not app_secret:
return {'error': '缺少FEISHU_APP_ID或FEISHU_APP_SECRET环境变量'}
try:
client = FeishuClient(app_id, app_secret)
# 创建文档
doc_title = data['title'][:100]
doc_content = f"""{data['text']}
---
原文链接: {data['source_url']}
剪藏时间: {data['clipped_at'][:10]}
"""
# 这里简化处理,实际需要调用飞书文档API
# 返回模拟结果
return {
'success': True,
'doc_title': doc_title,
'doc_url': f'https://feishu.cn/docx/mock_{hash(data["source_url"])}',
'message': f'已创建飞书文档: {doc_title}'
}
except Exception as e:
return {'error': f'飞书保存失败: {str(e)}'}
def main():
parser = argparse.ArgumentParser(description='网页剪藏工具')
parser.add_argument('url', help='要剪藏的网页URL')
parser.add_argument('--output', '-o', choices=['markdown', 'feishu', 'notion'],
default='markdown', help='输出格式')
parser.add_argument('--dir', '-d', help='本地保存目录')
args = parser.parse_args()
print(f"📎 正在剪藏: {args.url}")
# 提取内容
data = extract_content(args.url)
if 'error' in data:
print(f"❌ 提取失败: {data['error']}")
sys.exit(1)
print(f"✅ 提取成功: {data['title']}")
print(f" 字数: {len(data['text'])} 字")
print(f" 摘要: {data['excerpt'][:80]}...")
# 保存
if args.output == 'markdown':
filepath = save_to_markdown(data, args.dir)
print(f"💾 已保存到: {filepath}")
elif args.output == 'feishu':
result = save_to_feishu(data)
if 'error' in result:
print(f"❌ {result['error']}")
# 降级到本地保存
filepath = save_to_markdown(data, args.dir)
print(f"💾 已降级保存到本地: {filepath}")
else:
print(f"💾 {result['message']}")
print(f" 文档链接: {result['doc_url']}")
# 输出JSON结果(供OpenClaw调用)
result = {
'success': True,
'title': data['title'],
'word_count': len(data['text']),
'excerpt': data['excerpt'],
'source_url': data['source_url'],
'output_type': args.output
}
if args.output == 'markdown':
result['local_path'] = filepath
print(json.dumps(result, ensure_ascii=False))
if __name__ == '__main__':
main()