Skip to content

Commit 5526a91

Browse files
authored
Merge pull request #53 from fbsamples/profile-lookup
Profile Discovery
2 parents 7ca8a07 + 4293ce8 commit 5526a91

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed

src/index.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const PARAMS__RETURN_URL = 'return_url';
8181
const PARAMS__SCOPE = 'scope';
8282
const PARAMS__SEARCH_TYPE = 'search_type';
8383
const PARAMS__TEXT = 'text';
84+
const PARAMS__USERNAME = 'username';
8485

8586
// Read variables from environment
8687
require('dotenv').config();
@@ -118,6 +119,7 @@ const SCOPES = [
118119
'threads_manage_mentions',
119120
'threads_delete',
120121
'threads_location_tagging',
122+
'threads_profile_discovery',
121123
];
122124

123125
app.use(express.static('public'));
@@ -898,6 +900,99 @@ app.get('/oEmbed', async (req, res) => {
898900
});
899901
});
900902

903+
app.get('/profileLookup', async (req, res) => {
904+
const { username, before, after, limit } = req.query;
905+
if (!username) {
906+
return res.render('profile_lookup', {
907+
title: 'Profile Lookup',
908+
});
909+
}
910+
911+
const params = {
912+
[PARAMS__USERNAME]: username,
913+
}
914+
915+
const profileLookupUrl = buildGraphAPIURL(`profile_lookup`, params, req.session.access_token);
916+
917+
let response = {};
918+
try {
919+
response = await axios.get(profileLookupUrl, { httpsAgent: agent });
920+
} catch (e) {
921+
console.error(e?.response?.data?.error?.message ?? e.message);
922+
}
923+
924+
const displayName = response.data?.name;
925+
const profilePictureUrl = response.data?.profile_picture_url;
926+
const isVerified = response.data?.is_verified;
927+
const bio = response.data?.biography;
928+
const formatCount = count => count ? count.toLocaleString('en-US') : undefined;
929+
const followerCount = formatCount(response.data?.follower_count);
930+
const likesCount = formatCount(response.data?.likes_count);
931+
const quotesCount = formatCount(response.data?.quotes_count);
932+
const repliesCount = formatCount(response.data?.replies_count);
933+
const repostsCount = formatCount(response.data?.reposts_count);
934+
const viewsCount = formatCount(response.data?.views_count);
935+
936+
const profilePostsParams = {
937+
[PARAMS__FIELDS]: [
938+
FIELD__TEXT,
939+
FIELD__MEDIA_TYPE,
940+
FIELD__MEDIA_URL,
941+
FIELD__PERMALINK,
942+
FIELD__TIMESTAMP,
943+
].join(','),
944+
limit: limit ?? DEFAULT_THREADS_QUERY_LIMIT,
945+
[PARAMS__USERNAME]: username,
946+
};
947+
if (before) {
948+
profilePostsParams.before = before;
949+
}
950+
if (after) {
951+
profilePostsParams.after = after;
952+
}
953+
954+
let threads = [];
955+
let paging = {};
956+
957+
const queryThreadsUrl = buildGraphAPIURL(`profile_posts`, profilePostsParams, req.session.access_token);
958+
959+
try {
960+
const queryResponse = await axios.get(queryThreadsUrl, { httpsAgent: agent });
961+
threads = queryResponse.data.data;
962+
963+
if (queryResponse.data.paging) {
964+
const { next, previous } = queryResponse.data.paging;
965+
966+
if (next) {
967+
paging.nextUrl = getCursorUrlFromGraphApiPagingUrl(req, next);
968+
}
969+
970+
if (previous) {
971+
paging.previousUrl = getCursorUrlFromGraphApiPagingUrl(req, previous);
972+
}
973+
}
974+
} catch (e) {
975+
console.error(e?.response?.data?.error?.message ?? e.message);
976+
}
977+
978+
return res.render('profile_lookup', {
979+
title: 'Profile Lookup for @'.concat(username),
980+
username,
981+
displayName,
982+
profilePictureUrl,
983+
isVerified,
984+
bio,
985+
followerCount,
986+
likesCount,
987+
quotesCount,
988+
repliesCount,
989+
repostsCount,
990+
viewsCount,
991+
paging,
992+
threads,
993+
});
994+
});
995+
901996
https
902997
.createServer({
903998
key: fs.readFileSync(path.join(__dirname, '../'+ HOST +'-key.pem')),
@@ -1013,6 +1108,7 @@ function getCursorUrlFromGraphApiPagingUrl(req, graphApiPagingUrl) {
10131108
setUrlParamIfPresent(graphUrl, cursorUrl, 'limit');
10141109
setUrlParamIfPresent(graphUrl, cursorUrl, 'before');
10151110
setUrlParamIfPresent(graphUrl, cursorUrl, 'after');
1111+
setUrlParamIfPresent(graphUrl, cursorUrl, 'username');
10161112

10171113
return cursorUrl.href;
10181114
}

views/account.pug

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ block content
2020
button(onclick="location.href='/replies'") My Replies
2121
button(onclick="location.href='/mentions'") My Mentions
2222
button(onclick="location.href='/keywordSearch'") Search for Threads
23+
button(onclick="location.href='/profileLookup'") Profile Lookup
2324
button(onclick="location.href='/userInsights'") My Insights
2425
button(onclick="location.href='/publishingLimit'") Publishing Limit

views/profile_lookup.pug

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
extends layout
2+
3+
block content
4+
style(type="text/css").
5+
#example-accounts {
6+
margin: 0 auto;
7+
width: 100px;
8+
text-align: left;
9+
}
10+
form.profileLookup {
11+
margin-top: 50px;
12+
}
13+
14+
p Try looking up one of these profiles:
15+
ul#example-accounts
16+
li
17+
a(href='https://www.threads.net/@meta') @meta
18+
li
19+
a(href='https://www.threads.net/@threads') @threads
20+
li
21+
a(href='https://www.threads.net/@instagram') @instagram
22+
li
23+
a(href='https://www.threads.net/@facebook') @facebook
24+
25+
form.profileLookup(action='/profileLookup' id='form' method='GET')
26+
input(type='text' placeholder='Enter the username of a Threads profile to lookup' name='username' autocomplete='off')
27+
28+
input(type='submit' value='Search')
29+
30+
br
31+
br
32+
33+
if username
34+
div
35+
.pictures
36+
.profile-picture
37+
img(src=profilePictureUrl alt='User\'s profile picture' width=36 height=36)
38+
.verified
39+
if isVerified
40+
img(src='/img/verified.png' alt='Verified Badge' width=20 height=20)
41+
42+
br
43+
44+
table
45+
tbody
46+
tr
47+
th(colspan=2) Username
48+
td(colspan=2)=username
49+
tr
50+
th(colspan=2) Display Name
51+
td(colspan=2)=displayName
52+
tr
53+
th(colspan=2) Bio
54+
td(colspan=2)=bio
55+
tr
56+
th(colspan=2) Followers
57+
td(colspan=2)=followerCount
58+
tr
59+
th(colspan=2) Likes
60+
td(colspan=2)=likesCount
61+
tr
62+
th(colspan=2) Quotes
63+
td(colspan=2)=quotesCount
64+
tr
65+
th(colspan=2) Replies
66+
td(colspan=2)=repliesCount
67+
tr
68+
th(colspan=2) Reposts
69+
td(colspan=2)=repostsCount
70+
tr
71+
th(colspan=2) Views
72+
td(colspan=2)=viewsCount
73+
74+
br
75+
br
76+
77+
table.threads-list
78+
thead
79+
tr
80+
th ID
81+
th Created On
82+
th Media Type
83+
th Text
84+
th Permalink
85+
th Media URL
86+
tbody
87+
each thread in threads
88+
tr.threads-list-item
89+
td.thread-id
90+
a(href=`/threads/${thread.id}`)=thread.id
91+
td.thread-timestamp=thread.timestamp
92+
td.thread-type=thread.media_type
93+
td.thread-text=thread.text
94+
td.thread-permalink
95+
a(href=thread.permalink target='_blank') View on Threads
96+
td.thread-media-url
97+
if thread.media_url
98+
a(href=thread.media_url target='_blank') View Media File
99+
100+
div.paging
101+
if paging.nextUrl
102+
div.paging-next
103+
a(href=paging.nextUrl) Next
104+
105+
if paging.previousUrl
106+
div.paging-previous
107+
a(href=paging.previousUrl) Previous

0 commit comments

Comments
 (0)