@bunizao/contracts 0.8.0 → 0.10.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.
@@ -104,9 +104,10 @@ export interface ReaderMuteResult {
104
104
  so this can never be used to probe which addresses have commented. */
105
105
  export interface ReaderResendInput {
106
106
  email: string;
107
- /** Carries the original "notify me of replies" intent into the fresh
108
- verification link, recovered from the stale token's payload. Optional;
109
- a bare resend defaults to false. */
107
+ /** Newsletter opt-in, despite the name: carried into the fresh
108
+ verification link from the stale token's payload, and confirming the
109
+ address activates a newsletter subscription. Not the reply-mail switch
110
+ (that is `ReaderMe.notifyReplies`). Optional; a bare resend sends false. */
110
111
  notifyReplies?: boolean;
111
112
  locale?: CommentLocale;
112
113
  }
@@ -379,8 +380,10 @@ export interface CommentCreateInput {
379
380
  /** Signed server timestamp minted at first interaction with the compose
380
381
  box — see "The risk stack" step 4 (dwell time). */
381
382
  dwellToken: string;
382
- /** Reader accepted the post-comment subscribe offer. Only takes effect
383
- once the address is verified. */
383
+ /** Newsletter opt-in, despite the name: the reader accepted the
384
+ post-comment subscribe offer, and confirming the address activates a
385
+ newsletter subscription. Not the reply-mail switch (that is
386
+ `ReaderMe.notifyReplies`). Both clients send false. */
384
387
  notifyReplies?: boolean;
385
388
  locale?: CommentLocale;
386
389
  /** Client evidence, optional and never a gate -- see `ClientEvidence`. */
@@ -438,47 +441,10 @@ export interface CommentPolicy {
438
441
  requireVerifiedEmail: boolean;
439
442
  }
440
443
  export declare const DEFAULT_COMMENT_POLICY: CommentPolicy;
441
- /** Ghost internal tags that override the site-wide default, one knob each.
442
- *
443
- * Internal tags are the switch because they are the only per-post field the
444
- * author already edits in Ghost, and both halves of the system can read them:
445
- * the site sees them through the Admin API at build time, site-api through the
446
- * Content API at request time (`include=tags` returns internal tags). One
447
- * source of truth, no settings table, no admin screen to keep in sync.
448
- *
449
- * `#no-comments` predates the others and is kept: it always meant "this post
450
- * takes no more comments", which is `readonly`. */
451
444
  export declare const COMMENT_POLICY_TAGS: {
452
- readonly 'comments-off': (policy: CommentPolicy) => {
453
- /** Whether the heart can be pressed — on the post and on its comments.
454
- Independent of `mode`: a post can take reactions with comments off, and
455
- an open thread can refuse them. */
456
- reactions: boolean;
457
- /** Accept a comment only from an address that has been verified. Anonymous
458
- and unverified-email writers are refused rather than held. */
459
- requireVerifiedEmail: boolean;
460
- mode: CommentsMode;
461
- };
462
- readonly 'comments-readonly': (policy: CommentPolicy) => {
463
- /** Whether the heart can be pressed — on the post and on its comments.
464
- Independent of `mode`: a post can take reactions with comments off, and
465
- an open thread can refuse them. */
466
- reactions: boolean;
467
- /** Accept a comment only from an address that has been verified. Anonymous
468
- and unverified-email writers are refused rather than held. */
469
- requireVerifiedEmail: boolean;
470
- mode: CommentsMode;
471
- };
472
- readonly 'no-comments': (policy: CommentPolicy) => {
473
- /** Whether the heart can be pressed — on the post and on its comments.
474
- Independent of `mode`: a post can take reactions with comments off, and
475
- an open thread can refuse them. */
476
- reactions: boolean;
477
- /** Accept a comment only from an address that has been verified. Anonymous
478
- and unverified-email writers are refused rather than held. */
479
- requireVerifiedEmail: boolean;
480
- mode: CommentsMode;
481
- };
445
+ readonly 'comments-off': (policy: CommentPolicy) => CommentPolicy;
446
+ readonly 'comments-readonly': (policy: CommentPolicy) => CommentPolicy;
447
+ readonly 'no-comments': (policy: CommentPolicy) => CommentPolicy;
482
448
  readonly 'reactions-off': (policy: CommentPolicy) => {
483
449
  mode: CommentsMode;
484
450
  /** Accept a comment only from an address that has been verified. Anonymous
@@ -502,7 +468,8 @@ export interface CommentPolicyTagLike {
502
468
  slug?: string | null;
503
469
  }
504
470
  /** Fold a post's tags onto the site-wide default. Order does not matter:
505
- every tag sets one field, and a tag the map does not know is ignored. */
471
+ every tag sets one field, conflicting mode tags keep the stricter mode, and
472
+ a tag the map does not know is ignored. */
506
473
  export declare function commentPolicyFromTags(tags: readonly CommentPolicyTagLike[] | null | undefined, base?: CommentPolicy): CommentPolicy;
507
474
  /** Whether the post takes new comments — the one question the API asks. Both
508
475
  `readonly` and `off` answer no; the difference between them is drawn, not
@@ -547,17 +514,36 @@ export declare function avatarClass(seed: number): number;
547
514
  /** A seed in colour class `cls`; `variety` fills the digits that pick the
548
515
  expression, pose and head shape. */
549
516
  export declare function seedInClass(cls: number, variety: number): number;
550
- /** POST body for `READER_AVATAR_SEED_PATH`. */
517
+ /** How many faces an `offer` holds. */
518
+ export declare const AVATAR_OFFER_SIZE = 5;
519
+ /** POST body for `READER_AVATAR_SEED_PATH`.
520
+
521
+ - `issue` (the default): one seed, handed out and counted. The first face
522
+ a browser gets.
523
+ - `offer`: AVATAR_OFFER_SIZE candidates to choose from, nothing counted
524
+ or kept -- a reader browsing faces has not taken one yet.
525
+ - `choose`: `seed` becomes the caller's face, counted and, for a verified
526
+ reader, kept. */
551
527
  export interface AvatarSeedInput {
552
- /** The seed on screen now. The answer is always in a different colour
553
- class, so every re-roll visibly changes the face. */
528
+ /** The seed on screen now. `issue` and `offer` always answer in other
529
+ colour classes, so every new face visibly changes. */
554
530
  current?: number | null;
531
+ mode?: 'issue' | 'offer' | 'choose';
532
+ /** Required for `choose`. */
533
+ seed?: number;
555
534
  }
535
+ /** The answer to `issue` and `choose`. */
556
536
  export interface AvatarSeedResult {
557
- /** In the least-used colour class across the site, never `current`'s. */
537
+ /** For `issue`, in the least-used colour class across the site, never
538
+ `current`'s. For `choose`, the seed that was sent. */
558
539
  seed: number;
559
540
  /** True when the caller is a verified reader and the seed is now theirs
560
541
  everywhere. False for anyone else: the client keeps it and sends it as
561
542
  `CommentCreateInput.avatarSeed`. */
562
543
  persisted: boolean;
563
544
  }
545
+ /** The answer to `offer`: one seed in each of the AVATAR_OFFER_SIZE
546
+ least-used colour classes, none of them `current`'s. */
547
+ export interface AvatarSeedOfferResult {
548
+ seeds: number[];
549
+ }
package/dist/comments.js CHANGED
@@ -32,10 +32,11 @@ var DEFAULT_COMMENT_POLICY = {
32
32
  reactions: true,
33
33
  requireVerifiedEmail: false
34
34
  };
35
+ var withStricterMode = (policy, mode) => COMMENTS_MODES.indexOf(mode) > COMMENTS_MODES.indexOf(policy.mode) ? { ...policy, mode } : policy;
35
36
  var COMMENT_POLICY_TAGS = {
36
- "comments-off": (policy) => ({ ...policy, mode: "off" }),
37
- "comments-readonly": (policy) => ({ ...policy, mode: "readonly" }),
38
- "no-comments": (policy) => ({ ...policy, mode: "readonly" }),
37
+ "comments-off": (policy) => withStricterMode(policy, "off"),
38
+ "comments-readonly": (policy) => withStricterMode(policy, "readonly"),
39
+ "no-comments": (policy) => withStricterMode(policy, "readonly"),
39
40
  "reactions-off": (policy) => ({ ...policy, reactions: false }),
40
41
  "comments-verified": (policy) => ({ ...policy, requireVerifiedEmail: true })
41
42
  };
@@ -73,8 +74,10 @@ function seedInClass(cls, variety) {
73
74
  const span = Math.floor(MAX_AVATAR_SEED / AVATAR_CLASSES);
74
75
  return Math.abs(Math.trunc(variety)) % span * AVATAR_CLASSES + Math.abs(Math.trunc(cls)) % AVATAR_CLASSES;
75
76
  }
77
+ var AVATAR_OFFER_SIZE = 5;
76
78
  export {
77
79
  AVATAR_CLASSES,
80
+ AVATAR_OFFER_SIZE,
78
81
  COMMENTS_MODES,
79
82
  COMMENT_ANCHOR_PATTERN,
80
83
  COMMENT_ANCHOR_TOKEN_LENGTH,
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './analytics';
2
2
  export * from './admin';
3
3
  export * from './comments';
4
4
  export * from './content';
5
+ export * from './instagram';
5
6
  export * from './listening';
6
7
  export * from './messages';
7
8
  export * from './mood';
package/dist/index.js CHANGED
@@ -44,10 +44,11 @@ var DEFAULT_COMMENT_POLICY = {
44
44
  reactions: true,
45
45
  requireVerifiedEmail: false
46
46
  };
47
+ var withStricterMode = (policy, mode) => COMMENTS_MODES.indexOf(mode) > COMMENTS_MODES.indexOf(policy.mode) ? { ...policy, mode } : policy;
47
48
  var COMMENT_POLICY_TAGS = {
48
- "comments-off": (policy) => ({ ...policy, mode: "off" }),
49
- "comments-readonly": (policy) => ({ ...policy, mode: "readonly" }),
50
- "no-comments": (policy) => ({ ...policy, mode: "readonly" }),
49
+ "comments-off": (policy) => withStricterMode(policy, "off"),
50
+ "comments-readonly": (policy) => withStricterMode(policy, "readonly"),
51
+ "no-comments": (policy) => withStricterMode(policy, "readonly"),
51
52
  "reactions-off": (policy) => ({ ...policy, reactions: false }),
52
53
  "comments-verified": (policy) => ({ ...policy, requireVerifiedEmail: true })
53
54
  };
@@ -85,6 +86,7 @@ function seedInClass(cls, variety) {
85
86
  const span = Math.floor(MAX_AVATAR_SEED / AVATAR_CLASSES);
86
87
  return Math.abs(Math.trunc(variety)) % span * AVATAR_CLASSES + Math.abs(Math.trunc(cls)) % AVATAR_CLASSES;
87
88
  }
89
+ var AVATAR_OFFER_SIZE = 5;
88
90
  // src/content.ts
89
91
  var CONTENT_DOCUMENT_SOURCES = ["mood", "post"];
90
92
  var POST_LOCALE_RE = /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/;
@@ -131,6 +133,8 @@ var MOOD_SEARCH_PATH = "/v2/mood/search";
131
133
  var MOOD_IMAGE_PROXY_BASE_PATH = "/v2/images";
132
134
  var MOOD_MEDIA_PROXY_BASE_PATH = "/v2/media";
133
135
  var LISTENING_PATH = "/v2/listening";
136
+ var INSTAGRAM_PROFILE_PATH = "/v2/instagram";
137
+ var INSTAGRAM_AVATAR_PATH = "/v2/instagram/avatar";
134
138
  var COMMENTS_PATH = "/v2/comments";
135
139
  var COMMENT_PATH_PREFIX = "/v2/comments/";
136
140
  var OWNER_MESSAGES_PATH = "/v2/messages";
@@ -217,6 +221,7 @@ export {
217
221
  ADMIN_BASE_PATH,
218
222
  API_PREFIX,
219
223
  AVATAR_CLASSES,
224
+ AVATAR_OFFER_SIZE,
220
225
  BLOG_ANALYTICS_ARTICLE_ENDPOINT,
221
226
  BLOG_ANALYTICS_COMPLETION_SCROLL_DEPTH,
222
227
  BLOG_ANALYTICS_EVENTS_DEFAULT_LIMIT,
@@ -241,6 +246,8 @@ export {
241
246
  EVENT_STATUSES,
242
247
  GHOST_WEBHOOK_PATH,
243
248
  HEALTH_PATH,
249
+ INSTAGRAM_AVATAR_PATH,
250
+ INSTAGRAM_PROFILE_PATH,
244
251
  LEGACY_ADMIN_BASE_PATH,
245
252
  LEGACY_GHOST_WEBHOOK_PATH,
246
253
  LEGACY_HEALTH_PATH,
@@ -0,0 +1,29 @@
1
+ export interface InstagramProfileCounts {
2
+ posts: number;
3
+ followers: number;
4
+ following: number;
5
+ }
6
+ export interface InstagramProfileAvatar {
7
+ /** Absolute URL of the stored picture, served by site-api. */
8
+ url: string;
9
+ contentType: string;
10
+ bytes: number;
11
+ /** Hex SHA-256 of the stored bytes; the avatar route sends it as the ETag. */
12
+ sha256: string;
13
+ }
14
+ export interface InstagramProfileAttempt {
15
+ at: string;
16
+ ok: boolean;
17
+ /** Why the attempt failed, e.g. `profile:401`; null when it succeeded. */
18
+ error: string | null;
19
+ }
20
+ export interface InstagramProfile {
21
+ username: string;
22
+ fullName: string;
23
+ profileUrl: string;
24
+ avatar: InstagramProfileAvatar;
25
+ counts: InstagramProfileCounts;
26
+ /** When the stored profile was last read from Instagram. */
27
+ refreshedAt: string;
28
+ lastAttempt: InstagramProfileAttempt;
29
+ }
File without changes
package/dist/routes.d.ts CHANGED
@@ -12,6 +12,8 @@ export declare const MOOD_SEARCH_PATH: '/v2/mood/search';
12
12
  export declare const MOOD_IMAGE_PROXY_BASE_PATH: '/v2/images';
13
13
  export declare const MOOD_MEDIA_PROXY_BASE_PATH: '/v2/media';
14
14
  export declare const LISTENING_PATH: '/v2/listening';
15
+ export declare const INSTAGRAM_PROFILE_PATH: '/v2/instagram';
16
+ export declare const INSTAGRAM_AVATAR_PATH: '/v2/instagram/avatar';
15
17
  export declare const COMMENTS_PATH: '/v2/comments';
16
18
  export declare const COMMENT_PATH_PREFIX: '/v2/comments/';
17
19
  export declare const OWNER_MESSAGES_PATH: '/v2/messages';
package/dist/routes.js CHANGED
@@ -13,6 +13,8 @@ var MOOD_SEARCH_PATH = "/v2/mood/search";
13
13
  var MOOD_IMAGE_PROXY_BASE_PATH = "/v2/images";
14
14
  var MOOD_MEDIA_PROXY_BASE_PATH = "/v2/media";
15
15
  var LISTENING_PATH = "/v2/listening";
16
+ var INSTAGRAM_PROFILE_PATH = "/v2/instagram";
17
+ var INSTAGRAM_AVATAR_PATH = "/v2/instagram/avatar";
16
18
  var COMMENTS_PATH = "/v2/comments";
17
19
  var COMMENT_PATH_PREFIX = "/v2/comments/";
18
20
  var OWNER_MESSAGES_PATH = "/v2/messages";
@@ -45,6 +47,8 @@ export {
45
47
  COMMENT_PATH_PREFIX,
46
48
  GHOST_WEBHOOK_PATH,
47
49
  HEALTH_PATH,
50
+ INSTAGRAM_AVATAR_PATH,
51
+ INSTAGRAM_PROFILE_PATH,
48
52
  LEGACY_ADMIN_BASE_PATH,
49
53
  LEGACY_GHOST_WEBHOOK_PATH,
50
54
  LEGACY_HEALTH_PATH,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunizao/contracts",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Shared TypeScript contracts for buxx.me services and clients.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -48,6 +48,11 @@
48
48
  "import": "./dist/content.js",
49
49
  "default": "./dist/content.js"
50
50
  },
51
+ "./instagram": {
52
+ "types": "./dist/instagram.d.ts",
53
+ "import": "./dist/instagram.js",
54
+ "default": "./dist/instagram.js"
55
+ },
51
56
  "./listening": {
52
57
  "types": "./dist/listening.d.ts",
53
58
  "import": "./dist/listening.js",