@domandigital/gbp 0.6.0 → 0.7.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 +10 -0
- package/README.md +19 -0
- package/dist/index.cjs +58 -0
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +55 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 59e6d85: Read a client's Facebook Page recommendations from the file Doman Digital publishes, so a site needs no Meta token.
|
|
8
|
+
|
|
9
|
+
- **`getPublishedFacebookRecommendations({ client })`** fetches `https://files.domandigital.co.uk/reviews/<client>.facebook.json`, which the portal writes daily from the Page token it already holds.
|
|
10
|
+
- Returns Meta's own `rating` (null when Meta gives none), `count` (every recommendation), `recommends` (the positive ones) and `reviews`: positive recommendations with words, with `id`, `comment` and `createdAt`. There is no author, because Meta does not return one.
|
|
11
|
+
- Throws `GbpPublishedError` like `getPublishedReviews`, so the caller keeps its last good copy. Refuses the Google file, another client's file and a count that does not add up.
|
|
12
|
+
|
|
3
13
|
## 0.6.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -90,6 +90,25 @@ is no copy at all. Never fall back to a hardcoded number.
|
|
|
90
90
|
A site that reads this file needs none of the `GBP_*` or `GOOGLE_BUSINESS_*`
|
|
91
91
|
variables below.
|
|
92
92
|
|
|
93
|
+
### Facebook recommendations
|
|
94
|
+
|
|
95
|
+
The portal also publishes each client's Facebook Page recommendations beside
|
|
96
|
+
the Google file, at `<client-slug>.facebook.json`:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { getPublishedFacebookRecommendations } from "@domandigital/gbp";
|
|
100
|
+
|
|
101
|
+
const { rating, count, recommends, reviews } = await getPublishedFacebookRecommendations({
|
|
102
|
+
client: "rmp-electrical",
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Facebook has recommendations, not star reviews. `count` is every
|
|
107
|
+
recommendation on the Page, `recommends` the positive ones, and `rating` is
|
|
108
|
+
Meta's own overall figure (null when Meta gives none). `reviews` holds the
|
|
109
|
+
positive recommendations that have words. There is no author, because Meta
|
|
110
|
+
does not return one. It throws on failure like `getPublishedReviews`.
|
|
111
|
+
|
|
93
112
|
## Why Business Profile API, not Places API
|
|
94
113
|
|
|
95
114
|
Places API's `Review` object has no field for the business's reply, full
|
package/dist/index.cjs
CHANGED
|
@@ -36,14 +36,17 @@ __export(index_exports, {
|
|
|
36
36
|
getBusinessReviews: () => getBusinessReviews,
|
|
37
37
|
getBusinessReviewsSafe: () => getBusinessReviewsSafe,
|
|
38
38
|
getGoogleOAuthAccessToken: () => getGoogleOAuthAccessToken,
|
|
39
|
+
getPublishedFacebookRecommendations: () => getPublishedFacebookRecommendations,
|
|
39
40
|
getPublishedReviews: () => getPublishedReviews,
|
|
40
41
|
getPublishedReviewsSafe: () => getPublishedReviewsSafe,
|
|
41
42
|
hasGoogleOAuthCredentials: () => hasGoogleOAuthCredentials,
|
|
42
43
|
invalidateAccessToken: () => invalidateAccessToken,
|
|
43
44
|
isBusinessProfileConfigured: () => isBusinessProfileConfigured,
|
|
44
45
|
parseListReviewsResponse: () => parseListReviewsResponse,
|
|
46
|
+
parsePublishedFacebook: () => parsePublishedFacebook,
|
|
45
47
|
parsePublishedReviews: () => parsePublishedReviews,
|
|
46
48
|
parseRetryAfter: () => parseRetryAfter,
|
|
49
|
+
publishedFacebookUrl: () => publishedFacebookUrl,
|
|
47
50
|
publishedReviewsUrl: () => publishedReviewsUrl
|
|
48
51
|
});
|
|
49
52
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -516,6 +519,58 @@ async function getPublishedReviews(options) {
|
|
|
516
519
|
return { ...result, reviews: order === "shuffle" ? shuffle(reviews) : reviews };
|
|
517
520
|
}
|
|
518
521
|
|
|
522
|
+
// src/facebook-published.ts
|
|
523
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
524
|
+
var isTime2 = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v));
|
|
525
|
+
var isCount = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
526
|
+
function parsePublishedFacebook(body, client) {
|
|
527
|
+
if (!isRecord3(body)) throw new GbpPublishedError("invalid", client, "the file is not an object");
|
|
528
|
+
if (body.schema !== PUBLISHED_REVIEWS_SCHEMA) throw new GbpPublishedError("unknown_schema", client, `schema ${String(body.schema)}`);
|
|
529
|
+
if (body.client !== client || body.source !== "facebook") {
|
|
530
|
+
throw new GbpPublishedError("invalid", client, `the file is for ${String(body.client)} (${String(body.source)})`);
|
|
531
|
+
}
|
|
532
|
+
const { rating, count, recommends } = body;
|
|
533
|
+
if (rating !== null && (typeof rating !== "number" || !(rating >= 0 && rating <= 5))) {
|
|
534
|
+
throw new GbpPublishedError("invalid", client, "rating out of range");
|
|
535
|
+
}
|
|
536
|
+
if (!isCount(count) || !isCount(recommends) || recommends > count) {
|
|
537
|
+
throw new GbpPublishedError("invalid", client, "count or recommends is not a whole number within the count");
|
|
538
|
+
}
|
|
539
|
+
if (!isTime2(body.synced_at) || !isTime2(body.published_at) || !Array.isArray(body.reviews)) {
|
|
540
|
+
throw new GbpPublishedError("invalid", client, "synced_at, published_at or reviews missing");
|
|
541
|
+
}
|
|
542
|
+
const reviews = body.reviews.flatMap(
|
|
543
|
+
(v) => isRecord3(v) && typeof v.id === "string" && typeof v.comment === "string" && v.comment && isTime2(v.createdAt) ? [{ id: v.id, comment: v.comment, createdAt: v.createdAt }] : []
|
|
544
|
+
);
|
|
545
|
+
return { rating, count, recommends, reviews, syncedAt: body.synced_at, publishedAt: body.published_at };
|
|
546
|
+
}
|
|
547
|
+
function publishedFacebookUrl(client, baseUrl) {
|
|
548
|
+
if (!/^[a-z0-9][a-z0-9-]{0,99}$/.test(client)) throw new RangeError(`not a client slug: ${client}`);
|
|
549
|
+
const base = (baseUrl ?? process.env.GBP_REVIEWS_BASE_URL ?? DEFAULT_PUBLISHED_REVIEWS_BASE_URL).replace(/\/+$/, "");
|
|
550
|
+
return `${base}/${client}.facebook.json`;
|
|
551
|
+
}
|
|
552
|
+
async function getPublishedFacebookRecommendations(options) {
|
|
553
|
+
const { client, limit, next, request } = options;
|
|
554
|
+
const url = publishedFacebookUrl(client, options.baseUrl);
|
|
555
|
+
const init = {
|
|
556
|
+
headers: { Accept: "application/json" },
|
|
557
|
+
next: next ?? { revalidate: 86400, tags: ["facebook-reviews"] }
|
|
558
|
+
};
|
|
559
|
+
const { response } = await fetchWithRetry(url, init, "reviews.facebook", resolvePolicy(request));
|
|
560
|
+
if (!response.ok) {
|
|
561
|
+
const body2 = excerpt(await readErrorBody(response), 120);
|
|
562
|
+
throw new GbpPublishedError("http", client, String(response.status), response.status, body2 || void 0);
|
|
563
|
+
}
|
|
564
|
+
let body;
|
|
565
|
+
try {
|
|
566
|
+
body = await response.json();
|
|
567
|
+
} catch {
|
|
568
|
+
throw new GbpPublishedError("invalid", client, "the file is not JSON");
|
|
569
|
+
}
|
|
570
|
+
const result = parsePublishedFacebook(body, client);
|
|
571
|
+
return limit === void 0 ? result : { ...result, reviews: result.reviews.slice(0, limit) };
|
|
572
|
+
}
|
|
573
|
+
|
|
519
574
|
// src/safe.ts
|
|
520
575
|
var DEFAULT_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
521
576
|
var DEFAULT_MAX_STALE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -649,13 +704,16 @@ function getPublishedReviewsSafe(options) {
|
|
|
649
704
|
getBusinessReviews,
|
|
650
705
|
getBusinessReviewsSafe,
|
|
651
706
|
getGoogleOAuthAccessToken,
|
|
707
|
+
getPublishedFacebookRecommendations,
|
|
652
708
|
getPublishedReviews,
|
|
653
709
|
getPublishedReviewsSafe,
|
|
654
710
|
hasGoogleOAuthCredentials,
|
|
655
711
|
invalidateAccessToken,
|
|
656
712
|
isBusinessProfileConfigured,
|
|
657
713
|
parseListReviewsResponse,
|
|
714
|
+
parsePublishedFacebook,
|
|
658
715
|
parsePublishedReviews,
|
|
659
716
|
parseRetryAfter,
|
|
717
|
+
publishedFacebookUrl,
|
|
660
718
|
publishedReviewsUrl
|
|
661
719
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -282,6 +282,46 @@ declare function publishedReviewsUrl(client: string, baseUrl?: string): string;
|
|
|
282
282
|
*/
|
|
283
283
|
declare function getPublishedReviews(options: GetPublishedReviewsOptions): Promise<PublishedReviewsResult>;
|
|
284
284
|
|
|
285
|
+
interface FacebookRecommendation {
|
|
286
|
+
id: string;
|
|
287
|
+
comment: string;
|
|
288
|
+
/** ISO 8601. */
|
|
289
|
+
createdAt: string;
|
|
290
|
+
}
|
|
291
|
+
interface PublishedFacebookResult {
|
|
292
|
+
/** Meta's overall rating out of 5, or null when Meta gives none. */
|
|
293
|
+
rating: number | null;
|
|
294
|
+
/** Every recommendation on the Page, positive and negative. */
|
|
295
|
+
count: number;
|
|
296
|
+
/** The positive ones. */
|
|
297
|
+
recommends: number;
|
|
298
|
+
/** Positive recommendations with words, newest first. */
|
|
299
|
+
reviews: FacebookRecommendation[];
|
|
300
|
+
/** When the portal last read the Page in full. */
|
|
301
|
+
syncedAt: string;
|
|
302
|
+
publishedAt: string;
|
|
303
|
+
}
|
|
304
|
+
interface GetPublishedFacebookOptions {
|
|
305
|
+
/** The client's slug, e.g. `"rmp-electrical"`. */
|
|
306
|
+
client: string;
|
|
307
|
+
/** Default: `GBP_REVIEWS_BASE_URL`, else the Doman Digital address. */
|
|
308
|
+
baseUrl?: string;
|
|
309
|
+
/** Max recommendations to return. Default: all of them. */
|
|
310
|
+
limit?: number;
|
|
311
|
+
/** Cache hint passed to `fetch` for Next.js. Default: revalidate daily, tag `facebook-reviews`. */
|
|
312
|
+
next?: {
|
|
313
|
+
revalidate?: number;
|
|
314
|
+
tags?: string[];
|
|
315
|
+
};
|
|
316
|
+
request?: GbpRequestOptions;
|
|
317
|
+
}
|
|
318
|
+
/** Validate a published Facebook file. A malformed entry is skipped; a malformed file throws. */
|
|
319
|
+
declare function parsePublishedFacebook(body: unknown, client: string): PublishedFacebookResult;
|
|
320
|
+
/** The URL a client's Facebook file is read from. */
|
|
321
|
+
declare function publishedFacebookUrl(client: string, baseUrl?: string): string;
|
|
322
|
+
/** Fetch a client's published Facebook recommendations. Throws {@link GbpPublishedError} or `GbpTimeoutError`. */
|
|
323
|
+
declare function getPublishedFacebookRecommendations(options: GetPublishedFacebookOptions): Promise<PublishedFacebookResult>;
|
|
324
|
+
|
|
285
325
|
/**
|
|
286
326
|
* Typed failures, so a site can tell "reauthorise the Google account" from
|
|
287
327
|
* "Google is having a bad minute" from "our code looped", and log the
|
|
@@ -461,4 +501,4 @@ type SafePublishedReviewsResult = SafeReviewsResult<BusinessReviewsResult & Part
|
|
|
461
501
|
*/
|
|
462
502
|
declare function getPublishedReviewsSafe(options: GetPublishedReviewsOptions & SafeReviewsOptions): Promise<SafePublishedReviewsResult>;
|
|
463
503
|
|
|
464
|
-
export { type AccessTokenOptions, type BusinessReview, type BusinessReviewsResult, DEFAULT_AUTH_RETRY_MS, DEFAULT_MAX_PAGES, DEFAULT_MAX_STALE_MS, DEFAULT_PUBLISHED_REVIEWS_BASE_URL, DEFAULT_REPORT_INTERVAL_MS, DEFAULT_REQUEST_POLICY, GbpApiError, GbpAuthError, type GbpAuthErrorCode, GbpError, type GbpFailureReport, GbpPaginationError, GbpPublishedError, type GbpRequestOptions, GbpTimeoutError, type GetBusinessReviewsOptions, type GetPublishedReviewsOptions, PUBLISHED_REVIEWS_SCHEMA, type PublishedReviewsResult, type ReviewMedia, type ReviewOrder, type ReviewReply, type ReviewReplyState, type ReviewsSource, type SafePublishedReviewsResult, type SafeReviewsOptions, type SafeReviewsResult, getBusinessReviews, getBusinessReviewsSafe, getGoogleOAuthAccessToken, getPublishedReviews, getPublishedReviewsSafe, hasGoogleOAuthCredentials, invalidateAccessToken, isBusinessProfileConfigured, parseListReviewsResponse, parsePublishedReviews, parseRetryAfter, publishedReviewsUrl };
|
|
504
|
+
export { type AccessTokenOptions, type BusinessReview, type BusinessReviewsResult, DEFAULT_AUTH_RETRY_MS, DEFAULT_MAX_PAGES, DEFAULT_MAX_STALE_MS, DEFAULT_PUBLISHED_REVIEWS_BASE_URL, DEFAULT_REPORT_INTERVAL_MS, DEFAULT_REQUEST_POLICY, type FacebookRecommendation, GbpApiError, GbpAuthError, type GbpAuthErrorCode, GbpError, type GbpFailureReport, GbpPaginationError, GbpPublishedError, type GbpRequestOptions, GbpTimeoutError, type GetBusinessReviewsOptions, type GetPublishedFacebookOptions, type GetPublishedReviewsOptions, PUBLISHED_REVIEWS_SCHEMA, type PublishedFacebookResult, type PublishedReviewsResult, type ReviewMedia, type ReviewOrder, type ReviewReply, type ReviewReplyState, type ReviewsSource, type SafePublishedReviewsResult, type SafeReviewsOptions, type SafeReviewsResult, getBusinessReviews, getBusinessReviewsSafe, getGoogleOAuthAccessToken, getPublishedFacebookRecommendations, getPublishedReviews, getPublishedReviewsSafe, hasGoogleOAuthCredentials, invalidateAccessToken, isBusinessProfileConfigured, parseListReviewsResponse, parsePublishedFacebook, parsePublishedReviews, parseRetryAfter, publishedFacebookUrl, publishedReviewsUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -282,6 +282,46 @@ declare function publishedReviewsUrl(client: string, baseUrl?: string): string;
|
|
|
282
282
|
*/
|
|
283
283
|
declare function getPublishedReviews(options: GetPublishedReviewsOptions): Promise<PublishedReviewsResult>;
|
|
284
284
|
|
|
285
|
+
interface FacebookRecommendation {
|
|
286
|
+
id: string;
|
|
287
|
+
comment: string;
|
|
288
|
+
/** ISO 8601. */
|
|
289
|
+
createdAt: string;
|
|
290
|
+
}
|
|
291
|
+
interface PublishedFacebookResult {
|
|
292
|
+
/** Meta's overall rating out of 5, or null when Meta gives none. */
|
|
293
|
+
rating: number | null;
|
|
294
|
+
/** Every recommendation on the Page, positive and negative. */
|
|
295
|
+
count: number;
|
|
296
|
+
/** The positive ones. */
|
|
297
|
+
recommends: number;
|
|
298
|
+
/** Positive recommendations with words, newest first. */
|
|
299
|
+
reviews: FacebookRecommendation[];
|
|
300
|
+
/** When the portal last read the Page in full. */
|
|
301
|
+
syncedAt: string;
|
|
302
|
+
publishedAt: string;
|
|
303
|
+
}
|
|
304
|
+
interface GetPublishedFacebookOptions {
|
|
305
|
+
/** The client's slug, e.g. `"rmp-electrical"`. */
|
|
306
|
+
client: string;
|
|
307
|
+
/** Default: `GBP_REVIEWS_BASE_URL`, else the Doman Digital address. */
|
|
308
|
+
baseUrl?: string;
|
|
309
|
+
/** Max recommendations to return. Default: all of them. */
|
|
310
|
+
limit?: number;
|
|
311
|
+
/** Cache hint passed to `fetch` for Next.js. Default: revalidate daily, tag `facebook-reviews`. */
|
|
312
|
+
next?: {
|
|
313
|
+
revalidate?: number;
|
|
314
|
+
tags?: string[];
|
|
315
|
+
};
|
|
316
|
+
request?: GbpRequestOptions;
|
|
317
|
+
}
|
|
318
|
+
/** Validate a published Facebook file. A malformed entry is skipped; a malformed file throws. */
|
|
319
|
+
declare function parsePublishedFacebook(body: unknown, client: string): PublishedFacebookResult;
|
|
320
|
+
/** The URL a client's Facebook file is read from. */
|
|
321
|
+
declare function publishedFacebookUrl(client: string, baseUrl?: string): string;
|
|
322
|
+
/** Fetch a client's published Facebook recommendations. Throws {@link GbpPublishedError} or `GbpTimeoutError`. */
|
|
323
|
+
declare function getPublishedFacebookRecommendations(options: GetPublishedFacebookOptions): Promise<PublishedFacebookResult>;
|
|
324
|
+
|
|
285
325
|
/**
|
|
286
326
|
* Typed failures, so a site can tell "reauthorise the Google account" from
|
|
287
327
|
* "Google is having a bad minute" from "our code looped", and log the
|
|
@@ -461,4 +501,4 @@ type SafePublishedReviewsResult = SafeReviewsResult<BusinessReviewsResult & Part
|
|
|
461
501
|
*/
|
|
462
502
|
declare function getPublishedReviewsSafe(options: GetPublishedReviewsOptions & SafeReviewsOptions): Promise<SafePublishedReviewsResult>;
|
|
463
503
|
|
|
464
|
-
export { type AccessTokenOptions, type BusinessReview, type BusinessReviewsResult, DEFAULT_AUTH_RETRY_MS, DEFAULT_MAX_PAGES, DEFAULT_MAX_STALE_MS, DEFAULT_PUBLISHED_REVIEWS_BASE_URL, DEFAULT_REPORT_INTERVAL_MS, DEFAULT_REQUEST_POLICY, GbpApiError, GbpAuthError, type GbpAuthErrorCode, GbpError, type GbpFailureReport, GbpPaginationError, GbpPublishedError, type GbpRequestOptions, GbpTimeoutError, type GetBusinessReviewsOptions, type GetPublishedReviewsOptions, PUBLISHED_REVIEWS_SCHEMA, type PublishedReviewsResult, type ReviewMedia, type ReviewOrder, type ReviewReply, type ReviewReplyState, type ReviewsSource, type SafePublishedReviewsResult, type SafeReviewsOptions, type SafeReviewsResult, getBusinessReviews, getBusinessReviewsSafe, getGoogleOAuthAccessToken, getPublishedReviews, getPublishedReviewsSafe, hasGoogleOAuthCredentials, invalidateAccessToken, isBusinessProfileConfigured, parseListReviewsResponse, parsePublishedReviews, parseRetryAfter, publishedReviewsUrl };
|
|
504
|
+
export { type AccessTokenOptions, type BusinessReview, type BusinessReviewsResult, DEFAULT_AUTH_RETRY_MS, DEFAULT_MAX_PAGES, DEFAULT_MAX_STALE_MS, DEFAULT_PUBLISHED_REVIEWS_BASE_URL, DEFAULT_REPORT_INTERVAL_MS, DEFAULT_REQUEST_POLICY, type FacebookRecommendation, GbpApiError, GbpAuthError, type GbpAuthErrorCode, GbpError, type GbpFailureReport, GbpPaginationError, GbpPublishedError, type GbpRequestOptions, GbpTimeoutError, type GetBusinessReviewsOptions, type GetPublishedFacebookOptions, type GetPublishedReviewsOptions, PUBLISHED_REVIEWS_SCHEMA, type PublishedFacebookResult, type PublishedReviewsResult, type ReviewMedia, type ReviewOrder, type ReviewReply, type ReviewReplyState, type ReviewsSource, type SafePublishedReviewsResult, type SafeReviewsOptions, type SafeReviewsResult, getBusinessReviews, getBusinessReviewsSafe, getGoogleOAuthAccessToken, getPublishedFacebookRecommendations, getPublishedReviews, getPublishedReviewsSafe, hasGoogleOAuthCredentials, invalidateAccessToken, isBusinessProfileConfigured, parseListReviewsResponse, parsePublishedFacebook, parsePublishedReviews, parseRetryAfter, publishedFacebookUrl, publishedReviewsUrl };
|
package/dist/index.js
CHANGED
|
@@ -466,6 +466,58 @@ async function getPublishedReviews(options) {
|
|
|
466
466
|
return { ...result, reviews: order === "shuffle" ? shuffle(reviews) : reviews };
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
+
// src/facebook-published.ts
|
|
470
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
471
|
+
var isTime2 = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v));
|
|
472
|
+
var isCount = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
473
|
+
function parsePublishedFacebook(body, client) {
|
|
474
|
+
if (!isRecord3(body)) throw new GbpPublishedError("invalid", client, "the file is not an object");
|
|
475
|
+
if (body.schema !== PUBLISHED_REVIEWS_SCHEMA) throw new GbpPublishedError("unknown_schema", client, `schema ${String(body.schema)}`);
|
|
476
|
+
if (body.client !== client || body.source !== "facebook") {
|
|
477
|
+
throw new GbpPublishedError("invalid", client, `the file is for ${String(body.client)} (${String(body.source)})`);
|
|
478
|
+
}
|
|
479
|
+
const { rating, count, recommends } = body;
|
|
480
|
+
if (rating !== null && (typeof rating !== "number" || !(rating >= 0 && rating <= 5))) {
|
|
481
|
+
throw new GbpPublishedError("invalid", client, "rating out of range");
|
|
482
|
+
}
|
|
483
|
+
if (!isCount(count) || !isCount(recommends) || recommends > count) {
|
|
484
|
+
throw new GbpPublishedError("invalid", client, "count or recommends is not a whole number within the count");
|
|
485
|
+
}
|
|
486
|
+
if (!isTime2(body.synced_at) || !isTime2(body.published_at) || !Array.isArray(body.reviews)) {
|
|
487
|
+
throw new GbpPublishedError("invalid", client, "synced_at, published_at or reviews missing");
|
|
488
|
+
}
|
|
489
|
+
const reviews = body.reviews.flatMap(
|
|
490
|
+
(v) => isRecord3(v) && typeof v.id === "string" && typeof v.comment === "string" && v.comment && isTime2(v.createdAt) ? [{ id: v.id, comment: v.comment, createdAt: v.createdAt }] : []
|
|
491
|
+
);
|
|
492
|
+
return { rating, count, recommends, reviews, syncedAt: body.synced_at, publishedAt: body.published_at };
|
|
493
|
+
}
|
|
494
|
+
function publishedFacebookUrl(client, baseUrl) {
|
|
495
|
+
if (!/^[a-z0-9][a-z0-9-]{0,99}$/.test(client)) throw new RangeError(`not a client slug: ${client}`);
|
|
496
|
+
const base = (baseUrl ?? process.env.GBP_REVIEWS_BASE_URL ?? DEFAULT_PUBLISHED_REVIEWS_BASE_URL).replace(/\/+$/, "");
|
|
497
|
+
return `${base}/${client}.facebook.json`;
|
|
498
|
+
}
|
|
499
|
+
async function getPublishedFacebookRecommendations(options) {
|
|
500
|
+
const { client, limit, next, request } = options;
|
|
501
|
+
const url = publishedFacebookUrl(client, options.baseUrl);
|
|
502
|
+
const init = {
|
|
503
|
+
headers: { Accept: "application/json" },
|
|
504
|
+
next: next ?? { revalidate: 86400, tags: ["facebook-reviews"] }
|
|
505
|
+
};
|
|
506
|
+
const { response } = await fetchWithRetry(url, init, "reviews.facebook", resolvePolicy(request));
|
|
507
|
+
if (!response.ok) {
|
|
508
|
+
const body2 = excerpt(await readErrorBody(response), 120);
|
|
509
|
+
throw new GbpPublishedError("http", client, String(response.status), response.status, body2 || void 0);
|
|
510
|
+
}
|
|
511
|
+
let body;
|
|
512
|
+
try {
|
|
513
|
+
body = await response.json();
|
|
514
|
+
} catch {
|
|
515
|
+
throw new GbpPublishedError("invalid", client, "the file is not JSON");
|
|
516
|
+
}
|
|
517
|
+
const result = parsePublishedFacebook(body, client);
|
|
518
|
+
return limit === void 0 ? result : { ...result, reviews: result.reviews.slice(0, limit) };
|
|
519
|
+
}
|
|
520
|
+
|
|
469
521
|
// src/safe.ts
|
|
470
522
|
var DEFAULT_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
471
523
|
var DEFAULT_MAX_STALE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -598,13 +650,16 @@ export {
|
|
|
598
650
|
getBusinessReviews,
|
|
599
651
|
getBusinessReviewsSafe,
|
|
600
652
|
getGoogleOAuthAccessToken,
|
|
653
|
+
getPublishedFacebookRecommendations,
|
|
601
654
|
getPublishedReviews,
|
|
602
655
|
getPublishedReviewsSafe,
|
|
603
656
|
hasGoogleOAuthCredentials,
|
|
604
657
|
invalidateAccessToken,
|
|
605
658
|
isBusinessProfileConfigured,
|
|
606
659
|
parseListReviewsResponse,
|
|
660
|
+
parsePublishedFacebook,
|
|
607
661
|
parsePublishedReviews,
|
|
608
662
|
parseRetryAfter,
|
|
663
|
+
publishedFacebookUrl,
|
|
609
664
|
publishedReviewsUrl
|
|
610
665
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domandigital/gbp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Zero-dependency Google Business Profile client: OAuth refresh-token auth and a reviews (with owner replies) fetch, shared across Doman Digital / IDS client sites.",
|
|
6
6
|
"license": "Apache-2.0",
|