15 KiB
FurryPlace Plugins
This directory contains plugins that extend the FurryPlace UI using the FurryPlace SDK.
Quick Start
- Create a plugin file in this directory (e.g.,
my-plugin.js) - Refresh the page - Plugins are automatically discovered and loaded!
- Use the SDK to register buttons or add other functionality
✨ Auto-Loading: Plugins are automatically discovered from the
frontend-backup/plugins/directory. Any.jsfile you add here will be automatically loaded when the page loads. No need to manually edit HTML files!
Example Plugins
example-button.js
Complete example showing how to:
- Add custom buttons with icons
- Handle click events
- Use conditional rendering
- Apply custom styling
- Open external links
example-user-state.js
Demonstrates user state access:
- Display user stats and information
- Show droplet counter
- Monitor paint charge status
- Display level progress
- Conditional rendering based on login status
- Refresh user data from server
example-info-modal.js
Demonstrates info modal customization:
- Add custom sections to the info modal
- Display announcements and updates
- Show server statistics
- Add custom links and resources
- Display user-specific information
- Create interactive collapsible sections
- Add tips, tricks, and changelogs
Captcha Plugins
FurryPlace includes built-in support for captcha verification at different points:
- Login: When logging into an existing account
- Registration: When creating a new account
- Paint: When painting pixels (can be configured to require every N paints)
- Google OAuth: Before initiating Google OAuth flow
captcha-turnstile.js
Cloudflare Turnstile captcha integration:
- Free and privacy-friendly
- Lightweight and fast
- Automatic theme detection
- Configure per verification point
Configuration:
window.FURRYPLACE_CAPTCHA_CONFIG = {
login: {
enabled: true,
siteKey: 'your-turnstile-site-key',
theme: 'auto', // 'light', 'dark', or 'auto'
},
register: {
enabled: true,
siteKey: 'your-turnstile-site-key',
theme: 'auto',
},
paint: {
enabled: true,
siteKey: 'your-turnstile-site-key',
theme: 'dark',
},
googleOAuth: {
enabled: true,
siteKey: 'your-turnstile-site-key',
theme: 'light',
}
};
captcha-hcaptcha.js
hCaptcha integration:
- Privacy-focused alternative
- Customizable themes and sizes
- Supports compact mode
Configuration:
window.FURRYPLACE_CAPTCHA_CONFIG = {
login: {
enabled: true,
siteKey: 'your-hcaptcha-site-key',
theme: 'light', // 'light' or 'dark'
size: 'normal', // 'normal', 'compact', or 'invisible'
},
register: {
enabled: true,
siteKey: 'your-hcaptcha-site-key',
theme: 'dark',
size: 'normal',
},
paint: {
enabled: true,
siteKey: 'your-hcaptcha-site-key',
theme: 'dark',
size: 'compact',
}
};
captcha-recaptcha.js
Google reCAPTCHA v2 and v3 integration:
- Support for both v2 (checkbox) and v3 (invisible)
- Per-action configuration for v3
- Customizable themes for v2
Configuration:
window.FURRYPLACE_CAPTCHA_CONFIG = {
login: {
enabled: true,
siteKey: 'your-recaptcha-site-key',
version: 'v2', // 'v2' or 'v3'
theme: 'light', // v2 only
size: 'normal', // v2 only
action: 'login', // v3 only
},
paint: {
enabled: true,
siteKey: 'your-recaptcha-v3-site-key',
version: 'v3',
action: 'paint',
}
};
Important: Only load ONE captcha plugin at a time. Choose the one that matches your backend configuration.
FurryPlace SDK API
Button Registration
window.FurryPlaceSDK.registerButton(config)
Register a custom button in the UI.
Configuration Object:
{
id: string, // Required: Unique identifier
title: string, // Required: Tooltip text
icon: string|HTMLElement, // Required: SVG string or DOM element
onClick: Function, // Required: (context, event) => {}
position: string, // Optional: 'top', 'bottom', 'before-leaderboard', 'after-leaderboard'
className: string, // Optional: Custom CSS classes (default: 'btn btn-square shadow-md')
wrapperClass: string, // Optional: Wrapper div CSS classes
condition: Function, // Optional: (context) => boolean
disabled: boolean // Optional: Whether button is disabled
}
Positions:
'top'- First button in the container'bottom'- Last button in the container'before-leaderboard'- Before the leaderboard button'after-leaderboard'- After the leaderboard button (default)
Context Object (passed to onClick and condition):
{
sdk: {
version: string, // SDK version
refreshUserState: Function // Function to refresh user state
},
user: {
isLoggedIn: boolean, // User login status
id: number|null, // User ID
name: string|null, // Display name
discord: string|null, // Discord username
country: string|null, // User's country
banned: boolean, // Is user banned
role: string|null, // User role (admin/moderator/user)
level: number, // Current level
pixelsPainted: number, // Total pixels painted
droplets: number, // Currency amount
picture: string|null, // Profile picture URL
equippedFlag: string|null, // Equipped country flag
allianceId: number|null, // Alliance ID
allianceRole: string|null, // Alliance role
charges: {
current: number, // Current paint charges
max: number, // Maximum charges
cooldownMs: number, // Cooldown in milliseconds
percentage: number // Charge percentage (0-100)
},
hasChargesToPaint: boolean, // Can paint (>= 1 charge)
needsPhoneVerification: boolean, // Needs phone verification
isTimedOut: boolean, // Currently timed out
timeoutUntil: string|null // Timeout expiry ISO string
},
map: {} // Map context (future use)
}
User State Methods
await FurryPlaceSDK.getUser()
Get full user data (async). Returns user object or null if not logged in.
await FurryPlaceSDK.refreshUser()
Refresh user state from server and return updated data.
FurryPlaceSDK.isLoggedIn()
Returns true if user is logged in.
FurryPlaceSDK.getDroplets()
Get user's droplet count (currency).
FurryPlaceSDK.getCharges()
Get paint charge information:
{
current: number, // Current charges
max: number, // Max capacity
cooldownMs: number, // Recharge cooldown
percentage: number // Fill percentage (0-100)
}
FurryPlaceSDK.hasChargesToPaint()
Returns true if user has at least 1 charge.
FurryPlaceSDK.getLevel()
Get user's current level.
FurryPlaceSDK.getPixelsPainted()
Get total pixels painted by user.
FurryPlaceSDK.getAllianceId()
Get user's alliance ID or null.
FurryPlaceSDK.isInAlliance()
Returns true if user is in an alliance.
Info Modal Customization
FurryPlaceSDK.addInfoSection(config)
Add a custom section to the info modal (the one with rules and YouTube video).
Configuration Object:
{
id: string, // Required: Unique identifier
title: string, // Optional: Section title
content: string|HTMLElement|Function, // Required: HTML string, DOM element, or function
position: string, // Optional: 'top', 'bottom', 'after-video'
className: string // Optional: Custom CSS classes for section
}
Positions:
'top'- At the beginning of the modal'bottom'- At the end (before footer)'after-video'- Right after the YouTube video section
Content Types:
- String: HTML string that will be inserted
- HTMLElement: DOM element to append
- Function: Function that returns a string or HTMLElement
FurryPlaceSDK.removeInfoSection(id)
Remove a custom section from the info modal.
FurryPlaceSDK.getInfoSections()
Get list of all registered info sections.
Captcha Methods
FurryPlaceSDK.registerCaptchaCallback(verificationPoint, callback)
Register a callback function that provides captcha tokens for a specific verification point.
Parameters:
verificationPoint: One of'login','register','googleOAuth', or'paint'callback: Async function that returns a captcha token string
Example:
window.FurryPlaceSDK.registerCaptchaCallback('login', async () => {
// Show captcha UI and wait for user to complete it
const token = await showCaptchaWidget();
return token;
});
await FurryPlaceSDK.requestCaptcha(verificationPoint)
Request a captcha token for a specific verification point. This calls the registered callback.
Returns: Promise that resolves to a captcha token string or null if no callback is registered.
FurryPlaceSDK.hasCaptchaCallback(verificationPoint)
Check if a captcha callback is registered for a verification point.
Returns: true if callback is registered, false otherwise.
FurryPlaceSDK.unregisterCaptchaCallback(verificationPoint)
Remove a captcha callback for a verification point.
FurryPlaceSDK.getCaptchaCallbacks()
Get list of all verification points with registered callbacks.
Returns: Array of strings (verification point names).
Other SDK Methods
FurryPlaceSDK.unregisterButton(id)- Remove a registered buttonFurryPlaceSDK.getButtons()- Get list of all registered buttonsFurryPlaceSDK.getContext()- Get current context objectFurryPlaceSDK.init()- Manually initialize SDK (usually automatic)
Example Usage
Basic Button
(function() {
'use strict';
function waitForSDK(callback) {
if (window.FurryPlaceSDK) {
callback();
} else {
setTimeout(() => waitForSDK(callback), 100);
}
}
waitForSDK(() => {
window.FurryPlaceSDK.registerButton({
id: 'my-custom-button',
title: 'My Button',
position: 'bottom',
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor" class="size-5">
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/>
</svg>`,
onClick: (context) => {
alert('Button clicked!');
}
});
});
})();
Accessing User State
waitForSDK(() => {
// Synchronous methods
console.log('Logged in:', window.FurryPlaceSDK.isLoggedIn());
console.log('Droplets:', window.FurryPlaceSDK.getDroplets());
console.log('Level:', window.FurryPlaceSDK.getLevel());
// Async method for full user data
window.FurryPlaceSDK.getUser().then(user => {
if (user) {
console.log('User name:', user.name);
console.log('Alliance:', user.allianceId);
}
});
});
Button with User State Condition
// Only show button if user is logged in and has charges
window.FurryPlaceSDK.registerButton({
id: 'paint-helper',
title: 'Quick Paint',
position: 'top',
icon: `...`,
onClick: async (context) => {
const charges = context.user.charges;
alert(`You have ${charges.current}/${charges.max} charges`);
},
condition: (context) => {
return context.user?.isLoggedIn && context.user?.hasChargesToPaint;
}
});
Charge Monitor Example
window.FurryPlaceSDK.registerButton({
id: 'charge-monitor',
title: 'Charge Monitor',
position: 'bottom',
icon: `...`,
onClick: () => {
const charges = window.FurryPlaceSDK.getCharges();
alert(`
Current: ${charges.current}/${charges.max}
Percentage: ${charges.percentage.toFixed(1)}%
Can Paint: ${window.FurryPlaceSDK.hasChargesToPaint() ? 'Yes' : 'No'}
`);
},
condition: (context) => context.user?.isLoggedIn
});
Info Modal Section Examples
Simple HTML Section
window.FurryPlaceSDK.addInfoSection({
id: 'welcome-message',
title: 'Welcome!',
position: 'top',
content: '<p class="text-sm">Welcome to our server!</p>'
});
Dynamic Content with Function
window.FurryPlaceSDK.addInfoSection({
id: 'user-stats',
title: 'Your Stats',
position: 'after-video',
content: () => {
const level = window.FurryPlaceSDK.getLevel();
const droplets = window.FurryPlaceSDK.getDroplets();
return `
<div class="text-sm">
<p>Level: ${level}</p>
<p>Droplets: ${droplets}</p>
</div>
`;
}
});
Interactive Section with DOM Elements
window.FurryPlaceSDK.addInfoSection({
id: 'interactive-section',
title: 'Custom Links',
position: 'bottom',
content: () => {
const div = document.createElement('div');
div.className = 'flex gap-2';
const button = document.createElement('button');
button.className = 'btn btn-sm btn-primary';
button.textContent = 'Click Me';
button.onclick = () => alert('Button clicked!');
div.appendChild(button);
return div;
}
});
Finding SVG Icons
You can find Material Design icons at:
Copy the SVG code and use it as the icon parameter.
Tips
- Wrap your plugin in an IIFE to avoid polluting the global scope
- Wait for the SDK to be available before registering buttons
- Use unique IDs to avoid conflicts with other plugins
- Test button positions to find the best placement for your use case
- Check the console for SDK loading messages and errors
How Auto-Loading Works
The SDK automatically:
- Calls
/api/pluginsto get a list of all.jsfiles in theplugins/directory - Dynamically loads each plugin by creating
<script>tags - Reports loading progress in the browser console
You can check which plugins are loaded by looking at the console:
[FurryPlace SDK] Auto-discovering plugins...
[FurryPlace SDK] Found 2 plugin(s): ['example-button.js', 'my-plugin.js']
[FurryPlace SDK] Loaded plugin: example-button.js
[FurryPlace SDK] Loaded plugin: my-plugin.js
Note: If you need to disable a plugin, either delete it or rename it to something other than
.js(like.js.disabled)
Troubleshooting
Button not appearing?
- Check the browser console for errors
- Verify the SDK is loaded:
console.log(window.FurryPlaceSDK) - Ensure your button ID is unique
- Try different positions
Button appearing in wrong place?
- Try different
positionvalues - Check if the button container has changed in the UI
Icon not showing?
- Verify your SVG has
viewBoxandfill="currentColor" - Add
class="size-5"to the SVG element - Check for syntax errors in the SVG string