Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const changelog: Changelog[] = [
changes: `
- added support for Fansly stream titles and uptimes in feed suggestions list
- added emote suggestions to chat input
- added "Set Username Paint" button to user actions modal
- chatters can now set their username paint if the creator has enabled it and is a ZerGo0_Bot subscriber
- fixed emotes not showing up in chat when using new lines
`
},
Expand Down
27 changes: 26 additions & 1 deletion src/lib/api/zergo0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type {
ZerGo0Badge,
ZerGo0Emote,
ZerGo0Response,
ZerGo0UsernamePaint
ZerGo0UsernamePaint,
ZerGo0UsernamePaintSettings
} from '../types';
import { Cache } from '../utils/cache';
import { deduplicatedFetch } from '../utils/requestDeduplicator';
Expand Down Expand Up @@ -160,6 +161,30 @@ class Zergo0Api {
this.usernamePaintCache.set(username, paintPromise);
return paintPromise;
}

async getUsernamePaintSettings(creatorId: string): Promise<boolean> {
try {
const resp = await deduplicatedFetch(
`https://zergo0_bot.zergo0.dev/ftv/username-paint/usersettings?creatorId=${creatorId}`
);
if (!resp.ok) {
console.warn('Username paint settings request failed', resp);
return false;
}

const json = (await resp.json()) as ZerGo0Response<ZerGo0UsernamePaintSettings>;
if (!json || !json.success) {
console.warn('Could not parse username paint settings response');
return false;
}

// Check if allow_chatters_set_paint is enabled
return json.response.allow_chatters_set_paint === 'true';
} catch (error) {
console.warn('Username paint settings request failed', error);
return false;
}
}
}

export const zergo0Api = new Zergo0Api();
22 changes: 21 additions & 1 deletion src/lib/components/ui/useractions/ActionsButtonModal.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { zergo0Api } from '@/lib/api/zergo0';
import { sharedState } from '@/lib/state/state.svelte';
import { ActionType } from '@/lib/types';
import { Button } from '../button';
Expand All @@ -7,6 +8,7 @@
import ChangelogModal from './actions/ChangelogModal.svelte';
import ChatPollModal from './actions/ChatPollModal.svelte';
import GiveawayModal from './actions/GiveawayModal.svelte';
import UsernamePaintModal from './actions/UsernamePaintModal.svelte';

interface Props {
showModal: boolean;
Expand All @@ -17,6 +19,16 @@
let actionModal: any;

let action: ActionType = $state(ActionType.None);
let hasUsernamePaintPermission: boolean = $state(false);

// Check for username paint permissions when modal opens
$effect(() => {
if (showModal && sharedState.chatroomId) {
zergo0Api.getUsernamePaintSettings(sharedState.chatroomId).then((hasPermission) => {
hasUsernamePaintPermission = hasPermission;
});
}
});

function handleChangelog() {
action = ActionType.Changelog;
Expand All @@ -29,6 +41,10 @@
function handleStartGiveaway() {
action = ActionType.Giveaway;
}

function handleSetUsernamePaint() {
action = ActionType.UsernamePaint;
}
</script>

<Modal bind:showModal bind:this={actionModal}>
Expand All @@ -40,9 +56,11 @@
<div class="flex flex-col space-y-2">
<Button variant="secondary" onclick={handleChangelog} class="relative">
<UpdateDot class="-right-1 -top-1" />

Changelog
</Button>
{#if hasUsernamePaintPermission}
<Button variant="secondary" onclick={handleSetUsernamePaint}>Set Username Paint</Button>
{/if}

{#if sharedState.isOwner || sharedState.isModerator}
<Button variant="secondary" onclick={handleStartPoll}>Start Poll</Button>
Expand All @@ -59,4 +77,6 @@
<ChatPollModal bind:action />
{:else if action === ActionType.Giveaway}
<GiveawayModal bind:action />
{:else if action === ActionType.UsernamePaint}
<UsernamePaintModal bind:action />
{/if}
134 changes: 134 additions & 0 deletions src/lib/components/ui/useractions/actions/UsernamePaintModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<script lang="ts">
import { fanslyApi } from '@/lib/api/fansly.svelte';
import { usernamePaintDesigns } from '@/lib/entryPoints/accountCard';
import { sharedState } from '@/lib/state/state.svelte';
import { ActionType } from '@/lib/types';
import Button from '../../button/button.svelte';
import Label from '../../label/label.svelte';
import Modal from '../../modal/Modal.svelte';

interface Props {
action: ActionType;
}

let { action = $bindable() }: Props = $props();

let selectedPaintId: number = $state(0);
let previewText: string = $state('Preview');
let error: string | null = $state(null);

function onClose() {
action = ActionType.None;
}

async function onSavePaint() {
error = null;

try {
const result = await fanslyApi.sendChatMessage(
sharedState.chatroomId!,
`!setpaint ${selectedPaintId}`
);

if (!result) {
error = 'Failed to set username paint, please try again later.';
return;
}

onClose();

// Reload the page to apply the new username paint
setTimeout(() => {
window.location.reload();
}, 100);
} catch (err) {
error = 'An error occurred while setting username paint.';
console.error('Error setting username paint:', err);
}
}

$effect(() => {
// Load userpaint CSS if not already loaded
const head = document.head;
if (!head.querySelector('style#ftv-userpaint-css')) {
import('@/assets/userpaint.css?inline').then((module) => {
const style = document.createElement('style');
style.id = 'ftv-userpaint-css';
style.media = 'screen';
style.innerHTML = module.default;
document.head.appendChild(style);
});
}
});

const selectedDesign = $derived(
usernamePaintDesigns.find((design) => design.id === selectedPaintId) || usernamePaintDesigns[0]
);
</script>

<Modal showModal={true} {onClose}>
{#snippet header()}
<h2>Set Username Paint</h2>
{/snippet}

{#snippet body()}
<div class="flex flex-col space-y-4">
<div>
<Label for="paint-select">Select Username Paint</Label>
<select
id="paint-select"
class="mt-2 flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
bind:value={selectedPaintId}
>
{#each usernamePaintDesigns as design}
<option value={design.id}>{design.id}</option>
{/each}
</select>
</div>

<div>
<Label>Preview</Label>
<div class="mt-2 rounded-md border border-input bg-background p-4">
<div class="flex items-center justify-center">
<span
class="relative inline-block text-lg font-bold {selectedDesign.class
? 'userpaints-' + selectedDesign.class
: ''} {selectedDesign.textClass ? 'userpaints-' + selectedDesign.textClass : ''}"
>
{previewText}
{#if selectedDesign.gif}
<img
src="https://zergo0botcdn.zergo0.dev/assets/{selectedDesign.gif}.gif"
alt="Username paint effect"
class="userpaints-effect-overlay"
/>
{/if}
</span>
</div>
</div>
</div>

<div>
<Label for="preview-text">Preview Text</Label>
<input
id="preview-text"
type="text"
class="mt-2 flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
bind:value={previewText}
placeholder="Enter preview text"
/>
</div>

<hr />

{#if error}
<p class="text-xs text-red-500">{error}</p>
{/if}

<div class="flex justify-end space-x-2">
<Button variant="secondary" size="sm" class="w-full" onclick={onClose}>Cancel</Button>
<Button variant="default" size="sm" class="w-full" onclick={onSavePaint}>Save Paint</Button>
</div>
</div>
{/snippet}
</Modal>
7 changes: 6 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export enum ActionType {
None = 'none',
Changelog = 'changelog',
ChatPoll = 'chatPoll',
Giveaway = 'giveaway'
Giveaway = 'giveaway',
UsernamePaint = 'usernamePaint'
}

export type FanslyResponse<T> = {
Expand Down Expand Up @@ -717,3 +718,7 @@ export interface ZerGo0Badge {
export interface ZerGo0UsernamePaint {
usernamePaintId: number;
}

export interface ZerGo0UsernamePaintSettings {
allow_chatters_set_paint: string;
}
Loading