@zhin.js/plugin-roulette 0.1.6 → 0.1.8
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 +28 -0
- package/commands/roulette/[[action]]/index.js +11 -4
- package/lib/deadline-job.js +2 -11
- package/lib/game-flow.d.ts +3 -4
- package/lib/game-flow.js +46 -44
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/mute-penalty.js +7 -7
- package/lib/roulette-actions.d.ts +2 -2
- package/lib/roulette-command.js +4 -5
- package/lib/runtime.d.ts +1 -3
- package/lib/runtime.js +0 -16
- package/lib/session-service.d.ts +3 -8
- package/lib/session-service.js +22 -23
- package/lib/view.d.ts +2 -2
- package/middlewares/roulette-alias/index.js +15 -2
- package/middlewares/roulette-fire/index.js +9 -6
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @zhin.js/plugin-roulette
|
|
2
2
|
|
|
3
|
+
## 0.1.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a9f4e48: PATCH COMPATIBILITY CUTOVER: this batch intentionally changes plugin APIs. The maintainer explicitly chose patch releases with declared version boundaries. Upgrade the complete tested dependency set; patch numbering does not promise compatibility with the removed interfaces.
|
|
8
|
+
|
|
9
|
+
The first planned floors are zhin.js 1.1.19, Core 1.1.54, Adapter 1.1.26, FeatureKit 1.1.10, Plugin Runtime 1.1.18, Runtime/Isolate 1.1.16 and Agent 1.1.45. Keep old-interface plugins with the earlier locked stack until they are migrated. Adapter's independent Feature API changes from 1.0.0 to 2.0.0; official consumers declare ^2.0.0. Existing Feature API admission rejects explicitly incompatible ranges; omitted API metadata keeps its existing behavior and does not certify compatibility. Runtime engine and patch-only release gates remain unchanged.
|
|
10
|
+
|
|
11
|
+
Use the native-Client Adapter target contract, canonical FeatureKit AdapterRegistry augmentation and named Endpoint control/management requests. Route message controls from canonical message/conversation refs; retain exact source authority and propagate caller/account/explicit-Root-stop cancellation. Replace removed Agent activity feedback exports with source-bound feedback. HMR pause/resume belongs to concrete plugin instances; plugins choose accepted-work policy without changing Controller commit/rollback authority. Cooperative worker/process control cancellation does not kill an isolate or reopen retired business admission.
|
|
12
|
+
|
|
13
|
+
Provide authenticated historical-data preview, exact selected binding, transactional application, authoritative inspection and explicit resumption, including active games. Preserve before-images and journals; never replay an unconfirmed effect, infer an account from an alias, or reuse historical permission grants. Owner V2 approvals remain inactive legacy data, while new writes use V3. Rollback requires matching software, lockfile and verified data; installing old code alone cannot read a new V3 approval file. Conflicting after-images are never overwritten.
|
|
14
|
+
|
|
15
|
+
See docs/plans/adapter-operation-version-boundaries.md and the Chinese/English upgrade guide for the complete version inventory, custom-plugin changes, acceptance and recovery sequence. These code and metadata changes do not authorize merging, publication, deployment, login, production migration or removal of installed workspace patches.
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [2c3610a]
|
|
18
|
+
- Updated dependencies [a9f4e48]
|
|
19
|
+
- Updated dependencies [f8505b0]
|
|
20
|
+
- @zhin.js/core@1.1.54
|
|
21
|
+
- @zhin.js/game-kit@1.1.19
|
|
22
|
+
|
|
23
|
+
## 0.1.7
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [c58c589]
|
|
28
|
+
- @zhin.js/core@1.1.53
|
|
29
|
+
- @zhin.js/game-kit@1.1.18
|
|
30
|
+
|
|
3
31
|
## 0.1.6
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
-
import { gameCommandFeedback } from '@zhin.js/game-kit';
|
|
2
|
+
import { gameCommandFeedback, gamePlayerFromInput, replyGameOperation } from '@zhin.js/game-kit';
|
|
3
3
|
import { defineCommand } from 'zhin.js/command';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveRouletteFlow } from "../../../lib/runtime.js";
|
|
5
|
+
import { getRouletteHelp, normalizeRouletteAction } from "../../../lib/roulette-actions.js";
|
|
5
6
|
import { runRouletteCommand } from "../../../lib/roulette-command.js";
|
|
6
7
|
export default defineCommand({
|
|
7
8
|
description: '俄罗斯轮盘:多人轮流开枪,中弹者输', descriptionI18n: { en: "Russian roulette: take turns; the player hit loses" },
|
|
8
9
|
params: { action: { type: 'string', default: '' } },
|
|
9
10
|
async execute(context) {
|
|
10
11
|
const action = String(context.params.action ?? '');
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
const normalized = normalizeRouletteAction(action);
|
|
13
|
+
if (normalized === 'help')
|
|
14
|
+
return getRouletteHelp(context);
|
|
15
|
+
if (!normalized)
|
|
16
|
+
return gameCommandFeedback(context, 'unknown', action) + '\n\n' + getRouletteHelp(context);
|
|
17
|
+
const flow = resolveRouletteFlow(context), player = gamePlayerFromInput(context.input, context);
|
|
18
|
+
await replyGameOperation({ session: flow.services.sessions, player, reply: context.session?.reply,
|
|
19
|
+
run: () => runRouletteCommand(context, context.input, action) });
|
|
13
20
|
},
|
|
14
21
|
});
|
package/lib/deadline-job.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { outboundHostToken } from 'zhin.js';
|
|
2
|
-
import {
|
|
2
|
+
import { sendGameSessionMessage } from '@zhin.js/game-kit';
|
|
3
3
|
import { expireOverdueSessions } from './game-flow.js';
|
|
4
4
|
import { resolveRouletteFlow } from './runtime.js';
|
|
5
5
|
/** Turn and lobby deadlines are checked every five seconds. */
|
|
@@ -28,16 +28,7 @@ function readOutbound(context) {
|
|
|
28
28
|
async function announce(outbound, notice) {
|
|
29
29
|
const session = notice.session;
|
|
30
30
|
try {
|
|
31
|
-
await outbound.
|
|
32
|
-
locale: resolveLocale(session.locale),
|
|
33
|
-
adapter: session.adapter,
|
|
34
|
-
endpointKey: session.endpoint,
|
|
35
|
-
conversation: {
|
|
36
|
-
kind: session.channel_type,
|
|
37
|
-
id: session.channel_id,
|
|
38
|
-
},
|
|
39
|
-
content: notice.text,
|
|
40
|
-
});
|
|
31
|
+
await sendGameSessionMessage({ outbound, session, content: notice.text });
|
|
41
32
|
}
|
|
42
33
|
catch {
|
|
43
34
|
// The result is already stored; a failed notice must not stop the other sessions.
|
package/lib/game-flow.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type GamePlayer } from '@zhin.js/game-kit';
|
|
2
2
|
import type { RouletteConfig } from './config.js';
|
|
3
3
|
import type { RouletteSessionRow } from './models.js';
|
|
4
4
|
import type { MutePenaltyPort, PenaltyConversation } from './mute-penalty.js';
|
|
5
5
|
import { type RouletteCommandAction } from './roulette-actions.js';
|
|
6
|
-
import { type
|
|
6
|
+
import { type RouletteServices } from './session-service.js';
|
|
7
7
|
/** Everything one action needs, resolved from the current runtime generation. */
|
|
8
8
|
export interface RouletteFlow {
|
|
9
9
|
readonly services: RouletteServices;
|
|
@@ -12,8 +12,7 @@ export interface RouletteFlow {
|
|
|
12
12
|
}
|
|
13
13
|
/** Who sent the action, from the runtime message. */
|
|
14
14
|
export interface RouletteActor {
|
|
15
|
-
readonly message:
|
|
16
|
-
readonly endpoint: EndpointIdentity;
|
|
15
|
+
readonly message: GamePlayer;
|
|
17
16
|
readonly roles: readonly string[];
|
|
18
17
|
}
|
|
19
18
|
export interface ExpiredSessionNotice {
|
package/lib/game-flow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createMessages, resolveLocale } from 'zhin.js/i18n';
|
|
2
|
-
import { gameCommandHint, GameSessionConflictError, SessionRevisionConflictError, channelKey } from '@zhin.js/game-kit';
|
|
2
|
+
import { gameCommandHint, GameSessionConflictError, SessionRevisionConflictError, channelKey, gameTargetFromKey } from '@zhin.js/game-kit';
|
|
3
3
|
import { RouletteRuleError, loserOf } from './engine.js';
|
|
4
4
|
import { getRouletteHelp } from './roulette-actions.js';
|
|
5
5
|
import { playerFromMessage, stateOf, } from './session-service.js';
|
|
@@ -8,25 +8,27 @@ const DEADLINE_ACTOR = 'deadline';
|
|
|
8
8
|
const MANAGER_ROLES = ['master', 'trusted', 'owner', 'admin'];
|
|
9
9
|
function noGame(message) { return messages(message.locale, 'noGame') + gameCommandHint(message, 'roulette', 'open'); }
|
|
10
10
|
export async function runRouletteAction(flow, actor, action) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
return flow.services.sessions.runInScope({ scope: channelKey(actor.message), run: async () => {
|
|
12
|
+
const message = actor.message;
|
|
13
|
+
const locale = resolveLocale(message.locale);
|
|
14
|
+
if (action === 'help')
|
|
15
|
+
return getRouletteHelp(message);
|
|
16
|
+
if (message.target.kind === 'private')
|
|
17
|
+
return messages(locale, 'text1');
|
|
18
|
+
if (action === 'penalty-on' || action === 'penalty-off' || action === 'penalty-status') {
|
|
19
|
+
return handlePenaltySetting(flow, actor, action);
|
|
20
|
+
}
|
|
21
|
+
const notices = [];
|
|
22
|
+
let session = await flow.services.sessions.getActiveByChannel(channelKey(message));
|
|
23
|
+
if (session && isOverdue(session, Date.now())) {
|
|
24
|
+
const expired = await expireSession(flow, session);
|
|
25
|
+
if (expired)
|
|
26
|
+
notices.push(expired);
|
|
27
|
+
session = null;
|
|
28
|
+
}
|
|
29
|
+
const reply = await handleGameAction(flow, actor, action, session);
|
|
30
|
+
return [...notices, reply].join('\n\n');
|
|
31
|
+
} });
|
|
30
32
|
}
|
|
31
33
|
/** Called by the deadline schedule; each returned notice still has to be delivered. */
|
|
32
34
|
export async function expireOverdueSessions(flow, now, onNotice) {
|
|
@@ -34,14 +36,16 @@ export async function expireOverdueSessions(flow, now, onNotice) {
|
|
|
34
36
|
const notices = [];
|
|
35
37
|
for (const session of overdue) {
|
|
36
38
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
await flow.services.sessions.runInScope({ scope: session.channel_key, run: async () => {
|
|
40
|
+
const text = await expireSession(flow, session);
|
|
41
|
+
if (!text)
|
|
42
|
+
return;
|
|
43
|
+
const notice = { session, text };
|
|
44
|
+
notices.push(notice);
|
|
45
|
+
// Announce as soon as the result is stored, so a later failure cannot drop it.
|
|
46
|
+
if (onNotice)
|
|
47
|
+
await onNotice(notice);
|
|
48
|
+
} });
|
|
45
49
|
}
|
|
46
50
|
catch (error) {
|
|
47
51
|
// The session stays active when anything before the commit failed; the next sweep retries it.
|
|
@@ -67,7 +71,7 @@ async function handleGameAction(flow, actor, action, session) {
|
|
|
67
71
|
if (!gameAction)
|
|
68
72
|
return noGame(message);
|
|
69
73
|
try {
|
|
70
|
-
const result = await flow.services.sessions.applyAction(session, message
|
|
74
|
+
const result = await flow.services.sessions.applyAction(session, message.sender.id, gameAction, flow.config);
|
|
71
75
|
return describeResult(flow, gameAction, result, message);
|
|
72
76
|
}
|
|
73
77
|
catch (error) {
|
|
@@ -86,7 +90,7 @@ async function openLobby(flow, actor, session) {
|
|
|
86
90
|
return messages(locale, 'text4', { p0: describeStatus(stateOf(session), flow.config, { ...actor.message, locale }) });
|
|
87
91
|
}
|
|
88
92
|
try {
|
|
89
|
-
const created = await flow.services.sessions.createLobby(actor.message,
|
|
93
|
+
const created = await flow.services.sessions.createLobby(actor.message, flow.config);
|
|
90
94
|
return describeLobby(stateOf(created), flow.config, { ...actor.message, locale });
|
|
91
95
|
}
|
|
92
96
|
catch (error) {
|
|
@@ -165,7 +169,7 @@ async function applyLoserPenalty(flow, session, state, penaltyEnabled) {
|
|
|
165
169
|
async function handlePenaltySetting(flow, actor, action) {
|
|
166
170
|
const message = actor.message;
|
|
167
171
|
const locale = resolveLocale(message.locale);
|
|
168
|
-
if (message
|
|
172
|
+
if (message.target.kind !== 'group')
|
|
169
173
|
return messages(locale, 'text9');
|
|
170
174
|
const key = channelKey(message);
|
|
171
175
|
const settings = flow.services.settings;
|
|
@@ -177,13 +181,13 @@ async function handlePenaltySetting(flow, actor, action) {
|
|
|
177
181
|
if (!isManager(actor.roles))
|
|
178
182
|
return messages(locale, 'text10');
|
|
179
183
|
if (action === 'penalty-off') {
|
|
180
|
-
await settings.setMutePenalty(key, false, message
|
|
184
|
+
await settings.setMutePenalty(key, false, message.sender.id);
|
|
181
185
|
return messages(locale, 'text11');
|
|
182
186
|
}
|
|
183
187
|
if (support !== 'supported') {
|
|
184
188
|
return messages(locale, 'text12');
|
|
185
189
|
}
|
|
186
|
-
await settings.setMutePenalty(key, true, message
|
|
190
|
+
await settings.setMutePenalty(key, true, message.sender.id);
|
|
187
191
|
return messages(locale, 'text13', { p0: flow.config.muteSeconds });
|
|
188
192
|
}
|
|
189
193
|
function describePenaltyStatus(enabled, supported, config, locale) {
|
|
@@ -206,7 +210,7 @@ function toGameAction(action, message) {
|
|
|
206
210
|
return null;
|
|
207
211
|
}
|
|
208
212
|
function mayCancel(session, message, senderRoles) {
|
|
209
|
-
if (stateOf(session).hostId === message
|
|
213
|
+
if (stateOf(session).hostId === message.sender.id)
|
|
210
214
|
return true;
|
|
211
215
|
return isManager(senderRoles);
|
|
212
216
|
}
|
|
@@ -217,19 +221,17 @@ function isOverdue(session, now) {
|
|
|
217
221
|
return session.deadline_at > 0 && session.deadline_at <= now;
|
|
218
222
|
}
|
|
219
223
|
export function conversationOfSession(session) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
id: session.channel_id,
|
|
225
|
-
};
|
|
224
|
+
const target = gameTargetFromKey(session.channel_key);
|
|
225
|
+
if (!target)
|
|
226
|
+
throw new Error('Historical roulette session awaits explicit target migration');
|
|
227
|
+
return { endpointAdapter: target.endpoint.adapter, endpointId: target.endpoint.id, kind: target.kind, id: target.id };
|
|
226
228
|
}
|
|
227
229
|
function conversationOfActor(actor) {
|
|
228
230
|
return {
|
|
229
|
-
endpointAdapter: actor.endpoint.adapter,
|
|
230
|
-
endpointId: actor.endpoint.id,
|
|
231
|
-
kind: actor.message
|
|
232
|
-
id: actor.message
|
|
231
|
+
endpointAdapter: actor.message.target.endpoint.adapter,
|
|
232
|
+
endpointId: actor.message.target.endpoint.id,
|
|
233
|
+
kind: actor.message.target.kind,
|
|
234
|
+
id: actor.message.target.id,
|
|
233
235
|
};
|
|
234
236
|
}
|
|
235
237
|
const messages = createMessages({
|
package/lib/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { expireOverdueSessions, runRouletteAction, type RouletteActor, type Roul
|
|
|
4
4
|
export { DEADLINE_CRON, runDeadlineSweep } from './deadline-job.js';
|
|
5
5
|
export { resolveMutePenalty, type AdapterProjectionContext, type MutePenaltyPort, type MuteSupport, type PenaltyConversation, } from './mute-penalty.js';
|
|
6
6
|
export { ROULETTE_HELP, normalizeRouletteAction, type RouletteCommandAction } from './roulette-actions.js';
|
|
7
|
-
export {
|
|
8
|
-
export { RouletteGroupSettings, RouletteSessionService, createServices, type
|
|
7
|
+
export { rouletteServicesToken, resolveRouletteFlow } from './runtime.js';
|
|
8
|
+
export { RouletteGroupSettings, RouletteSessionService, createServices, type RouletteServices, } from './session-service.js';
|
package/lib/index.js
CHANGED
|
@@ -4,5 +4,5 @@ export { expireOverdueSessions, runRouletteAction } from './game-flow.js';
|
|
|
4
4
|
export { DEADLINE_CRON, runDeadlineSweep } from './deadline-job.js';
|
|
5
5
|
export { resolveMutePenalty, } from './mute-penalty.js';
|
|
6
6
|
export { ROULETTE_HELP, normalizeRouletteAction } from './roulette-actions.js';
|
|
7
|
-
export {
|
|
7
|
+
export { rouletteServicesToken, resolveRouletteFlow } from './runtime.js';
|
|
8
8
|
export { RouletteGroupSettings, RouletteSessionService, createServices, } from './session-service.js';
|
package/lib/mute-penalty.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { adapterFeatureId
|
|
1
|
+
import { adapterFeatureId } from 'zhin.js/adapter';
|
|
2
2
|
export function resolveMutePenalty(context) {
|
|
3
3
|
return {
|
|
4
4
|
support(conversation) {
|
|
@@ -11,25 +11,25 @@ export function resolveMutePenalty(context) {
|
|
|
11
11
|
if (!muteGroupMember) {
|
|
12
12
|
throw new Error('This Endpoint cannot mute group members');
|
|
13
13
|
}
|
|
14
|
-
await muteGroupMember(conversation.id, userId, durationSeconds);
|
|
14
|
+
await muteGroupMember({ groupId: conversation.id, userId, durationSeconds });
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** Resolve canonical identity, then capture its guarded management port from the same Index. */
|
|
19
19
|
function findMuteGroupMember(context, conversation) {
|
|
20
20
|
const adapters = readAdapterProjection(context);
|
|
21
21
|
if (!adapters)
|
|
22
22
|
return undefined;
|
|
23
23
|
if (!conversation.endpointAdapter || !conversation.endpointId)
|
|
24
24
|
return undefined;
|
|
25
|
-
let
|
|
25
|
+
let management;
|
|
26
26
|
try {
|
|
27
|
-
|
|
27
|
+
const id = adapters.resolve(conversation.endpointAdapter, conversation.endpointId);
|
|
28
|
+
management = id ? adapters.management(id) : undefined;
|
|
28
29
|
}
|
|
29
30
|
catch {
|
|
30
31
|
return undefined;
|
|
31
32
|
}
|
|
32
|
-
const management = resolveEndpointManagement(endpoint);
|
|
33
33
|
const muteGroupMember = management?.muteGroupMember;
|
|
34
34
|
if (typeof muteGroupMember !== 'function')
|
|
35
35
|
return undefined;
|
|
@@ -47,7 +47,7 @@ function readAdapterProjection(context) {
|
|
|
47
47
|
if (!projection || typeof projection !== 'object')
|
|
48
48
|
return undefined;
|
|
49
49
|
const lookup = projection;
|
|
50
|
-
if (typeof lookup.
|
|
50
|
+
if (typeof lookup.resolve !== 'function' || typeof lookup.management !== 'function')
|
|
51
51
|
return undefined;
|
|
52
52
|
return lookup;
|
|
53
53
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GamePlayer } from '@zhin.js/game-kit';
|
|
2
2
|
export type RouletteCommandAction = 'help' | 'open' | 'join' | 'start' | 'fire' | 'leave' | 'cancel' | 'status' | 'penalty-on' | 'penalty-off' | 'penalty-status';
|
|
3
3
|
/** Empty input means help; unknown words return null so normal chat is never hijacked. */
|
|
4
4
|
export declare function normalizeRouletteAction(raw: string): RouletteCommandAction | null;
|
|
5
|
-
export declare function getRouletteHelp(message?: Pick<
|
|
5
|
+
export declare function getRouletteHelp(message?: Pick<GamePlayer, 'locale' | 'formatCommand'>): string;
|
|
6
6
|
export declare const ROULETTE_HELP: string;
|
package/lib/roulette-command.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { gamePlayerFromInput } from '@zhin.js/game-kit';
|
|
2
2
|
import { runRouletteAction } from './game-flow.js';
|
|
3
3
|
import { normalizeRouletteAction, getRouletteHelp } from './roulette-actions.js';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveRouletteFlow, senderRolesOf } from './runtime.js';
|
|
5
5
|
/**
|
|
6
6
|
* Shared entry for the `roulette` command and the `轮盘` alias middleware.
|
|
7
7
|
* Returns null for unknown words so the alias middleware lets the chat through.
|
|
@@ -11,11 +11,10 @@ export async function runRouletteCommand(context, input, rawAction) {
|
|
|
11
11
|
if (!action)
|
|
12
12
|
return null;
|
|
13
13
|
if (action === 'help')
|
|
14
|
-
return getRouletteHelp(
|
|
14
|
+
return getRouletteHelp(gamePlayerFromInput(input, context));
|
|
15
15
|
const flow = resolveRouletteFlow(context);
|
|
16
16
|
const actor = {
|
|
17
|
-
message:
|
|
18
|
-
endpoint: endpointOf(input),
|
|
17
|
+
message: gamePlayerFromInput(input, context),
|
|
19
18
|
roles: senderRolesOf(input),
|
|
20
19
|
};
|
|
21
20
|
return runRouletteAction(flow, actor, action);
|
package/lib/runtime.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Locale } from 'zhin.js/i18n';
|
|
|
2
2
|
import { type Token } from 'zhin.js';
|
|
3
3
|
import type { RouletteFlow } from './game-flow.js';
|
|
4
4
|
import { type AdapterProjectionContext } from './mute-penalty.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { RouletteServices } from './session-service.js';
|
|
6
6
|
/** Owner-scoped services consumed by discovered commands, middleware and the deadline job. */
|
|
7
7
|
export declare const rouletteServicesToken: Token<RouletteServices>;
|
|
8
8
|
/** A command, middleware or schedule capability context. */
|
|
@@ -15,5 +15,3 @@ export interface RouletteCapabilityContext extends AdapterProjectionContext {
|
|
|
15
15
|
export declare function resolveRouletteFlow(context: RouletteCapabilityContext): RouletteFlow;
|
|
16
16
|
/** Framework roles (master / trusted / user) of whoever sent the message. */
|
|
17
17
|
export declare function senderRolesOf(input: unknown): readonly string[];
|
|
18
|
-
/** `message.conversation.endpoint`, or an empty identity (no mute possible) when absent. */
|
|
19
|
-
export declare function endpointOf(input: unknown): EndpointIdentity;
|
package/lib/runtime.js
CHANGED
|
@@ -22,19 +22,3 @@ export function senderRolesOf(input) {
|
|
|
22
22
|
return [];
|
|
23
23
|
return roles.filter((role) => typeof role === 'string');
|
|
24
24
|
}
|
|
25
|
-
/** `message.conversation.endpoint`, or an empty identity (no mute possible) when absent. */
|
|
26
|
-
export function endpointOf(input) {
|
|
27
|
-
const empty = { adapter: '', id: '' };
|
|
28
|
-
if (!input || typeof input !== 'object')
|
|
29
|
-
return empty;
|
|
30
|
-
const conversation = input.conversation;
|
|
31
|
-
if (!conversation || typeof conversation !== 'object')
|
|
32
|
-
return empty;
|
|
33
|
-
const endpoint = conversation.endpoint;
|
|
34
|
-
if (!endpoint || typeof endpoint !== 'object')
|
|
35
|
-
return empty;
|
|
36
|
-
const identity = endpoint;
|
|
37
|
-
if (typeof identity.adapter !== 'string' || typeof identity.id !== 'string')
|
|
38
|
-
return empty;
|
|
39
|
-
return { adapter: identity.adapter, id: identity.id };
|
|
40
|
-
}
|
package/lib/session-service.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { Database, Models } from '@zhin.js/core';
|
|
2
|
-
import { VersionedSessionService, type
|
|
2
|
+
import { VersionedSessionService, type GamePlayer } from '@zhin.js/game-kit';
|
|
3
3
|
import type { RouletteConfig } from './config.js';
|
|
4
4
|
import { type RoulettePlayer, type RouletteState } from './engine.js';
|
|
5
5
|
import type { RouletteSessionRow } from './models.js';
|
|
6
6
|
export type RouletteDatabase = Database<unknown, Models, string>;
|
|
7
|
-
/** `message.conversation.endpoint`: the adapter plugin owner and the Endpoint capability id. */
|
|
8
|
-
export interface EndpointIdentity {
|
|
9
|
-
readonly adapter: string;
|
|
10
|
-
readonly id: string;
|
|
11
|
-
}
|
|
12
7
|
export type RouletteAction = {
|
|
13
8
|
readonly type: 'join';
|
|
14
9
|
readonly player: RoulettePlayer;
|
|
@@ -36,7 +31,7 @@ export interface RouletteServiceOptions {
|
|
|
36
31
|
export declare class RouletteSessionService extends VersionedSessionService<RouletteSessionRow> {
|
|
37
32
|
#private;
|
|
38
33
|
constructor(database: RouletteDatabase, options?: RouletteServiceOptions);
|
|
39
|
-
createLobby(message:
|
|
34
|
+
createLobby(message: GamePlayer, config: RouletteConfig): Promise<RouletteSessionRow>;
|
|
40
35
|
/** Every seated player counts as busy, not just the host. */
|
|
41
36
|
getActiveForUser(channelKeyValue: string, userId: string): Promise<RouletteSessionRow | null>;
|
|
42
37
|
applyAction(session: RouletteSessionRow, actorId: string, action: RouletteAction, config: RouletteConfig): Promise<RouletteActionResult>;
|
|
@@ -56,4 +51,4 @@ export interface RouletteServices {
|
|
|
56
51
|
}
|
|
57
52
|
export declare function createServices(database: RouletteDatabase, options?: RouletteServiceOptions): RouletteServices;
|
|
58
53
|
export declare function stateOf(session: RouletteSessionRow): RouletteState;
|
|
59
|
-
export declare function playerFromMessage(message:
|
|
54
|
+
export declare function playerFromMessage(message: GamePlayer): RoulettePlayer;
|
package/lib/session-service.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encodeStorageIdentity } from 'zhin.js/plugin-runtime';
|
|
2
|
+
import { VersionedSessionService, generateCompactId, secureRandomInt, } from '@zhin.js/game-kit';
|
|
2
3
|
import { cancelGame, createLobby, currentPlayer, decodeRouletteState, expireLobby, expireTurn, isOpen, joinLobby, leaveGame, pullTrigger, startRound, } from './engine.js';
|
|
3
4
|
export class RouletteSessionService extends VersionedSessionService {
|
|
4
5
|
#chooseBulletChamber;
|
|
@@ -11,30 +12,28 @@ export class RouletteSessionService extends VersionedSessionService {
|
|
|
11
12
|
});
|
|
12
13
|
this.#chooseBulletChamber = options.chooseBulletChamber ?? secureRandomInt;
|
|
13
14
|
}
|
|
14
|
-
async createLobby(message,
|
|
15
|
+
async createLobby(message, config) {
|
|
15
16
|
const now = Date.now();
|
|
16
17
|
const host = playerFromMessage(message);
|
|
17
18
|
const state = createLobby(host, tableRules(config));
|
|
18
19
|
return this.createRow({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
updated_at: now,
|
|
37
|
-
created_at: now,
|
|
20
|
+
target: message.target,
|
|
21
|
+
fields: {
|
|
22
|
+
id: generateCompactId('rlt_'),
|
|
23
|
+
locale: message.locale ?? 'zh-CN',
|
|
24
|
+
endpoint_adapter: encodeStorageIdentity(message.target.endpoint.adapter),
|
|
25
|
+
endpoint_id: encodeStorageIdentity(message.target.endpoint.id),
|
|
26
|
+
host_id: host.id,
|
|
27
|
+
state_json: JSON.stringify(state),
|
|
28
|
+
phase: state.phase,
|
|
29
|
+
turn: '',
|
|
30
|
+
revision: 0,
|
|
31
|
+
processed_actions: '[]',
|
|
32
|
+
deadline_at: now + config.lobbyTimeoutSeconds * 1000,
|
|
33
|
+
status: 'active',
|
|
34
|
+
updated_at: now,
|
|
35
|
+
created_at: now,
|
|
36
|
+
},
|
|
38
37
|
});
|
|
39
38
|
}
|
|
40
39
|
/** Every seated player counts as busy, not just the host. */
|
|
@@ -121,8 +120,8 @@ export function stateOf(session) {
|
|
|
121
120
|
return decodeRouletteState(session.state_json);
|
|
122
121
|
}
|
|
123
122
|
export function playerFromMessage(message) {
|
|
124
|
-
const name = message
|
|
125
|
-
return { id: message
|
|
123
|
+
const name = message.sender.name?.trim() || message.sender.id;
|
|
124
|
+
return { id: message.sender.id, name };
|
|
126
125
|
}
|
|
127
126
|
function tableRules(config) {
|
|
128
127
|
return { chamberCount: config.chambers, maxPlayers: config.maxPlayers };
|
package/lib/view.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GamePlayer } from '@zhin.js/game-kit';
|
|
2
2
|
import type { RouletteConfig } from './config.js';
|
|
3
3
|
import { type RoulettePlayer, type RouletteState } from './engine.js';
|
|
4
|
-
type Presentation = Pick<
|
|
4
|
+
type Presentation = Pick<GamePlayer, 'locale' | 'formatCommand'>;
|
|
5
5
|
export declare const ROULETTE_TITLE: string;
|
|
6
6
|
export declare function describeLobby(state: RouletteState, config: RouletteConfig, presentation?: Presentation): string;
|
|
7
7
|
export declare function describeRoundStart(state: RouletteState, config: RouletteConfig, presentation?: Presentation): string;
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
-
import { defineGameCommandAliasMiddleware } from '@zhin.js/game-kit';
|
|
2
|
+
import { defineGameCommandAliasMiddleware, gamePlayerFromInput, replyGameOperation } from '@zhin.js/game-kit';
|
|
3
|
+
import { normalizeRouletteAction, getRouletteHelp } from "../../lib/roulette-actions.js";
|
|
4
|
+
import { resolveRouletteFlow } from "../../lib/runtime.js";
|
|
3
5
|
import { runRouletteCommand } from "../../lib/roulette-command.js";
|
|
4
6
|
/** `轮盘 加入` / `/轮盘赌 开枪` / `roulette start` route to the same logic as the command. */
|
|
5
7
|
export default defineGameCommandAliasMiddleware({
|
|
6
8
|
aliases: ['轮盘', '轮盘赌', 'roulette'],
|
|
7
9
|
async run(action, input, context) {
|
|
8
|
-
|
|
10
|
+
const normalized = normalizeRouletteAction(action);
|
|
11
|
+
if (!normalized)
|
|
12
|
+
return false;
|
|
13
|
+
if (normalized === 'help') {
|
|
14
|
+
await context.input.$reply(getRouletteHelp({ locale: context.input.locale,
|
|
15
|
+
formatCommand: path => context.input.formatCommand?.(path, context.owner.id) }));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const flow = resolveRouletteFlow(context), player = gamePlayerFromInput(input);
|
|
19
|
+
await replyGameOperation({ session: flow.services.sessions, player, reply: content => context.input.$reply(content),
|
|
20
|
+
run: () => runRouletteCommand(context, input, action) });
|
|
21
|
+
return true;
|
|
9
22
|
},
|
|
10
23
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
-
import { channelKey,
|
|
2
|
+
import { channelKey, gamePlayerFromInput, replyGameOperation } from '@zhin.js/game-kit';
|
|
3
3
|
import { defineMiddleware } from 'zhin.js/middleware';
|
|
4
4
|
import { runRouletteCommand } from "../../lib/roulette-command.js";
|
|
5
5
|
import { rouletteServicesToken } from "../../lib/runtime.js";
|
|
@@ -11,20 +11,23 @@ const FIRE_WORDS = ['开枪', '扣动扳机'];
|
|
|
11
11
|
export default defineMiddleware({
|
|
12
12
|
target: 'inbound',
|
|
13
13
|
async handle(context, next) {
|
|
14
|
+
if (!context.input.sender?.id) {
|
|
15
|
+
await next();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
14
18
|
const text = context.input.content?.trim() ?? '';
|
|
15
19
|
if (!FIRE_WORDS.includes(text)) {
|
|
16
20
|
await next();
|
|
17
21
|
return;
|
|
18
22
|
}
|
|
19
|
-
const message =
|
|
23
|
+
const message = gamePlayerFromInput(context.input);
|
|
20
24
|
const services = context.use(rouletteServicesToken);
|
|
21
25
|
const session = await services.sessions.getActiveByChannel(channelKey(message));
|
|
22
|
-
if (!session || session.turn !== message
|
|
26
|
+
if (!session || session.turn !== message.sender.id) {
|
|
23
27
|
await next();
|
|
24
28
|
return;
|
|
25
29
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
await context.input.$reply(reply);
|
|
30
|
+
await replyGameOperation({ session: services.sessions, player: message, reply: content => context.input.$reply(content),
|
|
31
|
+
run: () => runRouletteCommand(context, context.input, 'fire') });
|
|
29
32
|
},
|
|
30
33
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-roulette",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Zhin.js 俄罗斯轮盘 — 多人轮流开枪,可按群开启禁言惩罚 (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@zhin.js/core": "1.1.
|
|
34
|
-
"@zhin.js/game-kit": "1.1.
|
|
33
|
+
"@zhin.js/core": "1.1.54",
|
|
34
|
+
"@zhin.js/game-kit": "1.1.19"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"typescript": "^6.0.3",
|
|
38
38
|
"vitest": "^4.1.10",
|
|
39
|
-
"@zhin.js/schedule-feature": "1.1.
|
|
40
|
-
"@zhin.js/testing": "1.0.
|
|
41
|
-
"zhin.js": "1.1.
|
|
39
|
+
"@zhin.js/schedule-feature": "1.1.10",
|
|
40
|
+
"@zhin.js/testing": "1.0.7",
|
|
41
|
+
"zhin.js": "1.1.19"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"plugins": []
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@zhin.js/command": "^1.1.
|
|
83
|
-
"@zhin.js/middleware": "^1.1.
|
|
84
|
-
"zhin.js": "^1.1.
|
|
85
|
-
"@zhin.js/schedule-feature": "^1.1.
|
|
82
|
+
"@zhin.js/command": "^1.1.12",
|
|
83
|
+
"@zhin.js/middleware": "^1.1.11",
|
|
84
|
+
"zhin.js": "^1.1.19",
|
|
85
|
+
"@zhin.js/schedule-feature": "^1.1.10"
|
|
86
86
|
},
|
|
87
87
|
"peerDependenciesMeta": {
|
|
88
88
|
"@zhin.js/command": {
|