-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathlinkedin.suma
More file actions
130 lines (120 loc) · 5.22 KB
/
Copy pathlinkedin.suma
File metadata and controls
130 lines (120 loc) · 5.22 KB
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
profile = "social-media/posts@1.0"
provider = "linkedin"
// Uses LinkedIn's UGC Posts API: https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api#find-ugc-posts-by-authors
// There's a posts API beta but currently it seems broken for even basic retrieval: https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/posts-api-beta
"""
Retrieve posts from LinkedIn profile
Requires `r_organization_social` scope for reading data from organization's profile.
"""
map GetProfilePosts {
count = 10
inputPage = parseInt(input.page, 10)
start = isNaN(inputPage) ? 0 : inputPage
// https://stackoverflow.com/a/60450805/240963
projection = '(paging,elements*(id,created,specificContent(com.linkedin.ugc.ShareContent(shareMediaCategory,shareCommentary,media(*(media~:playableStreams,originalUrl,description,title,thumbnails))))))'
// We need tight control over parameters encoding, which isn't the case with query construct
queryParams = `?q=authors&sortBy=LAST_MODIFIED&authors=List(${encodeURIComponent(input.profileId)})&count=${count}&start=${start}&projection=${projection}`
http GET "/v2/ugcPosts{queryParams}" {
request {
headers {
"X-Restli-Protocol-Version" = "2.0.0",
"Authorization" = `Bearer ${parameters.accessToken}`,
}
}
response 200 "application/json" {
// LinkedIn returns paging.links but it seems they have a bug with double encoding projection parameter: https://stackoverflow.com/q/71250137/240963
// Instead we will just calculate next and previous pages' URLs ourselves
paging = body.paging
result = {
previousPage: (paging.start > 0) ? `${Math.max(0, paging.start - paging.count)}` : undefined,
// count is passed value, not the real count of items
nextPage: (paging.start + paging.count) < paging.total ? `${paging.start + paging.count}` : undefined,
}
result.posts = body.elements.map((element) => {
const content = element.specificContent['com.linkedin.ugc.ShareContent'] || {shareCommentary: {}, media: []};
let attachmentType = undefined;
switch (content.shareMediaCategory) {
case 'IMAGE':
attachmentType = 'image';
break;
case 'VIDEO':
attachmentType = 'video';
break;
case 'ARTICLE':
attachmentType = 'link';
break;
case 'NONE': // text-only posts; reshared posts?
case 'URN_REFERENCE': // polls?
case 'NATIVE_DOCUMENT': // document attachment (PDF)
break;
}
let attachments = [];
if (attachmentType) {
attachments = content.media.map((media) => {
const thumbnail = media.thumbnails && media.thumbnails[0] || {};
let videoUrl, duration, width, height;
if (attachmentType === 'video') {
const mediaElement = media['media~'] && media['media~'].elements.find((mediaEl) => {
return mediaEl.data['com.linkedin.digitalmedia.mediaartifact.AdaptiveStreamVideoWithAudio'] || mediaEl.data['com.linkedin.digitalmedia.mediaartifact.AdaptiveStreamVideo']
});
if (mediaElement) {
const artifact = mediaElement.data['com.linkedin.digitalmedia.mediaartifact.AdaptiveStreamVideoWithAudio'] || mediaElement.data['com.linkedin.digitalmedia.mediaartifact.AdaptiveStreamVideo']
width = artifact.videoStream.displaySize.width;
height = artifact.videoStream.displaySize.height;
duration = artifact.durationInMicroseconds / (1000 * 1000);
videoUrl = mediaElement.identifiers[0].identifier;
}
}
const description = media.description && media.description.text;
const altText = (attachmentType === 'image') ? description : undefined;
return {
type: attachmentType,
url: videoUrl || media.originalUrl,
preview: thumbnail.url,
duration: duration,
width: width || thumbnail.width,
height: height || thumbnail.height,
title: media.title && media.title.text,
altText: altText,
description: description,
};
});
}
const post = {
id: element.id,
// FIXME: timestamp to ISO date
// createdAt: `${element.created.time}`,
url: `https://www.linkedin.com/feed/update/${element.id}`,
text: content.shareCommentary.text,
attachments: attachments,
}
return post;
});
return map result result
}
response 400 "application/json" {
return map error {
title = "Bad Request",
detail = body.message,
}
}
response 401 "application/json" {
return map error {
title = "Unauthorized",
detail = body.message,
}
}
response 403 "application/json" {
return map error {
title = "Forbidden",
detail = body.message,
}
}
response 404 "application/json" {
return map error {
title = "Resource Not Found",
detail = body.message,
}
}
}
}