mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-09-04 06:55:21 +00:00
feat: expand emoji/name system with comprehensive animal mapping
- Create shared/names.js as single source of truth for emoji maps, noun
lists, adjectives, and username generation (215 animal emoji entries)
- Remove duplicated inline lists from popup.js and background.js; both
now import getAvatarForName / generateUsername from shared/names.js
- Add names.js to the build script sync list so it stays DRY across
extension builds
- Fix missing cat emoji: CoolCat now correctly resolves to 🐱
- Sort emoji map keys by length at lookup time to prevent substring
false-matches (e.g. 'caterpillar' before 'cat')
This commit is contained in:
+38
-9
@@ -1,4 +1,5 @@
|
|||||||
import { EVENTS, PROTOCOL_VERSION, OFFICIAL_SERVER_URL, OFFICIAL_SERVER_TOKEN, APP_VERSION, EPISODE_LOBBY_TIMEOUT, FORCE_SYNC_TIMEOUT } from './shared/constants.js';
|
import { EVENTS, PROTOCOL_VERSION, OFFICIAL_SERVER_URL, OFFICIAL_SERVER_TOKEN, APP_VERSION, EPISODE_LOBBY_TIMEOUT, FORCE_SYNC_TIMEOUT } from './shared/constants.js';
|
||||||
|
import { generateUsername } from './shared/names.js';
|
||||||
|
|
||||||
// --- State Management ---
|
// --- State Management ---
|
||||||
let socket = null;
|
let socket = null;
|
||||||
@@ -15,6 +16,8 @@ let pendingHistory = [];
|
|||||||
let eventQueue = [];
|
let eventQueue = [];
|
||||||
let isNamespaceJoined = false;
|
let isNamespaceJoined = false;
|
||||||
let lastActionState = { action: null, senderId: null, timestamp: 0, acks: [] };
|
let lastActionState = { action: null, senderId: null, timestamp: 0, acks: [] };
|
||||||
|
let localSeq = 0; // Monotonically increasing command sequence for this peer
|
||||||
|
const lastSeqBySender = {}; // senderId → last received seq (stale command guard)
|
||||||
|
|
||||||
// --- Boot Sequence Lock ---
|
// --- Boot Sequence Lock ---
|
||||||
let restorationTask = null;
|
let restorationTask = null;
|
||||||
@@ -35,7 +38,7 @@ function ensureState() {
|
|||||||
'logs', 'history', 'currentRoom', 'lastActionState',
|
'logs', 'history', 'currentRoom', 'lastActionState',
|
||||||
'eventQueue', 'isForceSyncInitiator', 'forceSyncAcks',
|
'eventQueue', 'isForceSyncInitiator', 'forceSyncAcks',
|
||||||
'forceSyncDeadline', 'reconnectFailed', 'reconnectStartTime', 'currentTabId', 'currentTabTitle',
|
'forceSyncDeadline', 'reconnectFailed', 'reconnectStartTime', 'currentTabId', 'currentTabTitle',
|
||||||
'episodeLobby'
|
'episodeLobby', 'localSeq'
|
||||||
], (data) => {
|
], (data) => {
|
||||||
clearTimeout(storageTimeout);
|
clearTimeout(storageTimeout);
|
||||||
if (data.currentTabId !== undefined) currentTabId = data.currentTabId;
|
if (data.currentTabId !== undefined) currentTabId = data.currentTabId;
|
||||||
@@ -84,6 +87,8 @@ function ensureState() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.localSeq !== undefined && !isNaN(data.localSeq)) localSeq = data.localSeq;
|
||||||
|
|
||||||
storageInitialized = true;
|
storageInitialized = true;
|
||||||
|
|
||||||
// Process any early logs/history that weren't captured in the spread
|
// Process any early logs/history that weren't captured in the spread
|
||||||
@@ -186,9 +191,7 @@ async function getSettings() {
|
|||||||
}
|
}
|
||||||
let username = data.username;
|
let username = data.username;
|
||||||
if (!username) {
|
if (!username) {
|
||||||
const adjs = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave', 'Calm', 'Sneaky', 'Lazy', 'Wild', 'Chill', 'Lucky', 'Epic', 'Swift', 'Bold', 'Mighty', 'Cosmic', 'Neon', 'Shadow', 'Crystal', 'Thunder', 'Silent', 'Golden', 'Fierce', 'Noble', 'Mystic', 'Frozen', 'Blazing', 'Sapphire', 'Iron', 'Crimson'];
|
username = generateUsername();
|
||||||
const nouns = ['Koala', 'Panda', 'Tiger', 'Eagle', 'Fox', 'Bear', 'Wolf', 'Lion', 'Hawk', 'Seal', 'Owl', 'Shark', 'Dragon', 'Phoenix', 'Falcon', 'Panther', 'Raven', 'Cobra', 'Lynx', 'Jaguar', 'Orca', 'Mantis', 'Viper', 'Condor', 'Badger', 'Otter', 'Rhino', 'Crane', 'Mongoose', 'Specter'];
|
|
||||||
username = `${adjs[Math.floor(Math.random() * adjs.length)]}${nouns[Math.floor(Math.random() * nouns.length)]}`;
|
|
||||||
chrome.storage.sync.set({ username }, () => {
|
chrome.storage.sync.set({ username }, () => {
|
||||||
resolve({
|
resolve({
|
||||||
serverUrl: data.serverUrl || '',
|
serverUrl: data.serverUrl || '',
|
||||||
@@ -576,6 +579,14 @@ function handleServerEvent(event, data) {
|
|||||||
case EVENTS.PAUSE:
|
case EVENTS.PAUSE:
|
||||||
case EVENTS.SEEK:
|
case EVENTS.SEEK:
|
||||||
case EVENTS.FORCE_SYNC_PREPARE:
|
case EVENTS.FORCE_SYNC_PREPARE:
|
||||||
|
if (data.senderId && typeof data.seq === 'number') {
|
||||||
|
const lastSeq = lastSeqBySender[data.senderId];
|
||||||
|
if (lastSeq !== undefined && data.seq <= lastSeq) {
|
||||||
|
addLog(`Ignored stale ${event} from ${data.senderId} (seq ${data.seq} <= ${lastSeq})`, 'warn');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastSeqBySender[data.senderId] = data.seq;
|
||||||
|
}
|
||||||
if (data.senderId) {
|
if (data.senderId) {
|
||||||
addToHistory(event, data.senderId);
|
addToHistory(event, data.senderId);
|
||||||
showNotification(data.senderId, event);
|
showNotification(data.senderId, event);
|
||||||
@@ -592,6 +603,11 @@ function handleServerEvent(event, data) {
|
|||||||
routeToContent(event, data);
|
routeToContent(event, data);
|
||||||
break;
|
break;
|
||||||
case EVENTS.FORCE_SYNC_ACK:
|
case EVENTS.FORCE_SYNC_ACK:
|
||||||
|
if (data.senderId && typeof data.seq === 'number') {
|
||||||
|
const lastSeq = lastSeqBySender[data.senderId];
|
||||||
|
if (lastSeq !== undefined && data.seq <= lastSeq) break;
|
||||||
|
lastSeqBySender[data.senderId] = data.seq;
|
||||||
|
}
|
||||||
if (isForceSyncInitiator) {
|
if (isForceSyncInitiator) {
|
||||||
forceSyncAcks.add(data.senderId);
|
forceSyncAcks.add(data.senderId);
|
||||||
chrome.storage.session.set({ forceSyncAcks: Array.from(forceSyncAcks) });
|
chrome.storage.session.set({ forceSyncAcks: Array.from(forceSyncAcks) });
|
||||||
@@ -621,6 +637,11 @@ function handleServerEvent(event, data) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EVENTS.FORCE_SYNC_EXECUTE:
|
case EVENTS.FORCE_SYNC_EXECUTE:
|
||||||
|
if (data?.senderId && typeof data.seq === 'number') {
|
||||||
|
const lastSeq = lastSeqBySender[data.senderId];
|
||||||
|
if (lastSeq !== undefined && data.seq <= lastSeq) break;
|
||||||
|
lastSeqBySender[data.senderId] = data.seq;
|
||||||
|
}
|
||||||
if (data?.senderId) {
|
if (data?.senderId) {
|
||||||
addToHistory(event, data.senderId);
|
addToHistory(event, data.senderId);
|
||||||
showNotification(data.senderId, event);
|
showNotification(data.senderId, event);
|
||||||
@@ -701,7 +722,7 @@ function handleServerEvent(event, data) {
|
|||||||
peer.muted = data.muted !== undefined ? data.muted : peer.muted;
|
peer.muted = data.muted !== undefined ? data.muted : peer.muted;
|
||||||
|
|
||||||
const timeSinceReactive = peer.lastReactiveUpdate ? (Date.now() - peer.lastReactiveUpdate) : Infinity;
|
const timeSinceReactive = peer.lastReactiveUpdate ? (Date.now() - peer.lastReactiveUpdate) : Infinity;
|
||||||
const ignoreStatus = timeSinceReactive < 1000;
|
const ignoreStatus = timeSinceReactive < 300;
|
||||||
|
|
||||||
if (!ignoreStatus) {
|
if (!ignoreStatus) {
|
||||||
peer.playbackState = data.playbackState !== undefined ? data.playbackState : peer.playbackState;
|
peer.playbackState = data.playbackState !== undefined ? data.playbackState : peer.playbackState;
|
||||||
@@ -797,8 +818,11 @@ function executeForceSync() {
|
|||||||
const executionTimestamp = Date.now();
|
const executionTimestamp = Date.now();
|
||||||
updateLastAction(EVENTS.FORCE_SYNC_EXECUTE, 'You', executionTimestamp);
|
updateLastAction(EVENTS.FORCE_SYNC_EXECUTE, 'You', executionTimestamp);
|
||||||
|
|
||||||
emit(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp });
|
localSeq++;
|
||||||
routeToContent(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp });
|
chrome.storage.session.set({ localSeq });
|
||||||
|
|
||||||
|
emit(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp, seq: localSeq });
|
||||||
|
routeToContent(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp, seq: localSeq });
|
||||||
addLog('Force Sync Executed', 'success');
|
addLog('Force Sync Executed', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,8 +887,10 @@ function executeEpisodeLobby() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const syncPayload = { targetTime: 0.0 };
|
const syncPayload = { targetTime: 0.0 };
|
||||||
emit(EVENTS.FORCE_SYNC_PREPARE, { ...syncPayload, peerId, actionTimestamp: timestamp });
|
localSeq++;
|
||||||
routeToContent(EVENTS.FORCE_SYNC_PREPARE, { ...syncPayload, actionTimestamp: timestamp });
|
chrome.storage.session.set({ localSeq });
|
||||||
|
emit(EVENTS.FORCE_SYNC_PREPARE, { ...syncPayload, peerId, actionTimestamp: timestamp, seq: localSeq });
|
||||||
|
routeToContent(EVENTS.FORCE_SYNC_PREPARE, { ...syncPayload, actionTimestamp: timestamp, seq: localSeq });
|
||||||
|
|
||||||
forceSyncTimeout = setTimeout(() => {
|
forceSyncTimeout = setTimeout(() => {
|
||||||
if (isForceSyncInitiator) {
|
if (isForceSyncInitiator) {
|
||||||
@@ -1137,10 +1163,13 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
|||||||
} else if (message.type === 'CONTENT_EVENT') {
|
} else if (message.type === 'CONTENT_EVENT') {
|
||||||
const processEvent = () => {
|
const processEvent = () => {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
|
localSeq++;
|
||||||
|
chrome.storage.session.set({ localSeq });
|
||||||
updateLastAction(message.action, 'You', timestamp);
|
updateLastAction(message.action, 'You', timestamp);
|
||||||
lastActionState.targetTime = message.payload?.targetTime !== undefined ? message.payload.targetTime : message.payload?.currentTime;
|
lastActionState.targetTime = message.payload?.targetTime !== undefined ? message.payload.targetTime : message.payload?.currentTime;
|
||||||
if (storageInitialized) chrome.storage.session.set({ lastActionState });
|
if (storageInitialized) chrome.storage.session.set({ lastActionState });
|
||||||
message.payload.actionTimestamp = timestamp;
|
message.payload.actionTimestamp = timestamp;
|
||||||
|
message.payload.seq = localSeq;
|
||||||
|
|
||||||
// Local Reactive Update
|
// Local Reactive Update
|
||||||
updateLocalPeerState(peerId, {
|
updateLocalPeerState(peerId, {
|
||||||
|
|||||||
+2
-21
@@ -1,5 +1,6 @@
|
|||||||
import { EVENTS, OFFICIAL_LANDING_PAGE_URL } from './shared/constants.js';
|
import { EVENTS, OFFICIAL_LANDING_PAGE_URL } from './shared/constants.js';
|
||||||
import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
|
import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
|
||||||
|
import { getAvatarForName, generateUsername } from './shared/names.js';
|
||||||
|
|
||||||
|
|
||||||
const elements = {
|
const elements = {
|
||||||
@@ -63,24 +64,6 @@ let errorToken = 0;
|
|||||||
let forceSyncDone = false;
|
let forceSyncDone = false;
|
||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
function getAvatarForName(username) {
|
|
||||||
if (!username) return '👤';
|
|
||||||
const lower = username.toLowerCase();
|
|
||||||
const map = {
|
|
||||||
'koala': '🐨', 'panda': '🐼', 'tiger': '🐯', 'eagle': '🦅',
|
|
||||||
'fox': '🦊', 'bear': '🐻', 'wolf': '🐺', 'lion': '🦁',
|
|
||||||
'hawk': '🦅', 'seal': '🦭', 'owl': '🦉', 'shark': '🦈',
|
|
||||||
'dragon': '🐉', 'phoenix': '🐦', 'falcon': '🦅', 'panther': '🐆',
|
|
||||||
'raven': '🐦⬛', 'cobra': '🐍', 'lynx': '🐈', 'jaguar': '🐆',
|
|
||||||
'orca': '🐋', 'mantis': '🦗', 'viper': '🐍', 'condor': '🦅',
|
|
||||||
'badger': '🦡', 'otter': '🦦', 'rhino': '🦏', 'crane': '🦩',
|
|
||||||
'mongoose': '🦦', 'specter': '👻'
|
|
||||||
};
|
|
||||||
for (const [key, emoji] of Object.entries(map)) {
|
|
||||||
if (lower.includes(key)) return emoji;
|
|
||||||
}
|
|
||||||
return '👤';
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Initialization ---
|
// --- Initialization ---
|
||||||
async function init() {
|
async function init() {
|
||||||
@@ -88,9 +71,7 @@ async function init() {
|
|||||||
const data = await chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'filterNoise', 'username', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite']);
|
const data = await chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'filterNoise', 'username', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite']);
|
||||||
let username = data.username;
|
let username = data.username;
|
||||||
if (!username) {
|
if (!username) {
|
||||||
const adjs = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave', 'Calm', 'Sneaky', 'Lazy', 'Wild', 'Chill', 'Lucky', 'Epic', 'Swift', 'Bold', 'Mighty', 'Cosmic', 'Neon', 'Shadow', 'Crystal', 'Thunder', 'Silent', 'Golden', 'Fierce', 'Noble', 'Mystic', 'Frozen', 'Blazing', 'Sapphire', 'Iron', 'Crimson'];
|
username = generateUsername();
|
||||||
const nouns = ['Koala', 'Panda', 'Tiger', 'Eagle', 'Fox', 'Bear', 'Wolf', 'Lion', 'Hawk', 'Seal', 'Owl', 'Shark', 'Dragon', 'Phoenix', 'Falcon', 'Panther', 'Raven', 'Cobra', 'Lynx', 'Jaguar', 'Orca', 'Mantis', 'Viper', 'Condor', 'Badger', 'Otter', 'Rhino', 'Crane', 'Mongoose', 'Specter'];
|
|
||||||
username = `${adjs[Math.floor(Math.random() * adjs.length)]}${nouns[Math.floor(Math.random() * nouns.length)]}`;
|
|
||||||
chrome.storage.sync.set({ username });
|
chrome.storage.sync.set({ username });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ if (!fs.existsSync(extSharedDir)) {
|
|||||||
fs.mkdirSync(extSharedDir, { recursive: true });
|
fs.mkdirSync(extSharedDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharedFiles = ['constants.js', 'blacklist.js', 'README.md'];
|
const sharedFiles = ['constants.js', 'blacklist.js', 'names.js', 'README.md'];
|
||||||
for (const file of sharedFiles) {
|
for (const file of sharedFiles) {
|
||||||
const src = path.join(masterSharedDir, file);
|
const src = path.join(masterSharedDir, file);
|
||||||
const dest = path.join(extSharedDir, file);
|
const dest = path.join(extSharedDir, file);
|
||||||
@@ -31,7 +31,7 @@ for (const file of sharedFiles) {
|
|||||||
}
|
}
|
||||||
fs.copyFileSync(src, dest);
|
fs.copyFileSync(src, dest);
|
||||||
}
|
}
|
||||||
console.log('✓ constants.js, blacklist.js, and README.md synced to extension/shared/');
|
console.log('✓ constants.js, blacklist.js, names.js, and README.md synced to extension/shared/');
|
||||||
|
|
||||||
// Read the base manifest
|
// Read the base manifest
|
||||||
const baseManifest = JSON.parse(fs.readFileSync(baseManifestPath, 'utf8'));
|
const baseManifest = JSON.parse(fs.readFileSync(baseManifestPath, 'utf8'));
|
||||||
|
|||||||
+281
@@ -0,0 +1,281 @@
|
|||||||
|
/**
|
||||||
|
* KoalaSync Shared Name Generation & Emoji Mapping
|
||||||
|
*
|
||||||
|
* ⚠️ WARNING: This is the SINGLE SOURCE OF TRUTH.
|
||||||
|
* If you edit this file, you MUST run: node scripts/build-extension.js
|
||||||
|
* to propagate changes to the extension.
|
||||||
|
*
|
||||||
|
* The emoji map covers every animal/creature that has a Unicode emoji.
|
||||||
|
* Entries are sorted by key length (longest first) at lookup time to
|
||||||
|
* prevent substring false-matches (e.g. "caterpillar" must be checked
|
||||||
|
* before "cat").
|
||||||
|
*
|
||||||
|
* If you add a new animal noun to USERNAME_NOUNS, ensure it has a
|
||||||
|
* corresponding entry in ANIMAL_EMOJI_MAP (or a substring that already
|
||||||
|
* maps to a suitable emoji).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const USERNAME_ADJECTIVES = [
|
||||||
|
'Happy', 'Cool', 'Fast', 'Smart', 'Brave', 'Calm', 'Sneaky', 'Lazy',
|
||||||
|
'Wild', 'Chill', 'Lucky', 'Epic', 'Swift', 'Bold', 'Mighty',
|
||||||
|
'Cosmic', 'Neon', 'Shadow', 'Crystal', 'Thunder', 'Silent', 'Golden',
|
||||||
|
'Fierce', 'Noble', 'Mystic', 'Frozen', 'Blazing', 'Sapphire', 'Iron', 'Crimson'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const USERNAME_NOUNS = [
|
||||||
|
'Koala', 'Panda', 'Tiger', 'Eagle', 'Fox', 'Bear', 'Wolf', 'Lion',
|
||||||
|
'Hawk', 'Seal', 'Owl', 'Shark', 'Dragon', 'Phoenix', 'Falcon',
|
||||||
|
'Panther', 'Raven', 'Cobra', 'Lynx', 'Jaguar', 'Orca', 'Mantis',
|
||||||
|
'Viper', 'Condor', 'Badger', 'Otter', 'Rhino', 'Crane', 'Mongoose',
|
||||||
|
'Specter',
|
||||||
|
'Cat', 'Dog', 'Deer', 'Bat', 'Gorilla', 'Monkey', 'Rabbit',
|
||||||
|
'Horse', 'Unicorn', 'Zebra', 'Leopard', 'Cheetah', 'Puma',
|
||||||
|
'Ram', 'Goat', 'Bull', 'Donkey', 'Moose',
|
||||||
|
'Elephant', 'Giraffe', 'Hippo', 'Sloth', 'Kangaroo',
|
||||||
|
'Raccoon', 'Hamster', 'Hedgehog', 'Skunk', 'Beaver', 'Bison',
|
||||||
|
'Camel', 'Llama', 'Hyena', 'Coyote',
|
||||||
|
'Mouse', 'Pig', 'Boar', 'Polar', 'Orangutan', 'Mammoth',
|
||||||
|
'Crow', 'Duck', 'Swan', 'Penguin', 'Parrot', 'Peacock',
|
||||||
|
'Dove', 'Dodo', 'Turkey', 'Flamingo', 'Chicken', 'Rooster', 'Goose',
|
||||||
|
'Dolphin', 'Whale', 'Crab', 'Lobster', 'Octopus', 'Squid',
|
||||||
|
'Jellyfish', 'Turtle',
|
||||||
|
'Crocodile', 'Lizard', 'Snake', 'Frog', 'Toad', 'Gecko',
|
||||||
|
'Bee', 'Ant', 'Spider', 'Scorpion', 'Butterfly', 'Ladybug',
|
||||||
|
'Beetle', 'Snail', 'Dragonfly', 'Caterpillar',
|
||||||
|
'Alien', 'Robot', 'Mermaid', 'Ghoul', 'Sprite', 'Cyborg',
|
||||||
|
'Dinosaur', 'Reaper', 'Wraith', 'Sphinx',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ANIMAL_EMOJI_MAP = {
|
||||||
|
'hippopotamus': '🦛',
|
||||||
|
'rhinoceros': '🦏',
|
||||||
|
'caterpillar': '🐛',
|
||||||
|
'chimpanzee': '🐵',
|
||||||
|
'orangutan': '🦧',
|
||||||
|
'blackbird': '🐦⬛',
|
||||||
|
'bumblebee': '🐝',
|
||||||
|
'cockatoo': '🦜',
|
||||||
|
'cockroach': '🪳',
|
||||||
|
'dragonfly': '🐉',
|
||||||
|
'grasshopper': '🦗',
|
||||||
|
'hedgehog': '🦔',
|
||||||
|
'jellyfish': '🪼',
|
||||||
|
'kangaroo': '🦘',
|
||||||
|
'ladybird': '🐞',
|
||||||
|
'ladybug': '🐞',
|
||||||
|
'porcupine': '🦔',
|
||||||
|
'scorpion': '🦂',
|
||||||
|
'tarantula': '🕷️',
|
||||||
|
'alligator': '🐊',
|
||||||
|
'anaconda': '🐍',
|
||||||
|
'antelope': '🦌',
|
||||||
|
'blowfish': '🐡',
|
||||||
|
'butterfly': '🦋',
|
||||||
|
'chameleon': '🦎',
|
||||||
|
'chipmunk': '🐿️',
|
||||||
|
'crocodile': '🐊',
|
||||||
|
'dinosaur': '🦖',
|
||||||
|
'elephant': '🐘',
|
||||||
|
'flamingo': '🦩',
|
||||||
|
'giraffe': '🦒',
|
||||||
|
'hamster': '🐹',
|
||||||
|
'leopard': '🐆',
|
||||||
|
'lobster': '🦞',
|
||||||
|
'mermaid': '🧜♀️',
|
||||||
|
'mongoose': '🦦',
|
||||||
|
'mosquito': '🦟',
|
||||||
|
'pangolin': '🦔',
|
||||||
|
'peacock': '🦚',
|
||||||
|
'penguin': '🐧',
|
||||||
|
'phoenix': '🐦🔥',
|
||||||
|
'raccoon': '🦝',
|
||||||
|
'seahorse': '🐴',
|
||||||
|
'sealion': '🦭',
|
||||||
|
'unicorn': '🦄',
|
||||||
|
'vampire': '🦇',
|
||||||
|
'warthog': '🐗',
|
||||||
|
'wolverine': '🦡',
|
||||||
|
'mammoth': '🦣',
|
||||||
|
'meerkat': '🦦',
|
||||||
|
'octopus': '🐙',
|
||||||
|
'opposum': '🐭',
|
||||||
|
'ostrich': '🐦',
|
||||||
|
'panther': '🐆',
|
||||||
|
'pelican': '🦩',
|
||||||
|
'rooster': '🐓',
|
||||||
|
'serpent': '🐍',
|
||||||
|
'specter': '👻',
|
||||||
|
'spectre': '👻',
|
||||||
|
'sparrow': '🐦',
|
||||||
|
'spider': '🕷️',
|
||||||
|
'sphinx': '🦁',
|
||||||
|
'squirrel': '🐿️',
|
||||||
|
'stingray': '🦈',
|
||||||
|
'termite': '🐜',
|
||||||
|
'tortoise': '🐢',
|
||||||
|
'turkey': '🦃',
|
||||||
|
'walrus': '🦭',
|
||||||
|
'wombat': '🦡',
|
||||||
|
'woodpecker': '🐦',
|
||||||
|
'alien': '👾',
|
||||||
|
'badger': '🦡',
|
||||||
|
'beaver': '🦫',
|
||||||
|
'beetle': '🪲',
|
||||||
|
'beluga': '🐋',
|
||||||
|
'bison': '🦬',
|
||||||
|
'bobcat': '🐱',
|
||||||
|
'buffalo': '🦬',
|
||||||
|
'bunny': '🐰',
|
||||||
|
'camel': '🐪',
|
||||||
|
'cheetah': '🐆',
|
||||||
|
'chicken': '🐔',
|
||||||
|
'cobra': '🐍',
|
||||||
|
'condor': '🦅',
|
||||||
|
'cougar': '🐆',
|
||||||
|
'coyote': '🐺',
|
||||||
|
'crane': '🦩',
|
||||||
|
'cricket': '🦗',
|
||||||
|
'crow': '🐦⬛',
|
||||||
|
'cyborg': '🤖',
|
||||||
|
'dolphin': '🐬',
|
||||||
|
'donkey': '🫏',
|
||||||
|
'dragon': '🐉',
|
||||||
|
'drake': '🐉',
|
||||||
|
'eagle': '🦅',
|
||||||
|
'falcon': '🦅',
|
||||||
|
'ferret': '🦦',
|
||||||
|
'gazelle': '🦌',
|
||||||
|
'gecko': '🦎',
|
||||||
|
'gerbil': '🐹',
|
||||||
|
'ghost': '👻',
|
||||||
|
'ghoul': '👻',
|
||||||
|
'goose': '🪿',
|
||||||
|
'gopher': '🐹',
|
||||||
|
'gorilla': '🦍',
|
||||||
|
'grizzly': '🐻',
|
||||||
|
'heron': '🦩',
|
||||||
|
'hippo': '🦛',
|
||||||
|
'hornet': '🐝',
|
||||||
|
'hyena': '🐺',
|
||||||
|
'iguana': '🦎',
|
||||||
|
'jackal': '🐺',
|
||||||
|
'jaguar': '🐆',
|
||||||
|
'kitten': '🐱',
|
||||||
|
'koala': '🐨',
|
||||||
|
'lemur': '🐒',
|
||||||
|
'lizard': '🦎',
|
||||||
|
'llama': '🦙',
|
||||||
|
'locust': '🦗',
|
||||||
|
'lynx': '🐱',
|
||||||
|
'macaw': '🦜',
|
||||||
|
'mantis': '🦗',
|
||||||
|
'mink': '🦦',
|
||||||
|
'monkey': '🐵',
|
||||||
|
'moose': '🦌',
|
||||||
|
'mouse': '🐭',
|
||||||
|
'orca': '🐋',
|
||||||
|
'otter': '🦦',
|
||||||
|
'oyster': '🦪',
|
||||||
|
'panda': '🐼',
|
||||||
|
'parrot': '🦜',
|
||||||
|
'pigeon': '🕊️',
|
||||||
|
'polar': '🐻❄️',
|
||||||
|
'poodle': '🐩',
|
||||||
|
'puffin': '🐧',
|
||||||
|
'puma': '🐆',
|
||||||
|
'rabbit': '🐰',
|
||||||
|
'raptor': '🦖',
|
||||||
|
'raven': '🐦⬛',
|
||||||
|
'reaper': '👻',
|
||||||
|
'rhino': '🦏',
|
||||||
|
'robin': '🐦',
|
||||||
|
'robot': '🤖',
|
||||||
|
'salmon': '🐟',
|
||||||
|
'shrimp': '🦐',
|
||||||
|
'skunk': '🦨',
|
||||||
|
'sloth': '🦥',
|
||||||
|
'snail': '🐌',
|
||||||
|
'snake': '🐍',
|
||||||
|
'sprite': '🧚',
|
||||||
|
'squid': '🦑',
|
||||||
|
'swan': '🦢',
|
||||||
|
'tapir': '🐗',
|
||||||
|
'tiger': '🐯',
|
||||||
|
'toad': '🐸',
|
||||||
|
'trout': '🐟',
|
||||||
|
'tuna': '🐟',
|
||||||
|
'turtle': '🐢',
|
||||||
|
'viper': '🐍',
|
||||||
|
'vulture': '🦅',
|
||||||
|
'weasel': '🦦',
|
||||||
|
'whale': '🐋',
|
||||||
|
'wolf': '🐺',
|
||||||
|
'wraith': '👻',
|
||||||
|
'zebra': '🦓',
|
||||||
|
'ape': '🦍',
|
||||||
|
'ant': '🐜',
|
||||||
|
'bat': '🦇',
|
||||||
|
'bee': '🐝',
|
||||||
|
'bug': '🐛',
|
||||||
|
'cat': '🐱',
|
||||||
|
'cow': '🐮',
|
||||||
|
'crab': '🦀',
|
||||||
|
'dog': '🐶',
|
||||||
|
'duck': '🦆',
|
||||||
|
'elk': '🦌',
|
||||||
|
'fly': '🪰',
|
||||||
|
'fox': '🦊',
|
||||||
|
'frog': '🐸',
|
||||||
|
'goat': '🐐',
|
||||||
|
'hawk': '🦅',
|
||||||
|
'hen': '🐔',
|
||||||
|
'hog': '🐷',
|
||||||
|
'lion': '🦁',
|
||||||
|
'mole': '🐭',
|
||||||
|
'moth': '🦋',
|
||||||
|
'mule': '🫏',
|
||||||
|
'owl': '🦉',
|
||||||
|
'pig': '🐷',
|
||||||
|
'ram': '🐏',
|
||||||
|
'rat': '🐀',
|
||||||
|
'seal': '🦭',
|
||||||
|
'shark': '🦈',
|
||||||
|
'wasp': '🐝',
|
||||||
|
'yak': '🐂',
|
||||||
|
'doe': '🦌',
|
||||||
|
'ewe': '🐑',
|
||||||
|
'buck': '🦌',
|
||||||
|
'ox': '🐂',
|
||||||
|
'bull': '🐂',
|
||||||
|
'dodo': '🦤',
|
||||||
|
'boar': '🐗',
|
||||||
|
'bear': '🐻',
|
||||||
|
'deer': '🦌',
|
||||||
|
'dove': '🕊️',
|
||||||
|
'fish': '🐟',
|
||||||
|
'hare': '🐰',
|
||||||
|
'horse': '🐴',
|
||||||
|
'lamb': '🐑',
|
||||||
|
'mare': '🐴',
|
||||||
|
'pony': '🐴',
|
||||||
|
'pup': '🐶',
|
||||||
|
'croc': '🐊',
|
||||||
|
'gnat': '🦟',
|
||||||
|
'gnu': '🦬',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getAvatarForName(username) {
|
||||||
|
if (!username) return '\u{1F464}';
|
||||||
|
const lower = username.toLowerCase();
|
||||||
|
const sorted = Object.entries(ANIMAL_EMOJI_MAP).sort((a, b) => b[0].length - a[0].length);
|
||||||
|
for (const [key, emoji] of sorted) {
|
||||||
|
if (lower.includes(key)) return emoji;
|
||||||
|
}
|
||||||
|
return '\u{1F464}';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUsername() {
|
||||||
|
const adj = USERNAME_ADJECTIVES[Math.floor(Math.random() * USERNAME_ADJECTIVES.length)];
|
||||||
|
const noun = USERNAME_NOUNS[Math.floor(Math.random() * USERNAME_NOUNS.length)];
|
||||||
|
return `${adj}${noun}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user