| 33 | } |
| 34 | |
| 35 | async function uploadToSlack(buffer, filename, message) { |
| 36 | // Step 1: get upload URL |
| 37 | let urlRes = await fetch('https://slack.com/api/files.getUploadURLExternal', { |
| 38 | method: 'POST', |
| 39 | headers: { |
| 40 | Authorization: `Bearer ${SLACK_TESTING_BOT_TOKEN}`, |
| 41 | 'Content-Type': 'application/x-www-form-urlencoded' |
| 42 | }, |
| 43 | body: new URLSearchParams({filename, length: buffer.byteLength}), |
| 44 | signal: AbortSignal.timeout(30000) |
| 45 | }); |
| 46 | let urlData = await urlRes.json(); |
| 47 | if (!urlData.ok) { |
| 48 | throw new Error(`Failed to initialize Slack file upload: ${urlData.error}`); |
| 49 | } |
| 50 | |
| 51 | let parsedUploadUrl = new URL(urlData.upload_url); |
| 52 | if (parsedUploadUrl.protocol !== 'https:' || parsedUploadUrl.hostname !== 'files.slack.com') { |
| 53 | throw new Error('Unexpected upload URL'); |
| 54 | } |
| 55 | // Step 2: upload file bytes |
| 56 | let uploadRes = await fetch(urlData.upload_url, { |
| 57 | method: 'POST', |
| 58 | headers: {'Content-Type': 'application/octet-stream'}, |
| 59 | body: buffer, |
| 60 | signal: AbortSignal.timeout(30000) |
| 61 | }); |
| 62 | if (!uploadRes.ok) { |
| 63 | throw new Error(`File upload failed: ${uploadRes.status} ${uploadRes.statusText}`); |
| 64 | } |
| 65 | |
| 66 | // Step 3: complete upload and share to channel |
| 67 | let completeRes = await fetch('https://slack.com/api/files.completeUploadExternal', { |
| 68 | method: 'POST', |
| 69 | headers: { |
| 70 | Authorization: `Bearer ${SLACK_TESTING_BOT_TOKEN}`, |
| 71 | 'Content-Type': 'application/json' |
| 72 | }, |
| 73 | body: JSON.stringify({ |
| 74 | files: [{id: urlData.file_id}], |
| 75 | channel_id: SLACK_CHANNEL_ID, |
| 76 | initial_comment: message |
| 77 | }), |
| 78 | signal: AbortSignal.timeout(30000) |
| 79 | }); |
| 80 | let completeData = await completeRes.json(); |
| 81 | if (!completeData.ok) { |
| 82 | throw new Error(`Failed to complete Slack file upload: ${completeData.error}`); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | async function main() { |
| 87 | let args = parseArgs({allowPositionals: true}); |