Skip to content

Commit

Permalink
Threads integration updating verifyLInk api / answerAbos and tiktok a…
Browse files Browse the repository at this point in the history
…pi v2 (#289)

Tiktok api v2 integration instead of the v1 version that will become
deprecated.
adding verifyThread function in verifyLink api to check the thread Url
from client
adding threadsAbos function in aswerAbos to fetch user account
followers_count
adding check/insta api to check if a user has an instagram account so
we'll allow him to add a threads account
  • Loading branch information
HamdiBenK committed Jul 17, 2023
2 parents 72f7452 + a1647db commit 3ab24f9
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 18 deletions.
118 changes: 118 additions & 0 deletions controllers/profile.controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var rp = require('axios');
const validator = require('validator')


const {
User,
GoogleProfile,
Expand Down Expand Up @@ -36,6 +37,7 @@ const {
updateFacebookPages,
tiktokAbos,
getFacebookUsername,
verifyThread,
} = require('../manager/oracles')

//var ejs = require('ejs')
Expand Down Expand Up @@ -884,6 +886,21 @@ module.exports.confrimChangeMail = async (req, res) => {
}
}

module.exports.checkThreads = async (req, res) => {
try{
let instaAccount = await FbPage.findOne({UserId : req.user._id, instagram_username : {$exists : true}});
if(!instaAccount) return makeResponseData(res, 200,'instagram_not_found')
if(instaAccount.threads_id) return makeResponseData(res, 200, 'threads_already_added')
return makeResponseData(res, 200, true)
}catch (err) {
return makeResponseError(
res,
500,
err.message ? err.message : err.error
)
}
}

module.exports.verifyLink = async (req, response) => {
try {
var userId = req.user._id
Expand Down Expand Up @@ -1002,6 +1019,19 @@ module.exports.verifyLink = async (req, response) => {
if (res === 'deactivate') deactivate = true
}

break
case '7':
const {threads_id} = await FbPage.findOne({
UserId: userId,
instagram_id: { $exists: true } ,
threads_id: { $exists: true }
},{threads_id : 1}).lean()
if (threads_id) {
linked = true
res = await verifyThread(idPost,threads_id)
if (res === 'deactivate') deactivate = true
}

break
default:
}
Expand Down Expand Up @@ -1109,3 +1139,91 @@ module.exports.ProfilPrivacy = async (req, res) => {
)
}
}


module.exports.addThreadsAccount = async (req,res) => {
try {
const instaAccount = await FbPage.findOne({UserId : req.user._id, instagram_username : {$exists : true}});
if(!instaAccount) return makeResponseData(res, 200,'instagram_not_found')
if(instaAccount.threads_id) return makeResponseData(res, 200,'threads_already_added')
const user = await axios.get(`https://www.threads.net/@${instaAccount.instagram_username}`);
let text = user.data.replace(/\s/g, '').replace(/\s/g, '');
const userID = text.match(/"user_id":"(\d+)"/)?.[1]
if(!userID) return makeResponseData(res, 200,'threads_not_found')
const lsdToken = await getLsdToken(text)
const currentUser = await fetchUserThreadData(lsdToken, userID);
if(currentUser) {
const userPicture = await axios.get(currentUser.profile_pic_url, { responseType: 'arraybuffer' })
const base64String = Buffer.from(userPicture.data, 'binary').toString('base64');
await FbPage.updateOne({
instagram_username: instaAccount.instagram_username,
}, {threads_id: currentUser.pk, threads_picture: base64String ? base64String : currentUser.profile_pic_url})
return makeResponseData(res, 200, 'threads_account_added', {username: instaAccount.instagram_username, picture: base64String ? base64String : currentUser.profile_pic_url, id: currentUser.pk})
}
return makeResponseData(res, 200, 'error')
} catch(err) {
return makeResponseError(
res,
500,
err.message ? err.message : err.error
)
}
}



module.exports.removeThreadsAccount = async (req,res) => {
const instaAccount = await FbPage.findOne({UserId : req.user._id, threads_id: req.params.id,instagram_username : {$exists : true}});
if(!instaAccount) return makeResponseData(res, 200,'instagram_not_found')
if(instaAccount.threads_id) {
await FbPage.updateOne({ UserId: req.user._id,threads_id: req.params.id }, {$unset: {threads_id:1, threads_picture:1}})
return makeResponseData(res, 200, 'deleted successfully')
} return makeResponseData(res, 200,'no_threads_found')

}









const getLsdToken = async (text) => {
const lsdTokenMatch = text.match(/"LSD",\[\],{"token":"(\w+)"},\d+\]/)?.[1];
return lsdTokenMatch;
};

const fetchUserThreadData = async (token, userID) => {
const data = {
lsd: token,
variables: `{"userID": ${userID}}`,
doc_id: '23996318473300828',
};

const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'X-ASBD-ID': '129477',
'X-FB-LSD': token,
'X-IG-App-ID': '5587632691339264',
};

const response = await axios.post(
'https://www.threads.net/api/graphql',
data,
{
headers: headers,
transformRequest: [(data) => {
return Object.entries(data)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}],
}
);

const user = response?.data?.data?.userData?.user
return user;
}
113 changes: 97 additions & 16 deletions manager/oracles.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,61 @@ exports.verifyInsta = async function (userId, idPost) {
}
}


// Validate the idPost parameter
function isValidIdPost(idPost) {
// Add your validation logic here
// For example, check if it's a non-empty string, or if it matches a specific format
return typeof idPost === 'string' && idPost.trim().length > 0;
}


exports.verifyThread = async (idPost, threads_id) => {
try {
const res = await axios.get(`https://www.threads.net/t/${idPost}`);

if (!isValidIdPost(idPost)) {
throw new Error('Invalid idPost');
}

let text = res.data;
text = text.replace(/\s/g, '');
text = text.replace(/\n/g, '');

const postID = text.match(/{"post_id":"(.*?)"}/)?.[1];
const lsdToken = text.match(/"LSD",\[\],{"token":"(\w+)"},\d+\]/)?.[1];

// THIS FUNCTION WILL GIVE US IF ACCOUNT EXIST OR NO ( TO LINK SATT ACCOUNT TO THREAD ACCOUNT )
const headers = {
'Authority': 'www.threads.net',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Cache-Control': 'no-cache',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://www.threads.net',
'Pragma': 'no-cache',
'Sec-Fetch-Site': 'same-origin',
'X-ASBD-ID': '129477',
'X-FB-LSD': lsdToken,
'X-IG-App-ID': '238260118697367',
};

const response = await axios.post("https://www.threads.net/api/graphql", {
'lsd': lsdToken,
'variables': JSON.stringify({
postID,
}),
'doc_id': '5587632691339264',
}, {
headers
});
let owner =response.data.data.data.containing_thread.thread_items[0].post.user.pk
return threads_id === owner;

} catch (err) {
return 'lien_invalid'
}
}
exports.verifyTwitter = async function (twitterProfile, userId, idPost) {
try {
const client = new Twitter({
Expand Down Expand Up @@ -306,6 +361,8 @@ exports.answerAbos = async (
tiktokProfile.followers = res ?? 0
await tiktokProfile.save()
break
case '7':
var res = await threadsAbos(idPost,id)
default:
var res = 0
break
Expand Down Expand Up @@ -473,6 +530,28 @@ exports.tiktokAbos = async (userId, access_token = null) => {
}
}


const threadsAbos = async (idPost, id, userName) => {
try {
var followers = 0
var campaign_link = await CampaignLink.findOne({ idPost }).lean()


let instagramUserName = campaign_link?.instagramUserName || userName
var fbPage = await FbPage.findOne({
UserId: id ,
instagram_username: instagramUserName ,
instagram_id: { $exists: true } ,
threads_id: { $exists: true }
})

if (fbPage) {

}
return followers
} catch (err) {}
}

exports.getPromApplyStats = async (
oracles,
link,
Expand Down Expand Up @@ -721,22 +800,24 @@ const tiktok = async (tiktokProfile, idPost) => {
let getUrl = `https://open-api.tiktok.com/oauth/refresh_token?client_key=${process.env.TIKTOK_KEY}&grant_type=refresh_token&refresh_token=${tiktokProfile.refreshToken}`
let resMedia = await rp.get(getUrl)
resMedia?.data?.data?.access_token && await TikTokProfile.updateOne({_id:tiktokProfile._id},{accessToken : resMedia?.data?.data.access_token})
let videoInfoResponse = await axios
.post('https://open-api.tiktok.com/video/query/', {
access_token: resMedia?.data?.data.access_token,
open_id: tiktokProfile.userTiktokId,
filters: {
video_ids: [idPost],
},
fields: [
'like_count',
'comment_count',
'share_count',
'view_count',
'cover_image_url',
],
})
.then((response) => response.data)
const data = {
filters: {
video_ids: [
idPost
]
}
};

let videoInfoResponse = await axios({
method: 'post',
url: 'https://open.tiktokapis.com/v2/video/query/?fields=id,title',
headers: {
'Authorization': "Bearer " +resMedia?.data?.data.access_token,
'Content-Type': 'application/json'
},
data
}).then((response) => response.data)


return {
likes: videoInfoResponse.data.videos[0].like_count,
Expand Down
6 changes: 5 additions & 1 deletion middleware/profileValidator.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ const schemas = {
addUserLegalProfileSchema: Joi.object({
type: Joi.string().required(),
typeProof: Joi.string().allow('').required()
}),
idThreadsAccount: Joi.object({
id: Joi.string().required(),
})


Expand Down Expand Up @@ -198,5 +201,6 @@ module.exports = {
deleteLinkedinChannelValidation: validationMiddleware(schemas.deleteLinkedinChannelSchema, 'params'),
verifyLinkValidation: validationMiddleware(schemas.verifyLinkSchema, 'params'),
ShareByActivityValidation: validationMiddleware(schemas.ShareByActivitySchema, 'params'),
addUserLegalProfileValidation: validationCustomMiddleware(schemas.uploadFileLegalKycSchema, 'file', schemas.addUserLegalProfileSchema, 'body')
addUserLegalProfileValidation: validationCustomMiddleware(schemas.uploadFileLegalKycSchema, 'file', schemas.addUserLegalProfileSchema, 'body'),
idThreadsAccountValidation: validationMiddleware(schemas.idThreadsAccount, 'params')
};
2 changes: 2 additions & 0 deletions model/fbPage.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const fbPageSchema = mongoose.Schema(
UserId: { type: Number, required: true, ref: 'user' },
id: { type: String },
instagram_id: { type: String },
threads_id : String,
threads_picture : String,
instagram_username: { type: String },
name: { type: String },
picture: { type: String },
Expand Down
13 changes: 12 additions & 1 deletion routes/profile.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ const {
ShareByActivity,
tiktokApiAbos,
ProfilPrivacy,
checkThreads,
addThreadsAccount,
removeThreadsAccount
} = require('../controllers/profile.controller')
const {
addFacebookChannel,
Expand Down Expand Up @@ -110,7 +113,8 @@ const {
deleteLinkedinChannelValidation,
verifyLinkValidation,
ShareByActivityValidation,
addUserLegalProfileValidation
addUserLegalProfileValidation,
idThreadsAccountValidation
} = require('../middleware/profileValidator.middleware')
const { sendNotificationTest } = require('../manager/notification')

Expand Down Expand Up @@ -1393,4 +1397,11 @@ router.get('/linkedin/ShareByActivity/:activity', verifyAuth, ShareByActivityVal
router.get('/Tiktok/ProfilPrivacy', verifyAuth, ProfilPrivacy)


router.get('/check/threads-account',verifyAuth,checkThreads)

router.get('/add/threads-account', verifyAuth, addThreadsAccount)

router.delete('/remove/threads-account/:id', verifyAuth, idThreadsAccountValidation,removeThreadsAccount)


module.exports = router

0 comments on commit 3ab24f9

Please sign in to comment.