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.
 
 

380 lines
12 KiB

import {
type FinalGeneratedContents,
type FinalGeneratedState,
type GeneratedContents,
type GeneratedState,
type InProgressGeneratedContents,
type InProgressGeneratedState,
RolledValues, rollOn, rollResultToString,
RollSelections,
type RollTable,
type RollTableAuthor,
RollTableDatabase,
type RollTableDetailsNoResults,
type RollTableResultFull,
} from '../../common/rolltable';
import { type PreparedQueries, type QueryOutput, TypedDBWrapper } from './querytypes';
import { DatabaseQueries } from './queries';
function processOperationResult(result: QueryOutput<(typeof DatabaseQueries)['getResultMappingsForDiscordSet']>[number] | undefined): (RollTableResultFull<RollTableDetailsNoResults> & {
status: 'updated' | 'existing'
}) | undefined {
if (!result) {
return result;
}
return {
full: true,
mappingId: result.mappingId,
textId: result.resultId,
text: result.resultText,
table: {
full: 'details',
id: result.tableId,
identifier: result.tableIdentifier,
name: result.tableName,
title: result.tableTitle,
emoji: result.tableEmoji,
header: result.tableHeader,
ordinal: result.tableOrdinal
},
author: (result.authorId === null || result.authorName === null || result.authorRelation === null) ? null : {
id: result.authorId,
name: result.authorName,
url: result.authorUrl,
relation: result.authorRelation
},
set: {
id: result.setId,
name: result.setName,
description: result.setDescription,
global: !!(result.setGlobal)
},
updated: new Date(result.updated),
status: result.status
};
}
export class Database {
private readonly db: TypedDBWrapper;
private readonly queries: PreparedQueries<typeof DatabaseQueries>;
constructor(db: D1Database) {
this.db = new TypedDBWrapper(db);
this.queries = this.db.prepareAll(DatabaseQueries);
}
async autocompleteTable(tableSoFar: string) {
return this.db.run(this.queries.autocompleteTable({
tableIdentifierSubstring: tableSoFar
}));
}
async autocompleteText(setSnowflake: string, tableIdentifier: string, partialText: string, includeGlobal: boolean) {
return this.db.run(this.queries.autocompleteTextForDiscordSet({
setSnowflake: setSnowflake,
tableIdentifierSubstring: tableIdentifier,
pattern: partialText,
includeGlobal
}));
}
async addResponseFromDiscord(timestamp: number, table: string | number, text: string, userId: string, username: string, setId: string) {
const [, , , , results] = await this.db.batch(
this.queries.addResultForAddMapping({ tableIdentifier: table, text }),
this.queries.addDiscordAuthorForAddMapping({ userSnowflake: userId, username }),
this.queries.addDiscordSetForAddMapping({ setSnowflake: setId, userSnowflake: userId }),
this.queries.addDiscordResultMapping({
timestamp,
tableIdentifier: table,
resultText: text,
userSnowflake: userId,
setSnowflake: setId
}),
this.queries.getResultMappingsForDiscordSet({
timestamp,
tableIdentifier: table,
text,
setSnowflake: setId,
includeGlobal: false
})
);
const result = processOperationResult(results[0]);
if (!result) {
throw Error('failed adding the new response');
}
return {
...result,
status: result.status === 'updated' ? 'added' : 'existed'
};
}
async editResponseFromDiscord(timestamp: number, table: number | string, oldText: string, newText: string, userId: string, username: string, setId: string): Promise<{
status: 'nonexistent'
} | {
status: 'noneditable',
old: RollTableResultFull<RollTableDetailsNoResults>
} | {
status: 'conflict' | 'updated',
old: RollTableResultFull<RollTableDetailsNoResults>,
new: RollTableResultFull<RollTableDetailsNoResults>,
}> {
const [oldResults, , , , newResults] = await this.db.batch(
this.queries.getResultMappingsForDiscordSet({
timestamp,
tableIdentifier: table,
text: oldText,
setSnowflake: setId,
includeGlobal: true
}),
this.queries.addResultForEditMapping({
tableIdentifier: table,
oldText,
newText,
setSnowflake: setId
}),
this.queries.addDiscordAuthorForEditMapping({
userSnowflake: userId,
username,
tableIdentifier: table,
oldText,
newText,
setSnowflake: setId
}),
this.queries.editMappingForDiscord({
timestamp,
tableIdentifier: table,
oldText,
newText,
userSnowflake: userId,
setSnowflake: setId
}),
this.queries.getResultMappingsForDiscordSet({
timestamp,
tableIdentifier: table,
text: newText,
setSnowflake: setId,
includeGlobal: false
})
);
const oldResult = processOperationResult(oldResults[0]);
if (!oldResult) {
return { status: 'nonexistent' };
}
if (oldResult.set?.global) {
return { status: 'noneditable', old: oldResult };
}
const newResult = processOperationResult(newResults[0]);
if (!newResult) {
throw Error('failed to update response');
}
return {
status: newResult.status === 'updated' ? 'updated' : 'conflict',
old: oldResult,
new: newResult
};
}
async deleteResponseFromDiscord(table: number | string, text: string, setId: string): Promise<{
status: 'nonexistent'
} | {
status: 'noneditable' | 'deleted',
old: RollTableResultFull<RollTableDetailsNoResults>
}> {
const [oldResults, deleted] = await this.db.batch(
this.queries.getResultMappingsForDiscordSet({
timestamp: null,
tableIdentifier: table,
text,
setSnowflake: setId,
includeGlobal: true
}),
this.queries.deleteDiscordResultMapping({
tableIdentifier: table,
text,
setSnowflake: setId
})
);
const oldResult = processOperationResult(oldResults[0]);
if (!oldResult) {
return {
status: 'nonexistent'
};
}
if (!deleted) {
return {
status: 'noneditable',
old: oldResult
};
}
return {
status: 'deleted',
old: oldResult
};
}
async getResponseFromDiscord(table: number | string, text: string, setId: string): Promise<{
status: 'nonexistent'
} | ({
status: 'existent',
} & RollTableResultFull<RollTableDetailsNoResults>)> {
const results = await this.db.run(this.queries.getResultMappingsForDiscordSet({
timestamp: null,
tableIdentifier: table,
text,
setSnowflake: setId,
includeGlobal: true
}));
const result = processOperationResult(results[0]);
if (!result) {
return {
status: 'nonexistent'
};
}
return {
...result,
status: 'existent'
};
}
async getDiscordAuthor(id: string): Promise<RollTableAuthor | null> {
return await this.db.run(this.queries.getDiscordAuthor({
userSnowflake: id
}));
}
async setDiscordAuthor(id: string, username: string, name: string | null, url: string | null): Promise<RollTableAuthor | null> {
const [, result] = await this.db.batch(
this.queries.setDiscordAuthor({
userSnowflake: id,
username: username,
name: name,
url: url
}),
this.queries.getDiscordAuthor({ userSnowflake: id })
);
return result;
}
private async getGeneratorDataForDiscordSet(reroll: true, setSnowflake: string | null, oldResults?: InProgressGeneratedContents | null, finalize?: false): Promise<InProgressGeneratedState & {
db: RollTableDatabase
}>
private async getGeneratorDataForDiscordSet(reroll: false, setSnowflake: string | null, oldResults?: null): Promise<RollTableDatabase>
private async getGeneratorDataForDiscordSet(reroll: false, setSnowflake: string | null, oldResults: GeneratedContents, finalize: false): Promise<InProgressGeneratedState & {
db: RollTableDatabase
}>
private async getGeneratorDataForDiscordSet(reroll: false, setSnowflake: string | null, oldResults: GeneratedContents, finalize: true): Promise<FinalGeneratedState & {
db: RollTableDatabase
}>
private async getGeneratorDataForDiscordSet(reroll: false, setSnowflake: string | null, oldResults: InProgressGeneratedContents): Promise<InProgressGeneratedState & {
db: RollTableDatabase
}>
private async getGeneratorDataForDiscordSet(reroll: false, setSnowflake: string | null, oldResults: FinalGeneratedContents): Promise<FinalGeneratedState & {
db: RollTableDatabase
}>
private async getGeneratorDataForDiscordSet(reroll: boolean, setSnowflake: string | null, oldResults: GeneratedContents, finalize?: boolean): Promise<GeneratedState & {
db: RollTableDatabase
}>
private async getGeneratorDataForDiscordSet(reroll: boolean, setSnowflake: string | null, oldResults?: GeneratedContents | null, finalize?: boolean): Promise<RollTableDatabase | (GeneratedState & {
db: RollTableDatabase
})> {
const oldHeaders = oldResults && oldResults.rolled.size > 0 ? Array.from(oldResults.rolled.keys()) : [];
const [tables, oldKeys, oldSelection, { mappings, sets, authors }] =
await this.db.batch(
this.queries.getTables({}),
this.queries.getTableIdsByIdentifierOrHeader({
identifiersOrHeaders: oldHeaders
}),
this.queries.getTableIdsByIdentifierOrHeader({
identifiersOrHeaders: oldResults && !oldResults.final && oldResults.selected.size > 0 ? Array.from(oldResults.selected) : []
}),
this.queries.getFullDatabaseForDiscordSet({
setSnowflake
}));
const db = new RollTableDatabase({
tables: tables.map(v => ({ ...v, full: 'details' })),
authors,
sets,
results: mappings.map(v => ({ ...v, updated: new Date(v.updated) }))
});
if (!oldResults && !reroll) {
return db;
}
const selected = new RollSelections(oldSelection.flatMap(v => {
if (v === null) {
return [];
}
const table = db.tables.get(v);
if (!table) {
return [];
}
return [table];
}));
const rolled = new RolledValues();
const rollKeys = oldResults ? oldKeys : tables.map(t => t.id);
for (let index = 0; index < rollKeys.length; index += 1) {
const tableId = rollKeys[index];
const lookupTable = tableId !== null ? db.tables.get(tableId) : null;
const oldHeader = oldHeaders[index];
const [oldEmoji, oldTitle] = oldHeader ? oldHeader.split(' ', 2) : ['', ''];
const table: RollTable = lookupTable ?? {
full: false,
header: oldHeader,
emoji: oldEmoji,
title: oldTitle,
ordinal: index
};
const text = oldResults?.rolled.get(oldHeader);
if (reroll && table.full && (!text || selected.has(table))) {
const result = rollOn(table)
rolled.add(result);
} else if (text) {
const lookupResult = text && table.full === 'results' ? table.resultsByText.get(text) : null;
const result = lookupResult ?? {
full: false,
text: text,
table
}
rolled.add(result);
}
}
return (finalize ?? oldResults?.final) ? {
final: true,
db,
rolled
} : {
final: false,
db,
rolled,
selected
};
}
async getGeneratorPageForDiscordSet(setSnowflake: string | null, oldResults?: InProgressGeneratedContents | null): Promise<InProgressGeneratedState & {
db: RollTableDatabase
}> {
return this.getGeneratorDataForDiscordSet(true, setSnowflake, oldResults, false);
}
async expandFromDiscordSet(setId: string, contents: FinalGeneratedContents): Promise<FinalGeneratedState>
async expandFromDiscordSet(setId: string, contents: InProgressGeneratedContents): Promise<InProgressGeneratedState>
async expandFromDiscordSet(setId: string, contents: GeneratedContents): Promise<GeneratedState>
async expandFromDiscordSet(setId: string, contents: GeneratedContents): Promise<GeneratedState> {
return this.getGeneratorDataForDiscordSet(false, setId, contents);
}
async generateFromDiscordSet(setId: string): Promise<InProgressGeneratedState> {
return this.getGeneratorDataForDiscordSet(true, setId);
}
async rerollFromDiscordSet(setId: string, existing: InProgressGeneratedContents): Promise<InProgressGeneratedState> {
return this.getGeneratorDataForDiscordSet(true, setId, existing);
}
async reopenFromDiscordSet(setId: string, existing: FinalGeneratedContents): Promise<InProgressGeneratedState> {
return this.getGeneratorDataForDiscordSet(false, setId, existing, false);
}
async finalizeFromDiscordSet(setId: string, existing: InProgressGeneratedContents): Promise<FinalGeneratedState> {
return this.getGeneratorDataForDiscordSet(false, setId, existing, true);
}
}