lightpdf-aipdf-mcp 0.1.43__py3-none-any.whl → 0.1.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import os
5
5
  import time
6
6
  from dataclasses import dataclass
7
7
  from typing import List, Optional, Dict, Any, Tuple
8
-
8
+ import urllib.parse
9
9
  import httpx
10
10
 
11
11
  @dataclass
@@ -15,6 +15,7 @@ class BaseResult:
15
15
  file_path: str
16
16
  error_message: Optional[str] = None
17
17
  download_url: Optional[str] = None
18
+ original_name: Optional[str] = None
18
19
 
19
20
  class Logger:
20
21
  """日志记录器类"""
@@ -103,9 +103,10 @@ class Converter(BaseApiClient):
103
103
  """PDF文档转换器"""
104
104
  def __init__(self, logger: Logger, file_handler: FileHandler):
105
105
  super().__init__(logger, file_handler)
106
- self.api_base_url = "https://techsz.aoscdn.com/api/tasks/document/conversion"
106
+ api_endpoint = os.environ.get("API_ENDPOINT", "devaw.aoscdn.com/tech")
107
+ self.api_base_url = f"https://{api_endpoint}/tasks/document/conversion"
107
108
 
108
- async def add_page_numbers(self, file_path: str, start_num: int = 1, position: str = "5", margin: int = 30, password: str = None) -> ConversionResult:
109
+ async def add_page_numbers(self, file_path: str, start_num: int = 1, position: str = "5", margin: int = 30, password: str = None, original_name: Optional[str] = None) -> ConversionResult:
109
110
  """为PDF文档添加页码
110
111
 
111
112
  Args:
@@ -114,6 +115,7 @@ class Converter(BaseApiClient):
114
115
  position: 页码显示位置,字符串类型,可选值1-6(左上/上中/右上/左下/下中/右下),默认为5(下中)
115
116
  margin: 页码显示的边距,整数类型,可选值10/30/60,默认为30
116
117
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
118
+ original_name: 原始文件名(可选)
117
119
 
118
120
  Returns:
119
121
  ConversionResult: 转换结果
@@ -122,6 +124,7 @@ class Converter(BaseApiClient):
122
124
  input_format = self.file_handler.get_input_format(file_path)
123
125
  if input_format != InputFormat.PDF:
124
126
  await self.logger.error(f"添加页码功能仅支持PDF文件,当前文件格式为: {self.file_handler.get_file_extension(file_path)}")
127
+ return ConversionResult(success=False, file_path=file_path, error_message="添加页码功能仅支持PDF文件", original_name=original_name)
125
128
 
126
129
  # 验证参数
127
130
  valid_positions = {"1", "2", "3", "4", "5", "6"}
@@ -129,15 +132,21 @@ class Converter(BaseApiClient):
129
132
 
130
133
  # 验证position参数
131
134
  if position not in valid_positions:
132
- await self.logger.error(f"无效的页码位置值: {position}。有效值为: 1(左上), 2(上中), 3(右上), 4(左下), 5(下中), 6(右下)")
135
+ error_msg = f"无效的页码位置值: {position}。有效值为: 1(左上), 2(上中), 3(右上), 4(左下), 5(下中), 6(右下)"
136
+ await self.logger.error(error_msg)
137
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
133
138
 
134
139
  # 验证margin参数
135
140
  if margin not in valid_margins:
136
- await self.logger.error(f"无效的页码边距值: {margin}。有效值为: 10, 30, 60")
141
+ error_msg = f"无效的页码边距值: {margin}。有效值为: 10, 30, 60"
142
+ await self.logger.error(error_msg)
143
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
137
144
 
138
145
  # 验证start_num是否为正整数
139
146
  if not isinstance(start_num, int) or start_num < 1:
140
- await self.logger.error(f"起始页码必须是正整数,当前值为: {start_num}")
147
+ error_msg = f"起始页码必须是正整数,当前值为: {start_num}"
148
+ await self.logger.error(error_msg)
149
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
141
150
 
142
151
  # 构建API参数
143
152
  extra_params = {
@@ -150,9 +159,9 @@ class Converter(BaseApiClient):
150
159
  await self.logger.log("info", f"正在为PDF添加页码(起始页码: {start_num}, 位置: {position}, 边距: {margin})...")
151
160
 
152
161
  # 调用convert_file方法处理API请求
153
- return await self.convert_file(file_path, "number-pdf", extra_params, password)
162
+ return await self.convert_file(file_path, "number-pdf", extra_params, password, original_name)
154
163
 
155
- async def convert_file(self, file_path: str, format: str, extra_params: dict = None, password: str = None) -> ConversionResult:
164
+ async def convert_file(self, file_path: str, format: str, extra_params: dict = None, password: str = None, original_name: Optional[str] = None) -> ConversionResult:
156
165
  """转换单个文件
157
166
 
158
167
  Args:
@@ -160,12 +169,14 @@ class Converter(BaseApiClient):
160
169
  format: 目标格式
161
170
  extra_params: 额外的API参数,例如去除水印
162
171
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
172
+ original_name: 原始文件名(可选)
163
173
 
164
174
  Returns:
165
175
  ConversionResult: 转换结果
166
176
  """
167
177
  if not self.api_key:
168
178
  await self.logger.error("未找到API_KEY。请在客户端配置API_KEY环境变量。")
179
+ return ConversionResult(success=False, file_path=file_path, error_message="未找到API_KEY", original_name=original_name)
169
180
 
170
181
  # 特殊格式:doc-repair用于去除水印,number-pdf用于添加页码,输出均为PDF
171
182
  is_special_operation = format in ["doc-repair", "number-pdf"]
@@ -180,38 +191,40 @@ class Converter(BaseApiClient):
180
191
  # 验证输入文件格式
181
192
  input_format = self.file_handler.get_input_format(file_path)
182
193
  if not input_format and not is_special_operation:
183
- await self.logger.error(f"不支持的输入文件格式: {self.file_handler.get_file_extension(file_path)}")
194
+ error_msg = f"不支持的输入文件格式: {self.file_handler.get_file_extension(file_path)}"
195
+ await self.logger.error(error_msg)
196
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
184
197
 
185
198
  # 如果是去除水印操作,检查是否PDF文件
186
199
  if format == "doc-repair" and input_format != InputFormat.PDF:
187
- await self.logger.error("去除水印功能仅支持PDF文件")
200
+ error_msg = "去除水印功能仅支持PDF文件"
201
+ await self.logger.error(error_msg)
202
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
188
203
 
189
204
  # 如果是添加页码操作,检查是否PDF文件
190
205
  if format == "number-pdf" and input_format != InputFormat.PDF:
191
- await self.logger.error("添加页码功能仅支持PDF文件")
206
+ error_msg = "添加页码功能仅支持PDF文件"
207
+ await self.logger.error(error_msg)
208
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
192
209
 
193
210
  # 验证输出格式(除去特殊操作外)
194
211
  if not is_special_operation:
195
212
  try:
196
213
  output_format = OutputFormat(format)
197
214
  except ValueError:
198
- await self.logger.error(f"不支持的输出格式: {format}")
215
+ error_msg = f"不支持的输出格式: {format}"
216
+ await self.logger.error(error_msg)
217
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
199
218
 
200
- # 验证格式转换是否支持
201
- if input_format: # 确保input_format有效
202
- available_formats = self.file_handler.get_available_output_formats(input_format)
203
- if output_format not in available_formats:
204
- await self.logger.error(
205
- f"不支持从 {input_format.value} 格式转换为 {output_format.value} 格式。"
206
- f"支持的输出格式: {', '.join(f.value for f in available_formats)}"
207
- )
208
219
  else:
209
220
  # 远程路径的情况,设置必要的变量以便后续使用
210
221
  if not is_special_operation:
211
222
  try:
212
223
  output_format = OutputFormat(format)
213
224
  except ValueError:
214
- await self.logger.error(f"不支持的输出格式: {format}")
225
+ error_msg = f"不支持的输出格式: {format}"
226
+ await self.logger.error(error_msg)
227
+ return ConversionResult(success=False, file_path=file_path, error_message=error_msg, original_name=original_name)
215
228
  else:
216
229
  output_format = OutputFormat.PDF
217
230
 
@@ -221,7 +234,7 @@ class Converter(BaseApiClient):
221
234
  # 验证文件
222
235
  exists = await self.file_handler.validate_file_exists(file_path)
223
236
  if not exists:
224
- return ConversionResult(success=False, file_path=file_path, error_message="文件不存在")
237
+ return ConversionResult(success=False, file_path=file_path, error_message="文件不存在", original_name=original_name)
225
238
 
226
239
  # 操作描述
227
240
  if format == "doc-repair":
@@ -239,7 +252,7 @@ class Converter(BaseApiClient):
239
252
  await self.logger.log("info", f"正在{operation_desc}...")
240
253
 
241
254
  import httpx
242
- async with httpx.AsyncClient(timeout=120.0) as client:
255
+ async with httpx.AsyncClient(timeout=600.0) as client:
243
256
  try:
244
257
  # 初始化extra_params(如果为None)
245
258
  if extra_params is None:
@@ -262,7 +275,8 @@ class Converter(BaseApiClient):
262
275
  success=True,
263
276
  file_path=file_path,
264
277
  error_message=None,
265
- download_url=download_url
278
+ download_url=download_url,
279
+ original_name=original_name
266
280
  )
267
281
 
268
282
  except Exception as e:
@@ -270,7 +284,8 @@ class Converter(BaseApiClient):
270
284
  success=False,
271
285
  file_path=file_path,
272
286
  error_message=str(e),
273
- download_url=None
287
+ download_url=None,
288
+ original_name=original_name
274
289
  )
275
290
 
276
291
  async def _create_task(self, client: httpx.AsyncClient, file_path: str, format: str, extra_params: dict = None) -> str:
@@ -29,7 +29,8 @@ class Editor(BaseApiClient):
29
29
  """PDF文档编辑器"""
30
30
  def __init__(self, logger: Logger, file_handler: FileHandler):
31
31
  super().__init__(logger, file_handler)
32
- self.api_base_url = "https://techsz.aoscdn.com/api/tasks/document/pdfedit"
32
+ api_endpoint = os.environ.get("API_ENDPOINT", "devaw.aoscdn.com/tech")
33
+ self.api_base_url = f"https://{api_endpoint}/tasks/document/pdfedit"
33
34
 
34
35
  async def _validate_pdf_file(self, file_path: str) -> bool:
35
36
  """验证文件是否为PDF格式
@@ -63,7 +64,7 @@ class Editor(BaseApiClient):
63
64
  log_msg += "..."
64
65
  await self.logger.log("info", log_msg)
65
66
 
66
- async def split_pdf(self, file_path: str, pages: str = "", password: Optional[str] = None, split_type: str = "page", merge_all: int = 1) -> EditResult:
67
+ async def split_pdf(self, file_path: str, pages: str = "", password: Optional[str] = None, split_type: str = "page", merge_all: int = 1, original_name: Optional[str] = None) -> EditResult:
67
68
  """拆分PDF文件
68
69
 
69
70
  Args:
@@ -72,19 +73,20 @@ class Editor(BaseApiClient):
72
73
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
73
74
  split_type: 拆分类型,可选值: "every"=每页拆分为一个文件, "page"=指定页面规则拆分,默认为"page"
74
75
  merge_all: 是否合并拆分后的文件,仅在split_type="page"时有效,0=不合并,1=合并,默认为1
76
+ original_name: 原始文件名(可选)
75
77
 
76
78
  Returns:
77
79
  EditResult: 拆分结果
78
80
  """
79
81
  # 验证输入文件是否为PDF
80
82
  if not await self._validate_pdf_file(file_path):
81
- return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
83
+ return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
82
84
 
83
85
  # 验证拆分类型
84
86
  valid_split_types = {"every", "page"}
85
87
  if split_type not in valid_split_types:
86
88
  await self.logger.error(f"无效的拆分类型: {split_type}。有效值为: every, page")
87
- return EditResult(success=False, file_path=file_path, error_message=f"无效的拆分类型: {split_type}。有效值为: every, page")
89
+ return EditResult(success=False, file_path=file_path, error_message=f"无效的拆分类型: {split_type}。有效值为: every, page", original_name=original_name)
88
90
 
89
91
  # 构建API参数
90
92
  extra_params = {
@@ -103,30 +105,31 @@ class Editor(BaseApiClient):
103
105
  await self._log_operation("拆分PDF文件", operation_details)
104
106
 
105
107
  # 调用edit_pdf方法处理API请求
106
- return await self.edit_pdf(file_path, EditType.SPLIT, extra_params, password)
108
+ return await self.edit_pdf(file_path, EditType.SPLIT, extra_params, password, original_name)
107
109
 
108
- async def merge_pdfs(self, file_paths: List[str], password: Optional[str] = None) -> EditResult:
110
+ async def merge_pdfs(self, file_paths: List[str], password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
109
111
  """合并多个PDF文件
110
112
 
111
113
  Args:
112
114
  file_paths: 要合并的PDF文件路径列表
113
115
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
116
+ original_name: 原始文件名(可选)
114
117
 
115
118
  Returns:
116
119
  EditResult: 合并结果
117
120
  """
118
121
  if len(file_paths) < 2:
119
122
  await self.logger.error("合并PDF至少需要两个文件")
120
- return EditResult(success=False, file_path=','.join(file_paths), error_message="合并PDF至少需要两个文件")
123
+ return EditResult(success=False, file_path=','.join(file_paths), error_message="合并PDF至少需要两个文件", original_name=original_name)
121
124
 
122
125
  # 验证所有文件是否都是PDF并且存在
123
126
  for pdf_file in file_paths:
124
127
  if not await self._validate_pdf_file(pdf_file):
125
- return EditResult(success=False, file_path=pdf_file, error_message="非PDF文件")
128
+ return EditResult(success=False, file_path=pdf_file, error_message="非PDF文件", original_name=original_name)
126
129
 
127
130
  exists = await self.file_handler.validate_file_exists(pdf_file)
128
131
  if not exists:
129
- return EditResult(success=False, file_path=pdf_file, error_message="文件不存在")
132
+ return EditResult(success=False, file_path=pdf_file, error_message="文件不存在", original_name=original_name)
130
133
 
131
134
  # 记录操作描述
132
135
  await self._log_operation("合并PDF文件", f"{len(file_paths)} 个文件")
@@ -147,7 +150,8 @@ class Editor(BaseApiClient):
147
150
  success=True,
148
151
  file_path=file_paths[0], # 使用第一个文件路径作为参考
149
152
  error_message=None,
150
- download_url=download_url
153
+ download_url=download_url,
154
+ original_name=original_name
151
155
  )
152
156
 
153
157
  except Exception as e:
@@ -155,10 +159,11 @@ class Editor(BaseApiClient):
155
159
  success=False,
156
160
  file_path=file_paths[0],
157
161
  error_message=str(e),
158
- download_url=None
162
+ download_url=None,
163
+ original_name=original_name
159
164
  )
160
165
 
161
- async def rotate_pdf(self, file_path: str, angle: int, pages: str = "", password: Optional[str] = None) -> EditResult:
166
+ async def rotate_pdf(self, file_path: str, angle: int, pages: str = "", password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
162
167
  """旋转PDF文件的页面
163
168
 
164
169
  Args:
@@ -166,19 +171,20 @@ class Editor(BaseApiClient):
166
171
  angle: 旋转角度,可选值为90、180、270
167
172
  pages: 指定要旋转的页面范围,例如 "1,3,5-7" 或 "" 表示所有页面
168
173
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
174
+ original_name: 原始文件名(可选)
169
175
 
170
176
  Returns:
171
177
  EditResult: 旋转结果
172
178
  """
173
179
  # 验证输入文件是否为PDF
174
180
  if not await self._validate_pdf_file(file_path):
175
- return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
181
+ return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
176
182
 
177
183
  # 验证旋转角度
178
184
  valid_angles = {90, 180, 270}
179
185
  if angle not in valid_angles:
180
186
  await self.logger.error("无效的旋转角度。角度必须是: 90, 180, 270")
181
- return EditResult(success=False, file_path=file_path, error_message="无效的旋转角度。角度必须是: 90, 180, 270")
187
+ return EditResult(success=False, file_path=file_path, error_message="无效的旋转角度。角度必须是: 90, 180, 270", original_name=original_name)
182
188
 
183
189
  # 构建API参数
184
190
  extra_params = {
@@ -190,27 +196,28 @@ class Editor(BaseApiClient):
190
196
  await self._log_operation("旋转PDF文件", f"参数: {angle_desc}")
191
197
 
192
198
  # 调用edit_pdf方法处理API请求
193
- return await self.edit_pdf(file_path, EditType.ROTATE, extra_params, password)
199
+ return await self.edit_pdf(file_path, EditType.ROTATE, extra_params, password, original_name)
194
200
 
195
- async def compress_pdf(self, file_path: str, image_quantity: int = 60, password: Optional[str] = None) -> EditResult:
201
+ async def compress_pdf(self, file_path: str, image_quantity: int = 60, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
196
202
  """压缩PDF文件
197
203
 
198
204
  Args:
199
205
  file_path: 要压缩的PDF文件路径
200
206
  image_quantity: 图片质量,范围1-100,默认为60
201
207
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
208
+ original_name: 原始文件名(可选)
202
209
 
203
210
  Returns:
204
211
  EditResult: 压缩结果
205
212
  """
206
213
  # 验证输入文件是否为PDF
207
214
  if not await self._validate_pdf_file(file_path):
208
- return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
215
+ return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
209
216
 
210
- # 验证图片质量
211
- if not 1 <= image_quantity <= 100:
212
- await self.logger.error(f"无效的图片质量: {image_quantity}。有效值范围为: 1-100")
213
- return EditResult(success=False, file_path=file_path, error_message=f"无效的图片质量: {image_quantity}。有效值范围为: 1-100")
217
+ # 验证图片质量范围
218
+ if not (1 <= image_quantity <= 100):
219
+ await self.logger.error("图片质量必须在1100之间")
220
+ return EditResult(success=False, file_path=file_path, error_message="图片质量必须在1100之间", original_name=original_name)
214
221
 
215
222
  # 构建API参数
216
223
  extra_params = {
@@ -221,60 +228,65 @@ class Editor(BaseApiClient):
221
228
  await self._log_operation("压缩PDF文件", f"图片质量: {image_quantity}")
222
229
 
223
230
  # 调用edit_pdf方法处理API请求
224
- return await self.edit_pdf(file_path, EditType.COMPRESS, extra_params, password)
231
+ return await self.edit_pdf(file_path, EditType.COMPRESS, extra_params, password, original_name)
225
232
 
226
- async def encrypt_pdf(self, file_path: str, password: str, original_password: Optional[str] = None) -> EditResult:
233
+ async def encrypt_pdf(self, file_path: str, password: str, original_password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
227
234
  """加密PDF文件
228
235
 
229
236
  Args:
230
237
  file_path: 要加密的PDF文件路径
231
- password: 设置的新密码(用于加密PDF)
232
- original_password: 文档原密码,如果文档已受密码保护,则需要提供(可选)
233
-
234
- 注意:
235
- 根据API文档,加密操作需要通过password参数指定要设置的新密码。
236
- 如果文档已经受密码保护,则使用original_password参数提供原密码进行解锁。
238
+ password: 要设置的新密码
239
+ original_password: 原始密码,如果文件已经加密,则需要提供(可选)
240
+ original_name: 原始文件名(可选)
237
241
 
238
242
  Returns:
239
243
  EditResult: 加密结果
240
244
  """
241
245
  # 验证输入文件是否为PDF
242
246
  if not await self._validate_pdf_file(file_path):
243
- return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
247
+ return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
248
+
249
+ # 验证新密码
250
+ if not password:
251
+ await self.logger.error("加密PDF文件需要提供新密码")
252
+ return EditResult(success=False, file_path=file_path, error_message="加密PDF文件需要提供新密码", original_name=original_name)
244
253
 
245
254
  # 构建API参数
246
255
  extra_params = {
247
- "password": password # 设置的新密码
256
+ "password": password # 新密码
248
257
  }
249
258
 
250
259
  # 记录操作描述
251
260
  await self._log_operation("加密PDF文件")
252
261
 
253
262
  # 调用edit_pdf方法处理API请求
254
- return await self.edit_pdf(file_path, EditType.ENCRYPT, extra_params, original_password)
263
+ return await self.edit_pdf(file_path, EditType.ENCRYPT, extra_params, original_password, original_name)
255
264
 
256
- async def decrypt_pdf(self, file_path: str, password: Optional[str] = None) -> EditResult:
265
+ async def decrypt_pdf(self, file_path: str, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
257
266
  """解密PDF文件
258
267
 
259
268
  Args:
260
269
  file_path: 要解密的PDF文件路径
261
- password: 文档密码,如果文档受密码保护,则需要提供(必须提供正确的密码才能解密)
262
-
263
- 注意:
264
- 该方法调用API的unlock功能,需要提供正确的PDF密码才能成功解密。
270
+ password: 文档密码,用于解锁已加密的文档
271
+ original_name: 原始文件名(可选)
265
272
 
266
273
  Returns:
267
274
  EditResult: 解密结果
268
275
  """
269
276
  # 验证输入文件是否为PDF
270
277
  if not await self._validate_pdf_file(file_path):
271
- return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
278
+ return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
279
+
280
+ # 验证密码
281
+ if not password:
282
+ await self.logger.error("解密PDF文件需要提供密码")
283
+ return EditResult(success=False, file_path=file_path, error_message="解密PDF文件需要提供密码", original_name=original_name)
272
284
 
273
285
  # 记录操作描述
274
286
  await self._log_operation("解密PDF文件")
275
287
 
276
288
  # 调用edit_pdf方法处理API请求
277
- return await self.edit_pdf(file_path, EditType.DECRYPT, {}, password)
289
+ return await self.edit_pdf(file_path, EditType.DECRYPT, {}, password, original_name)
278
290
 
279
291
  async def add_watermark(
280
292
  self,
@@ -282,56 +294,47 @@ class Editor(BaseApiClient):
282
294
  text: str,
283
295
  position: str, # 必需参数:位置,如"top", "center", "diagonal"等
284
296
  opacity: float = 0.5,
285
- deg: str = "-45", # 直接使用字符串格式的角度
286
297
  range: str = "", # 与API保持一致,使用range而非pages
287
298
  layout: Optional[str] = None, # 可选参数: "on"/"under"
288
299
  font_family: Optional[str] = None,
289
300
  font_size: Optional[int] = None,
290
301
  font_color: Optional[str] = None,
291
- password: Optional[str] = None
302
+ password: Optional[str] = None,
303
+ original_name: Optional[str] = None
292
304
  ) -> EditResult:
293
- """为PDF文件添加水印
305
+ """为PDF文件添加文本水印
294
306
 
295
307
  Args:
296
308
  file_path: 要添加水印的PDF文件路径
297
309
  text: 水印文本内容
298
- position: 水印位置,可选值:"topleft","top","topright","left","center",
299
- "right","bottomleft","bottom","bottomright","diagonal","reverse-diagonal"
300
- opacity: 透明度,0.0-1.0,默认为0.5
301
- deg: 倾斜角度,字符串格式,如"-45",默认为"-45"
302
- range: 指定页面范围,例如 "1,3,5-7" 或 "" 表示所有页面
303
- layout: 布局位置,可选值:"on"(在内容上)/"under"(在内容下)
310
+ position: 水印位置,如"top", "center", "diagonal"
311
+ opacity: 透明度,0.0-1.0,默认0.5
312
+ range: 页面范围,例如 "1,3,5-7" 或空字符串表示所有页面
313
+ layout: 布局方式:"on"=在内容上,"under"=在内容下,默认"on"
304
314
  font_family: 字体
305
315
  font_size: 字体大小
306
- font_color: 字体颜色,如"#ff0000"表示红色
316
+ font_color: 字体颜色,如 "#ff0000"
307
317
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
318
+ original_name: 原始文件名(可选)
308
319
 
309
320
  Returns:
310
321
  EditResult: 添加水印结果
311
322
  """
312
323
  # 验证输入文件是否为PDF
313
324
  if not await self._validate_pdf_file(file_path):
314
- return EditResult(success=False, file_path=file_path, error_message="非PDF文件")
325
+ return EditResult(success=False, file_path=file_path, error_message="非PDF文件", original_name=original_name)
315
326
 
316
- # 验证透明度
317
- if not 0.0 <= opacity <= 1.0:
318
- await self.logger.error(f"无效的透明度: {opacity}。有效值范围为: 0.0-1.0")
319
- return EditResult(success=False, file_path=file_path, error_message=f"无效的透明度: {opacity}。有效值范围为: 0.0-1.0")
320
-
321
- # 验证position参数
322
- valid_positions = {"topleft", "top", "topright", "left", "center", "right",
323
- "bottomleft", "bottom", "bottomright", "diagonal", "reverse-diagonal"}
324
- if position not in valid_positions:
325
- await self.logger.error(f"无效的位置: {position}")
326
- return EditResult(success=False, file_path=file_path, error_message=f"无效的位置: {position}")
327
+ # 验证必需参数
328
+ if not text:
329
+ await self.logger.error("水印文本不能为空")
330
+ return EditResult(success=False, file_path=file_path, error_message="水印文本不能为空", original_name=original_name)
327
331
 
328
332
  # 构建API参数
329
333
  extra_params = {
330
- "edit_type": "text", # 固定为文本水印
334
+ "edit_type": "text",
331
335
  "text": text,
332
336
  "position": position,
333
337
  "opacity": opacity,
334
- "deg": deg,
335
338
  "range": range
336
339
  }
337
340
 
@@ -349,9 +352,9 @@ class Editor(BaseApiClient):
349
352
  await self._log_operation("为PDF添加水印", f"文本: {text}, 位置: {position}, 透明度: {opacity}")
350
353
 
351
354
  # 调用edit_pdf方法处理API请求
352
- return await self.edit_pdf(file_path, EditType.ADD_WATERMARK, extra_params, password)
355
+ return await self.edit_pdf(file_path, EditType.ADD_WATERMARK, extra_params, password, original_name)
353
356
 
354
- async def edit_pdf(self, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None, password: Optional[str] = None) -> EditResult:
357
+ async def edit_pdf(self, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None, password: Optional[str] = None, original_name: Optional[str] = None) -> EditResult:
355
358
  """编辑PDF文件
356
359
 
357
360
  Args:
@@ -359,6 +362,7 @@ class Editor(BaseApiClient):
359
362
  edit_type: 编辑操作类型
360
363
  extra_params: 额外的API参数
361
364
  password: 文档密码,如果文档受密码保护,则需要提供(可选)
365
+ original_name: 原始文件名(可选)
362
366
 
363
367
  注意:
364
368
  1. 对于加密操作(protect),需要在extra_params中提供新密码
@@ -370,14 +374,14 @@ class Editor(BaseApiClient):
370
374
  """
371
375
  if not self.api_key:
372
376
  await self.logger.error("未找到API_KEY。请在客户端配置API_KEY环境变量。")
373
- return EditResult(success=False, file_path=file_path, error_message="未找到API_KEY。请在客户端配置API_KEY环境变量。")
377
+ return EditResult(success=False, file_path=file_path, error_message="未找到API_KEY。请在客户端配置API_KEY环境变量。", original_name=original_name)
374
378
 
375
379
  # 验证文件
376
380
  exists = await self.file_handler.validate_file_exists(file_path)
377
381
  if not exists:
378
- return EditResult(success=False, file_path=file_path, error_message="文件不存在")
382
+ return EditResult(success=False, file_path=file_path, error_message="文件不存在", original_name=original_name)
379
383
 
380
- async with httpx.AsyncClient(timeout=120.0) as client:
384
+ async with httpx.AsyncClient(timeout=600.0) as client:
381
385
  try:
382
386
  # 初始化extra_params(如果为None)
383
387
  if extra_params is None:
@@ -400,7 +404,8 @@ class Editor(BaseApiClient):
400
404
  success=True,
401
405
  file_path=file_path,
402
406
  error_message=None,
403
- download_url=download_url
407
+ download_url=download_url,
408
+ original_name=original_name
404
409
  )
405
410
 
406
411
  except Exception as e:
@@ -408,7 +413,8 @@ class Editor(BaseApiClient):
408
413
  success=False,
409
414
  file_path=file_path,
410
415
  error_message=str(e),
411
- download_url=None
416
+ download_url=None,
417
+ original_name=original_name
412
418
  )
413
419
 
414
420
  async def _create_task(self, client: httpx.AsyncClient, file_path: str, edit_type: EditType, extra_params: Dict[str, Any] = None) -> str:
@@ -4,6 +4,7 @@ import asyncio
4
4
  import os
5
5
  import sys
6
6
  import argparse
7
+ import json
7
8
  from typing import List, Dict, Any, Callable, TypeVar, Optional, Union
8
9
 
9
10
  # 第三方库导入
@@ -19,80 +20,68 @@ from .common import BaseResult, Logger, FileHandler
19
20
  from .converter import Converter, ConversionResult
20
21
  from .editor import Editor, EditResult
21
22
 
22
- # 加载环境变量
23
- load_dotenv()
24
-
25
23
  # 类型定义
26
24
  T = TypeVar('T', bound=BaseResult)
27
25
  ProcessFunc = Callable[[str], Any]
28
26
 
29
27
  def generate_result_report(
30
28
  results: List[BaseResult],
31
- operation_name: str,
32
- success_action: Optional[str] = None,
33
- failed_action: Optional[str] = None
29
+ operation_name: str
34
30
  ) -> str:
35
31
  """生成通用结果报告
36
32
 
37
33
  Args:
38
34
  results: 结果列表
39
35
  operation_name: 操作名称
40
- success_action: 成功动作描述
41
- failed_action: 失败动作描述
42
36
 
43
37
  Returns:
44
- str: 格式化的报告文本
38
+ str: JSON格式的报告文本
45
39
  """
46
40
  # 统计结果
47
41
  success_count = sum(1 for r in results if r.success)
48
42
  failed_count = len(results) - success_count
49
43
 
50
- # 生成报告头部
51
- report_lines = [
52
- f"{operation_name}结果:共 {len(results)} 个文件" + (
53
- f",成功 {success_count} 个" if success_count > 0 else ""
54
- ) + (
55
- f",失败 {failed_count} 个" if failed_count > 0 else ""
56
- ),
57
- ""
58
- ]
59
-
60
- # 添加成功的文件信息
61
- if success_count > 0 and success_action:
62
- report_lines.extend([f"[成功] {success_action}的文件:", ""])
63
- for i, result in enumerate(results):
64
- if result.success:
65
- report_lines.extend([
66
- f"[{i+1}] {result.file_path}",
67
- f"- 在线下载地址: {result.download_url}",
68
- ""
69
- ])
44
+ # 构建结果JSON对象
45
+ report_obj = {
46
+ "operation": operation_name,
47
+ "total": len(results),
48
+ "success_count": success_count,
49
+ "failed_count": failed_count,
50
+ "success_files": [],
51
+ "failed_files": []
52
+ }
70
53
 
71
- # 添加失败的文件信息
72
- if failed_count > 0 and failed_action:
73
- report_lines.extend([f"[失败] {failed_action}的文件:", ""])
74
- for i, result in enumerate(results):
75
- if not result.success:
76
- report_lines.extend([
77
- f"[{i+1}] {result.file_path}",
78
- f"- 错误: {result.error_message}",
79
- ""
80
- ])
54
+ for result in results:
55
+ if result.success:
56
+ # 添加成功的文件信息
57
+ report_obj["success_files"].append({
58
+ "file_path": result.file_path,
59
+ "original_name": result.original_name,
60
+ "download_url": result.download_url
61
+ })
62
+ else:
63
+ # 添加失败的文件信息
64
+ report_obj["failed_files"].append({
65
+ "file_path": result.file_path,
66
+ "original_name": result.original_name,
67
+ "error_message": result.error_message
68
+ })
81
69
 
82
- return "\n".join(report_lines)
70
+ # 返回JSON字符串
71
+ return json.dumps(report_obj, ensure_ascii=False)
83
72
 
84
73
  async def process_batch_files(
85
74
  file_objects: List[Dict[str, str]],
86
75
  logger: Logger,
87
- process_func: Callable[[str, Optional[str]], T],
76
+ process_func: Callable[[str, Optional[str], Optional[str]], T],
88
77
  operation_desc: Optional[str] = None
89
78
  ) -> List[T]:
90
79
  """通用批处理文件函数
91
80
 
92
81
  Args:
93
- file_objects: 文件对象列表,每个对象包含path和可选的password
82
+ file_objects: 文件对象列表,每个对象包含path和可选的password及name
94
83
  logger: 日志记录器
95
- process_func: 处理单个文件的异步函数,接收file_path和password参数
84
+ process_func: 处理单个文件的异步函数,接收file_path、passwordoriginal_name参数
96
85
  operation_desc: 操作描述,用于日志记录
97
86
 
98
87
  Returns:
@@ -108,7 +97,8 @@ async def process_batch_files(
108
97
  async with semaphore:
109
98
  file_path = file_obj["path"]
110
99
  password = file_obj.get("password")
111
- return await process_func(file_path, password)
100
+ original_name = file_obj.get("name")
101
+ return await process_func(file_path, password, original_name)
112
102
 
113
103
  # 创建任务列表
114
104
  tasks = [process_with_semaphore(file_obj) for file_obj in file_objects]
@@ -117,14 +107,16 @@ async def process_batch_files(
117
107
  # 单文件处理
118
108
  file_path = file_objects[0]["path"]
119
109
  password = file_objects[0].get("password")
120
- return [await process_func(file_path, password)]
110
+ original_name = file_objects[0].get("name")
111
+ return [await process_func(file_path, password, original_name)]
121
112
 
122
113
  async def process_conversion_file(
123
114
  file_path: str,
124
115
  format: str,
125
116
  converter: Converter,
126
117
  extra_params: Optional[Dict[str, Any]] = None,
127
- password: Optional[str] = None
118
+ password: Optional[str] = None,
119
+ original_name: Optional[str] = None
128
120
  ) -> ConversionResult:
129
121
  """处理单个文件转换"""
130
122
  is_page_numbering = format == "number-pdf"
@@ -136,47 +128,51 @@ async def process_conversion_file(
136
128
  extra_params.get("start_num", 1),
137
129
  extra_params.get("position", "5"),
138
130
  extra_params.get("margin", 30),
139
- password
131
+ password,
132
+ original_name
140
133
  )
141
134
  else:
142
135
  # 对于其他操作,使用convert_file方法
143
- return await converter.convert_file(file_path, format, extra_params, password)
136
+ return await converter.convert_file(file_path, format, extra_params, password, original_name)
144
137
 
145
138
  async def process_edit_file(
146
139
  file_path: str,
147
140
  edit_type: str,
148
141
  editor: Editor,
149
142
  extra_params: Dict[str, Any] = None,
150
- password: Optional[str] = None
143
+ password: Optional[str] = None,
144
+ original_name: Optional[str] = None
151
145
  ) -> EditResult:
152
146
  """处理单个文件编辑"""
153
147
  if edit_type == "decrypt":
154
- return await editor.decrypt_pdf(file_path, password)
148
+ return await editor.decrypt_pdf(file_path, password, original_name)
155
149
  elif edit_type == "add_watermark":
156
150
  return await editor.add_watermark(
157
151
  file_path=file_path,
158
152
  text=extra_params.get("text", "水印"),
159
153
  position=extra_params.get("position", "center"),
160
154
  opacity=extra_params.get("opacity", 0.5),
161
- deg=extra_params.get("deg", "0"),
162
155
  range=extra_params.get("range", ""),
163
156
  layout=extra_params.get("layout", "on"),
164
157
  font_family=extra_params.get("font_family"),
165
158
  font_size=extra_params.get("font_size"),
166
159
  font_color=extra_params.get("font_color"),
167
- password=password
160
+ password=password,
161
+ original_name=original_name
168
162
  )
169
163
  elif edit_type == "encrypt":
170
164
  return await editor.encrypt_pdf(
171
165
  file_path=file_path,
172
166
  password=extra_params.get("password", ""),
173
- original_password=password
167
+ original_password=password,
168
+ original_name=original_name
174
169
  )
175
170
  elif edit_type == "compress":
176
171
  return await editor.compress_pdf(
177
172
  file_path=file_path,
178
173
  image_quantity=extra_params.get("image_quantity", 60),
179
- password=password
174
+ password=password,
175
+ original_name=original_name
180
176
  )
181
177
  elif edit_type == "split":
182
178
  return await editor.split_pdf(
@@ -184,27 +180,31 @@ async def process_edit_file(
184
180
  pages=extra_params.get("pages", ""),
185
181
  password=password,
186
182
  split_type=extra_params.get("split_type", "page"),
187
- merge_all=extra_params.get("merge_all", 1)
183
+ merge_all=extra_params.get("merge_all", 1),
184
+ original_name=original_name
188
185
  )
189
186
  elif edit_type == "merge":
190
187
  # 对于合并操作,我们需要特殊处理,因为它需要处理多个文件
191
188
  return EditResult(
192
189
  success=False,
193
190
  file_path=file_path,
194
- error_message="合并操作需要使用特殊处理流程"
191
+ error_message="合并操作需要使用特殊处理流程",
192
+ original_name=original_name
195
193
  )
196
194
  elif edit_type == "rotate":
197
195
  return await editor.rotate_pdf(
198
196
  file_path=file_path,
199
197
  angle=extra_params.get("angle", 90),
200
198
  pages=extra_params.get("pages", ""),
201
- password=password
199
+ password=password,
200
+ original_name=original_name
202
201
  )
203
202
  else:
204
203
  return EditResult(
205
204
  success=False,
206
205
  file_path=file_path,
207
- error_message=f"不支持的编辑类型: {edit_type}"
206
+ error_message=f"不支持的编辑类型: {edit_type}",
207
+ original_name=original_name
208
208
  )
209
209
 
210
210
  async def process_tool_call(
@@ -247,8 +247,8 @@ async def process_tool_call(
247
247
  results = await process_batch_files(
248
248
  file_objects,
249
249
  logger,
250
- lambda file_path, password: process_edit_file(
251
- file_path, edit_type, editor, extra_params, password
250
+ lambda file_path, password, original_name: process_edit_file(
251
+ file_path, edit_type, editor, extra_params, password, original_name
252
252
  ),
253
253
  operation_desc
254
254
  )
@@ -256,9 +256,7 @@ async def process_tool_call(
256
256
  # 生成报告
257
257
  report_msg = generate_result_report(
258
258
  results,
259
- operation_desc,
260
- f"{edit_map.get(edit_type, edit_type)}成功",
261
- f"{edit_map.get(edit_type, edit_type)}失败"
259
+ operation_desc
262
260
  )
263
261
  else:
264
262
  # 转换操作
@@ -272,25 +270,19 @@ async def process_tool_call(
272
270
  if is_watermark_removal:
273
271
  operation_desc = "去除水印"
274
272
  task_type = "水印去除"
275
- success_action = "去除水印成功"
276
- failed_action = "水印去除失败"
277
273
  elif is_page_numbering:
278
274
  operation_desc = "添加页码"
279
275
  task_type = "添加页码"
280
- success_action = "添加页码成功"
281
- failed_action = "添加页码失败"
282
276
  else:
283
277
  operation_desc = f"转换为 {format} 格式"
284
278
  task_type = "转换"
285
- success_action = "转换成功"
286
- failed_action = "转换失败"
287
279
 
288
280
  # 处理文件
289
281
  results = await process_batch_files(
290
282
  file_objects,
291
283
  logger,
292
- lambda file_path, password: process_conversion_file(
293
- file_path, format, converter, extra_params, password
284
+ lambda file_path, password, original_name: process_conversion_file(
285
+ file_path, format, converter, extra_params, password, original_name
294
286
  ),
295
287
  operation_desc
296
288
  )
@@ -298,9 +290,7 @@ async def process_tool_call(
298
290
  # 生成报告
299
291
  report_msg = generate_result_report(
300
292
  results,
301
- task_type,
302
- success_action,
303
- failed_action
293
+ task_type
304
294
  )
305
295
 
306
296
  # 如果全部失败,记录错误
@@ -337,6 +327,10 @@ async def handle_list_tools() -> list[types.Tool]:
337
327
  "password": {
338
328
  "type": "string",
339
329
  "description": "文档密码,如果文档受密码保护,则需要提供此参数"
330
+ },
331
+ "name": {
332
+ "type": "string",
333
+ "description": "文件的原始文件名"
340
334
  }
341
335
  },
342
336
  "required": ["path"]
@@ -370,6 +364,10 @@ async def handle_list_tools() -> list[types.Tool]:
370
364
  "password": {
371
365
  "type": "string",
372
366
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
367
+ },
368
+ "name": {
369
+ "type": "string",
370
+ "description": "文件的原始文件名"
373
371
  }
374
372
  },
375
373
  "required": ["path"]
@@ -416,6 +414,10 @@ async def handle_list_tools() -> list[types.Tool]:
416
414
  "password": {
417
415
  "type": "string",
418
416
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
417
+ },
418
+ "name": {
419
+ "type": "string",
420
+ "description": "文件的原始文件名"
419
421
  }
420
422
  },
421
423
  "required": ["path"]
@@ -444,6 +446,10 @@ async def handle_list_tools() -> list[types.Tool]:
444
446
  "password": {
445
447
  "type": "string",
446
448
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
449
+ },
450
+ "name": {
451
+ "type": "string",
452
+ "description": "文件的原始文件名"
447
453
  }
448
454
  },
449
455
  "required": ["path"]
@@ -456,7 +462,7 @@ async def handle_list_tools() -> list[types.Tool]:
456
462
  },
457
463
  "position": {
458
464
  "type": "string",
459
- "description": "水印位置",
465
+ "description": "水印位置: 左上(topleft), 上中(top), 右上(topright), 左(left), 中(center), 右(right), 左下(bottomleft), 下(bottom), 右下(bottomright), 对角线(diagonal,-45度), 反对角线(reverse-diagonal,45度)",
460
466
  "enum": ["topleft", "top", "topright", "left", "center", "right",
461
467
  "bottomleft", "bottom", "bottomright", "diagonal", "reverse-diagonal"],
462
468
  "default": "center"
@@ -468,11 +474,6 @@ async def handle_list_tools() -> list[types.Tool]:
468
474
  "minimum": 0.0,
469
475
  "maximum": 1.0
470
476
  },
471
- "deg": {
472
- "type": "string",
473
- "description": "倾斜角度,字符串格式",
474
- "default": "0"
475
- },
476
477
  "range": {
477
478
  "type": "string",
478
479
  "description": "页面范围,例如 '1,3,5-7' 或 ''(空字符串或不设置)表示所有页面"
@@ -517,6 +518,10 @@ async def handle_list_tools() -> list[types.Tool]:
517
518
  "password": {
518
519
  "type": "string",
519
520
  "description": "PDF文档的密码,用于解锁文档,如果文档受密码保护,则需要提供此参数"
521
+ },
522
+ "name": {
523
+ "type": "string",
524
+ "description": "文件的原始文件名"
520
525
  }
521
526
  },
522
527
  "required": ["path", "password"]
@@ -545,6 +550,10 @@ async def handle_list_tools() -> list[types.Tool]:
545
550
  "password": {
546
551
  "type": "string",
547
552
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
553
+ },
554
+ "name": {
555
+ "type": "string",
556
+ "description": "文件的原始文件名"
548
557
  }
549
558
  },
550
559
  "required": ["path"]
@@ -577,6 +586,10 @@ async def handle_list_tools() -> list[types.Tool]:
577
586
  "password": {
578
587
  "type": "string",
579
588
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
589
+ },
590
+ "name": {
591
+ "type": "string",
592
+ "description": "文件的原始文件名"
580
593
  }
581
594
  },
582
595
  "required": ["path"]
@@ -612,6 +625,10 @@ async def handle_list_tools() -> list[types.Tool]:
612
625
  "password": {
613
626
  "type": "string",
614
627
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
628
+ },
629
+ "name": {
630
+ "type": "string",
631
+ "description": "文件的原始文件名"
615
632
  }
616
633
  },
617
634
  "required": ["path"]
@@ -656,6 +673,10 @@ async def handle_list_tools() -> list[types.Tool]:
656
673
  "password": {
657
674
  "type": "string",
658
675
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
676
+ },
677
+ "name": {
678
+ "type": "string",
679
+ "description": "文件的原始文件名"
659
680
  }
660
681
  },
661
682
  "required": ["path"]
@@ -684,6 +705,10 @@ async def handle_list_tools() -> list[types.Tool]:
684
705
  "password": {
685
706
  "type": "string",
686
707
  "description": "PDF文档密码,如果文档受密码保护,则需要提供此参数"
708
+ },
709
+ "name": {
710
+ "type": "string",
711
+ "description": "文件的原始文件名"
687
712
  }
688
713
  },
689
714
  "required": ["path"]
@@ -739,7 +764,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
739
764
  "add_watermark": {
740
765
  "edit_type": "add_watermark", # 编辑类型
741
766
  "is_edit_operation": True, # 标记为编辑操作
742
- "param_keys": ["text", "position", "opacity", "deg", "range", "layout",
767
+ "param_keys": ["text", "position", "opacity", "range", "layout",
743
768
  "font_family", "font_size", "font_color"] # 需要从arguments获取的参数
744
769
  },
745
770
  "protect_pdf": {
@@ -774,7 +799,6 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
774
799
  "position_watermark": "center", # 水印的位置默认值
775
800
  "margin": 30,
776
801
  "opacity": 0.5,
777
- "deg": "0", # 更新为"0"以匹配handle_list_tools中的默认值
778
802
  "range": "",
779
803
  "layout": "on", # 添加layout默认值
780
804
  "image_quantity": 60,
@@ -833,23 +857,33 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
833
857
  file_handler = FileHandler(logger)
834
858
  editor = Editor(logger, file_handler)
835
859
 
836
- # 提取文件路径和密码
860
+ # 提取文件路径、密码和原始名称
837
861
  file_paths = [file_obj["path"] for file_obj in file_objects]
838
862
  passwords = [file_obj.get("password") for file_obj in file_objects]
863
+ original_names = [file_obj.get("name") for file_obj in file_objects]
839
864
 
840
865
  # 由于merge_pdfs方法只接受一个密码参数,如果文件密码不同,可能需要特殊处理
841
866
  # 此处简化处理,使用第一个非空密码
842
867
  password = next((p for p in passwords if p), None)
843
868
 
869
+ # 合并文件名用于结果文件
870
+ merged_name = None
871
+ if any(original_names):
872
+ # 如果有原始文件名,则合并它们(最多使用前两个文件名)
873
+ valid_names = [name for name in original_names if name]
874
+ if valid_names:
875
+ if len(valid_names) == 1:
876
+ merged_name = valid_names[0]
877
+ else:
878
+ merged_name = f"{valid_names[0]}_{valid_names[1]}_等"
879
+
844
880
  # 直接调用merge_pdfs方法
845
- result = await editor.merge_pdfs(file_paths, password)
881
+ result = await editor.merge_pdfs(file_paths, password, merged_name)
846
882
 
847
883
  # 构建结果报告
848
884
  report_msg = generate_result_report(
849
885
  [result],
850
- "PDF合并",
851
- "合并成功",
852
- "合并失败"
886
+ "PDF合并"
853
887
  )
854
888
 
855
889
  # 如果失败,记录错误
@@ -868,6 +902,9 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
868
902
 
869
903
  async def main():
870
904
  """应用主入口"""
905
+ # 加载环境变量
906
+ load_dotenv()
907
+
871
908
  # 打印版本号
872
909
  try:
873
910
  import importlib.metadata
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightpdf-aipdf-mcp
3
- Version: 0.1.43
3
+ Version: 0.1.45
4
4
  Summary: MCP Server for LightPDF AI-PDF
5
5
  Author: LightPDF Team
6
6
  License: Proprietary
@@ -207,7 +207,6 @@ pip install lightpdf-aipdf-mcp
207
207
  - `text`: 水印文本内容
208
208
  - `position`: 水印位置,可选值包括"center"、"topleft"等
209
209
  - `opacity`: 透明度(0.0-1.0)
210
- - `deg`: 旋转角度
211
210
  - `range`: 页面范围,如 "1,3,5-7"
212
211
  - `layout`: 水印显示位置,"on"(在内容上)或"under"(在内容下)
213
212
  - 可选的字体设置: `font_family`、`font_size`、`font_color`
@@ -0,0 +1,9 @@
1
+ lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
2
+ lightpdf_aipdf_mcp/common.py,sha256=-7LU6gm-As_F8Ly68ssy15Vc9Zt_eNSnvDLEtVZDwlI,6633
3
+ lightpdf_aipdf_mcp/converter.py,sha256=gsYBLqE6EnMCpCYHaYjcdBD5mhXVqiTRvl1yLSqA01w,14773
4
+ lightpdf_aipdf_mcp/editor.py,sha256=KySdMurM8AZHwqSU1PpkSCCesQ4t2h-D8Mm12wf90CA,23539
5
+ lightpdf_aipdf_mcp/server.py,sha256=dd3lAg3DhFYPdgigpcicY_SiMRnwqXkxjbagj5ijSYY,39974
6
+ lightpdf_aipdf_mcp-0.1.45.dist-info/METADATA,sha256=XPjtwZ32XhGIZz0TkJ4EHNDhj6ZPLmiWZ0AJNbJuRcc,7906
7
+ lightpdf_aipdf_mcp-0.1.45.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ lightpdf_aipdf_mcp-0.1.45.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
9
+ lightpdf_aipdf_mcp-0.1.45.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
2
- lightpdf_aipdf_mcp/common.py,sha256=k0oNIKLzqo15UUJAkKJjYZBByLdBGpUPCV59FiqEgcY,6574
3
- lightpdf_aipdf_mcp/converter.py,sha256=MxjO1yqMqfOQ9B696IyjRbtB3oZpemtD7x8arz52zoc,13312
4
- lightpdf_aipdf_mcp/editor.py,sha256=CkL5mCM7CSUGD4ZAMoRdgBr3xBpJ-7bEC8InCip5q9A,22802
5
- lightpdf_aipdf_mcp/server.py,sha256=xErltv5YkV8j3Ja5cm_KgJsc9qqnyCC7RTYs1Id9tWI,37652
6
- lightpdf_aipdf_mcp-0.1.43.dist-info/METADATA,sha256=0SKzho1zsef_Ar44QCr2aREq7s9R49kfl1FPQRgYZTg,7928
7
- lightpdf_aipdf_mcp-0.1.43.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- lightpdf_aipdf_mcp-0.1.43.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
9
- lightpdf_aipdf_mcp-0.1.43.dist-info/RECORD,,