@orakl/protocol 0.7.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import type { MediaType, QuestionSource } from "./question.schema.ts";
1
+ import type { MediaCredit, MediaType, QuestionSource } from "./question.schema.ts";
2
2
  /**
3
3
  * One round in a player's multiplayer answer log. Misses recorded during the
4
4
  * player's hero tenure (timeout, disconnect, quit) appear with isCorrect=false
@@ -26,6 +26,7 @@ export interface GameAnswerRecord {
26
26
  * game is reviewed, so a reader re-signs it (see `resignBreakdown`). */
27
27
  mediaUrl?: string;
28
28
  mediaType?: MediaType;
29
+ mediaCredit?: MediaCredit;
29
30
  source?: QuestionSource;
30
31
  postAnswerNote?: string;
31
32
  }
package/dist/game.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AdvanceMode, Difficulty, TimerDuration } from "./game.settings.ts";
2
- import type { MediaType, QuestionSource, QuestionType } from "./question.schema.ts";
2
+ import type { MediaCredit, MediaType, QuestionSource, QuestionType } from "./question.schema.ts";
3
3
  export type { AdvanceMode, Difficulty, TimerDuration, } from "./game.settings.ts";
4
4
  export type PlayerRole = "player" | "observer";
5
5
  export type PlayerStatus = "pending" | "active" | "disconnected";
@@ -54,6 +54,8 @@ interface BaseQuizQuestion {
54
54
  answers: QuizAnswer[];
55
55
  mediaUrl?: string;
56
56
  mediaType?: MediaType;
57
+ /** Author and licence of the image; absent when the image has no credit. */
58
+ mediaCredit?: MediaCredit;
57
59
  /** Carried only so `toPlayerSafeQuestion` can strip it — never sent on the
58
60
  * wire during a game (map #912, decision 2). */
59
61
  source?: QuestionSource;
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export * from "./pages/common.schema.ts";
10
10
  export * from "./pages/history.schema.ts";
11
11
  export * from "./pages/quiz.schema.ts";
12
12
  export type { PlayerSafeQuestion } from "./player-safe-question.ts";
13
- export { DifficultySchema, type ImageChoiceQuestion, type ImageMatchingQuestion, isHttpsUrl, type Media, MediaSchema, type MediaType, MediaTypeSchema, PostAnswerNoteSchema, type Question, QuestionSchema, type QuestionSource, QuestionSourceSchema, type QuestionType, QuestionTypeSchema, type Source, SourceSchema, type TextChoiceQuestion, type TrueFalseQuestion, } from "./question.schema.ts";
13
+ export { ATTRIBUTION_LICENCES, CREDIT_LICENCE_LABEL, CREDIT_LICENCES, type CreditLicence, CreditLicenceSchema, DISTRIBUTABLE_LICENCES, DifficultySchema, type ImageChoiceQuestion, type ImageMatchingQuestion, isHttpsUrl, type Media, type MediaCredit, MediaCreditSchema, MediaSchema, type MediaType, MediaTypeSchema, PostAnswerNoteSchema, type Question, QuestionSchema, type QuestionSource, QuestionSourceSchema, type QuestionType, QuestionTypeSchema, type Source, SourceSchema, type TextChoiceQuestion, type TrueFalseQuestion, } from "./question.schema.ts";
14
14
  export { hasMedia, isImageMatching, isTextChoice, isTrueFalse, } from "./question.ts";
15
15
  export type { QuestionRating } from "./question-rating.ts";
16
16
  export { OptionalScoringModeSchema, ScoringModeSchema, } from "./scoring.schema.ts";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export * from "./pages/auth.schema.js";
5
5
  export * from "./pages/common.schema.js";
6
6
  export * from "./pages/history.schema.js";
7
7
  export * from "./pages/quiz.schema.js";
8
- export { DifficultySchema, isHttpsUrl, MediaSchema, MediaTypeSchema, PostAnswerNoteSchema, QuestionSchema, QuestionSourceSchema, QuestionTypeSchema, SourceSchema, } from "./question.schema.js";
8
+ export { ATTRIBUTION_LICENCES, CREDIT_LICENCE_LABEL, CREDIT_LICENCES, CreditLicenceSchema, DISTRIBUTABLE_LICENCES, DifficultySchema, isHttpsUrl, MediaCreditSchema, MediaSchema, MediaTypeSchema, PostAnswerNoteSchema, QuestionSchema, QuestionSourceSchema, QuestionTypeSchema, SourceSchema, } from "./question.schema.js";
9
9
  export { hasMedia, isImageMatching, isTextChoice, isTrueFalse, } from "./question.js";
10
10
  export { OptionalScoringModeSchema, ScoringModeSchema, } from "./scoring.schema.js";
11
11
  export { soloError, } from "./solo-ws.js";
@@ -590,6 +590,12 @@ export declare const CuratorHistoryEntryPageSchema: Schema.Struct<{
590
590
  readonly questionText?: string | undefined;
591
591
  readonly mediaUrl?: string | undefined;
592
592
  readonly mediaType?: "audio" | "video" | "image" | undefined;
593
+ readonly mediaCredit?: {
594
+ readonly licence: "cc0" | "public-domain" | "cc-by" | "cc-by-sa" | "own";
595
+ readonly author?: string | undefined;
596
+ readonly sourceUrl?: string | undefined;
597
+ readonly licenceUrl?: string | undefined;
598
+ } | undefined;
593
599
  readonly correct: number;
594
600
  readonly answered: number;
595
601
  readonly spread: {
@@ -615,6 +621,12 @@ export declare const CuratorHistoryEntryPageSchema: Schema.Struct<{
615
621
  readonly questionText?: string | undefined;
616
622
  readonly mediaUrl?: string | undefined;
617
623
  readonly mediaType?: "audio" | "video" | "image" | undefined;
624
+ readonly mediaCredit?: {
625
+ readonly licence: "cc0" | "public-domain" | "cc-by" | "cc-by-sa" | "own";
626
+ readonly author?: string | undefined;
627
+ readonly sourceUrl?: string | undefined;
628
+ readonly licenceUrl?: string | undefined;
629
+ } | undefined;
618
630
  }, never>>>>;
619
631
  }>;
620
632
  presetLimit: typeof Schema.Number;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- import { MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
2
+ import { MediaCreditSchema, MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
3
3
  import { ThemeModeSchema, UserPowerSchema, UserRoleSchema, } from "./common.schema.js";
4
4
  /** Page-load response bodies for `src/api/routes/pages.ts`. */
5
5
  /** A JSON array, decoded mutable — Effect's `Schema.Array` types its `.Type`
@@ -338,6 +338,7 @@ const QuestionRecapEntrySchema = Schema.Struct({
338
338
  questionText: Schema.optional(Schema.String),
339
339
  mediaUrl: Schema.optional(Schema.String),
340
340
  mediaType: Schema.optional(MediaTypeSchema),
341
+ mediaCredit: Schema.optional(MediaCreditSchema),
341
342
  source: Schema.optional(StoredQuestionSourceSchema),
342
343
  postAnswerNote: Schema.optional(Schema.String),
343
344
  answered: Schema.Number,
@@ -19,6 +19,12 @@ export declare const GameAnswerRecordSchema: Schema.Struct<{
19
19
  correctAnswerText: Schema.optional<typeof Schema.String>;
20
20
  mediaUrl: Schema.optional<typeof Schema.String>;
21
21
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
22
+ mediaCredit: Schema.optional<Schema.filter<Schema.Struct<{
23
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
24
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
25
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
26
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
27
+ }>>>;
22
28
  source: Schema.optional<Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
23
29
  readonly url: string;
24
30
  readonly label?: string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- import { MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
2
+ import { MediaCreditSchema, MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
3
3
  /** Schemas shared by more than one page-load body. */
4
4
  export const UserRoleSchema = Schema.Literal("anonymous", "display", "member", "curator", "admin");
5
5
  export const UserPowerSchema = Schema.Literal("can-access-mimic", "can-send-feedback", "can-import-quizzes", "can-manage-features", "can-add-questions", "can-manage-categories", "can-moderate-questions", "can-publish-questions", "is-power-user", "can-manage-avatars", "can-apply-for-curation", "can-1v1");
@@ -20,6 +20,7 @@ export const GameAnswerRecordSchema = Schema.Struct({
20
20
  correctAnswerText: Schema.optional(Schema.String),
21
21
  mediaUrl: Schema.optional(Schema.String),
22
22
  mediaType: Schema.optional(MediaTypeSchema),
23
+ mediaCredit: Schema.optional(MediaCreditSchema),
23
24
  source: Schema.optional(StoredQuestionSourceSchema),
24
25
  postAnswerNote: Schema.optional(Schema.String),
25
26
  });
@@ -213,6 +213,12 @@ export declare const GameReviewPageSchema: Schema.Struct<{
213
213
  correctAnswerText: Schema.optional<typeof Schema.String>;
214
214
  mediaUrl: Schema.optional<typeof Schema.String>;
215
215
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
216
+ mediaCredit: Schema.optional<Schema.filter<Schema.Struct<{
217
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
218
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
219
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
220
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
221
+ }>>>;
216
222
  source: Schema.optional<Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
217
223
  readonly url: string;
218
224
  readonly label?: string | undefined;
@@ -268,6 +274,12 @@ export declare const SoloReviewPageSchema: Schema.Struct<{
268
274
  questionId: Schema.NullOr<typeof Schema.String>;
269
275
  mediaUrl: Schema.NullOr<typeof Schema.String>;
270
276
  mediaType: Schema.NullOr<Schema.Literal<["image", "audio", "video"]>>;
277
+ mediaCredit: Schema.optional<Schema.NullOr<Schema.filter<Schema.Struct<{
278
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
279
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
280
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
281
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
282
+ }>>>>;
271
283
  source: Schema.Union<[typeof Schema.Null, Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
272
284
  readonly url: string;
273
285
  readonly label?: string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- import { MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
2
+ import { MediaCreditSchema, MediaTypeSchema, StoredQuestionSourceSchema, } from "../question.schema.js";
3
3
  import { GameAnswerRecordSchema, PlayerRoleSchema, UserPowerSchema, UserRoleSchema, } from "./common.schema.js";
4
4
  /** Page-load response bodies for `src/api/routes/pages-history.ts and admin-feature-flags.ts`. */
5
5
  /** Achievement badge id (mirrors `BadgeId` in `src/lib/badges.ts`). */
@@ -179,6 +179,8 @@ const SoloRunReviewEntrySchema = Schema.Struct({
179
179
  questionId: Schema.NullOr(Schema.String),
180
180
  mediaUrl: Schema.NullOr(Schema.String),
181
181
  mediaType: Schema.NullOr(MediaTypeSchema),
182
+ // Optional: a review served before credits existed has no key.
183
+ mediaCredit: Schema.optional(Schema.NullOr(MediaCreditSchema)),
182
184
  // Null first: the stored-source transform accepts any value, null included.
183
185
  source: Schema.Union(Schema.Null, StoredQuestionSourceSchema),
184
186
  postAnswerNote: Schema.NullOr(Schema.String),
@@ -135,6 +135,12 @@ export declare const QuizResultsPageSchema: Schema.Struct<{
135
135
  correctAnswerText: Schema.optional<typeof Schema.String>;
136
136
  mediaUrl: Schema.optional<typeof Schema.String>;
137
137
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
138
+ mediaCredit: Schema.optional<Schema.filter<Schema.Struct<{
139
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
140
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
141
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
142
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
143
+ }>>>;
138
144
  source: Schema.optional<Schema.transform<typeof Schema.Unknown, Schema.UndefinedOr<Schema.SchemaClass<{
139
145
  readonly url: string;
140
146
  readonly label?: string | undefined;
@@ -3,13 +3,40 @@ export declare const DifficultySchema: Schema.Literal<["easy", "medium", "hard"]
3
3
  export declare const MediaTypeSchema: Schema.Literal<["image", "audio", "video"]>;
4
4
  export declare const QuestionTypeSchema: Schema.Literal<["text_choice", "image_choice", "true_false", "finish_line", "audio_identify", "sequence_order", "estimate_range", "fill_blank", "matching_pairs", "progressive_reveal", "image_matching"]>;
5
5
  export declare const SourceSchema: Schema.Literal<["opentdb", "custom", "the-trivia-api"]>;
6
+ /** True for an https URL WHATWG-parses; false for anything else (including unparsable strings). */
7
+ export declare function isHttpsUrl(url: string): boolean;
8
+ /** Every licence an image credit may name. */
9
+ export declare const CREDIT_LICENCES: readonly ["cc0", "public-domain", "cc-by", "cc-by-sa", "own"];
10
+ export declare const CreditLicenceSchema: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
11
+ export type CreditLicence = typeof CreditLicenceSchema.Type;
12
+ /** Licences Orakl may distribute: a promoted or imported image must carry one. */
13
+ export declare const DISTRIBUTABLE_LICENCES: readonly CreditLicence[];
14
+ /** Licences whose terms require the author's name beside the image. */
15
+ export declare const ATTRIBUTION_LICENCES: readonly CreditLicence[];
16
+ export declare const CREDIT_LICENCE_LABEL: Record<CreditLicence, string>;
17
+ /** Who made an image and under which licence it is used (map #1570, decision 9). */
18
+ export declare const MediaCreditSchema: Schema.filter<Schema.Struct<{
19
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
20
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
21
+ /** The page the image was taken from, e.g. its Commons file page. */
22
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
23
+ /** The licence deed, version included. */
24
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
25
+ }>>;
26
+ export type MediaCredit = typeof MediaCreditSchema.Type;
6
27
  export declare const MediaSchema: Schema.Struct<{
7
28
  type: Schema.Literal<["image", "audio", "video"]>;
8
29
  url: typeof Schema.String;
9
30
  thumbnail: Schema.optional<typeof Schema.String>;
31
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
32
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
33
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
34
+ /** The page the image was taken from, e.g. its Commons file page. */
35
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
36
+ /** The licence deed, version included. */
37
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
38
+ }>>>;
10
39
  }>;
11
- /** True for an https URL WHATWG-parses; false for anything else (including unparsable strings). */
12
- export declare function isHttpsUrl(url: string): boolean;
13
40
  export declare const QuestionSourceSchema: Schema.Struct<{
14
41
  url: Schema.filter<Schema.filter<typeof Schema.String>>;
15
42
  label: Schema.optional<Schema.filter<typeof Schema.String>>;
@@ -38,6 +65,14 @@ declare const TextChoiceSchema: Schema.Struct<{
38
65
  type: Schema.Literal<["image", "audio", "video"]>;
39
66
  url: typeof Schema.String;
40
67
  thumbnail: Schema.optional<typeof Schema.String>;
68
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
69
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
70
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
71
+ /** The page the image was taken from, e.g. its Commons file page. */
72
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
73
+ /** The licence deed, version included. */
74
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
75
+ }>>>;
41
76
  }>>;
42
77
  }>;
43
78
  correctAnswer: typeof Schema.String;
@@ -66,6 +101,14 @@ declare const ImageChoiceSchema: Schema.Struct<{
66
101
  type: Schema.Literal<["image", "audio", "video"]>;
67
102
  url: typeof Schema.String;
68
103
  thumbnail: Schema.optional<typeof Schema.String>;
104
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
105
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
106
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
107
+ /** The page the image was taken from, e.g. its Commons file page. */
108
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
109
+ /** The licence deed, version included. */
110
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
111
+ }>>>;
69
112
  }>;
70
113
  }>;
71
114
  id: typeof Schema.String;
@@ -102,6 +145,14 @@ declare const TrueFalseSchema: Schema.Struct<{
102
145
  type: Schema.Literal<["image", "audio", "video"]>;
103
146
  url: typeof Schema.String;
104
147
  thumbnail: Schema.optional<typeof Schema.String>;
148
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
149
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
150
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
151
+ /** The page the image was taken from, e.g. its Commons file page. */
152
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
153
+ /** The licence deed, version included. */
154
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
155
+ }>>>;
105
156
  }>>;
106
157
  }>;
107
158
  question_fingerprint: Schema.optional<typeof Schema.String>;
@@ -128,6 +179,14 @@ declare const ImageMatchingSchema: Schema.Struct<{
128
179
  type: Schema.Literal<["image", "audio", "video"]>;
129
180
  url: typeof Schema.String;
130
181
  thumbnail: Schema.optional<typeof Schema.String>;
182
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
183
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
184
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
185
+ /** The page the image was taken from, e.g. its Commons file page. */
186
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
187
+ /** The licence deed, version included. */
188
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
189
+ }>>>;
131
190
  }>;
132
191
  }>;
133
192
  correctAnswer: typeof Schema.String;
@@ -166,6 +225,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
166
225
  type: Schema.Literal<["image", "audio", "video"]>;
167
226
  url: typeof Schema.String;
168
227
  thumbnail: Schema.optional<typeof Schema.String>;
228
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
229
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
230
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
231
+ /** The page the image was taken from, e.g. its Commons file page. */
232
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
233
+ /** The licence deed, version included. */
234
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
235
+ }>>>;
169
236
  }>>;
170
237
  }>;
171
238
  correctAnswer: typeof Schema.String;
@@ -193,6 +260,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
193
260
  type: Schema.Literal<["image", "audio", "video"]>;
194
261
  url: typeof Schema.String;
195
262
  thumbnail: Schema.optional<typeof Schema.String>;
263
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
264
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
265
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
266
+ /** The page the image was taken from, e.g. its Commons file page. */
267
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
268
+ /** The licence deed, version included. */
269
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
270
+ }>>>;
196
271
  }>;
197
272
  }>;
198
273
  id: typeof Schema.String;
@@ -228,6 +303,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
228
303
  type: Schema.Literal<["image", "audio", "video"]>;
229
304
  url: typeof Schema.String;
230
305
  thumbnail: Schema.optional<typeof Schema.String>;
306
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
307
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
308
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
309
+ /** The page the image was taken from, e.g. its Commons file page. */
310
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
311
+ /** The licence deed, version included. */
312
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
313
+ }>>>;
231
314
  }>>;
232
315
  }>;
233
316
  question_fingerprint: Schema.optional<typeof Schema.String>;
@@ -253,6 +336,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
253
336
  type: Schema.Literal<["image", "audio", "video"]>;
254
337
  url: typeof Schema.String;
255
338
  thumbnail: Schema.optional<typeof Schema.String>;
339
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
340
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
341
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
342
+ /** The page the image was taken from, e.g. its Commons file page. */
343
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
344
+ /** The licence deed, version included. */
345
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
346
+ }>>>;
256
347
  }>;
257
348
  }>;
258
349
  id: typeof Schema.String;
@@ -283,6 +374,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
283
374
  type: Schema.Literal<["image", "audio", "video"]>;
284
375
  url: typeof Schema.String;
285
376
  thumbnail: Schema.optional<typeof Schema.String>;
377
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
378
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
379
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
380
+ /** The page the image was taken from, e.g. its Commons file page. */
381
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
382
+ /** The licence deed, version included. */
383
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
384
+ }>>>;
286
385
  }>;
287
386
  }>;
288
387
  id: typeof Schema.String;
@@ -316,6 +415,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
316
415
  type: Schema.Literal<["image", "audio", "video"]>;
317
416
  url: typeof Schema.String;
318
417
  thumbnail: Schema.optional<typeof Schema.String>;
418
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
419
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
420
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
421
+ /** The page the image was taken from, e.g. its Commons file page. */
422
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
423
+ /** The licence deed, version included. */
424
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
425
+ }>>>;
319
426
  }>>;
320
427
  }>;
321
428
  correctAnswer: typeof Schema.String;
@@ -355,6 +462,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
355
462
  type: Schema.Literal<["image", "audio", "video"]>;
356
463
  url: typeof Schema.String;
357
464
  thumbnail: Schema.optional<typeof Schema.String>;
465
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
466
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
467
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
468
+ /** The page the image was taken from, e.g. its Commons file page. */
469
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
470
+ /** The licence deed, version included. */
471
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
472
+ }>>>;
358
473
  }>>;
359
474
  }>;
360
475
  correctAnswer: typeof Schema.String;
@@ -377,6 +492,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
377
492
  type: Schema.Literal<["image", "audio", "video"]>;
378
493
  url: typeof Schema.String;
379
494
  thumbnail: Schema.optional<typeof Schema.String>;
495
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
496
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
497
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
498
+ /** The page the image was taken from, e.g. its Commons file page. */
499
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
500
+ /** The licence deed, version included. */
501
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
502
+ }>>>;
380
503
  }>>;
381
504
  }>;
382
505
  correctAnswer: typeof Schema.String;
@@ -407,6 +530,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
407
530
  type: Schema.Literal<["image", "audio", "video"]>;
408
531
  url: typeof Schema.String;
409
532
  thumbnail: Schema.optional<typeof Schema.String>;
533
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
534
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
535
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
536
+ /** The page the image was taken from, e.g. its Commons file page. */
537
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
538
+ /** The licence deed, version included. */
539
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
540
+ }>>>;
410
541
  }>>;
411
542
  }>;
412
543
  correctAnswer: typeof Schema.String;
@@ -434,6 +565,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
434
565
  type: Schema.Literal<["image", "audio", "video"]>;
435
566
  url: typeof Schema.String;
436
567
  thumbnail: Schema.optional<typeof Schema.String>;
568
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
569
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
570
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
571
+ /** The page the image was taken from, e.g. its Commons file page. */
572
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
573
+ /** The licence deed, version included. */
574
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
575
+ }>>>;
437
576
  }>;
438
577
  }>;
439
578
  metadata: Schema.Struct<{
@@ -465,6 +604,14 @@ export declare const QuestionSchema: Schema.Union<[Schema.Struct<{
465
604
  type: Schema.Literal<["image", "audio", "video"]>;
466
605
  url: typeof Schema.String;
467
606
  thumbnail: Schema.optional<typeof Schema.String>;
607
+ credit: Schema.optional<Schema.filter<Schema.Struct<{
608
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
609
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
610
+ /** The page the image was taken from, e.g. its Commons file page. */
611
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
612
+ /** The licence deed, version included. */
613
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
614
+ }>>>;
468
615
  }>;
469
616
  }>;
470
617
  correctAnswer: typeof Schema.String;
@@ -4,11 +4,6 @@ export const DifficultySchema = Schema.Literal(...GAME.roundConfig.difficulties)
4
4
  export const MediaTypeSchema = Schema.Literal("image", "audio", "video");
5
5
  export const QuestionTypeSchema = Schema.Literal("text_choice", "image_choice", "true_false", "finish_line", "audio_identify", "sequence_order", "estimate_range", "fill_blank", "matching_pairs", "progressive_reveal", "image_matching");
6
6
  export const SourceSchema = Schema.Literal(...GAME.question.sources);
7
- export const MediaSchema = Schema.Struct({
8
- type: MediaTypeSchema,
9
- url: Schema.String,
10
- thumbnail: Schema.optional(Schema.String),
11
- });
12
7
  /** True for an https URL WHATWG-parses; false for anything else (including unparsable strings). */
13
8
  export function isHttpsUrl(url) {
14
9
  try {
@@ -18,6 +13,56 @@ export function isHttpsUrl(url) {
18
13
  return false;
19
14
  }
20
15
  }
16
+ /** Every licence an image credit may name. */
17
+ export const CREDIT_LICENCES = [
18
+ "cc0",
19
+ "public-domain",
20
+ "cc-by",
21
+ "cc-by-sa",
22
+ "own",
23
+ ];
24
+ export const CreditLicenceSchema = Schema.Literal(...CREDIT_LICENCES);
25
+ /** Licences Orakl may distribute: a promoted or imported image must carry one. */
26
+ export const DISTRIBUTABLE_LICENCES = [
27
+ "cc0",
28
+ "public-domain",
29
+ "cc-by",
30
+ "cc-by-sa",
31
+ ];
32
+ /** Licences whose terms require the author's name beside the image. */
33
+ export const ATTRIBUTION_LICENCES = [
34
+ "cc-by",
35
+ "cc-by-sa",
36
+ ];
37
+ export const CREDIT_LICENCE_LABEL = {
38
+ cc0: "CC0",
39
+ "public-domain": "Public domain",
40
+ "cc-by": "CC BY",
41
+ "cc-by-sa": "CC BY-SA",
42
+ own: "Uploader's own / rights confirmed",
43
+ };
44
+ const CreditUrlSchema = Schema.String.pipe(Schema.maxLength(2048, {
45
+ message: () => "Credit link must be 2048 characters or fewer",
46
+ }), Schema.filter(isHttpsUrl, { message: () => "Credit link must be https" }));
47
+ /** Who made an image and under which licence it is used (map #1570, decision 9). */
48
+ export const MediaCreditSchema = Schema.Struct({
49
+ licence: CreditLicenceSchema,
50
+ author: Schema.optional(Schema.String.pipe(Schema.maxLength(200, {
51
+ message: () => "Author must be 200 characters or fewer",
52
+ }))),
53
+ /** The page the image was taken from, e.g. its Commons file page. */
54
+ sourceUrl: Schema.optional(CreditUrlSchema),
55
+ /** The licence deed, version included. */
56
+ licenceUrl: Schema.optional(CreditUrlSchema),
57
+ }).pipe(Schema.filter((c) => !ATTRIBUTION_LICENCES.includes(c.licence) ||
58
+ Boolean(c.author?.trim()) ||
59
+ "Author is required for CC BY and CC BY-SA"));
60
+ export const MediaSchema = Schema.Struct({
61
+ type: MediaTypeSchema,
62
+ url: Schema.String,
63
+ thumbnail: Schema.optional(Schema.String),
64
+ credit: Schema.optional(MediaCreditSchema),
65
+ });
21
66
  // Security: source.url is stored in the DB and read back on results/history
22
67
  // pages; cap to match imageUrl's bound (curator.schema.ts) since both are
23
68
  // URLs of the same practical size.
package/dist/solo-ws.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { BadgeAward } from "./badges.ts";
2
2
  import type { PlayerSafeQuestion } from "./player-safe-question.ts";
3
- import type { MediaType, QuestionSource } from "./question.ts";
3
+ import type { MediaCredit, MediaType, QuestionSource } from "./question.schema.ts";
4
4
  import type { QuestionRating } from "./question-rating.ts";
5
5
  import type { RateAckReason } from "./ws.ts";
6
6
  export type SoloAuthMessage = {
@@ -189,6 +189,7 @@ export type SoloFinalMessage = {
189
189
  * question's image, source and note. Absent when the question has none. */
190
190
  mediaUrl?: string;
191
191
  mediaType?: MediaType;
192
+ mediaCredit?: MediaCredit;
192
193
  source?: QuestionSource;
193
194
  postAnswerNote?: string;
194
195
  /** From the same batched precheck as `solo:question`'s `alreadyFlagged`
@@ -14,10 +14,10 @@ export type OperatorQuestionRevision = QuestionRevision & {
14
14
  } | null;
15
15
  };
16
16
  /** One outcome of a bulk publish — an id that promoted, was already shared,
17
- * is flagged, or wasn't found. */
17
+ * is flagged, has an image without a distributable licence, or wasn't found. */
18
18
  export type PublishManyResult = {
19
19
  id: string;
20
- outcome: "promoted" | "already" | "flagged" | "not_found";
20
+ outcome: "promoted" | "already" | "flagged" | "unlicensed" | "not_found";
21
21
  published_at?: string;
22
22
  published_by?: string;
23
23
  };
@@ -42,6 +42,12 @@ export declare const ClientMessageSchema: Schema.Union<[Schema.Struct<{
42
42
  }>>>>;
43
43
  mediaUrl: Schema.optional<Schema.filter<typeof Schema.String>>;
44
44
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
45
+ mediaCredit: Schema.optional<Schema.filter<Schema.Struct<{
46
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
47
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
48
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
49
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
50
+ }>>>;
45
51
  }>, Schema.Struct<{
46
52
  type: Schema.Literal<["image_matching"]>;
47
53
  matchItems: Schema.Struct<{
@@ -59,6 +65,12 @@ export declare const ClientMessageSchema: Schema.Union<[Schema.Struct<{
59
65
  }>>>>;
60
66
  mediaUrl: Schema.optional<Schema.filter<typeof Schema.String>>;
61
67
  mediaType: Schema.optional<Schema.Literal<["image", "audio", "video"]>>;
68
+ mediaCredit: Schema.optional<Schema.filter<Schema.Struct<{
69
+ licence: Schema.Literal<["cc0", "public-domain", "cc-by", "cc-by-sa", "own"]>;
70
+ author: Schema.optional<Schema.filter<typeof Schema.String>>;
71
+ sourceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
72
+ licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
73
+ }>>>;
62
74
  }>]>>>>;
63
75
  }>, Schema.Struct<{
64
76
  type: Schema.Literal<["curator:advance"]>;
@@ -1,7 +1,7 @@
1
1
  import { Schema } from "effect";
2
2
  import { SELECTABLE_EMOTE_IDS } from "./emotes.js";
3
3
  import { GAME } from "./game.settings.js";
4
- import { DifficultySchema, MediaTypeSchema } from "./question.schema.js";
4
+ import { DifficultySchema, MediaCreditSchema, MediaTypeSchema, } from "./question.schema.js";
5
5
  import { OptionalScoringModeSchema } from "./scoring.schema.js";
6
6
  /**
7
7
  * Frame-level validation for the multiplayer game WebSocket (issue #734 hardening).
@@ -76,6 +76,7 @@ const BaseQuizQuestionFields = {
76
76
  answers: Schema.Array(QuizAnswerSchema).pipe(Schema.minItems(1), Schema.maxItems(8)),
77
77
  mediaUrl: Schema.optional(Schema.String.pipe(Schema.maxLength(2048))),
78
78
  mediaType: Schema.optional(MediaTypeSchema),
79
+ mediaCredit: Schema.optional(MediaCreditSchema),
79
80
  };
80
81
  const StandardPreloadedQuestionSchema = Schema.Struct({
81
82
  ...BaseQuizQuestionFields,
package/dist/ws.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { ErrorCode } from "./error-codes.ts";
3
3
  import type { AdvanceMode, Difficulty, DifficultyFilter, ScoringMode, TimerDuration } from "./game.settings.ts";
4
4
  import type { Player, PlayerRole, QuizQuestion } from "./game.ts";
5
5
  import type { GameAnswerRecord } from "./game-answer-record.ts";
6
- import type { MediaType, QuestionType } from "./question.schema.ts";
6
+ import type { MediaCredit, MediaType, QuestionType } from "./question.schema.ts";
7
7
  import type { QuestionRating } from "./question-rating.ts";
8
8
  import type { StreakFlourish } from "./streak-flourish.ts";
9
9
  export type ClientMessage = WsAuthMessage | PlayerAnswerMessage | PlayerEmoteMessage | PlayerRoleMessage | PlayerLeaveMessage | PlayerRateMessage | CuratorStartMessage | CuratorAdvanceMessage | CuratorStartRoleSelectionMessage | CuratorIntermissionMessage | CuratorPlayerActionMessage | CuratorStopMessage | CuratorResetMessage | WsPongMessage;
@@ -165,6 +165,7 @@ export interface GameQuestionMessage {
165
165
  };
166
166
  mediaUrl?: string;
167
167
  mediaType?: MediaType;
168
+ mediaCredit?: MediaCredit;
168
169
  timeRemaining: number;
169
170
  serverTs: number;
170
171
  /** This recipient's own already-submitted answer, if any — set only on a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orakl/protocol",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Orakl wire contract: WebSocket frames and API response schemas.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {