Skip to content

Instantly share code, notes, and snippets.

@spraddles
Last active August 22, 2023 12:15
Show Gist options
  • Save spraddles/21980a7c97fda3a0ce1d1c100384def1 to your computer and use it in GitHub Desktop.
Save spraddles/21980a7c97fda3a0ce1d1c100384def1 to your computer and use it in GitHub Desktop.
Linkedin API to create a video post
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()
@aliakbarazizi
Copy link

there is issue in this code
you need to split files in 4194304 not 4194303.

https://gist.github.com/spraddles/21980a7c97fda3a0ce1d1c100384def1#file-linkedin-api-js-L93

should be

exec(`cd ${tmpDir} && split -b 4194304 ./${fileName} part-`, (err, stdout, stderr) => {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment