PageSourceSearch

https://disniverse.com/assets/js/discord-widget.js

js disniverse.com collected 2026-10-09 05:20:29 UTC 4,663 bytes, 138 lines download raw bytes

1const GUILD_WIDGET_URL = 'https://discord.com/api/guilds/1043125489264513085/widget.json';
2const REFRESH_MS = 30_000;
3
4function escapeHtml(value) {
5  return String(value)
6    .replace(/&/g, '&')
7    .replace(/</g, '&lt;')
8    .replace(/>/g, '&gt;')
9    .replace(/"/g, '&quot;')
10    .replace(/'/g, '&#39;');
11}
12
13function safeAvatarUrl(url) {
14  if (typeof url !== 'string' || !url.startsWith('https://')) return '';
15  return url;
16}
17
18function statusColor(status) {
19  if (status === 'online') return '#43b581';
20  if (status === 'idle') return '#faa61a';
21  if (status === 'dnd') return '#f04747';
22  return '#747f8d';
23}
24
25function shuffleMembers(members, existingUsernames) {
26  const copy = members.slice();
27  if (!existingUsernames.length) {
28    copy.sort(() => Math.random() - 0.5);
29    return copy;
30  }
31  copy.sort((a, b) => {
32    const aKnown = existingUsernames.includes(a.username);
33    const bKnown = existingUsernames.includes(b.username);
34    if (aKnown && !bKnown) return -1;
35    if (!aKnown && bKnown) return 1;
36    return Math.random() - 0.5;
37  });
38  return copy;
39}
40
41function renderWidget(root, data, previousCount) {
42  const onlineCount = Number(data.presence_count) || 0;
43  const members = Array.isArray(data.members) ? data.members : [];
44  const onlineMembers = members.filter((m) => ['online', 'idle', 'dnd'].includes(m.status));
45  const existing = root.querySelector('.discord-card');
46  const existingUsernames = existing
47    ? [...existing.querySelectorAll('.discord-card__avatar')].map((el) => el.getAttribute('title') || '')
48    : [];
49
50  const preview = shuffleMembers(onlineMembers, existingUsernames).slice(0, 20);
51  const countClass = previousCount > 0 && previousCount !== onlineCount ? ' is-updated' : '';
52
53  let avatars = '';
54  preview.forEach((member) => {
55    const color = statusColor(member.status);
56    const username = escapeHtml(member.username || 'User');
57    const avatarUrl = safeAvatarUrl(member.avatar_url);
58    avatars += `<div class="discord-card__avatar" style="background-image:url('${avatarUrl}')" title="${username}"><span class="discord-card__status" style="background-color:${color}"></span></div>`;
59  });
60  if (onlineMembers.length > preview.length) {
61    avatars += `<div class="discord-card__more">+${onlineMembers.length - preview.length}</div>`;
62  }
63
64  root.innerHTML = `
65    <div class="discord-card">
66      <div class="discord-card__header">
67        <span class="discord-card__live" aria-hidden="true"></span>
68        <p class="discord-card__name">${escapeHtml(data.name || 'The Disniverse')}</p>
69        <div class="discord-card__stat">
70          <span class="discord-card__count${countClass}">${onlineCount}</span>
71          <span class="discord-card__label">online</span>
72        </div>
73      </div>
74      ${preview.length ? `<div class="discord-card__avatars" aria-hidden="true">${avatars}</div>` : ''}
75    </div>`;
76
77  const countEl = root.querySelector('.discord-card__count');
78  if (countEl && countClass) {
79    window.setTimeout(() => countEl.classList.remove('is-updated'), 500);
80  }
81
82  return onlineCount;
83}
84
85export function initDiscordWidget() {
86  const root = document.getElementById('discordWidget');
87  if (!root) return null;
88
89  let previousCount = 0;
90  let timerId = 0;
91  let abortController = null;
92
93  const showError = () => {
94    root.innerHTML = `
95      <div class="discord-card__error">
96        <p>Unable to load Discord widget</p>
97        <a href="https://discord.gg/YdMes7g6" class="btn btn--primary btn--sm discord-invite" target="_blank" rel="noopener noreferrer">Join Discord</a>
98      </div>`;
99    if (typeof window.applyDiscordInvites === 'function') {
100      window.applyDiscordInvites(root);
101    }
102  };
103
104  const refresh = async () => {
105    abortController?.abort();
106    abortController = new AbortController();
107
108    try {
109      const response = await fetch(GUILD_WIDGET_URL, { signal: abortController.signal });
110      if (!response.ok) throw new Error('widget fetch failed');
111      const data = await response.json();
112      previousCount = renderWidget(root, data, previousCount);
113      if (typeof window.applyDiscordInvites === 'function') {
114        window.applyDiscordInvites(root);
115      }
116    } catch (error) {
117      if (error.name !== 'AbortError') showError();
118    }
119  };
120
121  refresh();
122  timerId = window.setInterval(refresh, REFRESH_MS);
123
124  document.addEventListener('visibilitychange', () => {
125    if (document.hidden) {
126      window.clearInterval(timerId);
127      abortController?.abort();
128    } else {
129      refresh();
130      timerId = window.setInterval(refresh, REFRESH_MS);
131    }
132  });
133
134  return () => {
135    window.clearInterval(timerId);
136    abortController?.abort();
137  };
138}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.