This commit is contained in:
HuangHai
2026-01-28 16:54:11 +08:00
parent 4d8a4801b8
commit 53f0b49714
56 changed files with 13402 additions and 13 deletions

361
WeiXin/ChatMonitorAll.py Normal file
View File

@@ -0,0 +1,361 @@
# coding=utf-8
import os
import sys
import logging
import asyncio
import hashlib
import json
import numpy as np
import cv2
# 添加项目根目录到 sys.path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.append(project_root)
from WeiXin import WxUtil
from WeiXin.WxUtil import perform_input_action
from Util.LlmUtil import get_llm_response
from Util import Win32Patch
# 配置日志
log_dir = WxUtil.LOG_DIR
if not os.path.exists(log_dir):
os.makedirs(log_dir)
log_file_path = os.path.join(log_dir, "T2_ChatMonitor.log")
# 设置 logger
logger = logging.getLogger("T2_ChatMonitor")
logger.setLevel(logging.INFO)
if logger.hasHandlers():
logger.handlers.clear()
file_handler = logging.FileHandler(log_file_path, encoding='utf-8', mode='w')
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(file_handler)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(stream_handler)
logger.propagate = False
logger.info(f"🚀 日志文件路径: {os.path.abspath(log_file_path)}")
# 同时将 WxUtil 的日志也输出到同一个文件
wx_logger = logging.getLogger("WxUtil")
wx_logger.propagate = False # 防止日志向上传递导致重复 (因为 WxUtil 中调用了 basicConfig)
if not any(isinstance(h, logging.FileHandler) and os.path.abspath(h.baseFilename) == os.path.abspath(log_file_path) for h in wx_logger.handlers):
wx_logger.addHandler(file_handler)
wx_logger.addHandler(stream_handler) # 确保 WxUtil 也输出到控制台
class ChatMonitorBot:
"""
大张老师自动巡课系统 (CV版)
"""
def __init__(self):
self.device = None
self.screenshot_path = os.path.join(WxUtil.OUTPUT_DIR, "T2_ChatMonitor_live_shot.jpg")
self.debug_view_path = os.path.join(WxUtil.OUTPUT_DIR, "T2_ChatMonitor_debug_view.jpg")
self.dialogue_log = []
self.input_pos = None
self.last_screen_hash = None
self.last_processed_msg_hash = None
# [User Requested] 移除持久化存储,只在内存中记录,重启即忘
self.processed_hashes = set()
# 新增:记录已处理消息的元数据 (sender, time_display, type) 用于防止空内容重试循环
self.processed_meta = set()
self.check_interval = 3 # 检查频率 (秒)
self.persona = (
"你是一名1999年毕业、拥有27年一线教学经验的小学高级女教师,名叫‘大张老师’。你目前在‘长春市少惠林作文素养培养中心’工作。"
"你不仅是一位作文教学专家,更是一位心思细腻、能与家长共情的教育智者。"
"你的回复风格应该是:温柔、知性、亲切,就像一位邻家大姐姐在聊天。"
"【严格约束】:\n"
"1. 绝对禁止发散!绝对禁止幻觉!\n"
"2. 知道什么就说什么,不要乱讲话,不要自己编造内容!\n"
"3. 仅针对家长明确表达的内容进行回复。\n"
"4. 严禁使用列表格式。严禁使用‘首先、其次’等逻辑词。\n"
"5. 回复必须简练,字数严格控制在 50 字以内!\n"
"6. 对方问什么就答什么。例如问‘学校叫什么’,就只回答‘少惠林’,不要回复地址和电话!\n"
"如果涉及到校区信息,必须且只能使用以下真实数据:\n"
"- 单位/学校名称:长春市少惠林作文素养培养中心(简称:少惠林)\n"
"- 地址:南环城路与临河街交汇,TOUCH12街3楼325号\n"
"- 联系人:小张老师(电话:18686619970)\n"
"- 每学期开学招收小学三年级至六年级,初中七年级的学生入学,其它年段不招生。\n"
)
def _record_processed_hash(self, msg, msg_hash):
"""记录已处理的消息哈希和元数据 (仅内存)"""
self.processed_hashes.add(msg_hash)
# 记录元数据 (Sender, Time, Type)
if msg:
meta = (msg.get("sender", ""), msg.get("time_display", ""), msg.get("type", ""))
self.processed_meta.add(meta)
# 仅保留最近 100 条记录,防止无限增长
if len(self.processed_hashes) > 100:
# 简单丢弃旧的
temp = list(self.processed_hashes)[-100:]
self.processed_hashes = set(temp)
if len(self.processed_meta) > 100:
temp_meta = list(self.processed_meta)[-100:]
self.processed_meta = set(temp_meta)
async def get_reply(self, last_message_text, context_text=""):
prompt = (
f"【教师人设】:{self.persona}\n\n"
f"【上下文对话内容】:\n{context_text}\n\n"
f"【最后一条待回复消息】:\n{last_message_text}\n\n"
"【任务要求】:\n"
"请作为大张老师回复家长。**必须且只能针对最后一条消息进行回复!**\n"
"参考上下文对话内容,确保回复逻辑连贯。\n"
"严禁发散,严禁编造家长没说过的情况。如果不清楚家长的意图,就温柔询问。\n"
"字数严格控制在 50 字以内。直接输出回复正文。"
)
full_response = ""
async for chunk in get_llm_response(prompt, stream=False):
full_response += chunk
return full_response.strip().strip('"').strip('“').strip('”')
def step_1_prepare_env(self):
"""步骤1: 环境准备"""
logger.info("--- [Step 1] 环境准备 ---")
WxUtil.setup_script_environment()
return True
def step_2_connect_device(self):
"""步骤2: 连接设备"""
logger.info("--- [Step 2] 连接设备 ---")
self.device = WxUtil.connect_device()
if not self.device:
logger.error("❌ 设备连接失败,请检查手机是否连接且开启了调试模式")
return False
return True
def get_image_hash(self, file_path):
"""计算图片的 MD5 哈希值 (忽略顶部 100 像素的状态栏)"""
if not os.path.exists(file_path):
return None
try:
# 使用 OpenCV 读取图片
img = cv2.imread(file_path)
if img is None:
# 如果读取失败,回退到文件哈希
with open(file_path, "rb") as f:
return hashlib.md5(f.read()).hexdigest()
# 裁剪掉顶部 150 像素 (状态栏/时间)
h, w = img.shape[:2]
if h > 150:
cropped_img = img[150:h, 0:w]
else:
cropped_img = img
# 计算裁剪后数据的哈希
return hashlib.md5(cropped_img.tobytes()).hexdigest()
except Exception as e:
logger.error(f"计算哈希出错: {e}, 回退到文件哈希")
with open(file_path, "rb") as f:
return hashlib.md5(f.read()).hexdigest()
def get_stable_message_hash(self, msg):
"""
计算消息的稳定哈希值(忽略坐标等易变字段)
仅包含: sender, content, time_display, type
"""
if not msg:
return ""
stable_data = {
"sender": msg.get("sender", ""),
"content": msg.get("content") or "", # 确保 None 转为空字符串
"time_display": msg.get("time_display", ""),
"type": msg.get("type", "")
}
# 序列化并计算哈希
msg_str = json.dumps(stable_data, sort_keys=True, ensure_ascii=False)
return hashlib.md5(msg_str.encode('utf-8')).hexdigest()
async def run(self):
"""
主运行循环
"""
logger.info("🚀 正在启动 T2_ChatMonitor (Auto-Reply)...")
# 定义 JSON 序列化辅助函数
def numpy_serializer(obj):
if isinstance(obj, np.integer):
return int(obj)
if isinstance(obj, np.floating):
return float(obj)
if isinstance(obj, np.ndarray):
return obj.tolist()
raise TypeError(f"Type {type(obj)} not serializable")
# 1. 环境准备
if not self.step_1_prepare_env(): return
if not self.step_2_connect_device(): return
# [User Requested] 移除首屏概念,直接进入监控循环
# 以前说过什么都不管了,只关注最后一条
logger.info("🚀 启动完成,直接进入实时监控阶段...")
# 3. 进入循环阶段
while True:
try:
# A. 截图并计算哈希
self.device.screenshot(self.screenshot_path)
current_screen_hash = self.get_image_hash(self.screenshot_path)
# B. 如果屏幕无变化,则跳过识别
if current_screen_hash == self.last_screen_hash:
await asyncio.sleep(self.check_interval)
continue
self.last_screen_hash = current_screen_hash
logger.info("📸 屏幕发生变化,正在分析...")
# C. 分析最新图片
dialogue_log, input_pos = await WxUtil.analyze_chat_image(
self.screenshot_path,
self.debug_view_path,
device=self.device,
process_strategy="UNREAD", # 监控阶段:只处理带红点的新语音
restore_processed_voice=False # 不还原状态,防止红点未消导致重复转换
)
if not dialogue_log:
logger.info("😴 未识别到有效消息")
await asyncio.sleep(self.check_interval)
continue
logger.info(f"📊 当前识别到 {len(dialogue_log)} 条消息,最后一条: {dialogue_log[-1]}")
# 更新当前对话日志(可用于上下文参考)
self.dialogue_log = dialogue_log
self.input_pos = input_pos
# D. 只关注最后一条消息
last_msg = dialogue_log[-1]
# 计算稳定哈希(忽略坐标变化)
current_msg_hash = self.get_stable_message_hash(last_msg)
# E. 判断是否需要回复 (对方发送且非重复消息)
sender = last_msg.get('sender', '')
# Check if hash is already processed (in-memory only)
is_processed = current_msg_hash in self.processed_hashes
# Log only if it changed from last *in-memory* check to avoid spam
if is_processed and current_msg_hash != self.last_processed_msg_hash:
# logger.info(f"🚫 [监控] 消息哈希已存在于历史记录中,跳过回复 (Hash: {current_msg_hash})")
self.last_processed_msg_hash = current_msg_hash
if not is_processed and current_msg_hash != self.last_processed_msg_hash:
if sender != "我":
event_shot = WxUtil.get_next_debug_path("event_new_msg")
self.device.screenshot(event_shot)
logger.info(f"💡 [监控] 发现新消息: {last_msg},保存现场截图: {event_shot}")
# 获取上下文文本 (格式化为 Sender: Content)
context_text = "\n".join([f"{m.get('time_display', '') + ' ' if m.get('time_display') else ''}{m.get('sender')}: {m.get('content')}" for m in dialogue_log[:-1]])
last_content = last_msg.get('content') or ""
# 兜底逻辑:如果最后一条是语音且内容为空(可能因无红点未被 UNREAD 策略处理),尝试强制转换
if last_msg.get('type') == 'voice' and not last_content.strip():
logger.info("⚠️ [监控] 最后一条语音消息未获取到内容(可能已读无红点),尝试强制转换...")
# 强制使用 LAST 策略重试,并且不还原状态(保持文字展开),防止下次循环因无内容再次触发重试
dialogue_log_retry, _ = await WxUtil.analyze_chat_image(
self.screenshot_path,
self.debug_view_path,
device=self.device,
process_strategy="LAST",
restore_processed_voice=False
)
if dialogue_log_retry:
# 更新引用
self.dialogue_log = dialogue_log_retry
dialogue_log = dialogue_log_retry
last_msg = dialogue_log[-1]
last_content = last_msg.get('content') or ""
logger.info(f"🔄 [重试] 强制转换后内容: {last_content}")
# 重新构建哈希
current_msg_hash = self.get_stable_message_hash(last_msg)
# 再次检查是否已处理 (因为内容变了,哈希变了)
if current_msg_hash in self.processed_hashes:
logger.info(f"🚫 [重试] 转换后发现该消息已处理,跳过。")
self.last_processed_msg_hash = current_msg_hash
# 跳过本次循环的剩余部分
continue
# 生成回复
reply = await self.get_reply(last_content, context_text)
if reply:
logger.info(f"🤖 [监控] LLM 建议回复: {reply}")
if self.input_pos:
logger.info(f"⚡ [监控] 执行自动回复...")
# input_pos 是 ((x,y), box) 格式,取第一个元素坐标点
target_pos = self.input_pos[0] if isinstance(self.input_pos, (list, tuple)) and len(self.input_pos) == 2 and isinstance(self.input_pos[0], (list, tuple)) else self.input_pos
# 简单兼容处理:如果 input_pos[0] 是 tuple/list 且 input_pos[1] 是 None/box,则取 input_pos[0]
if isinstance(self.input_pos, (list, tuple)) and len(self.input_pos) == 2 and isinstance(self.input_pos[0], (list, tuple)):
target_pos = self.input_pos[0]
# [User Request] 语音转文字未关闭 -> 顺序不对
# 在回复发送前,尝试关闭语音转文字(如果最后一条是语音)
# 这样截图看起来更干净,且符合用户期望的顺序
if last_msg.get('type') == 'voice':
logger.info("🧹 [监控] 尝试关闭语音转文字显示...")
try:
# 点击语音消息中心,关闭文字
cx, cy = last_msg['center']
WxUtil.safe_device_click(self.device, cx, cy)
# 稍微等待界面刷新
await asyncio.sleep(1)
except Exception as e:
logger.warning(f"关闭语音转文字失败: {e}")
perform_input_action(self.device, target_pos, reply)
# 发送后截图留存
reply_sent_shot = WxUtil.get_next_debug_path("event_reply_sent")
self.device.screenshot(reply_sent_shot)
logger.info(f"✅ [监控] 回复已发送,保存发送后截图: {reply_sent_shot}")
self._record_processed_hash(last_msg, current_msg_hash)
self.last_processed_msg_hash = current_msg_hash
else:
logger.error("❌ 未找到输入框位置,无法发送回复")
else:
logger.info("⚪ [监控] LLM 认为无需回复")
self._record_processed_hash(last_msg, current_msg_hash)
self.last_processed_msg_hash = current_msg_hash
else:
# 是我发的消息,更新哈希,不再处理
self.last_processed_msg_hash = current_msg_hash
await asyncio.sleep(self.check_interval)
except Exception as e:
logger.error(f"Error in monitoring loop: {e}", exc_info=True)
await asyncio.sleep(self.check_interval)
async def run_main():
"""
运行自动巡课机器人
"""
bot = ChatMonitorBot()
await bot.run()
if __name__ == "__main__":
# 应用 Win32 补丁
Win32Patch.patch()
asyncio.run(run_main())

126
WeiXin/T2_Identify.py Normal file
View File

@@ -0,0 +1,126 @@
# coding=utf-8
import os
import sys
import logging
import cv2
import json
# Add project root
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.append(project_root)
from WeiXin import WxUtil
# Setup Logger
logger = logging.getLogger("T2_Identify")
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# Console Handler
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
# File Handler
log_file = os.path.join(WxUtil.LOG_DIR, "T2_Identify.log")
fh = logging.FileHandler(log_file, mode='w', encoding='utf-8')
fh.setFormatter(formatter)
logger.addHandler(fh)
def main():
logger.info("--- T2: Identify Elements ---")
# 1. Connect
d = WxUtil.connect_device()
if not d:
logger.error("Device connection failed.")
return
# 2. Screenshot/Image Source
if len(sys.argv) > 1:
screenshot_path = sys.argv[1]
logger.info(f"Using provided image: {screenshot_path}")
else:
screenshot_path = os.path.join(WxUtil.OUTPUT_DIR, "T2_Screenshot.jpg")
logger.info("Taking screenshot...")
try:
d.screenshot(screenshot_path)
logger.info(f"Screenshot saved to: {screenshot_path}")
except Exception as e:
logger.error(f"Failed to take screenshot: {e}")
# If screenshot fails, try to use existing one for debugging if it exists
if os.path.exists(screenshot_path):
logger.warning(f"Using existing screenshot at {screenshot_path}")
else:
return
# 3. Analyze (Using the internal _scan function to see raw detection)
logger.info("Scanning image for elements...")
messages, debug_img, chat_title = WxUtil._scan_chat_messages(screenshot_path)
# 4. Output Raw Detection
logger.info(f"Chat Title: {chat_title}")
logger.info(f"--- Raw Detection ({len(messages)} items) ---")
for i, msg in enumerate(messages):
# Format for readability
content = msg.get('content', '')
if content is None: content = "[None]"
type_str = msg.get('type', 'unknown')
sender_str = msg.get('sender', 'unknown')
center = msg.get('center', (0,0))
y = msg.get('y', 0)
time_disp = msg.get('time_display', 'N/A')
logger.info(f"[{i}] {type_str:<8} | {sender_str:<4} | y={y:<4} | time={time_disp} | {content}")
# 5. Merging Logic (Simulate what the bot SHOULD do)
logger.info("--- Merged Dialogues (Simulated) ---")
merged_msgs = []
if messages:
current_msg = messages[0].copy()
last_y = current_msg['y'] # Track Y of the last element in the current merge block
for i in range(1, len(messages)):
next_msg = messages[i]
# Merge Criteria:
# 1. Same Sender
# 2. Same Type (text)
# 3. Vertically close (y_diff < 100) relative to the LAST line, not the first
# 4. Not a timestamp
is_same_sender = (current_msg['sender'] == next_msg['sender'])
is_text = (current_msg['type'] == 'text' and next_msg['type'] == 'text')
# Use last_y to compare with next_msg['y']
y_diff = next_msg['y'] - last_y
is_close = (y_diff < 120) # Tolerance for line spacing (slightly increased)
if is_same_sender and is_text and is_close:
# Merge content
current_msg['content'] += " " + next_msg['content']
# Update last_y to this message's y
last_y = next_msg['y']
else:
merged_msgs.append(current_msg)
current_msg = next_msg.copy()
last_y = current_msg['y']
merged_msgs.append(current_msg)
for i, msg in enumerate(merged_msgs):
content = msg.get('content', '')
sender = msg.get('sender', 'unknown')
logger.info(f"[{i}] {sender}: {content}")
# 6. Save Debug Image
debug_path = os.path.join(WxUtil.OUTPUT_DIR, "T2_Identify_Debug.jpg")
if debug_img is not None:
cv2.imwrite(debug_path, debug_img)
logger.info(f"Debug image saved to: {debug_path}")
if __name__ == "__main__":
main()

85
WeiXin/T3_Voice.py Normal file
View File

@@ -0,0 +1,85 @@
# coding=utf-8
import os
import sys
import logging
import asyncio
import cv2
# Add project root
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.append(project_root)
from WeiXin import WxUtil
# Setup Logger
logger = logging.getLogger("T3_Voice")
# Configure Root Logger to ensure all logs (including WxUtil) go to file
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# Ensure Log Directory exists
if hasattr(WxUtil, 'LOG_DIR'):
log_dir = WxUtil.LOG_DIR
else:
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Logs")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# File Handler
log_file = os.path.join(log_dir, "T3_Voice.log")
fh = logging.FileHandler(log_file, mode='w', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
root_logger.addHandler(fh)
# Console Handler (if not already added by WxUtil)
if not any(isinstance(h, logging.StreamHandler) for h in root_logger.handlers):
ch = logging.StreamHandler()
ch.setFormatter(formatter)
root_logger.addHandler(ch)
async def main():
logger.info("--- T3: Voice Processing Test ---")
# 1. Connect
d = WxUtil.connect_device()
if not d:
logger.error("Device connection failed.")
return
# 2. Screenshot
screenshot_path = os.path.join(WxUtil.OUTPUT_DIR, "T3_Screenshot.jpg")
debug_path = os.path.join(WxUtil.OUTPUT_DIR, "T3_Debug.jpg")
logger.info("Taking screenshot...")
d.screenshot(screenshot_path)
logger.info("Initial screenshot taken.")
# 3. Use analyze_chat_image with strategy='ALL' to test conversion
# This simulates the real bot behavior for voice messages
logger.info("Running WxUtil.analyze_chat_image (Strategy=ALL)...")
# This function handles the click -> convert -> rescan loop internally
final_messages, _ = await WxUtil.analyze_chat_image(
screenshot_path,
debug_path,
device=d,
process_strategy="ALL"
)
# 4. Output Result
logger.info("--- Final Voice Results ---")
voice_msgs = [m for m in final_messages if m['type'] == 'voice']
if not voice_msgs:
logger.info("No voice messages found.")
else:
for i, msg in enumerate(voice_msgs):
content = msg.get('content')
converted_status = "YES" if msg.get('is_converted') else "NO"
logger.info(f"Voice [{i}] Sender: {msg['sender']} | Converted: {converted_status} | Content: {content}")
if __name__ == "__main__":
asyncio.run(main())

104
WeiXin/T4_Text.py Normal file
View File

@@ -0,0 +1,104 @@
# coding=utf-8
import os
import sys
import logging
import cv2
# Add project root
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.append(project_root)
from WeiXin import WxUtil
# Setup Logger
logger = logging.getLogger("T4_Text")
# Configure Root Logger to ensure all logs (including WxUtil) go to file
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# Ensure Log Directory exists
if hasattr(WxUtil, 'LOG_DIR'):
log_dir = WxUtil.LOG_DIR
else:
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Logs")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# File Handler
log_file = os.path.join(log_dir, "T4_Text.log")
fh = logging.FileHandler(log_file, mode='w', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
root_logger.addHandler(fh)
# Console Handler (if not already added by WxUtil)
if not any(isinstance(h, logging.StreamHandler) for h in root_logger.handlers):
ch = logging.StreamHandler()
ch.setFormatter(formatter)
root_logger.addHandler(ch)
def main():
logger.info("--- T4: Text Processing Test ---")
# 1. Connect
d = WxUtil.connect_device()
if not d:
logger.error("Device connection failed.")
return
# 2. Screenshot
screenshot_path = os.path.join(WxUtil.OUTPUT_DIR, "T4_Screenshot.jpg")
logger.info("Taking screenshot...")
d.screenshot(screenshot_path)
# 3. Scan
logger.info("Scanning image...")
messages, debug_img, _ = WxUtil._scan_chat_messages(screenshot_path)
# 4. Filter and Output
logger.info("--- Raw Text Messages ---")
text_msgs = [m for m in messages if m['type'] == 'text']
if not text_msgs:
logger.info("No text messages found.")
else:
for i, msg in enumerate(text_msgs):
logger.info(f"Text [{i}] Sender: {msg['sender']} | y={msg['y']} | Content: {msg['content']}")
# 5. Merging Logic (Same as T2)
logger.info("--- Merged Text Dialogues ---")
merged_msgs = []
if text_msgs:
current_msg = text_msgs[0].copy()
last_y = current_msg['y']
for i in range(1, len(text_msgs)):
next_msg = text_msgs[i]
is_same_sender = (current_msg['sender'] == next_msg['sender'])
y_diff = next_msg['y'] - last_y
is_close = (y_diff < 120)
if is_same_sender and is_close:
current_msg['content'] += " " + next_msg['content']
last_y = next_msg['y']
else:
merged_msgs.append(current_msg)
current_msg = next_msg.copy()
last_y = current_msg['y']
merged_msgs.append(current_msg)
for i, msg in enumerate(merged_msgs):
logger.info(f"[{i}] {msg['sender']}: {msg['content']}")
# Save debug
debug_path = os.path.join(WxUtil.OUTPUT_DIR, "T4_Debug.jpg")
cv2.imwrite(debug_path, debug_img)
logger.info(f"Debug image saved to: {debug_path}")
if __name__ == "__main__":
main()

80
WeiXin/T5_SendText.py Normal file
View File

@@ -0,0 +1,80 @@
# coding=utf-8
import os
import sys
import logging
import time
# Add project root
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.append(project_root)
from WeiXin import WxUtil
# Setup Logger
logger = logging.getLogger("T5_SendText")
# Configure Root Logger to ensure all logs go to file
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# Ensure Log Directory exists
if hasattr(WxUtil, 'LOG_DIR'):
log_dir = WxUtil.LOG_DIR
else:
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Logs")
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# File Handler
log_file = os.path.join(log_dir, "T5_SendText.log")
fh = logging.FileHandler(log_file, mode='w', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
root_logger.addHandler(fh)
# Console Handler
if not any(isinstance(h, logging.StreamHandler) for h in root_logger.handlers):
ch = logging.StreamHandler()
ch.setFormatter(formatter)
root_logger.addHandler(ch)
def main():
logger.info("--- T5: Text Input and Send Test ---")
# 1. Connect
d = WxUtil.connect_device()
if not d:
logger.error("Device connection failed.")
return
# 2. Prepare text
test_text = "我是AI返回的文本测试"
logger.info(f"Test Content: {test_text}")
# 3. Find Input Box and Send (Using perform_input_action which encapsulates the steps)
# We pass auto_send=True to click the send button
# The function will:
# - Ensure keyboard mode
# - Find input box (UI Tree -> Template -> Fallback)
# - Input text
# - Find and click Send button
logger.info("Step 1 & 2: Finding input box and writing text...")
# We can get a rough position first for logging, though perform_input_action is more robust
screenshot_path = os.path.join(WxUtil.OUTPUT_DIR, "T5_Initial_Check.jpg")
d.screenshot(screenshot_path)
input_pos, _ = WxUtil.find_input_box_center(screenshot_path)
logger.info(f"Initial target input position: {input_pos}")
success = WxUtil.perform_input_action(d, input_pos, test_text, auto_send=True)
if success:
logger.info("✅ Step 3: Send action completed successfully.")
else:
logger.error("❌ Send action failed.")
if __name__ == "__main__":
main()

Binary file not shown.

View File

@@ -0,0 +1,13 @@
@echo off
echo ==========================================
echo Switching to WPS Office Mode
echo ==========================================
echo.
if exist "%~dp0WpsInstaller\bin\Debug\WpsInstaller.exe" (
"%~dp0WpsInstaller\bin\Debug\WpsInstaller.exe" wps
) else (
echo Error: WpsInstaller.exe not found.
echo Please build the solution first.
)
echo.
pause

View File

@@ -0,0 +1,13 @@
@echo off
echo ==========================================
echo Switching to MS Word Mode (VSTO)
echo ==========================================
echo.
if exist "%~dp0WpsInstaller\bin\Debug\WpsInstaller.exe" (
"%~dp0WpsInstaller\bin\Debug\WpsInstaller.exe" word
) else (
echo Error: WpsInstaller.exe not found.
echo Please build the solution first.
)
echo.
pause

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Word = Microsoft.Office.Interop.Word;
namespace WordAddIn
{
/// <summary>
/// 全局上下文,用于存储当前 Word 应用程序实例。
/// 兼容 VSTO (Office) 和 COM Add-in (WPS)。
/// </summary>
public static class AddInContext
{
private static Word.Application _application;
/// <summary>
/// 获取或设置当前的 Word 应用程序实例。
/// </summary>
public static Word.Application CurrentApplication
{
get
{
// 如果尚未设置(例如在非 VSTO 环境下且未初始化),尝试回退到 Globals(仅限 VSTO)
if (_application == null)
{
try
{
// 注意:如果不在 VSTO 环境下,访问 Globals.ThisAddIn 可能会抛出异常或返回 null
if (Globals.ThisAddIn != null)
{
_application = Globals.ThisAddIn.Application;
}
}
catch
{
// 忽略异常,说明可能不是 VSTO 环境
}
}
return _application;
}
set
{
_application = value;
}
}
}
}

View File

@@ -49,6 +49,9 @@ namespace WordAddIn
//
// txtTopic
//
this.txtTopic.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtTopic.Location = new System.Drawing.Point(20, 50);
this.txtTopic.Multiline = true;
this.txtTopic.Name = "txtTopic";
@@ -58,6 +61,7 @@ namespace WordAddIn
//
// btnGenerate
//
this.btnGenerate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnGenerate.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnGenerate.Location = new System.Drawing.Point(240, 310);
this.btnGenerate.Name = "btnGenerate";
@@ -69,6 +73,7 @@ namespace WordAddIn
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(360, 310);
this.btnCancel.Name = "btnCancel";
@@ -83,13 +88,14 @@ namespace WordAddIn
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(500, 370);
this.MinimumSize = new System.Drawing.Size(400, 300);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnGenerate);
this.Controls.Add(this.txtTopic);
this.Controls.Add(this.lblTopic);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.MaximizeBox = true;
this.MinimizeBox = true;
this.Name = "CreateOutlineForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "创建大纲 - AI助手";

View File

@@ -90,10 +90,12 @@ namespace WordAddIn
{
this.Text = "生成配图选项 - 支持编辑提示词";
this.Size = new Size(500, 520);
this.MinimumSize = new Size(400, 400); // 设置最小尺寸
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.AutoScaleMode = AutoScaleMode.Font; // 支持 DPI 缩放
this.FormBorderStyle = FormBorderStyle.Sizable; // 允许调整大小
this.MaximizeBox = true; // 允许最大化
this.MinimizeBox = true;
this.lblStyle = new Label();
this.cmbStyles = new ComboBox();
@@ -114,6 +116,7 @@ namespace WordAddIn
this.cmbStyles.DropDownStyle = ComboBoxStyle.DropDownList;
this.cmbStyles.Location = new Point(20, 45);
this.cmbStyles.Size = new Size(440, 25);
this.cmbStyles.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; // 宽度自适应
foreach (var key in _styleTemplates.Keys)
{
this.cmbStyles.Items.Add(key);
@@ -130,19 +133,22 @@ namespace WordAddIn
this.txtFinalPrompt.Multiline = true;
this.txtFinalPrompt.ScrollBars = ScrollBars.Vertical;
this.txtFinalPrompt.Size = new Size(440, 280);
this.txtFinalPrompt.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; // 全向自适应
// chkOptimize
this.chkOptimize.AutoSize = true;
this.chkOptimize.Location = new Point(20, 400);
this.chkOptimize.Size = new Size(400, 20);
this.chkOptimize.Text = "使用 AI 优化提示词 (自动翻译成英文并丰富细节)";
this.chkOptimize.Checked = true;
this.chkOptimize.Checked = true;
this.chkOptimize.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; // 底部固定
// btnOk
this.btnOk.Text = "开始生成";
this.btnOk.DialogResult = DialogResult.OK;
this.btnOk.Location = new Point(280, 430);
this.btnOk.Size = new Size(90, 30);
this.btnOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; // 右下固定
this.btnOk.Click += BtnOk_Click;
// btnCancel
@@ -150,6 +156,7 @@ namespace WordAddIn
this.btnCancel.DialogResult = DialogResult.Cancel;
this.btnCancel.Location = new Point(380, 430);
this.btnCancel.Size = new Size(80, 30);
this.btnCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; // 右下固定
this.Controls.Add(this.lblStyle);
this.Controls.Add(this.cmbStyles);

View File

@@ -44,6 +44,9 @@ namespace WordAddIn
//
// tabControl
//
this.tabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tabControl.Location = new System.Drawing.Point(12, 12);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
@@ -52,6 +55,7 @@ namespace WordAddIn
//
// btnSave
//
this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnSave.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnSave.Location = new System.Drawing.Point(210, 310);
this.btnSave.Name = "btnSave";
@@ -63,6 +67,7 @@ namespace WordAddIn
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(320, 310);
this.btnCancel.Name = "btnCancel";
@@ -78,12 +83,13 @@ namespace WordAddIn
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(450, 360);
this.MinimumSize = new System.Drawing.Size(350, 300);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnSave);
this.Controls.Add(this.tabControl);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.MaximizeBox = true;
this.MinimizeBox = true;
this.Name = "OutlineSettingsForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "大纲样式配置";

View File

@@ -77,6 +77,9 @@ namespace WordAddIn
//
// tabControl
//
this.tabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tabControl.Controls.Add(this.tabFile);
this.tabControl.Controls.Add(this.tabText);
this.tabControl.Font = new System.Drawing.Font("微软雅黑", 11F);
@@ -111,6 +114,8 @@ namespace WordAddIn
//
// txtFilePath
//
this.txtFilePath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtFilePath.Font = new System.Drawing.Font("微软雅黑", 11F);
this.txtFilePath.Location = new System.Drawing.Point(30, 100);
this.txtFilePath.Name = "txtFilePath";
@@ -120,6 +125,7 @@ namespace WordAddIn
//
// btnBrowse
//
this.btnBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnBrowse.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnBrowse.Font = new System.Drawing.Font("微软雅黑", 10F);
this.btnBrowse.Location = new System.Drawing.Point(440, 98);
@@ -155,6 +161,7 @@ namespace WordAddIn
//
// lblWordCount
//
this.lblWordCount.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.lblWordCount.AutoSize = true;
this.lblWordCount.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Bold);
this.lblWordCount.Location = new System.Drawing.Point(30, 355);
@@ -165,6 +172,7 @@ namespace WordAddIn
//
// numWordCount
//
this.numWordCount.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.numWordCount.Font = new System.Drawing.Font("微软雅黑", 14F);
this.numWordCount.Increment = new decimal(new int[] {
100,
@@ -193,6 +201,7 @@ namespace WordAddIn
//
// lblWordUnit
//
this.lblWordUnit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.lblWordUnit.AutoSize = true;
this.lblWordUnit.Font = new System.Drawing.Font("微软雅黑", 12F);
this.lblWordUnit.Location = new System.Drawing.Point(330, 355);
@@ -203,6 +212,7 @@ namespace WordAddIn
//
// btnGenerate
//
this.btnGenerate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnGenerate.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(43)))), ((int)(((byte)(87)))), ((int)(((byte)(154)))));
this.btnGenerate.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnGenerate.DialogResult = System.Windows.Forms.DialogResult.OK;
@@ -220,6 +230,7 @@ namespace WordAddIn
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 12F);
@@ -238,15 +249,16 @@ namespace WordAddIn
this.BackColor = System.Drawing.Color.White;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(650, 520);
this.MinimumSize = new System.Drawing.Size(500, 400);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnGenerate);
this.Controls.Add(this.lblWordUnit);
this.Controls.Add(this.numWordCount);
this.Controls.Add(this.lblWordCount);
this.Controls.Add(this.tabControl);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.MaximizeBox = true;
this.MinimizeBox = true;
this.Name = "ReferenceGeneratorForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "素材写作 - AI助手";

View File

@@ -0,0 +1,120 @@
using System;
using System.Runtime.InteropServices;
using Extensibility;
using Microsoft.Office.Core;
using Word = Microsoft.Office.Interop.Word;
using System.Windows.Forms;
namespace WordAddIn
{
/// <summary>
/// WPS Office 兼容入口类。
/// 实现标准的 COM Add-in 接口 (IDTExtensibility2),以便 WPS 可以加载此插件。
/// 同时继承自 AiRibbon,复用 Ribbon UI 和业务逻辑。
/// </summary>
[ComVisible(true)]
[Guid("D8E52004-9D68-4509-8A11-66774D4F0523")] // 固定的 COM GUID
[ProgId("WordAddIn.WpsAddIn")] // 注册表中的 ProgID
public class WpsAddIn : AiRibbon, IDTExtensibility2, IRibbonExtensibility
{
private Word.Application _application;
private object _addInInstance;
private void Log(string message)
{
try
{
string logPath = @"C:\Temp\WpsAddIn.log";
string dir = System.IO.Path.GetDirectoryName(logPath);
if (!System.IO.Directory.Exists(dir))
{
System.IO.Directory.CreateDirectory(dir);
}
System.IO.File.AppendAllText(logPath, $"{DateTime.Now}: {message}{Environment.NewLine}");
}
catch { }
}
#region IDTExtensibility2 成员
/// <summary>
/// 插件加载时调用
/// </summary>
public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
{
Log("OnConnection called");
try
{
_application = Application as Word.Application;
_addInInstance = AddInInst;
if (_application == null)
{
Log("Application is null in OnConnection");
}
else
{
Log("Application obtained successfully");
}
// 初始化全局上下文,确保 AiRibbon 中的功能可以正常获取 Application 对象
AddInContext.CurrentApplication = _application;
}
catch (Exception ex)
{
Log($"Error in OnConnection: {ex.Message}");
MessageBox.Show("WPS 插件加载失败: " + ex.Message);
}
}
/// <summary>
/// 插件卸载时调用
/// </summary>
public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)
{
Log("OnDisconnection called");
_application = null;
_addInInstance = null;
// 清理上下文
if (AddInContext.CurrentApplication == _application)
{
AddInContext.CurrentApplication = null;
}
}
public void OnAddInsUpdate(ref Array custom)
{
Log("OnAddInsUpdate called");
}
public void OnStartupComplete(ref Array custom)
{
Log("OnStartupComplete called");
}
public void OnBeginShutdown(ref Array custom)
{
Log("OnBeginShutdown called");
}
#endregion
// 显式实现 IRibbonExtensibility,虽然基类实现了,但为了确保 COM 可见性和调试,这里显式重写/覆盖
public new string GetCustomUI(string RibbonID)
{
Log($"GetCustomUI called for RibbonID: {RibbonID}");
try
{
// 调用基类的方法获取 XML
string xml = base.GetCustomUI(RibbonID);
Log("GetCustomUI XML retrieved successfully");
return xml;
}
catch (Exception ex)
{
Log($"Error in GetCustomUI: {ex.Message}");
return null;
}
}
}
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2">
<asmv1:assemblyIdentity name="WordAddIn.dll" version="1.0.0.5" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" type="win32" />
<description xmlns="urn:schemas-microsoft-com:asm.v1">WordAddIn</description>
<application />
<entryPoint>
<co.v1:customHostSpecified />
</entryPoint>
<trustInfo>
<security>
<applicationRequestMinimum>
<PermissionSet Unrestricted="true" ID="Custom" SameSite="site" />
<defaultAssemblyRequest permissionSetReference="Custom" />
</applicationRequestMinimum>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!--
UAC 清单选项
如果要更改 Windows 用户帐户控制级别,请用以下节点之一替换
requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
如果要利用文件和注册表虚拟化提供
向后兼容性,请删除 requestedExecutionLevel 节点。
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentOS>
<osVersionInfo>
<os majorVersion="5" minorVersion="1" buildNumber="2600" servicePackMajor="0" />
</osVersionInfo>
</dependentOS>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.Windows.CommonLanguageRuntime" version="4.0.30319.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Accessibility" version="4.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.CSharp" version="4.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.Office.Tools" version="10.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" processorArchitecture="msil" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.Office.Tools.Common" version="10.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" processorArchitecture="msil" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.Office.Tools.v4.0.Framework" version="10.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" processorArchitecture="msil" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.Office.Tools.Word" version="10.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" processorArchitecture="msil" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="Microsoft.VisualStudio.Tools.Applications.Runtime" version="10.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" processorArchitecture="msil" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="mscorlib" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="stdole" version="7.0.3300.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Core" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Data" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Data.DataSetExtensions" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Drawing" version="4.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Net.Http" version="4.2.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Numerics" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Runtime.Serialization" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Windows.Forms" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Xml" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="preRequisite" allowDelayedBinding="true">
<assemblyIdentity name="System.Xml.Linq" version="4.0.0.0" publicKeyToken="B77A5C561934E089" language="neutral" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="Microsoft.Office.Tools.Common.v4.0.Utilities.dll" size="32664">
<assemblyIdentity name="Microsoft.Office.Tools.Common.v4.0.Utilities" version="10.0.0.0" publicKeyToken="B03F5F7F11D50A3A" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>jLCTF8Mm6bD4PDN+rnzN6q0+ReXaNgPh68kMWgatFwI=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="Newtonsoft.Json.dll" size="721320">
<assemblyIdentity name="Newtonsoft.Json" version="13.0.0.0" publicKeyToken="30AD4FE6B2A6AEED" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>8MB68OhNTdTaS9eCO6RTW8BIGzv2I+1Atlm2gUemu3U=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly dependencyType="install" allowDelayedBinding="true" codebase="WordAddIn.dll" size="92160">
<assemblyIdentity name="WordAddIn" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>Xc75xyRYUsYQipvtLXm0QDx+Cld2d4Let98h0RoSn60=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
<vstav3:addIn xmlns:vstav3="urn:schemas-microsoft-com:vsta.v3">
<vstav3:entryPointsCollection>
<vstav3:entryPoints>
<vstav3:entryPoint class="WordAddIn.ThisAddIn">
<assemblyIdentity name="WordAddIn" version="1.0.0.0" language="neutral" processorArchitecture="msil" />
</vstav3:entryPoint>
</vstav3:entryPoints>
</vstav3:entryPointsCollection>
<vstav3:update enabled="true">
<vstav3:expiration maximumAge="7" unit="days" />
</vstav3:update>
<vstav3:application>
<vstov4:customizations xmlns:vstov4="urn:schemas-microsoft-com:vsto.v4">
<vstov4:customization>
<vstov4:appAddIn application="Word" loadBehavior="3" keyName="WordAddIn">
<vstov4:friendlyName>WordAddIn</vstov4:friendlyName>
<vstov4:description>WordAddIn</vstov4:description>
<vstov4.1:ribbonTypes xmlns:vstov4.1="urn:schemas-microsoft-com:vsto.v4.1" />
</vstov4:appAddIn>
</vstov4:customization>
</vstov4:customizations>
</vstav3:application>
</vstav3:addIn>
<publisherIdentity name="CN=DESKTOP-Q6H7B6L\Administrator" issuerKeyHash="42606c52715d5764c2f9dab5bee6dcd76cc7a67d" /><Signature Id="StrongNameSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>C9avRX9nfKTHDCTvRZPXB995lkAo4zeFxLdQ276wuO4=</DigestValue></Reference></SignedInfo><SignatureValue>TdnelO/cwCkCCeDawTU6BuIEGjlDYbfV5PB+3/qnqGqnR6J4Y1uoIby1tsVvkS9DDMRC+1IixlDEeYKSn5LsDgSJW3Ayi8TBALUW9wvmVWyHlFCdGeCjpZi9SEVbKazkolOki6LiXJjS+7Rg+IwAYXfPunMYtWUizUXWNof7Aq0=</SignatureValue><KeyInfo Id="StrongNameKeyInfo"><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><msrel:RelData xmlns:msrel="http://schemas.microsoft.com/windows/rel/2005/reldata"><r:license xmlns:r="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:as="http://schemas.microsoft.com/windows/pki/2005/Authenticode"><r:grant><as:ManifestInformation Hash="eeb8b0bedb50b7c48537e328409679df07d79345ef240cc7a47c677f45afd60b" Description="" Url=""><as:assemblyIdentity name="WordAddIn.dll" version="1.0.0.5" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" type="win32" /></as:ManifestInformation><as:SignedBy /><as:AuthenticodePublisher><as:X509SubjectName>CN=DESKTOP-Q6H7B6L\Administrator</as:X509SubjectName></as:AuthenticodePublisher></r:grant><r:issuer><Signature Id="AuthenticodeSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>iYwkWAWU35knc6kiYK6gTxxE/Nh3uHxhbLQwEgreJ0M=</DigestValue></Reference></SignedInfo><SignatureValue>zJiYXR8G+maT6VbsHD2w6q1pgH76qSQ/KAXVc2T/fdOjU+a+9TGWKBIGseWr4x0G4lpY95+eD7msW9p1YMtV3P+H4shh1c5rLhwel6TYItdrtbcnqqLNzBbbtdhbGJ3jxH9JK9CSmIy636BegAEs+klj3T4zcEMw1PVYeCgjr1E=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q==</X509Certificate></X509Data></KeyInfo></Signature></r:issuer></r:license></msrel:RelData></KeyInfo></Signature></asmv1:assembly>

Binary file not shown.

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xrml="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:co.v1="urn:schemas-microsoft-com:clickonce.v1" xmlns:co.v2="urn:schemas-microsoft-com:clickonce.v2">
<assemblyIdentity name="WordAddIn.vsto" version="1.0.0.5" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" />
<description asmv2:publisher="WordAddIn" asmv2:product="WordAddIn" xmlns="urn:schemas-microsoft-com:asm.v1" />
<deployment install="false" />
<compatibleFrameworks xmlns="urn:schemas-microsoft-com:clickonce.v2">
<framework targetVersion="4.7.2" profile="Full" supportedRuntime="4.0.30319" />
</compatibleFrameworks>
<dependency>
<dependentAssembly dependencyType="install" codebase="WordAddIn.dll.manifest" size="14626">
<assemblyIdentity name="WordAddIn.dll" version="1.0.0.5" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" type="win32" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue>uc4ted455t5fnD/+JbLpdaTFK8cohGlzP/okvRpZkaY=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
<publisherIdentity name="CN=DESKTOP-Q6H7B6L\Administrator" issuerKeyHash="42606c52715d5764c2f9dab5bee6dcd76cc7a67d" /><Signature Id="StrongNameSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>u2iS9Fgei3TF/Y3nkcEa9EOIDuiXBAsXxxjBTjNWjyo=</DigestValue></Reference></SignedInfo><SignatureValue>qqTHg6m3s7kQmUxARD8Qs8ewpwcejuUvjYC5Y7gkadIdT1HDSFT2qGhJBzrjlJVE2jlj2azutIuYzz2Birxhhn3TQRatXfEcqirHeHJhLGw8rQNy6DJzltFeGGjoNDkojdVB9EdTBNG5WNXEMPQhXYAJQEZwvsoJzApigWljHF8=</SignatureValue><KeyInfo Id="StrongNameKeyInfo"><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><msrel:RelData xmlns:msrel="http://schemas.microsoft.com/windows/rel/2005/reldata"><r:license xmlns:r="urn:mpeg:mpeg21:2003:01-REL-R-NS" xmlns:as="http://schemas.microsoft.com/windows/pki/2005/Authenticode"><r:grant><as:ManifestInformation Hash="2a8f56334ec118c7170b0497e80e8843f41ac191e78dfdc5748b1e58f49268bb" Description="" Url=""><as:assemblyIdentity name="WordAddIn.vsto" version="1.0.0.5" publicKeyToken="06a92c46db516926" language="neutral" processorArchitecture="msil" xmlns="urn:schemas-microsoft-com:asm.v1" /></as:ManifestInformation><as:SignedBy /><as:AuthenticodePublisher><as:X509SubjectName>CN=DESKTOP-Q6H7B6L\Administrator</as:X509SubjectName></as:AuthenticodePublisher></r:grant><r:issuer><Signature Id="AuthenticodeSignature" xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" /><DigestValue>jNYRbwZELLiwmbasEVrI9F5SBTc5CX/3KhyNXa+oBJ8=</DigestValue></Reference></SignedInfo><SignatureValue>SO2h00e50l0Pn6zBPURS24wQu10eljbiaskGqQmZfBqsO0BRE6i2sDfoZKvA33Vp1hOeGml4boP+jPh6uezIWf74o7g6vH+f0iTtAOHrWiuKvlFV53YHOoH5Ps7Tq+U0bQ8LLyjx5ZEUBj+mlGel9U8QGnCIBx1rwZlyc+azI8k=</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaE=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue><X509Data><X509Certificate>MIICDTCCAXagAwIBAgIQJpSlsxfGbpRNgyjD5Q/yDjANBgkqhkiG9w0BAQsFADBFMUMwQQYDVQQDHjoARABFAFMASwBUAE8AUAAtAFEANgBIADcAQgA2AEwAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByMB4XDTI2MDEyNDAwMzYxNVoXDTI3MDEyNDA2MzYxNVowRTFDMEEGA1UEAx46AEQARQBTAEsAVABPAFAALQBRADYASAA3AEIANgBMAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA23ghUI75yy8GcLhPT5yA+v2ssRfSW+chNqMI+pq5OAr7RXQJOt/+bHdoT9NoS9GQkvblc7ekoUNShIcN601ZZOPgAxkXaQS+La14Wp8GOwnM1qHNJwgla4VAIYjmCj3CUMXLsxspOFVZKRnB6/idYku4Gzhc7NjRBAwe8qWBXaECAwEAATANBgkqhkiG9w0BAQsFAAOBgQC3GjWMhEFFrn9PGYrQVGNAaibG1sR/eRX3ZWNRjYLhiqbsdeHvm4CX/W0EWd9yZlbaej87XyeUbX+mC3q3yMFVPCRn/W65WYyBPbWTWmSfU9VLKKULAH6WqjS7CqEU3cQSuKcU6nYG4AJu2/FkQJaA82tr3pvcQD28HmOlDeNh0Q==</X509Certificate></X509Data></KeyInfo></Signature></r:issuer></r:license></msrel:RelData></KeyInfo></Signature></asmv1:assembly>

View File

@@ -0,0 +1 @@
dfceb9c0bcd6af0f5cce32fa9dd10ad83abf5bbc482d468e26e2dc4a9ed71baf

View File

@@ -0,0 +1,14 @@
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\WordAddIn.dll
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\WordAddIn.pdb
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\WordAddIn.dll.manifest
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\WordAddIn.vsto
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\Microsoft.Office.Tools.Common.v4.0.Utilities.dll
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\Newtonsoft.Json.dll
D:\dsWork\aiData\WordAddIn\WordAddIn\bin\Release\Newtonsoft.Json.xml
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.csproj.AssemblyReference.cache
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.Properties.Resources.resources
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.csproj.GenerateResource.cache
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.csproj.CoreCompileInputs.cache
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.csproj.Up2Date
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.dll
D:\dsWork\aiData\WordAddIn\WordAddIn\obj\Release\WordAddIn.pdb

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,344 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Diagnostics;
using System.Reflection;
namespace WpsInstaller
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("========================================");
Console.WriteLine(" WPS Add-in Installer Tool");
Console.WriteLine("========================================");
Console.WriteLine();
string mode = "wps"; // default
if (args.Length > 0)
{
mode = args[0].ToLower();
}
try
{
// 1. Locate WordAddIn.dll and WordAddIn.vsto
string currentDir = AppDomain.CurrentDomain.BaseDirectory;
string dllPath = Path.Combine(currentDir, "WordAddIn.dll");
string vstoPath = Path.Combine(currentDir, "WordAddIn.vsto");
if (!File.Exists(dllPath))
{
// Fallback for Dev scenario
string devPathDll = Path.GetFullPath(Path.Combine(currentDir, @"..\..\..\WordAddIn\bin\Debug\WordAddIn.dll"));
if (!File.Exists(devPathDll))
{
devPathDll = Path.GetFullPath(Path.Combine(currentDir, @"..\..\..\WordAddIn\bin\Release\WordAddIn.dll"));
}
if (File.Exists(devPathDll))
{
dllPath = devPathDll;
vstoPath = Path.ChangeExtension(dllPath, ".vsto");
}
else
{
Console.WriteLine("Error: Could not find WordAddIn.dll.");
Pause();
return;
}
}
Console.WriteLine("Found DLL: " + dllPath);
if (mode == "word")
{
Console.WriteLine("Mode: Switch to MS Word (VSTO)");
// 1. Clean WPS Keys
CleanupWpsKeys();
// 1.5 Clear ClickOnce cache to avoid upgrade conflicts
Console.WriteLine("Clearing ClickOnce cache to avoid upgrade conflicts...");
ClearClickOnceCache();
// 2. Register VSTO
if (RegisterVsto(vstoPath))
{
Console.WriteLine(" [OK] VSTO registered successfully.");
}
else
{
Console.WriteLine(" [FAIL] Failed to register VSTO.");
}
}
else
{
Console.WriteLine("Mode: Switch to WPS Office");
// 1. Register Assembly (COM Registration)
Console.WriteLine("Registering Assembly for COM Interop...");
if (RegisterAssembly(dllPath))
{
Console.WriteLine(" [OK] Assembly registered successfully.");
}
else
{
Console.WriteLine(" [FAIL] Failed to register assembly.");
Pause();
return;
}
// 2. Register for WPS (Registry Keys)
Console.WriteLine("Configuring WPS Registry Keys...");
if (RegisterForWPS())
{
Console.WriteLine(" [OK] WPS Registry keys configured successfully.");
}
else
{
Console.WriteLine(" [FAIL] Failed to configure WPS registry keys.");
Pause();
return;
}
// 3. Cleanup conflicting VSTO registration
Console.WriteLine("Checking for conflicting VSTO registrations...");
CleanupVstoKeys();
}
Console.WriteLine();
Console.WriteLine("========================================");
Console.WriteLine(" Operation Completed Successfully!");
Console.WriteLine("========================================");
}
catch (Exception ex)
{
Console.WriteLine("An unexpected error occurred: " + ex.Message);
Console.WriteLine(ex.StackTrace);
}
Pause();
}
static void Pause()
{
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
static void ClearClickOnceCache()
{
try
{
string localApp = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string cachePath = System.IO.Path.Combine(localApp, "Apps", "2.0");
if (Directory.Exists(cachePath))
{
Directory.Delete(cachePath, true);
Console.WriteLine(" [OK] Cleared ClickOnce cache at " + cachePath);
}
else
{
Console.WriteLine(" ClickOnce cache not found, skipping.");
}
}
catch (Exception ex)
{
Console.WriteLine(" Warning: Failed to clear ClickOnce cache: " + ex.Message);
}
}
static bool RegisterVsto(string vstoPath)
{
if (!File.Exists(vstoPath))
{
Console.WriteLine(" Error: .vsto file not found at " + vstoPath);
return false;
}
try
{
// Register via Registry for Development/Local use
// HKCU\Software\Microsoft\Office\Word\Addins\WordAddIn
string keyPath = @"Software\Microsoft\Office\Word\Addins\WordAddIn";
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key == null) return false;
key.SetValue("Description", "WordAddIn", RegistryValueKind.String);
key.SetValue("FriendlyName", "WordAddIn", RegistryValueKind.String);
key.SetValue("LoadBehavior", 3, RegistryValueKind.DWord);
// Point to vsto file with |vstolocal to indicate local file
key.SetValue("Manifest", vstoPath + "|vstolocal", RegistryValueKind.String);
Console.WriteLine(" Created Key: HKCU\\" + keyPath);
Console.WriteLine(" Manifest: " + vstoPath + "|vstolocal");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(" Error registering VSTO: " + ex.Message);
return false;
}
}
static void CleanupWpsKeys()
{
try
{
string keyPath = @"Software\Kingsoft\Office\Word\Addins\WordAddIn.WpsAddIn";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyPath, true))
{
if (key != null)
{
Console.WriteLine(" Found WPS key. Deleting...");
Registry.CurrentUser.DeleteSubKeyTree(keyPath);
Console.WriteLine(" [OK] WPS key deleted.");
}
}
}
catch (Exception ex)
{
Console.WriteLine(" Warning: Failed to cleanup WPS keys: " + ex.Message);
}
}
static bool RegisterAssembly(string assemblyPath)
{
try
{
// Use RegistrationServices to register the assembly
// This is equivalent to regasm /codebase
// Note: This requires the assembly to be strong-named if registered in GAC,
// but for simple codebase registration, it might work if run as Admin.
// However, standard RegistrationServices doesn't easily support /codebase option programmatically
// without some caveats.
// The most robust way for a utility is often to shell out to RegAsm.exe if available,
// OR use RegistrationServices.RegisterAssembly.
// Let's try RegistrationServices first.
RegistrationServices regService = new RegistrationServices();
Assembly asm = Assembly.LoadFrom(assemblyPath);
// RegisterAssembly adds entries to HKCR\CLSID\{GUID}
// We need to set the CodeBase registry key manually if not in GAC,
// or ensure RegistrationServices does it.
// Actually, RegistrationServices.RegisterAssembly does NOT set the CodeBase by default.
// It works like RegAsm without /codebase.
// So calling RegAsm.exe via process is safer to ensure /codebase behavior.
string regAsmPath = GetRegAsmPath();
if (string.IsNullOrEmpty(regAsmPath))
{
Console.WriteLine(" Warning: Could not find RegAsm.exe. Attempting manual registration...");
return ManualRegister(asm, assemblyPath);
}
string args = string.Format("\"{0}\" /codebase /tlb", assemblyPath);
ProcessStartInfo psi = new ProcessStartInfo(regAsmPath, args);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.CreateNoWindow = true;
Console.WriteLine(string.Format(" Executing: {0} {1}", psi.FileName, psi.Arguments));
using (Process p = Process.Start(psi))
{
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);
return p.ExitCode == 0;
}
}
catch (Exception ex)
{
Console.WriteLine(" Error registering assembly: " + ex.Message);
return false;
}
}
static bool ManualRegister(Assembly asm, string assemblyPath)
{
// Fallback: This is complex to do fully manually for all COM types.
// If RegAsm is missing, we might be in trouble.
// But usually on a machine with .NET Framework (needed to run this app), RegAsm is present.
return false;
}
static string GetRegAsmPath()
{
// Usually in C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe
// or Framework64
string windir = Environment.GetEnvironmentVariable("windir");
string fxDir = Path.Combine(windir, @"Microsoft.NET\Framework\v4.0.30319");
// Check for 64 bit OS and prefer Framework64 if this process is 64 bit,
// BUT WordAddIn is likely AnyCPU.
// If Word/WPS is 32-bit, we need 32-bit RegAsm. If 64-bit, we need 64-bit RegAsm.
// Ideally register both.
// For safety, let's try to find the one matching the current process architecture,
// or just use the 32-bit one as it is most common for Office.
string regAsm = Path.Combine(fxDir, "RegAsm.exe");
if (File.Exists(regAsm)) return regAsm;
return null;
}
static bool RegisterForWPS()
{
try
{
string keyPath = @"Software\Kingsoft\Office\Word\Addins\WordAddIn.WpsAddIn";
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key == null)
{
Console.WriteLine(" Error: Could not create registry key HKCU\\" + keyPath);
return false;
}
key.SetValue("LoadBehavior", 3, RegistryValueKind.DWord);
key.SetValue("FriendlyName", "AI Word Assistant for WPS", RegistryValueKind.String);
key.SetValue("Description", "AI Assistant", RegistryValueKind.String);
key.SetValue("CommandLineSafe", 0, RegistryValueKind.DWord);
Console.WriteLine(" Created Key: HKCU\\" + keyPath);
Console.WriteLine(" Set LoadBehavior = 3");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(" Error writing WPS registry keys: " + ex.Message);
return false;
}
}
static void CleanupVstoKeys()
{
try
{
// VSTO adds keys in HKCU\Software\Microsoft\Office\Word\Addins\WordAddIn
string vstoKeyPath = @"Software\Microsoft\Office\Word\Addins\WordAddIn";
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(vstoKeyPath, true))
{
if (key != null)
{
Console.WriteLine(" Found VSTO key in HKCU\\" + vstoKeyPath + ". Deleting to avoid conflicts...");
Registry.CurrentUser.DeleteSubKeyTree(vstoKeyPath);
Console.WriteLine(" [OK] VSTO key deleted.");
}
}
}
catch (Exception ex)
{
Console.WriteLine(" Warning: Failed to cleanup VSTO keys: " + ex.Message);
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WpsInstaller")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WpsInstaller")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a1b2c3d4-e5f6-4789-0011-223344556677")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A1B2C3D4-E5F6-4789-0011-223344556677}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>WpsInstaller</RootNamespace>
<AssemblyName>WpsInstaller</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.manifest" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="WpsInstaller.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]

View File

@@ -0,0 +1,5 @@
D:\dsWork\aiData\WordAddIn\WpsInstaller\bin\Debug\WpsInstaller.exe
D:\dsWork\aiData\WordAddIn\WpsInstaller\bin\Debug\WpsInstaller.pdb
D:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Debug\WpsInstaller.exe
D:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Debug\WpsInstaller.pdb
d:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Debug\WpsInstaller.csprojResolveAssemblyReference.cache

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]

View File

@@ -0,0 +1 @@
979fc59c1f2afcc3443738cd70ff09bf60f7ecb8fbff0eec3eb8b752a83b3e04

View File

@@ -0,0 +1,6 @@
D:\dsWork\aiData\WordAddIn\WpsInstaller\bin\Release\WpsInstaller.exe
D:\dsWork\aiData\WordAddIn\WpsInstaller\bin\Release\WpsInstaller.pdb
D:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Release\WpsInstaller.csproj.AssemblyReference.cache
D:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Release\WpsInstaller.csproj.CoreCompileInputs.cache
D:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Release\WpsInstaller.exe
D:\dsWork\aiData\WordAddIn\WpsInstaller\obj\Release\WpsInstaller.pdb

Binary file not shown.

Binary file not shown.