this repo has no description
at main 221 lines 6.3 kB view raw
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;