apick-api 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 4.3.0
2
+
3
+ - Add `createGoogleMapsPlaceJob(placeIdOrUrl)` with the `GoogleMapsPlace` result type: rating, reviews count, 1-5 star distribution, top reviews (up to 10), opening hours, busy hours, similar places and photos. 20 points per place, refunded if not found; text is returned in English.
4
+ - 구글 지도 장소 상세·별점 분포·대표 리뷰(최대 10건) 조회 접수 메서드 `createGoogleMapsPlaceJob` 과 결과 타입 `GoogleMapsPlace` 를 추가했습니다. 건당 20포인트, 장소를 찾지 못하면 환불됩니다.
5
+
6
+ # 4.2.1
7
+
8
+ - `youtubeSubtitle()` fails with guidance to use an original-language track when an auto-translated caption (`translated: true`) is refused; such calls are not charged. Examples now pick a manual or original-language track. YouTube requests interrupted by a server restart are retried automatically.
9
+ - 자동 번역 자막(`translated: true`)을 받지 못하면 원어 자막을 쓰라는 안내와 함께 실패로 응답합니다(과금 없음). 예제는 수동·원어 자막을 고르도록 바꿨습니다. 서버 재시작으로 끊긴 유튜브 요청은 서버가 자동으로 다시 시도합니다.
10
+
1
11
  # 4.2.0
2
12
 
3
13
  - Add `amazonProduct`, `xProfile` and `xPost`, and collection jobs `createInstagramPostsJob`, `createInstagramCommentsJob`, `createTiktokSearchJob`, `createTiktokVideoJob`, `createTiktokCommentsJob`, `createAmazonReviewsJob` with `getScrapeJob` and `waitForScrapeJob`, all with typed results.
package/README.md CHANGED
@@ -78,7 +78,7 @@ Leave the allowed-IP list blank for unrestricted access. To restrict access, reg
78
78
  | `youtubeMetadata(url)` | 유튜브 영상 정보 / YouTube video metadata | JSON |
79
79
  | `youtubeThumbnail(url)` | 유튜브 썸네일 / YouTube thumbnail | JPG |
80
80
  | `youtubeSubtitleList(url)` | 유튜브 자막 언어 목록 / YouTube subtitle languages | JSON |
81
- | `youtubeSubtitle(url, lang, options)` | 유튜브 자막 다운로드 / YouTube subtitles | VTT·SRT·TXT |
81
+ | `youtubeSubtitle(url, lang, options)` | 유튜브 자막 다운로드(원어 자막 권장) / YouTube subtitles (prefer original-language tracks) | VTT·SRT·TXT |
82
82
  | `youtubeSearch(query, options)` | 유튜브 검색(정렬·필터) / YouTube search with sort and filters | JSON |
83
83
  | `youtubeChannel(channel, options)` | 유튜브 채널 정보·탭 목록 / YouTube channel and tab items | JSON |
84
84
  | `youtubePlaylist(url, options)` | 유튜브 재생목록 / YouTube playlist | JSON |
@@ -99,6 +99,7 @@ Leave the allowed-IP list blank for unrestricted access. To restrict access, reg
99
99
  | `createTiktokVideoJob(url, options)` | 틱톡 영상 정보 접수 / TikTok video job | JSON |
100
100
  | `createTiktokCommentsJob(url, options)` | 틱톡 댓글 수집 접수(최대 100) / TikTok comments job | JSON |
101
101
  | `createAmazonReviewsJob(urlOrAsin, options)` | 아마존 리뷰 수집 접수(최대 100) / Amazon reviews job | JSON |
102
+ | `createGoogleMapsPlaceJob(placeIdOrUrl, options)` | 구글 지도 장소 상세·별점 분포·대표 리뷰 접수 / Google Maps place details and top reviews job | JSON |
102
103
  | `getScrapeJob(jobId)` / `waitForScrapeJob(jobId, options)` | 수집 작업 상태·결과(무료, 결과 건수로 정산) / Collection job status and results | JSON |
103
104
  | `createTtsJob(text, options)` | 한국어 내레이션 작업 접수 / Create TTS job | JSON |
104
105
  | `getTtsJob(jobId)` | TTS 작업 상태 / TTS job status | JSON |
package/docs/guide.en.md CHANGED
@@ -96,6 +96,15 @@ const done = await client.waitForScrapeJob(job.data.job_id); // every 10 s, up t
96
96
  console.log(done.data.status, done.data.charged_point, done.data.items);
97
97
  ```
98
98
 
99
+ For a Google Maps place, pass the `place_id` from `googleMapsSearch` to get details, the star distribution and top reviews (up to 10). 20 points per place; text is returned in English.
100
+
101
+ ```js
102
+ const places = await client.googleMapsSearch('cafes near Gangnam station');
103
+ const placeJob = await client.createGoogleMapsPlaceJob(places.data.items[0].place_id);
104
+ const place = await client.waitForScrapeJob(placeJob.data.job_id);
105
+ console.log(place.data.items[0].rating_distribution, place.data.items[0].top_reviews);
106
+ ```
107
+
99
108
  ## OCR
100
109
 
101
110
  OCR accepts PNG and JPEG files up to 50MB.
@@ -128,7 +137,9 @@ await screenshot.save('./example.jpeg');
128
137
  // Public YouTube videos: a video URL or the 11-character video ID
129
138
  const video = await client.youtubeMetadata('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
130
139
  const tracks = await client.youtubeSubtitleList(video.data.video_id);
131
- const subtitle = await client.youtubeSubtitle(video.data.video_id, 'en', { format: 'srt' });
140
+ // Auto-translated captions (translated=true) may be refused by YouTube, so pick a manual or original-language track.
141
+ const track = tracks.data.subtitles[0] || tracks.data.automatic_captions.find(item => !item.translated);
142
+ const subtitle = await client.youtubeSubtitle(video.data.video_id, track.lang, { format: 'srt' });
132
143
  await subtitle.save('./' + subtitle.filename);
133
144
  await (await client.youtubeThumbnail(video.data.video_id)).save('./thumbnail.jpg');
134
145
 
package/docs/guide.ko.md CHANGED
@@ -96,6 +96,15 @@ const done = await client.waitForScrapeJob(job.data.job_id); // 기본 10초 간
96
96
  console.log(done.data.status, done.data.charged_point, done.data.items);
97
97
  ```
98
98
 
99
+ 구글 지도 장소는 `googleMapsSearch` 결과의 `place_id` 로 상세·별점 분포·대표 리뷰(최대 10건)를 받습니다. 건당 20포인트이고 텍스트는 영어로 제공합니다.
100
+
101
+ ```js
102
+ const places = await client.googleMapsSearch('강남역 카페');
103
+ const placeJob = await client.createGoogleMapsPlaceJob(places.data.items[0].place_id);
104
+ const place = await client.waitForScrapeJob(placeJob.data.job_id);
105
+ console.log(place.data.items[0].rating_distribution, place.data.items[0].top_reviews);
106
+ ```
107
+
99
108
  ## OCR
100
109
 
101
110
  PNG와 JPEG를 지원하며 최대 크기는 50MB입니다.
@@ -128,7 +137,9 @@ await screenshot.save('./example.jpeg');
128
137
  // 유튜브 공개 영상: 영상 주소 또는 11자리 영상 ID
129
138
  const video = await client.youtubeMetadata('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
130
139
  const tracks = await client.youtubeSubtitleList(video.data.video_id);
131
- const subtitle = await client.youtubeSubtitle(video.data.video_id, 'en', { format: 'srt' });
140
+ // 자동 번역 자막(translated=true)은 유튜브 제한으로 받지 못할 수 있어 수동 자막이나 원어 자동 자막을 고른다.
141
+ const track = tracks.data.subtitles[0] || tracks.data.automatic_captions.find(item => !item.translated);
142
+ const subtitle = await client.youtubeSubtitle(video.data.video_id, track.lang, { format: 'srt' });
132
143
  await subtitle.save('./' + subtitle.filename);
133
144
  await (await client.youtubeThumbnail(video.data.video_id)).save('./thumbnail.jpg');
134
145
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apick-api",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "Official zero-dependency Node.js client for APICK data, AI, and image APIs. 에이픽 데이터·AI·이미지 API 공식 Node.js SDK.",
5
5
  "type": "module",
6
6
  "main": "./src/index.cjs",
package/src/index.cjs CHANGED
@@ -97,13 +97,19 @@ function profileInput(value) {
97
97
  return /^https?:\/\//i.test(text) ? { url: text } : { username: text.replace(/^@/, '') };
98
98
  }
99
99
 
100
+ // 구글 지도 장소 입력: 주소면 url, 아니면 place_id(googleMapsSearch 결과의 place_id)로 보낸다.
101
+ function placeInput(value) {
102
+ const text = requiredString('placeIdOrUrl', value, 2048);
103
+ return /^https?:\/\//i.test(text) ? { url: text } : { place_id: text };
104
+ }
105
+
100
106
  // 아마존 상품 입력: 주소면 url, 10자리 ASIN 이면 asin 으로 보낸다.
101
107
  function amazonInput(value) {
102
108
  const text = requiredString('urlOrAsin', value, 2048);
103
109
  return /^https?:\/\//i.test(text) ? { url: text } : { asin: text.toUpperCase() };
104
110
  }
105
111
 
106
- const SCRAPE_JOB_PRODUCTS = Object.freeze({ instagram_posts: 100, instagram_comments: 15, tiktok_search: 50, tiktok_video: 1, tiktok_comments: 100, amazon_reviews: 100 });
112
+ const SCRAPE_JOB_PRODUCTS = Object.freeze({ instagram_posts: 100, instagram_comments: 15, tiktok_search: 50, tiktok_video: 1, tiktok_comments: 100, amazon_reviews: 100, google_maps_place: 1 });
107
113
 
108
114
  // 수집 작업 공통 옵션: maxResults(상품별 상한), idempotencyKey(응답을 못 받아 다시 보낼 때 같은 접수로 처리).
109
115
  function scrapeJobOptions(product, payload, options) {
@@ -708,6 +714,11 @@ class ApickClient {
708
714
  return this._createScrapeJob('amazon_reviews', amazonInput(urlOrAsin), options);
709
715
  }
710
716
 
717
+ // 구글 지도 장소 상세·대표 리뷰(건당 고정가). 텍스트는 영어로 온다.
718
+ createGoogleMapsPlaceJob(placeIdOrUrl, options) {
719
+ return this._createScrapeJob('google_maps_place', placeInput(placeIdOrUrl), options);
720
+ }
721
+
711
722
  // 상태·결과 조회(무료). 끝난 작업은 실제 결과 건수로 정산돼 있다.
712
723
  getScrapeJob(jobId) {
713
724
  if (typeof jobId !== 'string' || !/^[a-f0-9]{32}$/.test(jobId)) throw new TypeError('jobId must be a 32-character job ID.');
package/src/index.d.ts CHANGED
@@ -194,7 +194,37 @@ export interface AmazonReview {
194
194
  readonly variant: string;
195
195
  }
196
196
 
197
- export type ScrapeJobProduct = "instagram_posts" | "instagram_comments" | "tiktok_search" | "tiktok_video" | "tiktok_comments" | "amazon_reviews";
197
+ export interface GoogleMapsPlace {
198
+ readonly name: string;
199
+ readonly category: string;
200
+ readonly categories: readonly string[];
201
+ readonly address: string;
202
+ readonly country_code: string;
203
+ readonly phone: string;
204
+ readonly website: string;
205
+ readonly description: string;
206
+ readonly rating: number | null;
207
+ readonly reviews_count: number | null;
208
+ /** 별점별 리뷰 수. */
209
+ readonly rating_distribution: Readonly<Record<"1" | "2" | "3" | "4" | "5", number | null>>;
210
+ /** 대표 리뷰(최대 10건). 작성자는 공개 표시 이름만. */
211
+ readonly top_reviews: readonly { readonly reviewer_name: string; readonly rating: number | null; readonly posted_at: string; readonly text: string }[];
212
+ readonly open_hours: Readonly<Record<string, string>>;
213
+ /** day: 1=월 … 7=일 */
214
+ readonly popular_times: readonly { readonly day: number | null; readonly hours: readonly { readonly hour: string; readonly busy_percent: number | null }[] }[];
215
+ readonly similar_places: readonly { readonly name: string; readonly category: string; readonly rating: number | null; readonly reviews_count: number | null }[];
216
+ readonly photo_urls: readonly string[];
217
+ readonly main_image_url: string;
218
+ readonly is_claimed: boolean;
219
+ readonly permanently_closed: boolean;
220
+ readonly temporarily_closed: boolean;
221
+ readonly latitude: number | null;
222
+ readonly longitude: number | null;
223
+ readonly place_id: string;
224
+ readonly map_url: string;
225
+ }
226
+
227
+ export type ScrapeJobProduct = "instagram_posts" | "instagram_comments" | "tiktok_search" | "tiktok_video" | "tiktok_comments" | "amazon_reviews" | "google_maps_place";
198
228
 
199
229
  export interface ScrapeJobOptions {
200
230
  /** 최대 결과 수. 이 수 × 단가를 예약하고 실제 결과 건수만 차감한다(instagram_comments 15, tiktok_search 50, 그 밖 100). */
@@ -455,6 +485,7 @@ export interface YoutubeMetadata {
455
485
  age_limit: number | null; chapters: YoutubeChapter[]; thumbnail: string | null; thumbnails: YoutubeThumbnail[];
456
486
  subtitle_languages: string[]; automatic_caption_count: number;
457
487
  }
488
+ /** translated=true 인 자동 번역 자막은 유튜브 제한으로 받지 못할 수 있다. 수동 자막이나 원어 자동 자막을 권장한다. */
458
489
  export interface YoutubeSubtitleTrack { lang: string; name: string | null; auto: boolean; formats: string[]; translated?: boolean; }
459
490
  export interface YoutubeSubtitleList {
460
491
  video_id: string; title: string | null; original_language: string | null;
@@ -623,6 +654,7 @@ export class ApickClient {
623
654
  createTiktokVideoJob(url: string, options?: Pick<ScrapeJobOptions, "idempotencyKey">): Promise<ApickResult<ScrapeJob<TiktokVideo>>>;
624
655
  createTiktokCommentsJob(url: string, options?: ScrapeJobOptions): Promise<ApickResult<ScrapeJob<SocialComment>>>;
625
656
  createAmazonReviewsJob(urlOrAsin: string, options?: ScrapeJobOptions): Promise<ApickResult<ScrapeJob<AmazonReview>>>;
657
+ createGoogleMapsPlaceJob(placeIdOrUrl: string, options?: Pick<ScrapeJobOptions, "idempotencyKey">): Promise<ApickResult<ScrapeJob<GoogleMapsPlace>>>;
626
658
  getScrapeJob<T = unknown>(jobId: string): Promise<ApickResult<ScrapeJob<T>>>;
627
659
  waitForScrapeJob<T = unknown>(jobId: string, options?: { intervalMs?: number; timeoutMs?: number }): Promise<ApickResult<ScrapeJob<T>>>;
628
660
  screenshot(url: string): Promise<ApickBinaryResult>;