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
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}
|
|
}
|
|
}
|
|
|