wangzhibo
2025-07-30 25087cbe79c8c4992551477d55d9db8bbea2202e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# -*- coding: utf-8 -*-
from datetime import datetime
 
from playwright.async_api import Page
import asyncio
from auto_media_publisher.uploaders.uploader_base import UploaderBase
 
HOMEPAGE_URL="https://www.tiktok.com"
CONTENT_UPLOAD_URL="https://www.tiktok.com/tiktokstudio/upload?from=webapp"
 
VIDEO_PUBLISH_SECCESSFULL_URL = "https://www.tiktok.com/tiktokstudio/content"
                                 
Tk_Locator_tk_iframe = '[data-tt="Upload_index_iframe"]'
Tk_Locator_default = 'body'
 
class UploaderTiktok(UploaderBase):
   
    async def set_schedule_time(self, page):
        schedule_input_element = self.locator_base.get_by_label('Schedule')
        await schedule_input_element.wait_for(state='visible')  # 确保按钮可见
 
        await schedule_input_element.click(force=True)
        if await self.locator_base.locator('div.TUXButton-content >> text=Allow').count():
            await self.locator_base.locator('div.TUXButton-content >> text=Allow').click()
 
        scheduled_picker = self.locator_base.locator('div.scheduled-picker')
        await scheduled_picker.locator('div.TUXInputBox').nth(1).click()
 
        calendar_month = await self.locator_base.locator(
            'div.calendar-wrapper span.month-title').inner_text()
 
        n_calendar_month = datetime.strptime(calendar_month, '%B').month
 
        schedule_month = self.scheduled_time.month
 
        if n_calendar_month != schedule_month:
            if n_calendar_month < schedule_month:
                arrow = self.locator_base.locator('div.calendar-wrapper span.arrow').nth(-1)
            else:
                arrow = self.locator_base.locator('div.calendar-wrapper span.arrow').nth(0)
            await arrow.click()
 
        # day set
        valid_days_locator = self.locator_base.locator(
            'div.calendar-wrapper span.day.valid')
        valid_days = await valid_days_locator.count()
        for i in range(valid_days):
            day_element = valid_days_locator.nth(i)
            text = await day_element.inner_text()
            if text.strip() == str(self.scheduled_time.day):
                await day_element.click()
                break
        # time set
        await scheduled_picker.locator('div.TUXInputBox').nth(0).click()
 
        hour_str = self.scheduled_time.strftime("%H")
        correct_minute = int(self.scheduled_time.minute / 5)
        minute_str = f"{correct_minute:02d}"
 
        hour_selector = f"span.tiktok-timepicker-left:has-text('{hour_str}')"
        minute_selector = f"span.tiktok-timepicker-right:has-text('{minute_str}')"
 
        # pick hour first
        await page.wait_for_timeout(1000)  # 等待500毫秒
        await self.locator_base.locator(hour_selector).click()
        # click time button again
        await page.wait_for_timeout(1000)  # 等待500毫秒
        # pick minutes after
        await self.locator_base.locator(minute_selector).click()
 
        # click title to remove the focus.
        # await self.locator_base.locator("h1:has-text('Upload video')").click()
 
    async def handle_upload_error(self, page):
        self.logger.info("video upload error retrying.")
        select_file_button = self.locator_base.locator('button[aria-label="Select file"]')
        async with page.expect_file_chooser() as fc_info:
            await select_file_button.click()
        file_chooser = await fc_info.value
        await file_chooser.set_files(self.path_media)
 
    async def add_title_tags(self, page):
 
        editor_locator = self.locator_base.locator('div.public-DraftEditor-content')
        await editor_locator.click()
        await page.keyboard.press("End")
        await page.keyboard.press("Control+A")
        await page.keyboard.press("Delete")
        await page.keyboard.press("End")
        await page.wait_for_timeout(1000)  # 等待1秒
        await page.keyboard.insert_text(self.title)
        await page.wait_for_timeout(1000)  # 等待1秒
        await page.keyboard.press("End")
        await page.keyboard.press("Enter")
 
        # tag part
        for index, tag in enumerate(self.tags, start=1):
            self.logger.info("Setting the %s tag" % index)
            await page.keyboard.press("End")
            await page.wait_for_timeout(1000)  # 等待1秒
            await page.keyboard.insert_text("#" + tag + " ")
            await page.keyboard.press("Space")
            await page.wait_for_timeout(1000)  # 等待1秒
 
            await page.keyboard.press("Backspace")
            await page.keyboard.press("End")
 
    async def upload_thumbnails(self, page):
        await self.locator_base.locator(".cover-container").click()
        await self.locator_base.locator(".cover-edit-container >> text=Upload cover").click()
        async with page.expect_file_chooser() as fc_info:
            await self.locator_base.locator(".upload-image-upload-area").click()
            file_chooser = await fc_info.value
            await file_chooser.set_files(self.path_cover)
        await self.locator_base.locator('div.cover-edit-panel:not(.hide-panel)').get_by_role(
            "button", name="Confirm").click()
        await page.wait_for_timeout(3000)  # wait 3s, fix it later
 
    async def change_language(self, page):
        # set the language to english
        await page.goto(HOMEPAGE_URL)
        await page.wait_for_load_state('domcontentloaded')
        await page.wait_for_selector('[data-e2e="nav-more-menu"]')
        # 已经设置为英文, 省略这个步骤
        if await page.locator('[data-e2e="nav-more-menu"]').text_content() == "More":
            return
 
        await page.locator('[data-e2e="nav-more-menu"]').click()
        await page.locator('[data-e2e="language-select"]').click()
        await page.locator('#creator-tools-selection-menu-header >> text=English').nth(1).click()
 
    async def click_publish(self, page):
        success_flag_div = 'div.common-modal-confirm-modal'
        while True:
            try:
                publish_button = self.locator_base.locator('div.button-group button').nth(0)
                if await publish_button.count():
                    await publish_button.click()
 
                await page.wait_for_url(VIDEO_PUBLISH_SECCESSFULL_URL,  timeout=3000)
                self.logger.info("  [-] video published success")
                break
            except Exception as e:
                self.logger.exception(f"  [-] Exception: {e}")
                self.logger.info("  [-] video publishing")
                await asyncio.sleep(0.5)
 
    async def detect_upload_status(self, page):
        while True:
            try:
                # if await self.locator_base.locator('div.btn-post > button').get_attribute("disabled") is None:
                if await self.locator_base.locator(
                        'div.button-group > button >> text=Post').get_attribute("disabled") is None:
                    self.logger.info("  [-]video uploaded.")
                    break
                else:
                    self.logger.info("  [-] video uploading...")
                    await asyncio.sleep(2)
                    if await self.locator_base.locator(
                            'button[aria-label="Select file"]').count():
                        self.logger.info("  [-] found some error while uploading now retry...")
                        await self.handle_upload_error(page)
            except:
                self.logger.info("  [-] video uploading...")
                await asyncio.sleep(2)
 
    async def choose_base_locator(self, page):
        # await page.wait_for_selector('div.upload-container')
        if await page.locator('iframe[data-tt="Upload_index_iframe"]').count():
            self.locator_base = page.frame_locator(Tk_Locator_tk_iframe)
        else:
            self.locator_base = page.locator(Tk_Locator_default) 
 
    async def _upload_core(self) -> bool:
        try:
 
            page = await self.context.new_page()
            await self.change_language(page)
            await page.goto(CONTENT_UPLOAD_URL, timeout=120000)
            self.logger.info(f'[+]Uploading-------{self.title}.mp4')
 
            await page.wait_for_url(CONTENT_UPLOAD_URL, timeout=120000)
 
            try:
                await page.wait_for_selector('iframe[data-tt="Upload_index_iframe"], div.upload-container', timeout=10000)
                self.logger.info("Either iframe or div appeared.")
            except Exception as e:
                self.logger.error("Neither iframe nor div appeared within the timeout.")
 
            await self.choose_base_locator(page)
 
            upload_button = self.locator_base.locator(
                'button:has-text("Select video"):visible')
            await upload_button.wait_for(state='visible')  # 确保按钮可见
 
            async with page.expect_file_chooser() as fc_info:
                await upload_button.click()
            file_chooser = await fc_info.value
            await file_chooser.set_files(self.path_media)
 
            await self.add_title_tags(page)
            # detect upload status
            await self.detect_upload_status(page)
            if self.path_cover:
                self.logger.info(f'[+] Uploading thumbnail file {self.title}.png')
                await self.upload_thumbnails(page)
 
            if self.scheduled_time != 0:
                await self.set_schedule_time(page)
 
            await self.click_publish(page)
 
            await self.context.storage_state(path=f"{self.cookie_path}")  # save cookie
            self.logger.info('tiktok  [-] update cookie!')
            await asyncio.sleep(2)  # close delay for look the video status
          
            return True
        except Exception as e:
            self.logger.error(f"{self.user} upload video {self.path_media} fails, error is :{e}")
            return False