this repo has no description
1import "@atcute/atproto";
2import { getAuth } from "./auth";
3import {
4 isPowerId,
5 parentPowers,
6 powerData,
7 type powerId,
8} from "../consts/astral";
9import { DevVielleDndAstral } from "../lexicons";
10import { map } from "nanostores";
11import type { ActorIdentifier } from "@atcute/lexicons";
12import { Client, simpleFetchHandler } from "@atcute/client";
13import type { XRPCQueries, XRPCProcedures } from "@atcute/lexicons/ambient";
14import {
15 CompositeDidDocumentResolver,
16 CompositeHandleResolver,
17 DohJsonHandleResolver,
18 LocalActorResolver,
19 PlcDidDocumentResolver,
20 WebDidDocumentResolver,
21 WellKnownHandleResolver,
22} from "@atcute/identity-resolver";
23
24export const isValidPowers = (data: powerObject): boolean =>
25 // all forceSelect are included in powers
26 Object.entries(powerData)
27 .filter(([_, { forceSelect }]) => forceSelect)
28 .reduce((acc, [id]) => acc && data.powers.includes(id), true) &&
29 // all powers are valid ids (power id not of #invalid or #locked)
30 data.powers.reduce(
31 (acc, curr) =>
32 acc &&
33 isPowerId(curr) &&
34 curr !== "dev.vielle.dnd.power#invalid" &&
35 curr !== "dev.vielle.dnd.power#locked",
36 true,
37 ) &&
38 // all powers parents are unlocked
39 data.powers.reduce(
40 (acc, curr) =>
41 acc &&
42 parentPowers[curr].reduce(
43 (acc, curr) => acc && data.powers.includes(curr),
44 true,
45 ),
46 true,
47 ) &&
48 // number of powers <= points
49 data.points >= data?.powers.length;
50
51const auth = (() => {
52 let client: Client<XRPCQueries, XRPCProcedures> | undefined,
53 did: `did:${string}:${string}` | undefined;
54
55 return async (): Promise<
56 [Exclude<typeof client, undefined>, Exclude<typeof did, undefined>]
57 > => {
58 if (!(client && did)) {
59 const [n_client, _, n_did] = await getAuth(true);
60 [client, did] = [n_client, n_did];
61 }
62 return [client, did];
63 };
64})();
65
66type Prettify<T> = {
67 [K in keyof T]: T[K];
68} & {};
69
70export type powerObject = Prettify<Omit<DevVielleDndAstral.Main, "$type">>;
71
72const requiredPowers = Object.entries(powerData)
73 .filter(([id, { forceSelect }]) => isPowerId(id) && forceSelect)
74 .map(([id]) => id as powerId);
75const initial: powerObject = {
76 points: requiredPowers.length,
77 powers: [...requiredPowers],
78};
79
80const powers = map<powerObject>({
81 points: initial.points,
82 powers: [...initial.powers],
83});
84
85async function set(next: powerObject) {
86 if (!isValidPowers(next)) {
87 const repair = confirm(
88 "Your powerlist is not valid. Would you like to repair it? This will reset your powers.",
89 );
90 if (!repair) throw new TypeError("Invalid power layout.");
91 else {
92 set({ points: initial.points, powers: [...initial.powers] });
93 }
94 }
95 const [client, did] = await auth();
96 const { ok, data } = await client.post("com.atproto.repo.putRecord", {
97 input: {
98 repo: did,
99 collection: "dev.vielle.dnd.astral",
100 rkey: "self",
101 record: {
102 $type: "dev.vielle.dnd.astral",
103 ...next,
104 },
105 },
106 });
107 if (!ok) {
108 console.error(data);
109 throw data;
110 }
111 powers.set(next);
112}
113
114// set a specific key; same as just calling set
115function setKey(key: "points", next: powerObject["points"]): Promise<void>;
116function setKey(key: "powers", next: powerObject["powers"]): Promise<void>;
117function setKey(
118 key: "points" | "powers",
119 next: powerObject["points" | "powers"],
120): Promise<void> {
121 // just pass call into set; it handles validation and auth
122 return set({
123 ...powers.get(),
124 [key]: next,
125 });
126}
127
128async function loadSelf() {
129 const [client, did] = await auth();
130 const { ok, data } = await client.get("com.atproto.repo.getRecord", {
131 params: {
132 repo: did,
133 collection: "dev.vielle.dnd.astral",
134 rkey: "self",
135 },
136 });
137
138 console.log(data);
139
140 if (ok) {
141 const res = await DevVielleDndAstral.mainSchema["~standard"].validate(
142 data.value,
143 );
144 if (res.issues) throw TypeError("Data not valid");
145
146 console.log(res.value, isValidPowers(res.value));
147 if (!isValidPowers(res.value)) {
148 const repair = confirm(
149 "Your powerlist is not valid. Would you like to repair it? This will reset your powers.",
150 );
151 if (!repair) throw new TypeError("Invalid power layout.");
152 else {
153 set({ points: initial.points, powers: [...initial.powers] });
154 }
155 } else
156 powers.set({
157 points: res.value.points,
158 powers: [...res.value.powers],
159 });
160
161 // getRecord errored, try create the record
162 // this does not update the power list as it sets it to the current value (assumed default)
163 } else {
164 const { ok } = await client.post("com.atproto.repo.createRecord", {
165 input: {
166 repo: did,
167 collection: "dev.vielle.dnd.astral",
168 rkey: "self",
169 record: powers.get(),
170 },
171 });
172 if (!ok) throw new Error("Could not load or create data");
173 }
174}
175
176async function loadUser(user: ActorIdentifier) {
177 const client = new Client({
178 handler: simpleFetchHandler({
179 service: await new LocalActorResolver({
180 handleResolver: new CompositeHandleResolver({
181 methods: {
182 http: new WellKnownHandleResolver(),
183 dns: new DohJsonHandleResolver({
184 dohUrl: "https://mozilla.cloudflare-dns.com/dns-query",
185 }),
186 },
187 strategy: "race",
188 }),
189 didDocumentResolver: new CompositeDidDocumentResolver({
190 methods: {
191 web: new WebDidDocumentResolver(),
192 plc: new PlcDidDocumentResolver(),
193 },
194 }),
195 })
196 .resolve(user)
197 .then((doc) => doc.pds),
198 }),
199 });
200
201 const { ok, data } = await client.get("com.atproto.repo.getRecord", {
202 params: {
203 repo: user,
204 collection: "dev.vielle.dnd.astral",
205 rkey: "self",
206 },
207 });
208
209 if (!ok) throw new Error("Record not found. User may not have astral powers");
210 const value = data.value;
211 const res = await DevVielleDndAstral.mainSchema["~standard"].validate(value);
212 if (res.issues) throw new TypeError("Record was not valid");
213 powers.set({
214 points: res.value.points,
215 powers: [...res.value.powers],
216 });
217}
218
219const proxy = { ...powers, set, setKey, loadSelf, loadUser };
220
221export default proxy;