Deroino commited on
Commit ·
46f6718
1
Parent(s): 46de6a9
feat(wechat-channel): add draft upload functionality for video channel
Browse files- Add isDraft parameter to TencentVideo class to control publishing behavior
- Modify click_publish method to click "保存草稿" instead of "发表" when isDraft is true
- Update post_video_tencent function to accept and pass is_draft parameter
- Add is_draft parameter to /postVideo API endpoint to receive frontend data
- Add "视频号上传草稿" checkbox in frontend that appears only for video channel platform
- Update defaultTabInit to include isDraft property with default value false
- Include isDraft in publishData sent to backend API
- Implement proper URL checking for draft vs publish success confirmation
The new functionality allows users to save WeChat Channel videos as drafts
instead of immediately publishing them when the checkbox is selected.
- myUtils/postVideo.py +2 -2
- sau_backend.py +2 -1
- sau_frontend/src/views/PublishCenter.vue +13 -2
- uploader/tencent_uploader/main.py +29 -12
myUtils/postVideo.py
CHANGED
|
@@ -10,7 +10,7 @@ from utils.constant import TencentZoneTypes
|
|
| 10 |
from utils.files_times import generate_schedule_time_next_day
|
| 11 |
|
| 12 |
|
| 13 |
-
def post_video_tencent(title,files,tags,account_file,category=TencentZoneTypes.LIFESTYLE.value,enableTimer=False,videos_per_day = 1, daily_times=None,start_days = 0):
|
| 14 |
# 生成文件的完整路径
|
| 15 |
account_file = [Path(BASE_DIR / "cookiesFile" / file) for file in account_file]
|
| 16 |
files = [Path(BASE_DIR / "videoFile" / file) for file in files]
|
|
@@ -25,7 +25,7 @@ def post_video_tencent(title,files,tags,account_file,category=TencentZoneTypes.L
|
|
| 25 |
print(f"视频文件名:{file}")
|
| 26 |
print(f"标题:{title}")
|
| 27 |
print(f"Hashtag:{tags}")
|
| 28 |
-
app = TencentVideo(title, str(file), tags, publish_datetimes[index], cookie, category)
|
| 29 |
asyncio.run(app.main(), debug=False)
|
| 30 |
|
| 31 |
|
|
|
|
| 10 |
from utils.files_times import generate_schedule_time_next_day
|
| 11 |
|
| 12 |
|
| 13 |
+
def post_video_tencent(title,files,tags,account_file,category=TencentZoneTypes.LIFESTYLE.value,enableTimer=False,videos_per_day = 1, daily_times=None,start_days = 0, is_draft=False):
|
| 14 |
# 生成文件的完整路径
|
| 15 |
account_file = [Path(BASE_DIR / "cookiesFile" / file) for file in account_file]
|
| 16 |
files = [Path(BASE_DIR / "videoFile" / file) for file in files]
|
|
|
|
| 25 |
print(f"视频文件名:{file}")
|
| 26 |
print(f"标题:{title}")
|
| 27 |
print(f"Hashtag:{tags}")
|
| 28 |
+
app = TencentVideo(title, str(file), tags, publish_datetimes[index], cookie, category, is_draft)
|
| 29 |
asyncio.run(app.main(), debug=False)
|
| 30 |
|
| 31 |
|
sau_backend.py
CHANGED
|
@@ -363,6 +363,7 @@ def postVideo():
|
|
| 363 |
productLink = data.get('productLink', '')
|
| 364 |
productTitle = data.get('productTitle', '')
|
| 365 |
thumbnail_path = data.get('thumbnail', '')
|
|
|
|
| 366 |
|
| 367 |
videos_per_day = data.get('videosPerDay')
|
| 368 |
daily_times = data.get('dailyTimes')
|
|
@@ -376,7 +377,7 @@ def postVideo():
|
|
| 376 |
start_days)
|
| 377 |
case 2:
|
| 378 |
post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
|
| 379 |
-
start_days)
|
| 380 |
case 3:
|
| 381 |
post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
|
| 382 |
start_days, thumbnail_path, productLink, productTitle)
|
|
|
|
| 363 |
productLink = data.get('productLink', '')
|
| 364 |
productTitle = data.get('productTitle', '')
|
| 365 |
thumbnail_path = data.get('thumbnail', '')
|
| 366 |
+
is_draft = data.get('isDraft', False) # 新增参数:是否保存为草稿
|
| 367 |
|
| 368 |
videos_per_day = data.get('videosPerDay')
|
| 369 |
daily_times = data.get('dailyTimes')
|
|
|
|
| 377 |
start_days)
|
| 378 |
case 2:
|
| 379 |
post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
|
| 380 |
+
start_days, is_draft)
|
| 381 |
case 3:
|
| 382 |
post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
|
| 383 |
start_days, thumbnail_path, productLink, productTitle)
|
sau_frontend/src/views/PublishCenter.vue
CHANGED
|
@@ -402,6 +402,15 @@
|
|
| 402 |
/>
|
| 403 |
</div>
|
| 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
<!-- 定时发布 -->
|
| 406 |
<div class="schedule-section">
|
| 407 |
<h3>定时发布</h3>
|
|
@@ -534,7 +543,8 @@ const defaultTabInit = {
|
|
| 534 |
dailyTimes: ['10:00'], // 每天发布时间点列表
|
| 535 |
startDays: 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
|
| 536 |
publishStatus: null, // 发布状态,包含message和type
|
| 537 |
-
publishing: false // 发布状态,用于控制按钮loading效果
|
|
|
|
| 538 |
}
|
| 539 |
|
| 540 |
// helper to create a fresh deep-copied tab from defaultTabInit
|
|
@@ -791,7 +801,8 @@ const confirmPublish = async (tab) => {
|
|
| 791 |
startDays: tab.scheduleEnabled ? tab.startDays || 0 : 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
|
| 792 |
category: 0, //表示非原创
|
| 793 |
productLink: tab.productLink.trim() || '', // 商品链接
|
| 794 |
-
productTitle: tab.productTitle.trim() || '' // 商品名称
|
|
|
|
| 795 |
}
|
| 796 |
|
| 797 |
// 调用后端发布API
|
|
|
|
| 402 |
/>
|
| 403 |
</div>
|
| 404 |
|
| 405 |
+
<!-- 草稿选项 (仅在视频号可见) -->
|
| 406 |
+
<div v-if="tab.selectedPlatform === 2" class="draft-section">
|
| 407 |
+
<el-checkbox
|
| 408 |
+
v-model="tab.isDraft"
|
| 409 |
+
label="视频号上传草稿"
|
| 410 |
+
class="draft-checkbox"
|
| 411 |
+
/>
|
| 412 |
+
</div>
|
| 413 |
+
|
| 414 |
<!-- 定时发布 -->
|
| 415 |
<div class="schedule-section">
|
| 416 |
<h3>定时发布</h3>
|
|
|
|
| 543 |
dailyTimes: ['10:00'], // 每天发布时间点列表
|
| 544 |
startDays: 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
|
| 545 |
publishStatus: null, // 发布状态,包含message和type
|
| 546 |
+
publishing: false, // 发布状态,用于控制按钮loading效果
|
| 547 |
+
isDraft: false // 是否保存为草稿,仅视频号平台可见
|
| 548 |
}
|
| 549 |
|
| 550 |
// helper to create a fresh deep-copied tab from defaultTabInit
|
|
|
|
| 801 |
startDays: tab.scheduleEnabled ? tab.startDays || 0 : 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
|
| 802 |
category: 0, //表示非原创
|
| 803 |
productLink: tab.productLink.trim() || '', // 商品链接
|
| 804 |
+
productTitle: tab.productTitle.trim() || '', // 商品名称
|
| 805 |
+
isDraft: tab.isDraft // 是否保存为草稿,仅视频号平台使用
|
| 806 |
}
|
| 807 |
|
| 808 |
// 调用后端发布API
|
uploader/tencent_uploader/main.py
CHANGED
|
@@ -82,13 +82,14 @@ async def weixin_setup(account_file, handle=False):
|
|
| 82 |
|
| 83 |
|
| 84 |
class TencentVideo(object):
|
| 85 |
-
def __init__(self, title, file_path, tags, publish_date: datetime, account_file, category=None):
|
| 86 |
self.title = title # 视频标题
|
| 87 |
self.file_path = file_path
|
| 88 |
self.tags = tags
|
| 89 |
self.publish_date = publish_date
|
| 90 |
self.account_file = account_file
|
| 91 |
self.category = category
|
|
|
|
| 92 |
self.local_executable_path = LOCAL_CHROME_PATH or None
|
| 93 |
|
| 94 |
async def set_schedule_time_tencent(self, page, publish_date):
|
|
@@ -185,21 +186,37 @@ class TencentVideo(object):
|
|
| 185 |
async def click_publish(self, page):
|
| 186 |
while True:
|
| 187 |
try:
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
break
|
| 194 |
except Exception as e:
|
| 195 |
current_url = page.url
|
| 196 |
-
if
|
| 197 |
-
|
| 198 |
-
|
|
|
|
|
|
|
| 199 |
else:
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
async def detect_upload_status(self, page):
|
| 205 |
while True:
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
class TencentVideo(object):
|
| 85 |
+
def __init__(self, title, file_path, tags, publish_date: datetime, account_file, category=None, is_draft=False):
|
| 86 |
self.title = title # 视频标题
|
| 87 |
self.file_path = file_path
|
| 88 |
self.tags = tags
|
| 89 |
self.publish_date = publish_date
|
| 90 |
self.account_file = account_file
|
| 91 |
self.category = category
|
| 92 |
+
self.is_draft = is_draft # 是否保存为草稿
|
| 93 |
self.local_executable_path = LOCAL_CHROME_PATH or None
|
| 94 |
|
| 95 |
async def set_schedule_time_tencent(self, page, publish_date):
|
|
|
|
| 186 |
async def click_publish(self, page):
|
| 187 |
while True:
|
| 188 |
try:
|
| 189 |
+
if self.is_draft:
|
| 190 |
+
# 点击"保存草稿"按钮
|
| 191 |
+
draft_button = page.locator('div.form-btns button:has-text("保存草稿")')
|
| 192 |
+
if await draft_button.count():
|
| 193 |
+
await draft_button.click()
|
| 194 |
+
# 等待跳转到草稿箱页面或确认保存成功
|
| 195 |
+
await page.wait_for_url("**/post/list**", timeout=5000) # 使用通配符匹配包含post/list的URL
|
| 196 |
+
tencent_logger.success(" [-]视频草稿保存成功")
|
| 197 |
+
else:
|
| 198 |
+
# 点击"发表"按钮
|
| 199 |
+
publish_button = page.locator('div.form-btns button:has-text("发表")')
|
| 200 |
+
if await publish_button.count():
|
| 201 |
+
await publish_button.click()
|
| 202 |
+
await page.wait_for_url("https://channels.weixin.qq.com/platform/post/list", timeout=5000)
|
| 203 |
+
tencent_logger.success(" [-]视频发布成功")
|
| 204 |
break
|
| 205 |
except Exception as e:
|
| 206 |
current_url = page.url
|
| 207 |
+
if self.is_draft:
|
| 208 |
+
# 检查是否在草稿相关的页面
|
| 209 |
+
if "post/list" in current_url or "draft" in current_url:
|
| 210 |
+
tencent_logger.success(" [-]视频草稿保存成功")
|
| 211 |
+
break
|
| 212 |
else:
|
| 213 |
+
# 检查是否在发布列表页面
|
| 214 |
+
if "https://channels.weixin.qq.com/platform/post/list" in current_url:
|
| 215 |
+
tencent_logger.success(" [-]视频发布成功")
|
| 216 |
+
break
|
| 217 |
+
tencent_logger.exception(f" [-] Exception: {e}")
|
| 218 |
+
tencent_logger.info(" [-] 视频正在发布中...")
|
| 219 |
+
await asyncio.sleep(0.5)
|
| 220 |
|
| 221 |
async def detect_upload_status(self, page):
|
| 222 |
while True:
|