@bunizao/contracts 0.21.0 → 0.23.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/dist/desk.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ /** Mac power state as the reporter sees it. */
2
+ export declare const DESK_MAC_STATES: readonly ['on', 'locked', 'sleeping', 'paused'];
3
+ export type DeskMacState = (typeof DESK_MAC_STATES)[number];
4
+ /**
5
+ * An app on the desk. `label` is at most 24 letters, digits, spaces and dots;
6
+ * site-api turns anything else into "something else". `icon` is the
7
+ * lowercase hex sha256 of a PNG (at most 128 px a side) already uploaded to
8
+ * deskIconPath(icon), or null.
9
+ */
10
+ export interface DeskApp {
11
+ label: string;
12
+ icon: string | null;
13
+ }
14
+ /** Report body version the reporter sends; site-api rejects any other. */
15
+ export declare const DESK_REPORT_VERSION = 1;
16
+ /**
17
+ * Body of the owner-only, HMAC-signed POST to DESK_REPORT_PATH. The server
18
+ * ignores a report whose sentAt is not newer than the stored one, and
19
+ * computes expiry from its own clock plus ttl.
20
+ */
21
+ export interface DeskReport {
22
+ v: typeof DESK_REPORT_VERSION;
23
+ /** Reporter clock, epoch milliseconds. Only used for ordering. */
24
+ sentAt: number;
25
+ mac: DeskMacState;
26
+ /** Seconds since the last keyboard or pointer input. */
27
+ idleSeconds: number;
28
+ /** True while an app holds a display-sleep assertion (video, a call). */
29
+ screenHeld: boolean;
30
+ /** Apps in use, most active first; `other` for an unlisted one. Never window titles or bundle ids. */
31
+ apps: DeskApp[];
32
+ /** IANA zone of the Mac, e.g. Australia/Melbourne. */
33
+ timeZone: string;
34
+ /** Seconds this report stays valid without a successor. */
35
+ ttl: number;
36
+ }
37
+ export declare const DESK_PRESENCE_STATES: readonly ['awake', 'idle', 'asleep', 'offline'];
38
+ export type DeskPresenceState = (typeof DESK_PRESENCE_STATES)[number];
39
+ export declare const DESK_SKIES: readonly ['clear', 'cloudy', 'fog', 'drizzle', 'rain', 'snow', 'storm'];
40
+ export type Sky = (typeof DESK_SKIES)[number];
41
+ export interface DeskPresence {
42
+ state: DeskPresenceState;
43
+ /** Empty unless awake or idle; an unlisted app reads "something else" with no icon. */
44
+ apps: DeskApp[];
45
+ /** Seconds of lease left when computed; null when offline or asleep. */
46
+ validFor: number | null;
47
+ }
48
+ export interface DeskPlace {
49
+ /** IANA zone the clock shows; the last pushed zone, or the default. */
50
+ timeZone: string;
51
+ /** City named by the zone; null when the zone names none (e.g. UTC). */
52
+ city: string | null;
53
+ }
54
+ export interface DeskWeather {
55
+ /** Degrees Celsius, rounded. */
56
+ temperature: number;
57
+ sky: Sky;
58
+ /** Daylight at the place right now. */
59
+ day: boolean;
60
+ }
61
+ /** Response of GET DESK_PATH. Fields are only ever added. */
62
+ export interface DeskNow {
63
+ presence: DeskPresence;
64
+ place: DeskPlace;
65
+ /** Null when the weather source is unavailable. */
66
+ weather: DeskWeather | null;
67
+ }
68
+ /** Close code the live socket uses when it is full; poll instead. */
69
+ export declare const DESK_LIVE_FULL_CLOSE_CODE = 1013;
70
+ /**
71
+ * Message the live socket at DESK_LIVE_PATH sends on connect and whenever
72
+ * presence or place changes. Weather is not pushed; read it from DESK_PATH.
73
+ * The socket answers the text `ping` with `pong`. Fields are only ever added.
74
+ */
75
+ export interface DeskLive {
76
+ presence: DeskPresence;
77
+ place: DeskPlace;
78
+ }
package/dist/desk.js ADDED
@@ -0,0 +1,13 @@
1
+ // src/desk.ts
2
+ var DESK_MAC_STATES = ["on", "locked", "sleeping", "paused"];
3
+ var DESK_REPORT_VERSION = 1;
4
+ var DESK_PRESENCE_STATES = ["awake", "idle", "asleep", "offline"];
5
+ var DESK_SKIES = ["clear", "cloudy", "fog", "drizzle", "rain", "snow", "storm"];
6
+ var DESK_LIVE_FULL_CLOSE_CODE = 1013;
7
+ export {
8
+ DESK_LIVE_FULL_CLOSE_CODE,
9
+ DESK_MAC_STATES,
10
+ DESK_PRESENCE_STATES,
11
+ DESK_REPORT_VERSION,
12
+ DESK_SKIES
13
+ };
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 './desk';
5
6
  export * from './instagram';
6
7
  export * from './listening';
7
8
  export * from './messages';
package/dist/index.js CHANGED
@@ -145,6 +145,12 @@ function parsePostLocaleTag(tagName) {
145
145
  }
146
146
  return canonicalSlug ? { locale, canonicalSlug } : { locale };
147
147
  }
148
+ // src/desk.ts
149
+ var DESK_MAC_STATES = ["on", "locked", "sleeping", "paused"];
150
+ var DESK_REPORT_VERSION = 1;
151
+ var DESK_PRESENCE_STATES = ["awake", "idle", "asleep", "offline"];
152
+ var DESK_SKIES = ["clear", "cloudy", "fog", "drizzle", "rain", "snow", "storm"];
153
+ var DESK_LIVE_FULL_CLOSE_CODE = 1013;
148
154
  // src/messages.ts
149
155
  var MESSAGE_LOCALES = ["zh", "en"];
150
156
  var MESSAGE_MIN_BODY_LENGTH = 2;
@@ -174,6 +180,18 @@ var MOOD_TOPICS = [
174
180
  { id: "travel", zh: "出行旅行", en: "Travel & Transport" },
175
181
  { id: "daily-life", zh: "日常生活", en: "Daily Life" }
176
182
  ];
183
+ var MOOD_TOPIC_GROUPS = [
184
+ { id: "ai", zh: "AI", en: "AI", topics: ["ai"] },
185
+ { id: "tech", zh: "数码与折腾", en: "Tech & Tinkering", topics: ["devices", "apps-services", "coding"] },
186
+ { id: "study", zh: "学习", en: "Study", topics: ["study-school"] },
187
+ { id: "food", zh: "吃喝", en: "Food & Drink", topics: ["food-drink"] },
188
+ { id: "money", zh: "钱与消费", en: "Money & Shopping", topics: ["money-finance", "shopping-style"] },
189
+ { id: "people", zh: "朋友与感情", en: "Friends & Love", topics: ["friends", "romance"] },
190
+ { id: "feelings", zh: "心情与想法", en: "Feelings & Thoughts", topics: ["self-feelings", "reflections"] },
191
+ { id: "health", zh: "身体与睡眠", en: "Health & Sleep", topics: ["health-sleep"] },
192
+ { id: "media", zh: "音乐与娱乐", en: "Music & Media", topics: ["music", "media-entertainment", "internet-culture"] },
193
+ { id: "life", zh: "生活与出行", en: "Daily Life & Travel", topics: ["daily-life", "travel"] }
194
+ ];
177
195
  var MOOD_COMMENT_ORIGINS = ["telegram", "web"];
178
196
  // src/notify.ts
179
197
  var NOTIFY_CHANNELS = ["mood", "blog", "privacy", "announcement"];
@@ -193,6 +211,13 @@ var MOOD_SEARCH_PATH = "/v2/mood/search";
193
211
  var MOOD_IMAGE_PROXY_BASE_PATH = "/v2/images";
194
212
  var MOOD_MEDIA_PROXY_BASE_PATH = "/v2/media";
195
213
  var LISTENING_PATH = "/v2/listening";
214
+ var DESK_PATH = "/v2/desk";
215
+ var DESK_LIVE_PATH = "/v2/desk/live";
216
+ var DESK_REPORT_PATH = "/v2/desk/report";
217
+ var DESK_ICONS_PATH = "/v2/desk/icons";
218
+ function deskIconPath(hash) {
219
+ return `${DESK_ICONS_PATH}/${hash}`;
220
+ }
196
221
  var INSTAGRAM_PROFILE_PATH = "/v2/instagram";
197
222
  var INSTAGRAM_AVATAR_PATH = "/v2/instagram/avatar";
198
223
  var COMMENTS_PATH = "/v2/comments";
@@ -305,6 +330,15 @@ export {
305
330
  CONTENT_DOCUMENT_SOURCES,
306
331
  DEFAULT_COMMENT_POLICY,
307
332
  DEFAULT_REACTION_EMOJI,
333
+ DESK_ICONS_PATH,
334
+ DESK_LIVE_FULL_CLOSE_CODE,
335
+ DESK_LIVE_PATH,
336
+ DESK_MAC_STATES,
337
+ DESK_PATH,
338
+ DESK_PRESENCE_STATES,
339
+ DESK_REPORT_PATH,
340
+ DESK_REPORT_VERSION,
341
+ DESK_SKIES,
308
342
  EVENT_STATUSES,
309
343
  GHOST_WEBHOOK_PATH,
310
344
  HEALTH_PATH,
@@ -339,6 +373,7 @@ export {
339
373
  MOOD_SEARCH_PATH,
340
374
  MOOD_SENTIMENT_LABELS,
341
375
  MOOD_TOPICS,
376
+ MOOD_TOPIC_GROUPS,
342
377
  MUSICKIT_TOKEN_PATH,
343
378
  NEWSLETTER_ANALYTICS_CLICK_ENDPOINT,
344
379
  NEWSLETTER_ANALYTICS_OPEN_ENDPOINT,
@@ -394,6 +429,7 @@ export {
394
429
  avatarClass,
395
430
  commentAnchorToken,
396
431
  commentPolicyFromTags,
432
+ deskIconPath,
397
433
  isAvatarSeed,
398
434
  parsePostLocaleTag,
399
435
  seedInClass,
package/dist/mood.d.ts CHANGED
@@ -88,6 +88,61 @@ export declare const MOOD_TOPICS: readonly [{
88
88
  readonly en: 'Daily Life';
89
89
  }];
90
90
  export type MoodTopicId = (typeof MOOD_TOPICS)[number]['id'];
91
+ /** How the yearly view counts topics: eighteen bars are too fine to read a
92
+ year by, so it shows these ten. Posts keep their MOOD_TOPICS ids; every id
93
+ sits in exactly one group. */
94
+ export declare const MOOD_TOPIC_GROUPS: readonly [{
95
+ readonly id: 'ai';
96
+ readonly zh: 'AI';
97
+ readonly en: 'AI';
98
+ readonly topics: readonly ["ai"];
99
+ }, {
100
+ readonly id: 'tech';
101
+ readonly zh: '数码与折腾';
102
+ readonly en: 'Tech & Tinkering';
103
+ readonly topics: readonly ["devices", "apps-services", "coding"];
104
+ }, {
105
+ readonly id: 'study';
106
+ readonly zh: '学习';
107
+ readonly en: 'Study';
108
+ readonly topics: readonly ["study-school"];
109
+ }, {
110
+ readonly id: 'food';
111
+ readonly zh: '吃喝';
112
+ readonly en: 'Food & Drink';
113
+ readonly topics: readonly ["food-drink"];
114
+ }, {
115
+ readonly id: 'money';
116
+ readonly zh: '钱与消费';
117
+ readonly en: 'Money & Shopping';
118
+ readonly topics: readonly ["money-finance", "shopping-style"];
119
+ }, {
120
+ readonly id: 'people';
121
+ readonly zh: '朋友与感情';
122
+ readonly en: 'Friends & Love';
123
+ readonly topics: readonly ["friends", "romance"];
124
+ }, {
125
+ readonly id: 'feelings';
126
+ readonly zh: '心情与想法';
127
+ readonly en: 'Feelings & Thoughts';
128
+ readonly topics: readonly ["self-feelings", "reflections"];
129
+ }, {
130
+ readonly id: 'health';
131
+ readonly zh: '身体与睡眠';
132
+ readonly en: 'Health & Sleep';
133
+ readonly topics: readonly ["health-sleep"];
134
+ }, {
135
+ readonly id: 'media';
136
+ readonly zh: '音乐与娱乐';
137
+ readonly en: 'Music & Media';
138
+ readonly topics: readonly ["music", "media-entertainment", "internet-culture"];
139
+ }, {
140
+ readonly id: 'life';
141
+ readonly zh: '生活与出行';
142
+ readonly en: 'Daily Life & Travel';
143
+ readonly topics: readonly ["daily-life", "travel"];
144
+ }];
145
+ export type MoodTopicGroupId = (typeof MOOD_TOPIC_GROUPS)[number]['id'];
91
146
  export interface MoodSentiment {
92
147
  label: MoodSentimentLabel;
93
148
  score: number;
@@ -128,13 +183,19 @@ export interface MoodYearDay {
128
183
  temperature: number | null;
129
184
  }
130
185
  export interface MoodYearTopic {
131
- /** A MoodTopicId, kept as string: a snapshot can outlive a vocabulary change. */
132
- topic: string;
186
+ /** A MoodTopicGroupId, kept as string: a snapshot can outlive a vocabulary change. */
187
+ group: string;
188
+ /** Analysed posts with at least one topic in the group. */
133
189
  count: number;
134
190
  }
135
191
  export interface MoodYearWord {
192
+ /** The spelling the year used most. */
136
193
  word: string;
194
+ /** Posts that name it. */
137
195
  count: number;
196
+ /** Message ids of the newest posts that name it, newest first, at most
197
+ five: where the word came from. */
198
+ posts: number[];
138
199
  }
139
200
  /** One calendar year of the owner's own words. Forwards are left out: they
140
201
  are someone else's mood. Built once a day into KV, never per request. */
@@ -148,9 +209,10 @@ export interface MoodYear {
148
209
  /** Days with at least one post, ascending; empty days are absent. */
149
210
  days: MoodYearDay[];
150
211
  sentiment: Record<MoodSentimentLabel, number>;
151
- /** Most frequent first. */
212
+ /** Topic groups, most frequent first. */
152
213
  topics: MoodYearTopic[];
153
- /** Word-cloud keywords, most frequent first, at most 120. */
214
+ /** Word-cloud keywords used on at least two days, most distinctive first,
215
+ at most 120. */
154
216
  words: MoodYearWord[];
155
217
  generatedAt: string;
156
218
  }
package/dist/mood.js CHANGED
@@ -21,10 +21,23 @@ var MOOD_TOPICS = [
21
21
  { id: "travel", zh: "出行旅行", en: "Travel & Transport" },
22
22
  { id: "daily-life", zh: "日常生活", en: "Daily Life" }
23
23
  ];
24
+ var MOOD_TOPIC_GROUPS = [
25
+ { id: "ai", zh: "AI", en: "AI", topics: ["ai"] },
26
+ { id: "tech", zh: "数码与折腾", en: "Tech & Tinkering", topics: ["devices", "apps-services", "coding"] },
27
+ { id: "study", zh: "学习", en: "Study", topics: ["study-school"] },
28
+ { id: "food", zh: "吃喝", en: "Food & Drink", topics: ["food-drink"] },
29
+ { id: "money", zh: "钱与消费", en: "Money & Shopping", topics: ["money-finance", "shopping-style"] },
30
+ { id: "people", zh: "朋友与感情", en: "Friends & Love", topics: ["friends", "romance"] },
31
+ { id: "feelings", zh: "心情与想法", en: "Feelings & Thoughts", topics: ["self-feelings", "reflections"] },
32
+ { id: "health", zh: "身体与睡眠", en: "Health & Sleep", topics: ["health-sleep"] },
33
+ { id: "media", zh: "音乐与娱乐", en: "Music & Media", topics: ["music", "media-entertainment", "internet-culture"] },
34
+ { id: "life", zh: "生活与出行", en: "Daily Life & Travel", topics: ["daily-life", "travel"] }
35
+ ];
24
36
  var MOOD_COMMENT_ORIGINS = ["telegram", "web"];
25
37
  export {
26
38
  MOOD_AI_MODELS,
27
39
  MOOD_COMMENT_ORIGINS,
28
40
  MOOD_SENTIMENT_LABELS,
29
- MOOD_TOPICS
41
+ MOOD_TOPICS,
42
+ MOOD_TOPIC_GROUPS
30
43
  };
package/dist/routes.d.ts CHANGED
@@ -12,6 +12,13 @@ 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 DESK_PATH: '/v2/desk';
16
+ /** WebSocket upgrade; the server pushes DeskLive messages. */
17
+ export declare const DESK_LIVE_PATH: '/v2/desk/live';
18
+ export declare const DESK_REPORT_PATH: '/v2/desk/report';
19
+ /** PUT (owner-signed) and GET an app icon PNG by its sha256: `${DESK_ICONS_PATH}/<hash>`. */
20
+ export declare const DESK_ICONS_PATH: '/v2/desk/icons';
21
+ export declare function deskIconPath(hash: string): string;
15
22
  export declare const INSTAGRAM_PROFILE_PATH: '/v2/instagram';
16
23
  export declare const INSTAGRAM_AVATAR_PATH: '/v2/instagram/avatar';
17
24
  export declare const COMMENTS_PATH: '/v2/comments';
package/dist/routes.js CHANGED
@@ -13,6 +13,13 @@ 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 DESK_PATH = "/v2/desk";
17
+ var DESK_LIVE_PATH = "/v2/desk/live";
18
+ var DESK_REPORT_PATH = "/v2/desk/report";
19
+ var DESK_ICONS_PATH = "/v2/desk/icons";
20
+ function deskIconPath(hash) {
21
+ return `${DESK_ICONS_PATH}/${hash}`;
22
+ }
16
23
  var INSTAGRAM_PROFILE_PATH = "/v2/instagram";
17
24
  var INSTAGRAM_AVATAR_PATH = "/v2/instagram/avatar";
18
25
  var COMMENTS_PATH = "/v2/comments";
@@ -45,6 +52,10 @@ export {
45
52
  API_PREFIX,
46
53
  COMMENTS_PATH,
47
54
  COMMENT_PATH_PREFIX,
55
+ DESK_ICONS_PATH,
56
+ DESK_LIVE_PATH,
57
+ DESK_PATH,
58
+ DESK_REPORT_PATH,
48
59
  GHOST_WEBHOOK_PATH,
49
60
  HEALTH_PATH,
50
61
  INSTAGRAM_AVATAR_PATH,
@@ -81,5 +92,6 @@ export {
81
92
  READER_PREFERENCES_PATH,
82
93
  READER_RESEND_PATH,
83
94
  READER_VERIFY_PATH,
84
- TELEGRAM_WEBHOOK_PATH
95
+ TELEGRAM_WEBHOOK_PATH,
96
+ deskIconPath
85
97
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunizao/contracts",
3
- "version": "0.21.0",
3
+ "version": "0.23.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
+ "./desk": {
52
+ "types": "./dist/desk.d.ts",
53
+ "import": "./dist/desk.js",
54
+ "default": "./dist/desk.js"
55
+ },
51
56
  "./instagram": {
52
57
  "types": "./dist/instagram.d.ts",
53
58
  "import": "./dist/instagram.js",