Scenario generator for vore roleplay and story ideas. https://scenario-generator.deliciousreya.net/responses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

180 lines
8.0 KiB

import { type RollableTables, RollTable, RollTableOrder, ValueAccess } from './rolltable.js';
interface DbResponse {
tableId: RollTable,
text: string,
}
export enum UpdateResult {
Updated = 0,
NewConflict = 1,
OldGlobal = 2,
NoOldText = 3,
}
export enum DeleteResult {
Deleted = 0,
OldGlobal = 2,
NoOldText = 3,
}
function buildRollableTables(responses: DbResponse[]): RollableTables {
const out: {[key in RollTable]?: string[]} = {}
for (const table of RollTableOrder) {
out[table] = []
}
for (const { tableId, text } of responses) {
out[tableId]?.push(text)
}
return out as RollableTables
}
export class DbAccess {
private readonly getResponsesInServerQuery: D1PreparedStatement
private readonly getResponsesInDMQuery: D1PreparedStatement;
private readonly putResponseQuery: D1PreparedStatement;
private readonly checkResponseAlreadyExistsQuery: D1PreparedStatement;
private readonly getResponsesGlobal: D1PreparedStatement;
private readonly updateResponseQuery: D1PreparedStatement;
private readonly deleteResponseQuery: D1PreparedStatement;
constructor(db: D1Database) {
this.getResponsesGlobal = db.prepare(
`SELECT DISTINCT tableId, text FROM responses
WHERE access = ${ValueAccess.Global}`)
this.getResponsesInServerQuery = db.prepare(
`SELECT DISTINCT tableId, text FROM responses
WHERE access = ${ValueAccess.Global}
OR (access = ${ValueAccess.Server} AND serverSnowflake = ?);`)
this.getResponsesInDMQuery = db.prepare(
`SELECT DISTINCT tableId, text FROM responses
WHERE access = ${ValueAccess.Global}
OR (access = ${ValueAccess.CreatorDM} AND userSnowflake = ?);`)
this.putResponseQuery = db.prepare(
`INSERT OR IGNORE INTO responses (id, tableId, text, timestamp, userSnowflake, serverSnowflake, access) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING timestamp, access;`)
this.updateResponseQuery = db.prepare(
`UPDATE responses SET text = ?3, timestamp = ?4, userSnowflake = ?5, serverSnowflake = ?6
WHERE tableId = ?1 AND text = ?2
AND ((?7 = ${ValueAccess.CreatorDM} AND access = ${ValueAccess.CreatorDM} AND userSnowflake = ?5)
OR (?7 = ${ValueAccess.Server} AND access = ${ValueAccess.Server} AND serverSnowflake = ?6))
RETURNING timestamp, access;`)
this.deleteResponseQuery = db.prepare(
`DELETE FROM responses
WHERE tableId = ?1 AND text = ?2
AND ((?5 = ${ValueAccess.CreatorDM} AND access = ${ValueAccess.CreatorDM} AND userSnowflake = ?3)
OR (?5 = ${ValueAccess.Server} AND access = ${ValueAccess.Server} AND serverSnowflake = ?4))
RETURNING timestamp, access;`)
this.checkResponseAlreadyExistsQuery = db.prepare(
`SELECT timestamp, access FROM responses
WHERE tableId = ?1 AND text = ?2
AND (access = ${ValueAccess.Global}
OR (?3 = ${ValueAccess.CreatorDM} AND access = ${ValueAccess.CreatorDM} AND userSnowflake = ?4)
OR (?3 = ${ValueAccess.Server} AND access = ${ValueAccess.Server} AND serverSnowflake = ?4));`)
}
async getGlobalResponses(): Promise<RollableTables> {
const {results} = await this.getResponsesGlobal.all<DbResponse>()
return buildRollableTables(results)
}
async getResponsesInServer(inServerSnowflake: string): Promise<RollableTables> {
const statement = this.getResponsesInServerQuery.bind(inServerSnowflake)
const {results} = await statement.all<DbResponse>()
return buildRollableTables(results)
}
async getResponsesInDMWith(withUserSnowflake: string): Promise<RollableTables> {
const statement = this.getResponsesInDMQuery.bind(withUserSnowflake)
const {results} = await statement.all<DbResponse>()
return buildRollableTables(results)
}
async putResponse(requestTimestamp: number, table: RollTable, text: string, fromUserSnowflake: string, inServerSnowflake: string|null, access?: ValueAccess): Promise<{
timestamp: number,
access: ValueAccess,
inserted: boolean
}> {
const effectiveAccess = access ?? (inServerSnowflake ? ValueAccess.Server : ValueAccess.CreatorDM)
const relevantSnowflake = access === ValueAccess.Server ? inServerSnowflake : access === ValueAccess.CreatorDM ? fromUserSnowflake : null
const existingResponseStatement = this.checkResponseAlreadyExistsQuery.bind(table, text, effectiveAccess, relevantSnowflake)
const existingResponse = await existingResponseStatement.first<{timestamp: number, access: ValueAccess}>()
if (existingResponse) {
return {
timestamp: existingResponse.timestamp,
access: existingResponse.access,
inserted: false,
}
}
const statement = this.putResponseQuery.bind(
requestTimestamp, table, text, requestTimestamp, fromUserSnowflake, inServerSnowflake, effectiveAccess)
const result = await statement.first<{timestamp: number, access: ValueAccess}>()
if (!result) {
throw Error("no response from insert")
}
return {
timestamp: result.timestamp,
access: result.access,
inserted: true
}
}
async updateResponse(timestamp: number, table: RollTable, oldText: string, newText: string, userId: string, guildId: string | null, access?: ValueAccess): Promise<{result: UpdateResult.NoOldText|UpdateResult.Updated} | {result: UpdateResult.NewConflict, timestamp: number, access: ValueAccess} | {result: UpdateResult.OldGlobal, timestamp: number, access: ValueAccess.Global}> {
const effectiveAccess = access ?? (guildId ? ValueAccess.Server : ValueAccess.CreatorDM)
const relevantSnowflake = access === ValueAccess.Server ? guildId : access === ValueAccess.CreatorDM ? userId : null
const existingOldResponseStatement = this.checkResponseAlreadyExistsQuery.bind(table, oldText, effectiveAccess, relevantSnowflake)
const existingOldResponse = await existingOldResponseStatement.first<{timestamp: number, access: ValueAccess}>()
if (!existingOldResponse) {
return {
result: UpdateResult.NoOldText
}
} else if (existingOldResponse.access === ValueAccess.Global) {
return {
timestamp: existingOldResponse.timestamp,
access: existingOldResponse.access,
result: UpdateResult.OldGlobal,
}
}
const existingNewResponseStatement = this.checkResponseAlreadyExistsQuery.bind(table, newText, effectiveAccess, relevantSnowflake)
const existingNewResponse = await existingNewResponseStatement.first<{timestamp: number, access: ValueAccess}>()
if (existingNewResponse) {
return {
result: UpdateResult.NewConflict,
timestamp: existingNewResponse.timestamp,
access: existingNewResponse.access,
}
}
const statement = this.updateResponseQuery.bind(
table, oldText, newText, timestamp, userId, guildId, effectiveAccess)
await statement.run()
return {result: UpdateResult.Updated}
}
async deleteResponse(table: RollTable, text: string, userId: string, guildId: string | null, access?: ValueAccess): Promise<
{result: DeleteResult.Deleted, timestamp: number, access: ValueAccess} |
{result: DeleteResult.NoOldText} |
{result: DeleteResult.OldGlobal, timestamp: number, access: ValueAccess.Global}
> {
const effectiveAccess = access ?? (guildId ? ValueAccess.Server : ValueAccess.CreatorDM)
const relevantSnowflake = access === ValueAccess.Server ? guildId : access === ValueAccess.CreatorDM ? userId : null
const existingOldResponseStatement = this.checkResponseAlreadyExistsQuery.bind(table, text, effectiveAccess, relevantSnowflake)
const existingOldResponse = await existingOldResponseStatement.first<{timestamp: number, access: ValueAccess}>()
if (!existingOldResponse) {
return {
result: DeleteResult.NoOldText
}
} else if (existingOldResponse.access === ValueAccess.Global) {
return {
timestamp: existingOldResponse.timestamp,
access: existingOldResponse.access,
result: DeleteResult.OldGlobal,
}
}
const statement = this.deleteResponseQuery.bind(
table, text, userId, guildId, effectiveAccess)
const deleted = await statement.first<{timestamp: number, access: ValueAccess}>()
if (!deleted) {
throw Error("no response from delete")
}
return {result: DeleteResult.Deleted, timestamp: deleted.timestamp, access: deleted.access}
}
}