Compare commits

..

No commits in common. "52de1f49ac291cb4a92fc3e41813266deee7d285" and "815ca4d5282c50b89fc4b835f572967f40a2931b" have entirely different histories.

147 changed files with 2057 additions and 3158 deletions

19
.vscode/settings.json vendored
View file

@ -2,9 +2,24 @@
"files.associations": {
"*.css": "tailwindcss"
},
"json.schemas": [
{
"fileMatch": ["/src/content/series/**"],
"url": "./.astro/collections/series.schema.json"
},
{
"fileMatch": ["/src/content/tag-categories/**"],
"url": "./.astro/collections/tag-categories.schema.json"
},
{
"fileMatch": ["/src/content/users/**"],
"url": "./.astro/collections/users.schema.json"
}
],
"yaml.schemas": {
"./.astro/collections/series.schema.json": "/src/data/series/**",
"./.astro/collections/users.schema.json": "/src/data/users/**"
"./.astro/collections/series.schema.json": "/src/content/series/**",
"./.astro/collections/tag-categories.schema.json": "/src/content/tag-categories/**",
"./.astro/collections/users.schema.json": "/src/content/users/**"
},
"[astro]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"

View file

@ -2,8 +2,6 @@
Static website built in Astro + Typescript + TailwindCSS.
For attributions, see [`licenses.toml`](src/data/licenses.toml).
## Requirements
- Node.js 20+
@ -34,7 +32,6 @@ The following optional environment variables can be set within a `.env` file:
| Name | Type | Description |
| ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `APACHE_CONFIG` | boolean | If set to true, generates an `.htaccess` Apache config file at the root of the output directory. |
| `PUBLISH_DRAFTS` | boolean | If set to true, includes drafts in the production build. Published drafts still won't be directly indexed by any other pages. |
### Export story for upload
@ -56,3 +53,14 @@ Then, if you're using rsync, after configuring the `gallerybm` host (or the name
```bash
rsync --delete-after -acP dist/ gallerybm:/home/public
```
Or if you prefer LFTP, create a `.env` file at the root of the project:
```env
DEPLOY_LFTP_HOST=https://example-webdav-server.com
DEPLOY_LFTP_USER=example_user
DEPLOY_LFTP_PASSWORD=sup3r_s3cr3t_password
DEPLOY_LFTP_TARGETFOLDER=sites/gallery.badmanners.xyz/
```
Then run `npm run deploy-lftp`.

View file

@ -3,7 +3,7 @@ import tailwindIntegration from "@astrojs/tailwind";
import markdownIntegration from "@astropub/md";
import mdxIntegration from "@astrojs/mdx";
import htaccessIntegration from "astro-htaccess";
import pagefindIntegration from "./src/integrations/pagefind";
import pagefindIntegration from "astro-pagefind";
import alpinejsIntegration from "@astrojs/alpinejs";
import { AI_BOTS } from "./src/data/ai_bots";
@ -17,9 +17,7 @@ export default defineConfig({
markdownIntegration(),
mdxIntegration(),
alpinejsIntegration(),
pagefindIntegration({
forceLanguage: "en",
}),
pagefindIntegration(),
htaccessIntegration({
generateHtaccessFile: import.meta.env.APACHE_CONFIG === "true",
customRules: [
@ -60,14 +58,15 @@ export default defineConfig({
redirects: {
"/stories/1": "/stories",
},
env: {
schema: {
APACHE_CONFIG: envField.boolean({ context: "server", access: "public", default: false }),
PUBLISH_DRAFTS: envField.boolean({
context: "server",
access: "public",
default: false,
}),
experimental: {
env: {
schema: {
PUBLISH_DRAFTS: envField.boolean({
context: "server",
access: "public",
default: false,
}),
},
},
},
});

View file

@ -1,5 +1,5 @@
---
# id: some-custom-id
# slug: some-custom-slug
title: Example Blog Post
# pubDate: 2024-01-01
isDraft: true

View file

@ -1,5 +1,5 @@
---
# id: some-custom-id
# slug: some-custom-slug
title: Example Game
# shortTitle: Example
# pubDate: 2024-01-01

View file

@ -1,5 +1,5 @@
---
# id: some-custom-id
# slug: some-custom-slug
title: Example Story
# shortTitle: Example
# pubDate: 2024-01-01

2973
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "gallery.badmanners.xyz",
"type": "module",
"version": "1.15.1",
"version": "1.12.1",
"scripts": {
"postinstall": "astro sync",
"dev": "astro dev",
@ -12,26 +12,27 @@
"check": "astro check",
"astro": "astro",
"prettier": "prettier --write .",
"export-story": "tsx scripts/export-story.ts"
"export-story": "tsx scripts/export-story.ts",
"deploy-lftp": "dotenv tsx scripts/deploy-lftp.ts --"
},
"dependencies": {
"@astrojs/alpinejs": "^0.4.0",
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.0.1",
"@astrojs/rss": "^4.0.9",
"@astrojs/tailwind": "^5.1.3",
"@astrojs/mdx": "^3.1.7",
"@astrojs/rss": "^4.0.7",
"@astrojs/tailwind": "^5.1.1",
"@astropub/md": "^1.0.0",
"@pagefind/default-ui": "^1.2.0",
"@tailwindcss/typography": "^0.5.15",
"@types/alpinejs": "^3.13.10",
"alpinejs": "^3.14.1",
"astro": "^5.0.1",
"astro-htaccess": "^0.2.3",
"astro": "^4.15.11",
"astro-htaccess": "^0.2.0",
"astro-pagefind": "^1.6.0",
"clsx": "^2.1.1",
"fluid-tailwind": "^1.0.3",
"github-slugger": "^2.0.0",
"marked": "^14.1.2",
"pagefind": "^1.2.0",
"pagefind": "^1.1.1",
"polywasm": "^0.1.5",
"reading-time": "^1.5.0",
"sanitize-html": "^2.13.1",
@ -45,11 +46,11 @@
"@types/node": "^22.7.4",
"@types/sanitize-html": "^2.13.0",
"commander": "^12.1.0",
"dotenv-cli": "^7.4.2",
"fetch-retry": "^6.0.0",
"prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.8",
"sirv": "^3.0.0",
"tsx": "^4.19.1"
}
}

63
scripts/deploy-lftp.ts Normal file
View file

@ -0,0 +1,63 @@
import { spawn } from "node:child_process";
import { join, normalize as n } from "node:path/posix";
import { program, Option } from "commander";
interface DeployLftpOptions {
host: string;
user: string;
password: string;
targetFolder: string;
sourceFolder: string;
assetsFolder: string;
}
async function deployLftp({ host, user, password, targetFolder, sourceFolder, assetsFolder }: DeployLftpOptions) {
const process = spawn(
"lftp",
[
"-c",
[
`open -u ${user},${password.replaceAll(/([ \t.,:;?!`'"^|*+#&$\(\)\[\]{}<>\\/-])/, "\\$1")} ${host}`,
`mirror --reverse --include-glob ${join(assetsFolder, "*")} --delete --only-missing --no-perms --verbose ${n(sourceFolder)} ${n(targetFolder)}`,
`mirror --reverse --exclude-glob ${join(assetsFolder, "*")} --delete --no-perms --verbose ${n(sourceFolder)} ${n(targetFolder)}`,
`bye`,
].join("\n"),
],
{
stdio: "inherit",
},
);
await new Promise((resolve, reject) => {
process.on("close", (code) => (code === 0 ? resolve(0) : reject(`lftp failed with code ${code}`)));
});
}
await program
.name("deploy-lftp")
.description("Deploy files to remote server with LFTP")
.addOption(
new Option("-h, --host <hostname>", "Hostname of the LFTP (i.e. WebDav, SCP, SFTP...) remote.").env(
"DEPLOY_LFTP_HOST",
),
)
.addOption(new Option("-u, --user <username>", "Username portion of the LFTP credentials").env("DEPLOY_LFTP_USER"))
.addOption(
new Option("-p, --password <pass>", "Password portion of the LFTP credentials").env("DEPLOY_LFTP_PASSWORD"),
)
.addOption(
new Option("-t, --target-folder <remoteDir>", "Folder to mirror files to in the LFTP remote").env(
"DEPLOY_LFTP_TARGETFOLDER",
),
)
.addOption(
new Option("-s, --source-folder <localDir>", "Folder to read files from in the local machine")
.env("DEPLOY_LFTP_SOURCEFOLDER")
.default("dist/"),
)
.addOption(
new Option("-a, --assets-folder <localDir>", "Directory inside of --source-folder of assets with hash-based names")
.env("DEPLOY_LFTP_ASSETSFOLDER")
.default("assets/"),
)
.action(deployLftp)
.parseAsync();

View file

@ -7,7 +7,7 @@ import { createInterface } from "node:readline";
import { program } from "commander";
import fetchRetryWrapper from "fetch-retry";
import type { HealthcheckResponse } from "../src/pages/api/healthcheck";
import type { ExportStoryResponse } from "../src/pages/api/export-story/[...id]";
import type { ExportStoryResponse } from "../src/pages/api/export-story/[...slug]";
function getRTFStyles(rtfSource: string) {
const matches = rtfSource.matchAll(
@ -46,7 +46,7 @@ const isLibreOfficeRunning = async () =>
lines.on("close", () => res(false));
});
async function exportStory(id: string, options: { outputDir: string }) {
async function exportStory(slug: string, options: { outputDir: string }) {
/* Check that LibreOffice is not running */
if (await isLibreOfficeRunning()) {
console.error("ERROR: LibreOffice cannot be open while this command is running!");
@ -83,7 +83,7 @@ async function exportStory(id: string, options: { outputDir: string }) {
lines.on("close", reject);
});
console.log(`Astro listening on ${astroURL}`);
const response = await fetchRetry(new URL(`/api/healthcheck`, astroURL), { retries: 5, retryDelay: 2000 });
const response = await fetchRetry(new URL(`api/healthcheck`, astroURL), { retries: 5, retryDelay: 2000 });
if (!response.ok) {
throw new Error(response.statusText);
}
@ -103,11 +103,7 @@ async function exportStory(id: string, options: { outputDir: string }) {
let storyText = "";
try {
console.log("Getting data from Astro...");
const response = await fetchRetry(new URL(`/api/export-story/${id}`, astroURL), {
retries: 5,
retryDelay: 1000,
retryOn: [404],
});
const response = await fetch(new URL(`api/export-story/${slug}`, astroURL));
if (!response.ok) {
throw new Error(`Failed to reach export-story API (status code ${response.status})`);
}
@ -118,7 +114,7 @@ async function exportStory(id: string, options: { outputDir: string }) {
// Story
(async () => {
storyText = data.story;
await writeFile(join(outputDir, `${id}.txt`), storyText);
await writeFile(join(outputDir, `${slug}.txt`), storyText);
})(),
// Descriptions
Object.entries(data.description).map(
@ -133,7 +129,7 @@ async function exportStory(id: string, options: { outputDir: string }) {
.replace(/\?(&?[a-z][a-zA-Z0-9_-]+=[a-zA-Z0-9_-]*)*$/, "");
await copyFile(thumbnailPath, join(outputDir, `thumbnail${thumbnailPath.match(/\.[^.]+$/)![0]}`));
} else {
const thumbnail = await fetchRetry(data.thumbnail, { retries: 2, retryDelay: 2000 });
const thumbnail = await fetchRetry(data.thumbnail, { retries: 2, retryDelay: 10000 });
if (!thumbnail.ok) {
throw new Error("Failed to get thumbnail");
}
@ -157,9 +153,9 @@ async function exportStory(id: string, options: { outputDir: string }) {
console.log("Parsing story into output formats...");
// Process output files in parallel
await Promise.all([
// ${id}.md
writeFile(join(outputDir, `${id}.md`), storyText.replaceAll(/=(?==)/g, "= ").replaceAll("*", "\\*")),
// ${id}.rtf
// ${slug}.md
writeFile(join(outputDir, `${slug}.md`), storyText.replaceAll(/=(?==)/g, "= ").replaceAll("*", "\\*")),
// ${slug}.rtf
(async () => {
const tempDir = await mkdtemp(join(tmpdir(), "export-story-"));
await writeFile(join(tempDir, "temp.txt"), storyText.replaceAll(/\n\n+/g, "\n"));
@ -178,7 +174,7 @@ async function exportStory(id: string, options: { outputDir: string }) {
console.warn(`Missing RTF style "Normal"! Skipping RTF file generation.`);
} else {
await writeFile(
join(outputDir, `${id}.rtf`),
join(outputDir, `${slug}.rtf`),
rtfText.replaceAll(rtfStyles["Preformatted Text"], rtfStyles["Normal"]),
);
}
@ -191,7 +187,7 @@ async function exportStory(id: string, options: { outputDir: string }) {
await program
.name("export-story")
.description("Generate and export formatted upload files for a story")
.argument("<story-id>", `ID portion of the story's URL (eg. "the-lost-of-the-marshes/chapter-1")`)
.argument("<story-slug>", `Slug portion of the story's URL (eg. "the-lost-of-the-marshes/chapter-1")`)
.option("-o, --output-dir <directory>", `Empty or inexistent directory path to export files to`)
.action(exportStory)
.parseAsync();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,5 +1,5 @@
---
import type { CopyrightedCharacters } from "src/content.config";
import type { CopyrightedCharacters } from "@content/config";
import { t, type Lang } from "@i18n";
import UserComponent from "./UserComponent.astro";
import CopyrightedCharactersItem from "./CopyrightedCharactersItem.astro";

View file

@ -1,5 +1,5 @@
---
import type { Posts } from "src/content.config";
import type { Posts } from "@content/config";
import { t, type Lang } from "@i18n";
import { IconEkasPortal, IconFurAffinity, IconInkbunny, IconSoFurry, IconWeasyl } from "./icons/brands";
@ -16,53 +16,90 @@ const isVisible = eka || furaffinity || inkbunny || sofurry || weasyl;
{
isVisible ? (
<section id="external-posts-section" class="my-5 px-2 font-serif" aria-describedby="title-external-posts">
<Fragment>
<>
<h2 id="title-external-posts" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
{t(lang, "published_content/syndication_see_also_on")}
</h2>
<ul id="external-posts" class="flex flex-wrap justify-around">
{eka ? (
<li class="min-w-12 grow">
<a class="u-syndication text-link flex w-full flex-col" href={eka.link} target="_blank">
<a
class="u-syndication text-link w-full"
href={eka.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_eka")}
>
<IconEkasPortal class="mx-auto" width="2rem" height="2rem" />
<span class="text-center text-xs">{t(lang, "published_content/syndication_eka")}</span>
<span class="sr-only">{t(lang, "published_content/syndication_eka")}</span>
</a>
</li>
) : null}
{furaffinity ? (
<li class="min-w-12 grow">
<a class="u-syndication text-link flex w-full flex-col" href={furaffinity.link} target="_blank">
<a
class="u-syndication text-link w-full"
href={furaffinity.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_furaffinity")}
>
<IconFurAffinity class="mx-auto" width="2rem" height="2rem" />
<span class="text-center text-xs">{t(lang, "published_content/syndication_furaffinity")}</span>
<span class="sr-only">{t(lang, "published_content/syndication_furaffinity")}</span>
</a>
</li>
) : null}
{inkbunny ? (
<li class="min-w-12 grow">
<a class="u-syndication text-link flex w-full flex-col" href={inkbunny.link} target="_blank">
<a
class="u-syndication text-link w-full"
href={inkbunny.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_inkbunny")}
>
<IconInkbunny class="mx-auto" width="2rem" height="2rem" />
<span class="text-center text-xs">{t(lang, "published_content/syndication_inkbunny")}</span>
<span class="sr-only">{t(lang, "published_content/syndication_inkbunny")}</span>
</a>
</li>
) : null}
{sofurry ? (
<li class="min-w-12 grow">
<a class="u-syndication text-link flex w-full flex-col" href={sofurry.link} target="_blank">
<a
class="u-syndication text-link w-full"
href={sofurry.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_sofurry")}
>
<IconSoFurry class="mx-auto" width="2rem" height="2rem" />
<span class="text-center text-xs">{t(lang, "published_content/syndication_sofurry")}</span>
<span class="sr-only">{t(lang, "published_content/syndication_sofurry")}</span>
</a>
</li>
) : null}
{weasyl ? (
<li class="min-w-12 grow">
<a class="u-syndication text-link flex w-full flex-col" href={weasyl.link} target="_blank">
<a
class="u-syndication text-link w-full"
href={weasyl.link}
target="_blank"
aria-label={t(lang, "published_content/syndication_weasyl")}
>
<IconWeasyl class="mx-auto" width="2rem" height="2rem" />
<span class="text-center text-xs">{t(lang, "published_content/syndication_weasyl")}</span>
<span class="sr-only">{t(lang, "published_content/syndication_weasyl")}</span>
</a>
</li>
) : null}
</ul>
</Fragment>
</>
</section>
) : null
}
<script>
import tippy from "tippy.js";
import "tippy.js/dist/tippy.css";
const clipboardItems = document.querySelectorAll<HTMLElement>("ul#external-posts a[aria-label]");
tippy(clipboardItems, {
content: (el) => (el as HTMLElement).getAttribute("aria-label")!,
theme: "bm",
});
</script>

View file

@ -1,4 +1,5 @@
---
slug: ssh-all-the-way-down
title: SSH all the way down!
pubDate: 2024-09-22
isAgeRestricted: false

View file

@ -1,4 +1,5 @@
---
slug: supercharged-ssh-apps-on-sish
title: Supercharged SSH applications on sish
pubDate: 2024-09-23
isAgeRestricted: false

View file

@ -1,5 +1,4 @@
import { defineCollection, reference, z } from "astro:content";
import { file, glob } from "astro/loaders";
// Constants
@ -259,7 +258,7 @@ export type Posts = PublishedContent["posts"];
// Content collections
const storiesCollection = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/data/stories" }),
type: "content",
schema: ({ image }) =>
z
.object({
@ -288,7 +287,7 @@ const storiesCollection = defineCollection({
});
const gamesCollection = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/data/games" }),
type: "content",
schema: ({ image }) =>
z
.object({
@ -310,7 +309,7 @@ const gamesCollection = defineCollection({
});
const blogCollection = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/data/blog" }),
type: "content",
schema: ({ image }) =>
z
.object({
@ -329,7 +328,7 @@ const blogCollection = defineCollection({
// Data collections
const usersCollection = defineCollection({
loader: glob({ pattern: "*.{yml,yaml}", base: "./src/data/users" }),
type: "data",
schema: ({ image }) =>
z
.object({
@ -350,7 +349,7 @@ const usersCollection = defineCollection({
});
const seriesCollection = defineCollection({
loader: glob({ pattern: "*.{yml,yaml}", base: "./src/data/series" }),
type: "data",
schema: z.object({
// Required parameters
name: z.string(),
@ -359,10 +358,11 @@ const seriesCollection = defineCollection({
});
const tagCategoriesCollection = defineCollection({
loader: file("./src/data/tags.yaml"),
type: "data",
schema: z.object({
// Required parameters
category: z.string(),
name: z.string(),
index: z.number().int(),
tags: z
.array(
z.object({

View file

@ -32,7 +32,6 @@ tags:
- similar size
- implied perma endo
- endo trait theft
- partial vore
- straight sex
- gay sex
- hyper

View file

@ -9,7 +9,7 @@ thumbnail: /src/assets/thumbnails/stories/bm_10_birdroom.png
description: |
Beetle finds an odd-shaped friend deep in his work, and does what he does best: be a messy distraction.
This silly short was inspired by <a href="https://booru.badmanners.xyz/post/14" target="_blank">a wonderful commission</a> that I got from [Eli-Eternity](https://www.furaffinity.net/user/eli-eternity) and was supposed to go along with it, but this story doesn't even come close to doing it justice. Seriously, check out his piece if you haven't! It's turned out amazing, and he's done an incredible job, and I love it so much!
This silly short was inspired by <a href="https://booru.badmanners.xyz/index.php?q=post/view/4" target="_blank">a wonderful commission</a> that I got from [Eli-Eternity](https://www.furaffinity.net/user/eli-eternity) and was supposed to go along with it, but this story doesn't even come close to doing it justice. Seriously, check out his piece if you haven't! It's turned out amazing, and he's done an incredible job, and I love it so much!
Why are you still reading this instead of clicking the link above?!
posts:

View file

@ -1,4 +1,5 @@
---
slug: engaged-in-reality
title: Engaged in Reality
pubDate: 2024-11-01
authors: bad-manners

View file

@ -10,7 +10,7 @@ thumbnail: /src/assets/thumbnails/stories/bm_8_flavorful_favor.png
description: |
One, fearful and furry; one, formidable and feathery; and one, fired-up for the follow-up. A threesome fatefully forced into a fantastical face-to-face.
So many ideas that I want to write...! I just haven't found a lot of motivation to write as much as before. But I'm still working, and haven't gone anywhere. At least I finally managed to churn out this one, featuring Beetle, one of my OCs! You might recognize him from <a href="https://booru.badmanners.xyz/post/13" target="_blank">one of the few things</a> I actually uploaded this last month. Quite a fun personality to play with, along with my sona Sam and another OC, Muno - someone might remember that last name from one of my previous stories. All of my characters featured in stories are fun really, with a few that I wanna revisit or new ones that I make for each new setting. Just wish I had a better writing rhythm to put all those ideas to life like I used to, though! Managing to finish this story is a start, I guess.
So many ideas that I want to write...! I just haven't found a lot of motivation to write as much as before. But I'm still working, and haven't gone anywhere. At least I finally managed to churn out this one, featuring Beetle, one of my OCs! You might recognize him from <a href="https://booru.badmanners.xyz/index.php?q=post/view/3" target="_blank">one of the few things</a> I actually uploaded this last month. Quite a fun personality to play with, along with my sona Sam and another OC, Muno - someone might remember that last name from one of my previous stories. All of my characters featured in stories are fun really, with a few that I wanna revisit or new ones that I make for each new setting. Just wish I had a better writing rhythm to put all those ideas to life like I used to, though! Managing to finish this story is a start, I guess.
posts:
eka: https://aryion.com/g4/view/884215
furaffinity: https://www.furaffinity.net/view/51778522/

View file

@ -9,7 +9,7 @@ thumbnail: /src/assets/thumbnails/stories/bm_ff_14_part_of_the_show.png
description: |
You attend a show, unaware of how personal it can turn out...
This is a story based off of a YCH that I got from [Helkumurrr](https://www.furaffinity.net/user/helkumurrr) which <a href="https://booru.badmanners.xyz/post/15" target="_blank">you should definitely check out</a>. First piece with myself as pred, to complement my previous commission where I was prey and also with a short (and hopefully fun) story to go along with it!
This is a story based off of a YCH that I got from [Helkumurrr](https://www.furaffinity.net/user/helkumurrr) which <a href="https://booru.badmanners.xyz/index.php?q=post/view/5" target="_blank">you should definitely check out</a>. First piece with myself as pred, to complement my previous commission where I was prey and also with a short (and hopefully fun) story to go along with it!
posts:
eka: https://aryion.com/g4/view/902197
furaffinity: https://www.furaffinity.net/view/52509501/

View file

@ -1,4 +1,5 @@
---
slug: playing-it-safe
title: Playing It Safe
pubDate: 2024-08-08
authors: bad-manners

View file

@ -13,6 +13,7 @@ description: |
posts:
eka: https://aryion.com/g4/view/1030875
furaffinity: https://www.furaffinity.net/view/57459926/
inkbunny: https://inkbunny.net/s/3377727
sofurry: https://www.sofurry.com/view/2156241
sofurrybeta: https://sofurrybeta.com/s/ZmM3QrW1
weasyl: https://www.weasyl.com/~badmanners/submissions/2396279/c-rose-s-binge

View file

@ -27,8 +27,6 @@ tags:
- willing prey
- unwilling prey
- same size
- public vore
- casual vore
- flash fiction
---

View file

@ -35,7 +35,6 @@ tags:
- willing prey
- macro predator
- size difference
- public vore
- gay sex
- transformation
series: the-lost-of-the-marshes

View file

@ -0,0 +1,23 @@
name: Types of vore
index: 1
tags:
- name: { en: oral vore, tok: moku musi kepeken uta }
description:
en: Scenarios where prey are consumed by the predator's mouth.
tok: jan li moku musi e jan ante kepeken uta.
- name: anal vore
description: Scenarios where prey are consumed by the predator's butt/anus.
- name: cock vore
description: Scenarios where prey are consumed by the predator's penis.
- name: unbirth
description: Scenarios where prey are consumed by the predator's vagina/vulva, sometimes also called "vaginal vore".
- name: tail vore
description: Scenarios where prey are consumed by the predator's tail, through an opening or mouth on the appendage.
- name: slit vore
description: Scenarios where prey are consumed by the predator's genital slit.
- name: sheath vore
description: Scenarios where prey are consumed by the predator's genital sheath around their penis.
- name: nipple vore
description: Scenarios where prey are consumed by the predator's breast, through the nipples or cleavage.
- name: chest maw vore
description: Scenarios where prey are consumed by the predator's chest, through a maw on their chest cavity.

View file

@ -0,0 +1,13 @@
name: Recurring characters
index: 10
tags:
- name: Sam Brendan
description: |
Content that features my character and fursona <a href="https://badmanners.xyz/sam_brendan/" x-data="{ href: 'https://badmanners.xyz/sam_brendan', suffix: '?ageVerified=true' }" x-bind:href="ageVerified ? href + suffix : href" target="_blank" >Sam, the mimic x maned wolf hybrid</a>.
- name: Beetle
description: |
Content that features my character <a href="https://booru.badmanners.xyz/index.php?q=post/view/3" target="_blank">Beetle, the gryphon</a>.
- name: Muno
description: Content that features my character Muno, the snake.
- name: Yolk
description: Content that features <a href="https://www.furaffinity.net/user/vampire101" target="_blank" rel="nofollow">Vampire101's</a> fursona, Yolk the monkey.

View file

@ -0,0 +1,23 @@
name: Body types
index: 2
tags:
- name: anthro predator
description: Scenarios where at least one of the predators is an anthropomorphic animal, i.e. generally regarded as a "furry".
- name: feral predator
description: Scenarios where at least one of the predators is an animal based on a real or mythological creature.
- name: taur predator
description: Scenarios where at least one of the predators is a multi-legged centaur-like creature, with an animal lower body and anthropomorphic upper body.
- name: { en: ambiguous predator, tok: sijelo pi jan pi wawa mute li ale }
description:
en: Scenarios where the body type of at least one of the predators is left ambiguous.
tok: jan pi moku musi pi wawa mute la toki tan sijelo li lon ala.
- name: human prey
description: Scenarios where at least one of the prey is a human person.
- name: anthro prey
description: Scenarios where at least one of the prey is an anthropomorphic animal, i.e. generally regarded as a "furry".
- name: feral prey
description: Scenarios where at least one of the predators is an animal based on a real or mythological creature.
- name: { en: ambiguous prey, tok: sijelo pi jan pi wawa lili li ale }
description:
en: Scenarios where the body type of at least one of the predators is left ambiguous.
tok: jan pi moku musi pi wawa lili la toki tan sijelo li lon ala.

View file

@ -0,0 +1,35 @@
name: Genders
index: 3
tags:
- name: male predator
description: Scenarios where at least one of the predators is a man and/or male-presenting.
related:
- trans male predator
- name: trans male predator
description: Scenarios where at least one of the predators is a man and/or male-presenting; more specifically, undergoing or or having undergone gender transition.
related:
- male predator
- name: female predator
description: Scenarios where at least one of the predators is a woman and/or female-presenting.
- name: non-binary predator
description: Scenarios where at least one of the predators has a non-binary gender expression, be they genderless/agender, intersex, androgynous, gender-fluid, non-binary and/or non-binary-presenting, et cetera, regardless of undergoing or having undergone gender transition or not.
- name: { en: ambiguous gender predator, tok: jan pi wawa mute li meli anu mije }
description:
en: Scenarios where the gender at least one of the predators is left ambiguous.
tok: jan pi moku musi pi wawa mute la toki tan meli anu mije anu tonsi li lon ala.
- name: male prey
description: Scenarios where at least one of the prey is a man and/or male-presenting.
- name: female prey
description: Scenarios where at least one of the prey is a woman and/or female-presenting.
related:
- trans female prey
- name: trans female prey
description: Scenarios where at least one of the prey is a woman and/or female-presenting; more specifically, undergoing or having undergone gender transition.
related:
- female prey
- name: non-binary prey
description: Scenarios where at least one of the predators has a non-binary gender expression, be they genderless/agender, intersex, androgynous, gender-fluid, non-binary and/or non-binary-presenting, et cetera, regardless of undergoing or having undergone gender transition or not.
- name: { en: ambiguous gender prey, tok: jan pi wawa lili li meli anu mije }
description:
en: Scenarios where the gender at least one of the predators is left ambiguous.
tok: jan pi moku musi pi wawa lili la toki tan meli anu mije anu tonsi li lon ala.

View file

@ -0,0 +1,17 @@
name: Relative size
index: 4
tags:
- name: macro predator
description: Scenarios where at least one of the predators has a size/height one or more orders of magnitude larger than average.
- name: micro prey
description: Scenarios where at least one of the prey has a size/height one or more orders of magnitude smaller than average.
- name: size difference
description: Scenarios where at least one of the predators has a notably larger size/height than one of their prey.
- name: similar size
description: Scenarios where at least one of the predators has a slightly larger size/height than one of their prey.
- name: same size
description: Scenarios where at least one of the predators has the same size/height as one of their prey, or their sizes are comparable.
- name: smaller predator
description: Scenarios where at least one of the predators has a notably smaller size/height than one of their prey.
related:
- role reversal

View file

@ -0,0 +1,23 @@
name: Willingness
index: 5
tags:
- name: { en: willing predator, tok: jan pi wawa mute li wile e moku musi }
description:
en: Scenarios where at least one of the predators participates in vore willingly.
tok: jan pi moku musi pi wawa mute la jan li wile e moku musi.
- name: semi-willing predator
description: Scenarios where the willingness of at least one of the predators might be partial, oscillating between willing and unwilling, somewhere in-between, or ambiguous.
- name: unwilling predator
description: Scenarios where at least one of the predators participates in vore unwillingly.
- name: asleep predator
description: Scenarios where at least one of the predators participates in vore while asleep.
- name: willing prey
description: Scenarios where at least one of the prey participates in vore willingly.
- name: semi-willing prey
description: Scenarios where the willingness of at least one of the prey might be partial, oscillating between willing and unwilling, somewhere in-between, or ambiguous.
- name: { en: unwilling prey, tok: jan pi wawa lili li wile ala e moku musi }
description:
en: Scenarios where at least one of the prey participates in vore unwillingly.
tok: jan pi moku musi pi wawa lili la jan li wile ala e moku musi.
- name: asleep prey
description: Scenarios where at least one of the predators participates in vore while asleep.

View file

@ -0,0 +1,73 @@
name: Vore-related scenarios
index: 6
tags:
- name: point of view
description: Scenarios where the narration takes the perspective of one of the characters, generally in first person.
- name: long-term endo
description: Scenarios where prey remains safely inside of their predator for an extended period of time.
related:
- perma endo
- implied perma endo
- name: perma endo
description: Scenarios where prey remains safely inside of their predator for an indefinite period of time.
related:
- long-term endo
- implied perma endo
- name: implied perma endo
description: Scenarios where prey is implied to remain safely inside of their predator for an indefinite period of time.
related:
- long-term endo
- perma endo
- name: full tour
description: Scenarios where prey passes all the way through their predator's gastrointestinal system, entering through one end and exiting through the opposite one.
related:
- implied full tour
- name: implied full tour
description: Scenarios where prey is implied to pass all the way through their predator's gastrointestinal system, entering through one end and supposedly exiting through the opposite one.
related:
- full tour
- name: regurgitation
description: Scenarios where prey are explicitly let out through the same spot they entered their predator, specifically their mouth.
related:
- implied regurgitation
- oral vore
- name: implied regurgitation
description: Scenarios where prey are implied to have been let out through the same spot they entered their predator, specifically their mouth.
related:
- regurgitation
- oral vore
- name: prey transfer
description: Scenarios where prey are transferred from one predator into another, or transferred from one part of their predator to another.
- name: object vore
description: Scenarios where predators consume objects, alongside their prey or in place of them.
- name: role reversal
description: Scenarios where predators become prey and vice-versa, where dominant characters become prey, or where submissive characters become predators.
related:
- smaller predator
- name: nested vore
description: Scenarios where a predator becomes prey to another predator, or a prey consumes another prey.
related:
- multiple prey
- role reversal
- name: multiple prey
description: Scenarios where one predator consumes multiple prey at once.
- name: messy stomach
description: Scenarios where a prey visits a messy stomach, usually with semi-digested food inside.
related:
- oral vore
- name: hammerspace vore
description: Scenarios where a predator's body doesn't grow proportionally around the prey they consume, either at a substantially lower rate or not at all.
related:
- multiple prey
- name: bladder vore
description: Scenarios where prey consumed through the predator's urethra end up in their bladder.
related:
- cock vore
- name: soul vore
description: Scenarios where predators consume a soul instead of their prey's body.
- name: endo trait theft
description: Scenarios where predators acquire traits from their prey without digestion.
related:
- transformation
- name: Vore Day
description: Stories created in commemoration of Vore Day, which is celebrated on August 8th, and/or are set in said day.

View file

@ -0,0 +1,25 @@
name: Sexual content
index: 7
tags:
- name: nudity
description: Scenarios where a character is nude and displaying their sexual features, without necessarily participating in a sexual situation.
- name: masturbation
description: Scenarios where a character sexually stimulates themselves.
- name: straight sex
description: Scenarios where two or more characters of different genders participate in sexual activity.
related:
- orgy
- name: gay sex
description: Scenarios where two or more characters of the same gender, both men and/or male-presenting, participate in sexual activity.
related:
- orgy
- name: lesbian sex
description: Scenarios where two or more characters of the same gender, both women and/or female-presenting, participate in sexual activity.
related:
- orgy
- name: orgy
description: Scenarios where more than two characters participate in sexual activity freely.
related:
- straight sex
- gay sex
- lesbian sex

View file

@ -0,0 +1,31 @@
name: Other kinks
index: 8
tags:
- name: hyper
description: Scenarios where a character has abnormally large features compared to their body, usually genitalia.
- name: egg play
description: Scenarios with sexual action involving eggs.
related:
- object vore
- name: transformation
description: Scenarios where a character changes body and/or species.
related:
- endo trait theft
- name: netorare
description: Scenarios involving a character cheating on their partner with or without consent, and/or cuckoldry.
- name: sizeplay
description: Scenarios where a character changes size.
- name: inflation
description: Scenarios where a character expands out with the excessive intake of fluids, including semen.
- name: daddy play
description: Scenarios where a character refers to another as their "daddy" in a kinky manner, as part of a sexual situation or roleplay.
- name: BDSM
description: Scenarios related to bondage, sadism, masochism, dominance, and/or submission in a kinky manner, such as roleplay.
- name: dubcon
description: Scenarios where sexual consent is dubious.
- name: plushie
description: Scenarios with sexual action involving stuffed toys or plushie-like characters.
- name: wardrobe malfunction
description: Scenarios where a mishap happens with someone's clothes, such as accidentally revealing body parts and/or bursting open.
- name: pet play
description: Scenarios with roleplay where one (or more) of the participants plays the part of "pet".

View file

@ -0,0 +1,27 @@
name: Type of content
index: 9
tags:
- name: request
description: Stories made by someone else's request, as a gift.
- name: commission
description: Stories made as part of a commission to someone else.
- name: { en: flash fiction, tok: lipu lili }
description:
en: Short-format stories with no more than 2,500 words.
tok: lipu li jo e nanpa nimi lili.
- name: toki pona
description:
en: Stories written in toki pona, the language of good.
tok: lipu li kepeken toki pona.
- name: behind the scenes
description: Content where I go over the process of making other content.
- name: retrospective
description: Document detailing the good and bad parts during the creation process of a certain project.
- name: commentary
description: Content where the creator gives their thoughts on a certain project of theirs.
- name: technical post
description: Content where I explain the technical details of a certain system or technology.
- name: programming
description: Posts dedicated to programming, coding, and system architecture.
- name: game development
description: Content where I detail the process of creating a game.

View file

@ -0,0 +1,4 @@
name: Dee Lumeni
links:
eka: https://aryion.com/g4/user/KeeperofLillies
preferredLink: eka

Some files were not shown because too many files have changed in this diff Show more