@xbibzlibrary/tiktokscrap
Version:
Powerful TikTok Scraper and Downloader Library
122 lines (96 loc) • 4.08 kB
text/typescript
import BaseScraper from './base';
import { TikTokHashtag, TikTokVideo, TikTokPhoto, TikTokScrapOptions, TikTokScrapResult, TikTokHashtagFeedOptions } from '../types';
import { ValidationError, NotFoundError } from '../errors';
export class HashtagScraper extends BaseScraper {
constructor(options: TikTokScrapOptions = {}) {
super(options);
}
public async getHashtagByName(hashtag: string): Promise<TikTokScrapResult<TikTokHashtag>> {
return this.executeRequest(async () => {
if (!this.validator.validateTikTokHashtag(hashtag)) {
throw new ValidationError('Invalid TikTok hashtag');
}
// Remove # if present
const cleanHashtag = hashtag.startsWith('#') ? hashtag.substring(1) : hashtag;
const url = this.buildUrl('https://www.tiktok.com', `/tag/${cleanHashtag}`);
const response = await this.http.get(url);
if (response.status !== 200) {
throw new NotFoundError('Hashtag not found');
}
return this.parser.parseHashtagData(response.data);
}, `Get hashtag by name: ${hashtag}`);
}
public async getHashtagFeed(options: TikTokHashtagFeedOptions): Promise<TikTokScrapResult<TikTokVideo[] | TikTokPhoto[]>> {
return this.executeRequest(async () => {
this.validator.validateHashtagFeedOptions(options);
const { hashtag, cursor = 0, count = 20 } = options;
// First, get hashtag info to extract id
const hashtagResult = await this.getHashtagByName(hashtag);
if (!hashtagResult.success || !hashtagResult.data) {
throw new NotFoundError('Hashtag not found');
}
const url = this.buildUrl('https://www.tiktok.com', '/api/challenge/item_list/', {
count,
id: hashtagResult.data.id,
type: 3, // 3 for hashtag
secUid: '',
maxCursor: cursor,
minCursor: 0,
retryType: 0,
isWeb: 1
});
const response = await this.http.get(url);
if (response.status !== 200) {
throw new NotFoundError('Could not fetch hashtag feed');
}
const data = response.data;
if (!data.body || !data.body.itemListData) {
return [];
}
return data.body.itemListData.map((item: any) => {
// Check if it's a video or photo
if (item.video) {
return this.parser.parseVideoObject(item);
} else if (item.images) {
return this.parser.parsePhotoObject(item);
}
throw new ValidationError('Unknown item type in hashtag feed');
});
}, `Get hashtag feed for ${options.hashtag}: ${options.count}`);
}
public async getHashtagVideos(hashtag: string, cursor: number = 0, count: number = 20): Promise<TikTokScrapResult<TikTokVideo[]>> {
return this.getHashtagFeed({
hashtag,
cursor,
count
}) as Promise<TikTokScrapResult<TikTokVideo[]>>;
}
public async getHashtagPhotos(hashtag: string, cursor: number = 0, count: number = 20): Promise<TikTokScrapResult<TikTokPhoto[]>> {
return this.getHashtagFeed({
hashtag,
cursor,
count
}) as Promise<TikTokScrapResult<TikTokPhoto[]>>;
}
public async getTrendingHashtags(count: number = 20): Promise<TikTokScrapResult<TikTokHashtag[]>> {
return this.executeRequest(async () => {
if (count <= 0 || count > 100) {
throw new ValidationError('Count must be between 1 and 100');
}
const url = this.buildUrl('https://www.tiktok.com', '/api/challenge/discover/', {
count,
isWeb: 1
});
const response = await this.http.get(url);
if (response.status !== 200) {
throw new NotFoundError('Could not fetch trending hashtags');
}
const data = response.data;
if (!data.body || !data.body.challengeList) {
return [];
}
return data.body.challengeList.map((item: any) => this.parser.parseHashtagObject(item));
}, `Get trending hashtags: ${count}`);
}
}
export default HashtagScraper;