Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Threads integration updating verifyLInk api / answerAbos and tiktok api v2 #289

Merged
merged 9 commits into from
Jul 17, 2023
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 @@
}
}


// 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}`);
Fixed Show fixed Hide fixed

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

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 @@
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 @@
}
}


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

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query object depends on a
user-provided value
.


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 @@
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 @@
ShareByActivity,
tiktokApiAbos,
ProfilPrivacy,
checkThreads,
addThreadsAccount,
removeThreadsAccount
} = require('../controllers/profile.controller')
const {
addFacebookChannel,
Expand Down Expand Up @@ -110,7 +113,8 @@
deleteLinkedinChannelValidation,
verifyLinkValidation,
ShareByActivityValidation,
addUserLegalProfileValidation
addUserLegalProfileValidation,
idThreadsAccountValidation
} = require('../middleware/profileValidator.middleware')
const { sendNotificationTest } = require('../manager/notification')

Expand Down Expand Up @@ -1329,7 +1333,7 @@
* "500":
* description: error:<br> server error
*/
router.get('/link/verify/:typeSN/:idUser/:idPost', verifyAuth, verifyLinkValidation,verifyLink)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

/**
* @swagger
Expand Down Expand Up @@ -1393,4 +1397,11 @@
router.get('/Tiktok/ProfilPrivacy', verifyAuth, ProfilPrivacy)


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

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

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

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

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

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.


module.exports = router
Loading