Skip to content
This repository has been archived by the owner on Sep 10, 2021. It is now read-only.

Commit

Permalink
feat: add methods for searching users and hashtags (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
szdc authored Aug 22, 2018
1 parent de88ef3 commit 6dc8525
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 0 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const api = new TikTokAPI(params, { signURL });

* [.loginWithEmail(email, password)](#loginwithemailemail-password)
* [.getUser(id)](#getuserid)
* [.searchUsers(params)](#searchusersparams)
* [.listPosts(params)](#listpostsparams)
* [.listFollowers(params)](#listfollowersparams)
* [.listFollowing(params)](#listfollowingparams)
Expand All @@ -59,6 +60,7 @@ const api = new TikTokAPI(params, { signURL });
* [.listComments(params)](#listcommentsparams)
* [.postComment(postId, text, [tags])](#postcommentpostid-text-tags)
* [.listCategories(params)](#listcategoriesparams)
* [.searchHashtags(params)](#searchhashtagsparams)

#### .loginWithEmail(email, password)

Expand Down Expand Up @@ -93,6 +95,26 @@ api.getUser('<user_id>')

See the [user types](src/types/user.d.ts) for the response data.

#### .searchUsers(params)

Searches for users.

```javascript
api.searchUsers({
keyword: 'example',
count: 10,
cursor: 0,
})
.then(res => console.log(res.data.user_list))
.catch(console.log);

// Outputs:
// [{ user_info: {...}, position: [], uniqposition: [] }, ...]

```

See the [search types](src/types/search.d.ts) for the complete request/response objects.

#### .listPosts(params)

Lists a user's posts.
Expand Down Expand Up @@ -264,6 +286,26 @@ api.listCategories({

See the [category types](src/types/category.d.ts) for the complete request/response objects.

#### .searchHashtags(params)

Searches for hashtags.

```javascript
api.searchHashtags({
keyword: 'example',
count: 10,
cursor: 0,
})
.then(res => console.log(res.data.challenge_list))
.catch(console.log);

// Outputs:
// [{ challenge_info: {...}, position: [] }, ...]

```

See the [search types](src/types/search.d.ts) for the complete request/response objects.

## Resources

* [Reverse engineering the musical.ly API](https://medium.com/@szdc/reverse-engineering-the-musical-ly-api-662331008eb3)
Expand Down
26 changes: 26 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
BaseResponseData,
FollowRequest,
FollowResponse,
HashtagSearchResponse,
LikePostRequest,
LikePostResponse,
ListCategoriesRequest,
Expand All @@ -27,10 +28,13 @@ import {
PostCommentRequest,
PostCommentResponse,
RequiredUserDefinedRequestParams,
SearchRequest,
StaticRequestParams,
Tag,
TikTokAPIConfig,
UserProfileResponse,
UserSearchRequest,
UserSearchResponse,
} from './types';
import {
paramsOrder,
Expand Down Expand Up @@ -121,6 +125,17 @@ export default class TikTokAPI {
getUser = (userId: string) =>
this.request.get<UserProfileResponse | BaseResponseData>('aweme/v1/user/', { params: { user_id: userId } })

/**
* Searches for users.
*
* @param params
* @returns {AxiosPromise<UserSearchResponse | BaseResponseData>}
*/
searchUsers = (params: UserSearchRequest) =>
this.request.get<UserSearchResponse | BaseResponseData>('aweme/v1/discover/search/', {
params: withDefaultListParams(params),
})

/**
* Lists a user's posts.
*
Expand Down Expand Up @@ -258,6 +273,17 @@ export default class TikTokAPI {
params: withDefaultListParams(params),
})

/**
* Searches for hashtags.
*
* @param params
* @returns {AxiosPromise<HashtagSearchResponse | BaseResponseData>}
*/
searchHashtags = (params: SearchRequest) =>
this.request.get<HashtagSearchResponse | BaseResponseData>('aweme/v1/challenge/search/', {
params: withDefaultListParams(params),
})

/**
* Transform using JSONBig to store big numbers accurately (e.g. user IDs) as strings.
*
Expand Down
1 change: 1 addition & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './login';
export * from './music';
export * from './post';
export * from './request';
export * from './search';
export * from './tag';
export * from './user';
export * from './video';
68 changes: 68 additions & 0 deletions src/types/search.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { ChallengeInfo } from './category';
import { CountOffsetParams, ListRequestParams, ListResponseData } from './request';
import { CommonUserDetails } from './user';

export interface SearchRequest extends ListRequestParams, CountOffsetParams {
/** The term to search for */
keyword: string;
}

export interface UserSearchRequest extends SearchRequest {
/** The scope of the search - users = 1 */
type?: number;
}

export interface UserSearchResponse extends ListResponseData, CountOffsetParams {
/** A list of users that match the search term */
user_list: UserSearchResult[];

/** The scope of the search - users = 1 */
type: number;
}

export interface UserSearchResult {
/** If the user's nickname contains the search term, this array contains the location of the term */
position: SubstringPosition[];

/** If the user's username (unique_id) contains the search term, this array contains the location of the term */
uniqid_position: SubstringPosition[];

/** Information about the user */
user_info: CommonUserDetails;
}

export interface HashtagSearchResponse extends ListResponseData, CountOffsetParams {
/** A list of hashtags that match the search term */
challenge_list: HashtagSearchResult[];

/** True if a challenge matches the search term */
is_match: boolean;

/** 1 if the search term is disabled */
keyword_disabled: 0 | 1;
}

export interface HashtagSearchResult {
/** Information about the hashtag */
challenge_info: ChallengeInfo;

/** If the hashtag contains the search term, this array contains the location of the term */
position: SubstringPosition[];
}

/**
* Represents the location of a substring in a string.
*
* e.g. For the string "The quick brown fox", the substring "quick" would be:
* {
* begin: 4,
* end: 6
* }
*/
export interface SubstringPosition {
/** The start index of the substring */
begin: number;

/** The end index of the substring */
end: number;
}
79 changes: 79 additions & 0 deletions test/search.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import MockAdapter from 'axios-mock-adapter';
import { assert } from 'chai';
import { describe, it } from 'mocha';

import TikTokAPI, {
ChallengeInfo,
CommonUserDetails,
HashtagSearchResponse,
UserSearchResponse,
} from '../src';
import {
loadTestData,
mockConfig,
mockParams,
} from './util';

describe('#searchUsers()', () => {
it('a successful response should match the interface', async () => {
const api = new TikTokAPI(mockParams, mockConfig);
const mock = new MockAdapter(api.request);
mock
.onGet(new RegExp('aweme/v1/discover/search/\?.*'))
.reply(200, loadTestData('searchUsers.json'), {});

const res = await api.searchUsers({ keyword: 'example', count: 10, cursor: 0 });
const expected: UserSearchResponse = {
extra: {
now: 1000000000000,
},
user_list: [
{
position: [{
begin: 0,
end: 6,
}],
uniqid_position: [],
user_info: {} as CommonUserDetails,
},
],
type: 1,
cursor: 1,
has_more: 1,
status_code: 0,
};
assert.deepStrictEqual(res.data, expected);
});
});

describe('#searchHashtags()', () => {
it('a successful response should match the interface', async () => {
const api = new TikTokAPI(mockParams, mockConfig);
const mock = new MockAdapter(api.request);
mock
.onGet(new RegExp('aweme/v1/challenge/search/\?.*'))
.reply(200, loadTestData('searchHashtags.json'), {});

const res = await api.searchHashtags({ keyword: 'example', count: 10, cursor: 0 });
const expected: HashtagSearchResponse = {
extra: {
now: 1000000000000,
},
challenge_list: [
{
challenge_info: {} as ChallengeInfo,
position: [{
begin: 0,
end: 6,
}],
},
],
is_match: true,
keyword_disabled: 0,
cursor: 1,
has_more: 1,
status_code: 0,
};
assert.deepStrictEqual(res.data, expected);
});
});
19 changes: 19 additions & 0 deletions test/testdata/searchHashtags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extra": {
"now": 1000000000000
},
"challenge_list": [
{
"challenge_info": {},
"position": [{
"begin": 0,
"end": 6
}]
}
],
"is_match": true,
"keyword_disabled": 0,
"cursor": 1,
"has_more": 1,
"status_code": 0
}
19 changes: 19 additions & 0 deletions test/testdata/searchUsers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extra": {
"now": 1000000000000
},
"user_list": [
{
"position": [{
"begin": 0,
"end": 6
}],
"uniqid_position": [],
"user_info": {}
}
],
"type": 1,
"cursor": 1,
"has_more": 1,
"status_code": 0
}

0 comments on commit 6dc8525

Please sign in to comment.