From b57953b05416847c1d836809d7060b0ba49cff49 Mon Sep 17 00:00:00 2001 From: Tobi Saputra Date: Wed, 10 Jul 2024 15:50:35 +0700 Subject: [PATCH] feat: update prettier --- .prettierrc | 14 ++---- src/index.ts | 61 +++++++++++++++++++---- src/utils/downloader/ssstik.ts | 53 ++++++++++++++++---- src/utils/downloader/tiktokApi.ts | 56 +++++++++++++++++---- src/utils/search/liveSearch.ts | 42 +++++++++++++--- src/utils/search/stalker.ts | 83 +++++++++++++++++++++++++------ src/utils/search/userSearch.ts | 38 +++++++++++--- 7 files changed, 273 insertions(+), 74 deletions(-) diff --git a/.prettierrc b/.prettierrc index 772a35a..ca96016 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,16 +1,8 @@ { "arrowParens": "always", "endOfLine": "auto", - "printWidth": 333, + "printWidth": 80, "semi": false, "tabWidth": 2, - "trailingComma": "none", - "overrides": [ - { - "files": ["./src/types/*.ts"], - "options": { - "printWidth": 150 - } - } - ] -} \ No newline at end of file + "trailingComma": "none" +} diff --git a/src/index.ts b/src/index.ts index e84015c..051a0de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,8 +16,18 @@ import { StalkResult } from "./types/search/stalker" import { SearchLive } from "./utils/search/liveSearch" import { TiktokLiveSearchResponse } from "./types/search/liveSearch" -type TiktokDownloaderResponse = T extends "v1" ? TiktokAPIResponse : T extends "v2" ? SSSTikResponse : T extends "v3" ? MusicalDownResponse : TiktokAPIResponse -type TiktokSearchResponse = T extends "user" ? TiktokUserSearchResponse : T extends "live" ? any : TiktokLiveSearchResponse +type TiktokDownloaderResponse = T extends "v1" + ? TiktokAPIResponse + : T extends "v2" + ? SSSTikResponse + : T extends "v3" + ? MusicalDownResponse + : TiktokAPIResponse +type TiktokSearchResponse = T extends "user" + ? TiktokUserSearchResponse + : T extends "live" + ? any + : TiktokLiveSearchResponse export = { /** @@ -28,8 +38,11 @@ export = { * @returns {Promise} */ - Downloader: async (url: string, options?: { version: T }): Promise> => { - switch (options?.version) { + Downloader: async ( + url: string, + options?: { version: T } + ): Promise> => { + switch (options?.version.toLowerCase()) { case "v1": { const response = await TiktokAPI(url) return response as TiktokDownloaderResponse @@ -57,18 +70,36 @@ export = { * @param {number} options.page - The page of search (optional) * @returns {Promise} */ - Search: async (query: string, options: { type: T; cookie?: string; page?: number; proxy?: string }): Promise> => { - switch (options?.type) { + Search: async ( + query: string, + options: { type: T; cookie?: string; page?: number; proxy?: string } + ): Promise> => { + switch (options?.type.toLowerCase()) { case "user": { - const response = await SearchUser(query, options?.cookie, options?.page, options?.proxy) + const response = await SearchUser( + query, + options?.cookie, + options?.page, + options?.proxy + ) return response as TiktokSearchResponse } case "live": { - const response = await SearchLive(query, options?.cookie, options?.page, options?.proxy) + const response = await SearchLive( + query, + options?.cookie, + options?.page, + options?.proxy + ) return response as TiktokSearchResponse } default: { - const response = await SearchUser(query, options?.cookie, options?.page, options?.proxy) + const response = await SearchUser( + query, + options?.cookie, + options?.page, + options?.proxy + ) return response as TiktokSearchResponse } } @@ -80,8 +111,16 @@ export = { * @param {string} options.cookie - Your Tiktok Cookie (optional) * @returns {Promise} */ - StalkUser: async (username: string, options?: { cookie?: string; postLimit?: number; proxy?: string }): Promise => { - const response = await StalkUser(username, options?.cookie, options?.postLimit, options?.proxy) + StalkUser: async ( + username: string, + options?: { cookie?: string; postLimit?: number; proxy?: string } + ): Promise => { + const response = await StalkUser( + username, + options?.cookie, + options?.postLimit, + options?.proxy + ) return response } } diff --git a/src/utils/downloader/ssstik.ts b/src/utils/downloader/ssstik.ts index 8405b89..92c4989 100644 --- a/src/utils/downloader/ssstik.ts +++ b/src/utils/downloader/ssstik.ts @@ -1,7 +1,12 @@ import Axios from "axios" import asyncRetry from "async-retry" import { load } from "cheerio" -import { Author, Statistics, SSSTikFetchTT, SSSTikResponse } from "../../types/downloader/ssstik" +import { + Author, + Statistics, + SSSTikFetchTT, + SSSTikResponse +} from "../../types/downloader/ssstik" import { _ssstikapi, _ssstikurl } from "../../constants/api" import { HttpsProxyAgent } from "https-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent" @@ -11,16 +16,25 @@ import { SocksProxyAgent } from "socks-proxy-agent" * BASE URL : https://ssstik.io */ -const TiktokURLregex = /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ +const TiktokURLregex = + /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ const fetchTT = (proxy?: string) => new Promise(async (resolve) => { Axios(_ssstikurl, { method: "GET", headers: { - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0" + "User-Agent": + "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0" }, - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) .then(({ data }) => { const regex = /s_tt\s*=\s*["']([^"']+)["']/ @@ -29,7 +43,10 @@ const fetchTT = (proxy?: string) => const value = match[1] return resolve({ status: "success", result: value }) } else { - return resolve({ status: "error", message: "Failed to get the request form!" }) + return resolve({ + status: "error", + message: "Failed to get the request form!" + }) } }) .catch((e) => resolve({ status: "error", message: e.message })) @@ -52,17 +69,20 @@ export const SSSTik = (url: string, proxy?: string) => }) } const tt: SSSTikFetchTT = await fetchTT(proxy) - if (tt.status !== "success") return resolve({ status: "error", message: tt.message }) + if (tt.status !== "success") + return resolve({ status: "error", message: tt.message }) const response = asyncRetry( async () => { const res = await Axios(_ssstikapi, { method: "POST", headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "Content-Type": + "application/x-www-form-urlencoded; charset=UTF-8", Origin: _ssstikurl, Referer: _ssstikurl + "/en", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0" + "User-Agent": + "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0" }, data: new URLSearchParams( Object.entries({ @@ -71,7 +91,14 @@ export const SSSTik = (url: string, proxy?: string) => tt: tt.result }) ), - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) if (res.status === 200 && res.data !== "") return res.data @@ -94,8 +121,12 @@ export const SSSTik = (url: string, proxy?: string) => nickname: $("h2").text().trim() } const statistics: Statistics = { - likeCount: $("#trending-actions > .justify-content-start").text().trim(), - commentCount: $("#trending-actions > .justify-content-center").text().trim(), + likeCount: $("#trending-actions > .justify-content-start") + .text() + .trim(), + commentCount: $("#trending-actions > .justify-content-center") + .text() + .trim(), shareCount: $("#trending-actions > .justify-content-end").text().trim() } diff --git a/src/utils/downloader/tiktokApi.ts b/src/utils/downloader/tiktokApi.ts index dcdbfe3..59e9b36 100644 --- a/src/utils/downloader/tiktokApi.ts +++ b/src/utils/downloader/tiktokApi.ts @@ -2,11 +2,19 @@ import Axios from "axios" import asyncRetry from "async-retry" import { _tiktokvFeed, _tiktokurl } from "../../constants/api" import { _tiktokApiParams } from "../../constants/params" -import { Author, TiktokAPIResponse, Statistics, Music, responseParser, Video } from "../../types/downloader/tiktokApi" +import { + Author, + TiktokAPIResponse, + Statistics, + Music, + responseParser, + Video +} from "../../types/downloader/tiktokApi" import { HttpsProxyAgent } from "https-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent" -const TiktokURLregex = /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ +const TiktokURLregex = + /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ /** * Tiktok API Downloader @@ -26,7 +34,14 @@ export const TiktokAPI = (url: string, proxy?: string) => url = url.replace("https://vm", "https://vt") Axios(url, { method: "HEAD", - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) .then(async ({ request }) => { const { responseUrl } = request.res @@ -34,7 +49,8 @@ export const TiktokAPI = (url: string, proxy?: string) => if (ID === null) return resolve({ status: "error", - message: "Failed to fetch tiktok url. Make sure your tiktok url is correct!" + message: + "Failed to fetch tiktok url. Make sure your tiktok url is correct!" }) ID = ID[0] @@ -43,7 +59,8 @@ export const TiktokAPI = (url: string, proxy?: string) => if (!data2?.content) { return resolve({ status: "error", - message: "Failed to fetch tiktok data. Make sure your tiktok url is correct!" + message: + "Failed to fetch tiktok data. Make sure your tiktok url is correct!" }) } @@ -59,11 +76,16 @@ export const TiktokAPI = (url: string, proxy?: string) => id: content.aweme_id, createTime: content.create_time, description: content.desc, - hashtag: content.text_extra.filter((x) => x.hashtag_name !== undefined).map((v) => v.hashtag_name), + hashtag: content.text_extra + .filter((x) => x.hashtag_name !== undefined) + .map((v) => v.hashtag_name), isADS: content.is_ads, author, statistics, - images: content.image_post_info.images?.map((v) => v?.display_image?.url_list[0]) || [], + images: + content.image_post_info.images?.map( + (v) => v?.display_image?.url_list[0] + ) || [], music } }) @@ -86,7 +108,9 @@ export const TiktokAPI = (url: string, proxy?: string) => id: content.aweme_id, createTime: content.create_time, description: content.desc, - hashtag: content.text_extra.filter((x) => x.hashtag_name !== undefined).map((v) => v.hashtag_name), + hashtag: content.text_extra + .filter((x) => x.hashtag_name !== undefined) + .map((v) => v.hashtag_name), isADS: content.is_ads, author, statistics, @@ -99,7 +123,10 @@ export const TiktokAPI = (url: string, proxy?: string) => .catch((e) => resolve({ status: "error", message: e.message })) }) -const fetchTiktokData = async (ID: string, proxy?: string): Promise | null => { +const fetchTiktokData = async ( + ID: string, + proxy?: string +): Promise | null => { try { const response = asyncRetry( async () => { @@ -112,9 +139,16 @@ const fetchTiktokData = async (ID: string, proxy?: string): Promise} */ -export const SearchLive = async (keyword: string, cookie?: any, page: number = 1, proxy?: string) => +export const SearchLive = async ( + keyword: string, + cookie?: any, + page: number = 1, + proxy?: string +) => new Promise(async (resolve) => { Axios(_tiktokSearchLiveFull(_liveSearchParams(keyword, page)), { method: "GET", headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", - cookie: typeof cookie === "object" ? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ") : cookie + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", + cookie: + typeof cookie === "object" + ? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ") + : cookie }, - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) .then(({ data }) => { // Cookie Invalid - if (data.status_code === 2483) return resolve({ status: "error", message: "Invalid cookie!" }) + if (data.status_code === 2483) + return resolve({ status: "error", message: "Invalid cookie!" }) // Another Error - if (data.status_code !== 0) return resolve({ status: "error", message: data.status_msg || "An error occurred! Please report this issue to the developer." }) - if (!data.data) return resolve({ status: "error", message: "Live not found!" }) + if (data.status_code !== 0) + return resolve({ + status: "error", + message: + data.status_msg || + "An error occurred! Please report this issue to the developer." + }) + if (!data.data) + return resolve({ status: "error", message: "Live not found!" }) const result = [] data.data.forEach((v: any) => { @@ -63,7 +87,9 @@ export const SearchLive = async (keyword: string, cookie?: any, page: number = 1 followingCount: content.owner.follow_info.following_count, followerCount: content.owner.follow_info.follower_count } as OwnerStats, - isVerified: content.owner?.authentication_info?.custom_verify === "verified account" || false + isVerified: + content.owner?.authentication_info?.custom_verify === + "verified account" || false } as Owner } diff --git a/src/utils/search/stalker.ts b/src/utils/search/stalker.ts index 1bd311e..bc48500 100644 --- a/src/utils/search/stalker.ts +++ b/src/utils/search/stalker.ts @@ -2,7 +2,13 @@ import Axios from "axios" import qs from "qs" import { load } from "cheerio" import { _tiktokGetPosts, _tiktokurl } from "../../constants/api" -import { AuthorPost, Posts, StalkResult, Stats, Users } from "../../types/search/stalker" +import { + AuthorPost, + Posts, + StalkResult, + Stats, + Users +} from "../../types/search/stalker" import { _userPostsParams, _xttParams } from "../../constants/params" import { createCipheriv } from "crypto" import { HttpsProxyAgent } from "https-proxy-agent" @@ -17,26 +23,48 @@ import { SocksProxyAgent } from "socks-proxy-agent" * @returns {Promise} */ -export const StalkUser = (username: string, cookie?: any, postLimit?: number, proxy?: string): Promise => +export const StalkUser = ( + username: string, + cookie?: any, + postLimit?: number, + proxy?: string +): Promise => new Promise(async (resolve) => { username = username.replace("@", "") Axios.get(`${_tiktokurl}/@${username}`, { headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36", - cookie: typeof cookie === "object" ? cookie.map((v) => `${v.name}=${v.value}`).join("; ") : cookie + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36", + cookie: + typeof cookie === "object" + ? cookie.map((v) => `${v.name}=${v.value}`).join("; ") + : cookie }, - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) .then(async ({ data }) => { const $ = load(data) - const result = JSON.parse($("script#__UNIVERSAL_DATA_FOR_REHYDRATION__").text()) - if (!result["__DEFAULT_SCOPE__"] && !result["__DEFAULT_SCOPE__"]["webapp.user-detail"]) { + const result = JSON.parse( + $("script#__UNIVERSAL_DATA_FOR_REHYDRATION__").text() + ) + if ( + !result["__DEFAULT_SCOPE__"] && + !result["__DEFAULT_SCOPE__"]["webapp.user-detail"] + ) { return resolve({ status: "error", message: "User not found!" }) } - const dataUser = result["__DEFAULT_SCOPE__"]["webapp.user-detail"]["userInfo"] + const dataUser = + result["__DEFAULT_SCOPE__"]["webapp.user-detail"]["userInfo"] const posts: Posts[] = await parsePosts(dataUser, postLimit, proxy) const { users, stats } = parseDataUser(dataUser, posts) @@ -58,13 +86,26 @@ export const StalkUser = (username: string, cookie?: any, postLimit?: number, pr * https://github.com/atharahmed/tiktok-private-api/blob/020ede2eaa6021bcd363282d8cef1aacaff2f88c/src/repositories/user.repository.ts#L148 */ -const request = async (secUid: string, cursor = 0, count = 30, proxy?: string) => { +const request = async ( + secUid: string, + cursor = 0, + count = 30, + proxy?: string +) => { const { data } = await Axios.get(`${_tiktokGetPosts(_userPostsParams())}`, { headers: { - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35", + "user-agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35", "X-tt-params": xttparams(_xttParams(secUid, cursor, count)) }, - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) return data @@ -102,7 +143,11 @@ const parseDataUser = (dataUser: any, posts: Posts[]) => { return { users, stats } } -const parsePosts = async (dataUser: any, postLimit?: number, proxy?: string): Promise => { +const parsePosts = async ( + dataUser: any, + postLimit?: number, + proxy?: string +): Promise => { // Posts Result let hasMore = true let cursor: number | null = null @@ -137,7 +182,9 @@ const parsePosts = async (dataUser: any, postLimit?: number, proxy?: string): Pr } if (v.imagePost) { - const images: string[] = v.imagePost.images.map((img: any) => img.imageURL.urlList[0]) + const images: string[] = v.imagePost.images.map( + (img: any) => img.imageURL.urlList[0] + ) posts.push({ id: v.id, @@ -205,6 +252,12 @@ const parsePosts = async (dataUser: any, postLimit?: number, proxy?: string): Pr } const xttparams = (params: any) => { - const cipher = createCipheriv("aes-128-cbc", "webapp1.0+202106", "webapp1.0+202106") - return Buffer.concat([cipher.update(params), cipher.final()]).toString("base64") + const cipher = createCipheriv( + "aes-128-cbc", + "webapp1.0+202106", + "webapp1.0+202106" + ) + return Buffer.concat([cipher.update(params), cipher.final()]).toString( + "base64" + ) } diff --git a/src/utils/search/userSearch.ts b/src/utils/search/userSearch.ts index 394824d..53bf794 100644 --- a/src/utils/search/userSearch.ts +++ b/src/utils/search/userSearch.ts @@ -14,22 +14,46 @@ import { SocksProxyAgent } from "socks-proxy-agent" * @returns {Promise} */ -export const SearchUser = (username: string, cookie?: any, page: number = 1, proxy?: string): Promise => +export const SearchUser = ( + username: string, + cookie?: any, + page: number = 1, + proxy?: string +): Promise => new Promise(async (resolve) => { Axios(_tiktokSearchUserFull(_userSearchParams(username, page)), { method: "GET", headers: { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", - cookie: typeof cookie === "object" ? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ") : cookie + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", + cookie: + typeof cookie === "object" + ? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ") + : cookie }, - httpsAgent: (proxy && (proxy.startsWith("http") || proxy.startsWith("https") ? new HttpsProxyAgent(proxy) : proxy.startsWith("socks") ? new SocksProxyAgent(proxy) : undefined)) || undefined + httpsAgent: + (proxy && + (proxy.startsWith("http") || proxy.startsWith("https") + ? new HttpsProxyAgent(proxy) + : proxy.startsWith("socks") + ? new SocksProxyAgent(proxy) + : undefined)) || + undefined }) .then(({ data }) => { // Cookie Invalid - if (data.status_code === 2483) return resolve({ status: "error", message: "Invalid cookie!" }) + if (data.status_code === 2483) + return resolve({ status: "error", message: "Invalid cookie!" }) // Another Error - if (data.status_code !== 0) return resolve({ status: "error", message: data.status_msg || "An error occurred! Please report this issue to the developer." }) - if (!data.user_list) return resolve({ status: "error", message: "User not found!" }) + if (data.status_code !== 0) + return resolve({ + status: "error", + message: + data.status_msg || + "An error occurred! Please report this issue to the developer." + }) + if (!data.user_list) + return resolve({ status: "error", message: "User not found!" }) const result = [] for (let i = 0; i < data.user_list.length; i++) {