Last active
August 22, 2023 12:15
-
-
Save spraddles/21980a7c97fda3a0ce1d1c100384def1 to your computer and use it in GitHub Desktop.
Linkedin API to create a video post
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import axios from 'axios' | |
import fs, { readFileSync } from 'fs' | |
import { Blob } from 'buffer' | |
import { exec } from 'child_process' | |
import path from 'path' | |
import stream from 'stream' | |
import { promisify } from 'util' | |
import dotenv from 'dotenv' | |
dotenv.config() | |
export const selfPostLinkedin = async (description, tags, video) => { | |
const LINKEDIN_ACCESS_TOKEN = process.env.LINKEDIN_ACCESS_TOKEN | |
const LINKEDIN_PERS_ID = process.env.LINKEDIN_PERS_ID | |
const LINKEDIN_API_VERSION = '202211' | |
const tmpDir = '_tmpLinkedInFiles' | |
var fileName = '' | |
var relFilePath = '' | |
var absFilePath = '' | |
var requestData = {} | |
var etags = [] | |
var videoString = '' | |
// create tmp dir | |
const makeTmpDir = async () => { | |
if(fs.existsSync(tmpDir)) { fs.rmSync(tmpDir, { recursive: true, force: true }) } | |
await fs.existsSync(tmpDir) || await fs.mkdirSync(tmpDir) | |
} | |
// create file | |
const createFile = () => { | |
const name = (Math.random()*1e32).toString(36) + '.mp4' | |
return fileName = name | |
} | |
// download file | |
const downloadFile = async () => { | |
const path = './' + tmpDir + '/' + fileName | |
const response = await axios({ | |
method: 'GET', | |
url: video, | |
responseType: 'stream' | |
}) | |
const finishedDownload = promisify(stream.finished) | |
const writer = fs.createWriteStream(path) | |
response.data.pipe(writer) | |
await finishedDownload(writer) | |
return relFilePath = path | |
} | |
// get filesize | |
const getFileSize = async () => { | |
absFilePath = await path.resolve(relFilePath) | |
const buffer = fs.readFileSync(absFilePath) | |
const blob = new Blob([buffer], {type: 'video/mp4'}) | |
const arrayBuffer = await blob.arrayBuffer() | |
const size = arrayBuffer.byteLength | |
return Number(size) | |
} | |
// initialize | |
const initializeUpload = async (fileSize) => { | |
try { | |
const response = await axios({ | |
method: 'post', | |
url: 'https://api.linkedin.com/rest/videos?action=initializeUpload', | |
data: { | |
initializeUploadRequest: { | |
'owner': `urn:li:person:${LINKEDIN_PERS_ID}`, | |
'fileSizeBytes': fileSize, | |
'uploadCaptions': false, | |
'uploadThumbnail': false | |
} | |
}, | |
headers: { | |
'Authorization': `Bearer ${LINKEDIN_ACCESS_TOKEN}`, | |
'Content-Type': 'application/json', | |
'LinkedIn-Version': LINKEDIN_API_VERSION, | |
'X-Restli-Protocol-Version': '2.0.0' | |
} | |
}) | |
return requestData = response.data.value | |
} | |
catch(e) { | |
console.log('initializeUpload error!') | |
console.log(e) | |
} | |
} | |
// break up file as per 'uploadInstructions' | |
const splitVideo = () => { | |
const promise = new Promise(function (resolve, reject) { | |
exec(`cd ${tmpDir} && split -b 4194303 ./${fileName} part-`, (err, stdout, stderr) => { | |
if (err) { | |
console.error(err) | |
reject('splitVideo: promise rejected') | |
} else { | |
// console.log(`stdout: ${stdout}`) | |
// console.log(`stderr: ${stderr}`) | |
return resolve() | |
} | |
}) | |
}) | |
return promise | |
} | |
// getter | |
const getFilesArray = async () => { | |
var filesArray = [] | |
const tmpPartPath = './' + tmpDir | |
const regex = /part-/g | |
var files = fs.readdirSync(tmpPartPath).filter((elm) => elm.match(regex)) | |
files.forEach((file, index) => { | |
filesArray.push(tmpPartPath + '/' + file) | |
}) | |
return filesArray | |
} | |
// upload parts | |
const uploadParts = async (files) => { | |
var axiosRequests = [] | |
const parts = requestData.uploadInstructions | |
parts.forEach((part, index) => { | |
const readFile = fs.readFileSync(files[index]) | |
axiosRequests.push( | |
axios({ | |
method: 'put', | |
url: parts[index].uploadUrl, | |
data: readFile, | |
headers: { | |
'Authorization': `Bearer ${LINKEDIN_ACCESS_TOKEN}`, | |
'Content-Type': 'application/octet-stream', | |
'LinkedIn-Version': LINKEDIN_API_VERSION, | |
'X-Restli-Protocol-Version': '2.0.0' | |
} | |
}) | |
) | |
}) | |
return axios.all(axiosRequests).then( | |
axios.spread((...responses) => { | |
var axiosReponses = [] | |
responses.forEach((response) => { | |
if (response.status === 200) { | |
axiosReponses.push(response) | |
} | |
else { | |
console.log(response) | |
} | |
}) | |
return axiosReponses | |
}) | |
) | |
} | |
// getter | |
const getEtags = (responses) => { | |
var array = [] | |
responses.forEach((response) => { | |
array.push(response.headers.etag) | |
}) | |
return etags = array | |
} | |
// getter | |
const getVideoString = () => { | |
var string = requestData.video | |
console.log(string) | |
return videoString = string | |
} | |
// finalize upload | |
const finalizeVideo = async () => { | |
try { | |
const data = { | |
'finalizeUploadRequest': { | |
'video': videoString, | |
'uploadToken': '', | |
'uploadedPartIds': etags | |
} | |
} | |
const response = await axios({ | |
method: 'post', | |
url: 'https://api.linkedin.com/rest/videos?action=finalizeUpload', | |
data: data, | |
headers: { | |
'Authorization': `Bearer ${LINKEDIN_ACCESS_TOKEN}`, | |
'Content-Type': 'application/json', | |
'LinkedIn-Version': LINKEDIN_API_VERSION, | |
'X-Restli-Protocol-Version': '2.0.0' | |
} | |
}) | |
return response | |
} | |
catch(e) { | |
console.log('finalizeVideo error!') | |
console.log(e.response) | |
} | |
} | |
// get status | |
const getUploadStatus = async () => { | |
try { | |
const response = await axios({ | |
method: 'get', | |
url: `https://api.linkedin.com/rest/videos/${videoString}`, | |
headers: { | |
'Authorization': `Bearer ${LINKEDIN_ACCESS_TOKEN}`, | |
'Content-Type': 'application/json', | |
'LinkedIn-Version': LINKEDIN_API_VERSION | |
} | |
}) | |
return handleStatus(response.data) | |
} | |
catch(e) { | |
console.log('checkUploadStatus error!') | |
console.log(e.data) | |
} | |
} | |
// status handler | |
const handleStatus = async (response) => { | |
if(response.status == 'WAITING_UPLOAD' || response.status == 'PROCESSING') { | |
await new Promise((resolve) => setTimeout(resolve, 5000)) | |
console.log('uploading video...') | |
return await getUploadStatus() | |
} | |
if(response.status == 'AVAILABLE') { | |
return console.log('video uploaded!') | |
} | |
else { | |
return console.log('handleStatus error!') | |
} | |
} | |
// remove tmp dir | |
const removeTmpDir = async () => { | |
const promise = new Promise(function (resolve, reject) { | |
exec(`rm -r ./${tmpDir}`, (err, stdout, stderr) => { | |
if (err) { | |
console.error(err) | |
reject('removeTmpDir: promise rejected') | |
} else { | |
// console.log(`stdout: ${stdout}`) | |
// console.log(`stderr: ${stderr}`) | |
return resolve() | |
} | |
}) | |
}) | |
return promise | |
} | |
// create post | |
const createPost = async () => { | |
try { | |
const response = await axios({ | |
method: 'post', | |
url: 'https://api.linkedin.com/rest/posts', | |
data: { | |
'author': `urn:li:person:${LINKEDIN_PERS_ID}`, | |
'commentary': description + ' ' + tags, | |
'visibility': 'PUBLIC', | |
'distribution': { | |
'feedDistribution': 'MAIN_FEED', | |
'targetEntities': [], | |
'thirdPartyDistributionChannels': [] | |
}, | |
'content': { | |
'media': { | |
'title': '', | |
'id': videoString | |
} | |
}, | |
'lifecycleState': 'PUBLISHED', | |
'isReshareDisabledByAuthor': false | |
}, | |
headers: { | |
'Authorization': `Bearer ${LINKEDIN_ACCESS_TOKEN}`, | |
'Content-Type': 'application/json', | |
'LinkedIn-Version': LINKEDIN_API_VERSION, | |
'X-Restli-Protocol-Version': '2.0.0' | |
} | |
}) | |
return response | |
} | |
catch(e) { | |
console.log('createPost error!') | |
console.log(e) | |
} | |
} | |
await makeTmpDir() | |
.then(async () => createFile()) | |
.then(async () => await downloadFile()) | |
.then(async () => await getFileSize()) | |
.then(async (fileSize) => await initializeUpload(fileSize)) | |
.then(async () => await splitVideo()) | |
.then(async () => await getFilesArray()) | |
.then(async (files) => await uploadParts(files)) | |
.then(async (responses) => await getEtags(responses)) | |
.then(async () => await getVideoString()) | |
.then(async (videoString) => await finalizeVideo(videoString)) | |
.then(async () => await getUploadStatus()) | |
.then(async () => await removeTmpDir()) | |
.then(async () => await createPost()) | |
.then(response => console.log(response)) | |
} | |
selfPostLinkedin() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
there is issue in this code
you need to split files in
4194304
not4194303
.https://gist.github.com/spraddles/21980a7c97fda3a0ce1d1c100384def1#file-linkedin-api-js-L93
should be