@@ -9,6 +9,8 @@ module.exports = {
99 try {
1010 if ( data . channel === "xiaohongshu" ) {
1111 await publishXiaohongshu ( data ) ;
12+ } else if ( data . channel === "bilibili" ) {
13+ await publishBilibili ( data ) ;
1214 }
1315 } catch ( error ) {
1416 console . error ( "Error in publish post:" , error ) ;
@@ -35,12 +37,7 @@ let browser;
3537 */
3638async function publishXiaohongshu ( data ) {
3739 if ( ! browser ) {
38- browser = await chromium . launchPersistentContext (
39- path . join ( userDataDir , "browser_data" ) ,
40- {
41- headless : false ,
42- }
43- ) ;
40+ browser = await launchBrowser ( ) ;
4441 }
4542 const page = await browser . newPage ( ) ;
4643 try {
@@ -76,37 +73,157 @@ async function publishXiaohongshu(data) {
7673 // Wait for upload progress to appear
7774 await page . waitForSelector ( ".uploading" , { timeout : 10000 } ) ;
7875
79- // Wait for upload to complete (100%)
80- while ( true ) {
81- const progressText = await page . $eval (
82- ".uploading .stage" ,
83- ( el ) => el . textContent
84- ) ;
85- // Match the text that contains "上传中" followed by a percentage
86- const progressMatch = progressText ?. match ( / 上 传 中 \s * ( \d + ) % / ) ;
87-
88- if ( ! progressMatch ) {
89- throw new Error ( "Could not find upload progress percentage" ) ;
90- }
91-
92- const progress = parseInt ( progressMatch [ 1 ] ) ;
93- console . log ( `⏳Upload progress: ${ progress } %` ) ;
76+ // Wait a bit more to ensure the upload is fully processed
77+ await page . waitForTimeout ( 1000 ) ;
9478
95- if ( progress === 100 ) {
96- console . log ( "Upload completed!" ) ;
97- break ;
98- }
79+ const [ content , uploadComplete ] = await Promise . all ( [
80+ fillXiaohongshuContent ( page , data . title , data . content ) ,
81+ waitForXiaohongshuUploadComplete ( page ) ,
82+ ] ) ;
9983
100- // Wait a bit before checking again
101- await page . waitForTimeout ( 1000 ) ;
102- }
84+ console . log ( "🦄🦄uploadComplete:" , uploadComplete ) ;
10385
104- // Wait a bit more to ensure the upload is fully processed
105- await page . waitForTimeout ( 5000 ) ;
86+ // Wait a bit to ensure content is properly set
87+ await page . waitForTimeout ( 1000 ) ;
10688 } catch ( error ) {
10789 console . error ( "Error during video upload:" , error ) ;
10890 throw error ;
10991 } finally {
11092 // await page.close();
11193 }
11294}
95+
96+ async function fillXiaohongshuContent ( page , title , content ) {
97+ // fill in title
98+ await page . waitForSelector (
99+ 'input.d-text[placeholder="填写标题会有更多赞哦~"]' ,
100+ { timeout : 10000 } // Increase timeout if necessary
101+ ) ;
102+
103+ // Focus on the input field
104+ await page . focus ( 'input.d-text[placeholder="填写标题会有更多赞哦~"]' ) ;
105+ await page . fill (
106+ 'input.d-text[placeholder="填写标题会有更多赞哦~"]' ,
107+ title || ""
108+ ) ;
109+ // fill in title by typing
110+ // await page.keyboard.type(title || "", { delay: 100 });
111+
112+ // Fill in the content by clipboard copying pasting
113+ await page . evaluate ( async ( text ) => {
114+ await navigator . clipboard . writeText ( text ) ;
115+ } , content || "" ) ;
116+ await page . waitForTimeout ( 1000 ) ;
117+ await page . waitForSelector ( ".ql-editor" ) ;
118+ await page . focus ( ".ql-editor" ) ;
119+ // await page.keyboard.type(content || "", { delay: 100 });
120+ console . log ( "platform:" , process . platform ) ;
121+ await page . keyboard . press (
122+ process . platform === "darwin" ? "Meta+V" : "Control+V"
123+ ) ;
124+ await page . waitForTimeout ( 2000 ) ;
125+ // add hashtags
126+ const tags = getTagsFromContent ( content || "" ) ;
127+ console . log ( "🦄🦄tags:" , tags ) ;
128+ for ( const tag of tags ) {
129+ await page . keyboard . type ( "#" ) ;
130+ await page . waitForTimeout ( 100 ) ;
131+ await page . keyboard . type ( tag , { delay : 300 } ) ;
132+ await page . waitForTimeout ( 1000 ) ;
133+ await page . keyboard . press ( "Enter" ) ;
134+ }
135+
136+ await page . waitForTimeout ( 1000 ) ;
137+
138+ return true ;
139+ }
140+
141+ async function waitForXiaohongshuUploadComplete ( page ) {
142+ // Wait for upload to complete (100%)
143+ while ( true ) {
144+ const progressText = await page . evaluate ( ( ) => {
145+ return document . querySelector ( ".stage" ) ?. textContent || "" ;
146+ } ) ;
147+
148+ // Check if the text contains "上传成功" (Upload Successful)
149+ if ( progressText . includes ( "上传成功" ) ) {
150+ console . log ( "Upload completed!" ) ;
151+ return true ;
152+ }
153+
154+ // Match the text that contains "上传中" followed by a percentage
155+ const progressMatch = progressText . match ( / 上 传 中 \s * ( \d + ) % / ) ;
156+
157+ if ( ! progressMatch ) {
158+ throw new Error ( "Could not find upload progress percentage" ) ;
159+ }
160+
161+ const progress = parseInt ( progressMatch [ 1 ] ) ;
162+ console . log ( `⏳Upload progress: ${ progress } %` ) ;
163+
164+ if ( progress === 99 ) {
165+ console . log ( "Upload completed!" ) ;
166+ break ;
167+ }
168+
169+ // Wait a bit before checking again
170+ await page . waitForTimeout ( 3000 ) ;
171+ }
172+ return false ;
173+ }
174+
175+ async function launchBrowser ( ) {
176+ return await chromium . launchPersistentContext (
177+ path . join ( userDataDir , "browser_data" ) ,
178+ {
179+ headless : false ,
180+ channel : "chrome" , // Use real Chrome
181+ }
182+ ) ;
183+ }
184+
185+ /**
186+ * @param {PublishData } data - The data for publishing the post
187+ */
188+
189+ async function publishBilibili ( data ) {
190+ if ( ! browser ) {
191+ browser = await launchBrowser ( ) ;
192+ }
193+ const page = await browser . newPage ( ) ;
194+ try {
195+ await page . goto ( "https://member.bilibili.com/platform/upload/video/frame" ) ;
196+ await page . waitForTimeout ( 3000 ) ; // Let Vue UI settle
197+
198+ // Ensure the "上传视频" button is visible and clickable
199+ const uploadButton = await page . waitForSelector ( ".bcc-upload-wrapper" , {
200+ timeout : 10000 ,
201+ state : "visible" ,
202+ } ) ;
203+
204+ // Listen for the file chooser BEFORE clicking
205+ const [ fileChooser ] = await Promise . all ( [
206+ page . waitForEvent ( "filechooser" ) ,
207+ uploadButton . click ( ) , // This triggers file picker
208+ ] ) ;
209+
210+ // Use the filechooser to set your file
211+ await fileChooser . setFiles ( data . video ) ;
212+
213+ console . log ( "File selected via file chooser" ) ;
214+ } catch ( err ) {
215+ console . error ( "Upload error:" , err ) ;
216+ throw err ;
217+ }
218+ }
219+
220+ /**
221+ * @param {string } content - The content of the post
222+ * @returns {string[] } - The tags of the post
223+ */
224+ function getTagsFromContent ( content ) {
225+ const tags = content . match ( / # ( \w + ) / g) ;
226+ const ret = tags ? tags . map ( ( tag ) => tag . slice ( 1 ) ) : [ ] ;
227+ console . log ( "🦄🦄ret:" , ret ) ;
228+ return ret ;
229+ }
0 commit comments