mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +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 { generateUsername } from './shared/names.js';
|
||||
|
||||
// --- State Management ---
|
||||
let socket = null;
|
||||
@@ -15,6 +16,8 @@ let pendingHistory = [];
|
||||
let eventQueue = [];
|
||||
let isNamespaceJoined = false;
|
||||
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 ---
|
||||
let restorationTask = null;
|
||||
@@ -35,7 +38,7 @@ function ensureState() {
|
||||
'logs', 'history', 'currentRoom', 'lastActionState',
|
||||
'eventQueue', 'isForceSyncInitiator', 'forceSyncAcks',
|
||||
'forceSyncDeadline', 'reconnectFailed', 'reconnectStartTime', 'currentTabId', 'currentTabTitle',
|
||||
'episodeLobby'
|
||||
'episodeLobby', 'localSeq'
|
||||
], (data) => {
|
||||
clearTimeout(storageTimeout);
|
||||
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;
|
||||
|
||||
// Process any early logs/history that weren't captured in the spread
|
||||
@@ -186,9 +191,7 @@ async function getSettings() {
|
||||
}
|
||||
let username = data.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'];
|
||||
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)]}`;
|
||||
username = generateUsername();
|
||||
chrome.storage.sync.set({ username }, () => {
|
||||
resolve({
|
||||
serverUrl: data.serverUrl || '',
|
||||
@@ -576,6 +579,14 @@ function handleServerEvent(event, data) {
|
||||
case EVENTS.PAUSE:
|
||||
case EVENTS.SEEK:
|
||||
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) {
|
||||
addToHistory(event, data.senderId);
|
||||
showNotification(data.senderId, event);
|
||||
@@ -592,6 +603,11 @@ function handleServerEvent(event, data) {
|
||||
routeToContent(event, data);
|
||||
break;
|
||||
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) {
|
||||
forceSyncAcks.add(data.senderId);
|
||||
chrome.storage.session.set({ forceSyncAcks: Array.from(forceSyncAcks) });
|
||||
@@ -621,6 +637,11 @@ function handleServerEvent(event, data) {
|
||||
}
|
||||
break;
|
||||
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) {
|
||||
addToHistory(event, data.senderId);
|
||||
showNotification(data.senderId, event);
|
||||
@@ -701,7 +722,7 @@ function handleServerEvent(event, data) {
|
||||
peer.muted = data.muted !== undefined ? data.muted : peer.muted;
|
||||
|
||||
const timeSinceReactive = peer.lastReactiveUpdate ? (Date.now() - peer.lastReactiveUpdate) : Infinity;
|
||||
const ignoreStatus = timeSinceReactive < 1000;
|
||||
const ignoreStatus = timeSinceReactive < 300;
|
||||
|
||||
if (!ignoreStatus) {
|
||||
peer.playbackState = data.playbackState !== undefined ? data.playbackState : peer.playbackState;
|
||||
@@ -797,8 +818,11 @@ function executeForceSync() {
|
||||
const executionTimestamp = Date.now();
|
||||
updateLastAction(EVENTS.FORCE_SYNC_EXECUTE, 'You', executionTimestamp);
|
||||
|
||||
emit(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp });
|
||||
routeToContent(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp });
|
||||
localSeq++;
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -863,8 +887,10 @@ function executeEpisodeLobby() {
|
||||
});
|
||||
|
||||
const syncPayload = { targetTime: 0.0 };
|
||||
emit(EVENTS.FORCE_SYNC_PREPARE, { ...syncPayload, peerId, actionTimestamp: timestamp });
|
||||
routeToContent(EVENTS.FORCE_SYNC_PREPARE, { ...syncPayload, actionTimestamp: timestamp });
|
||||
localSeq++;
|
||||
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(() => {
|
||||
if (isForceSyncInitiator) {
|
||||
@@ -1137,10 +1163,13 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
} else if (message.type === 'CONTENT_EVENT') {
|
||||
const processEvent = () => {
|
||||
const timestamp = Date.now();
|
||||
localSeq++;
|
||||
chrome.storage.session.set({ localSeq });
|
||||
updateLastAction(message.action, 'You', timestamp);
|
||||
lastActionState.targetTime = message.payload?.targetTime !== undefined ? message.payload.targetTime : message.payload?.currentTime;
|
||||
if (storageInitialized) chrome.storage.session.set({ lastActionState });
|
||||
message.payload.actionTimestamp = timestamp;
|
||||
message.payload.seq = localSeq;
|
||||
|
||||
// Local Reactive Update
|
||||
updateLocalPeerState(peerId, {
|
||||
|
||||
+2
-21
@@ -1,5 +1,6 @@
|
||||
import { EVENTS, OFFICIAL_LANDING_PAGE_URL } from './shared/constants.js';
|
||||
import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
|
||||
import { getAvatarForName, generateUsername } from './shared/names.js';
|
||||
|
||||
|
||||
const elements = {
|
||||
@@ -63,24 +64,6 @@ let errorToken = 0;
|
||||
let forceSyncDone = false;
|
||||
|
||||
// --- 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 ---
|
||||
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']);
|
||||
let username = data.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'];
|
||||
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)]}`;
|
||||
username = generateUsername();
|
||||
chrome.storage.sync.set({ username });
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ if (!fs.existsSync(extSharedDir)) {
|
||||
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) {
|
||||
const src = path.join(masterSharedDir, file);
|
||||
const dest = path.join(extSharedDir, file);
|
||||
@@ -31,7 +31,7 @@ for (const file of sharedFiles) {
|
||||
}
|
||||
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
|
||||
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