1(() => { 2 "use strict"; 3 4 const FAVORITE_STREAMERS_KEY = "favorite_streamers_v1"; 5 const FAVORITE_GAMES_KEY = "favorite_games_v1"; 6 const ID_LIMIT = 3000; 7 8 function safeParse(jsonText, fallback) { 9 if (!jsonText) { 10 return fallback; 11 } 12 try { 13 return JSON.parse(jsonText); 14 } catch (error) { 15 console.warn("favorite parse error", error); 16 return fallback; 17 } 18 } 19 20 function safeString(value) { 21 if (value === null || value === undefined) { 22 return ""; 23 } 24 return String(value).trim(); 25 } 26 27 function normalizeId(value) { 28 const id = safeString(value); 29 if (!id) { 30 return ""; 31 } 32 return id.slice(0, 128); 33 } 34 35 function sanitizeMap(rawMap, normalizer) { 36 const result = {}; 37 if (!rawMap || typeof rawMap !== "object") { 38 return result; 39 } 40 41 Object.keys(rawMap).forEach((rawKey) => { 42 const normalized = normalizer(rawMap[rawKey]); 43 if (!normalized.id) { 44 return; 45 } 46 result[normalized.id] = normalized; 47 }); 48 49 return result; 50 } 51 52 function normalizeStreamer(entry) { 53 const id = normalizeId(entry && entry.id); 54 const displayName = safeString(entry && entry.display_name).slice(0, 200); 55 const login = safeString(entry && entry.login).slice(0, 200); 56 return { 57 id, 58 display_name: displayName, 59 login 60 }; 61 } 62 63 function normalizeGame(entry) { 64 const id = normalizeId(entry && entry.id); 65 const name = safeString(entry && entry.name).slice(0, 200); 66 return { 67 id, 68 name 69 }; 70 } 71 72 function readMap(storageKey, normalizer) { 73 const parsed = safeParse(localStorage.getItem(storageKey), {}); 74 const map = sanitizeMap(parsed, normalizer); 75 return map; 76 } 77 78 function writeMap(storageKey, map) { 79 localStorage.setItem(storageKey, JSON.stringify(map)); 80 } 81 82 function mapToList(map) { 83 return Object.values(map); 84 } 85 86 function mapKeysLimited(map) { 87 return Object.keys(map).slice(0, ID_LIMIT); 88 } 89 90 function upsert(storageKey, entry, normalizer) { 91 const normalized = normalizer(entry); 92 if (!normalized.id) { 93 return false; 94 } 95 const map = readMap(storageKey, normalizer); 96 map[normalized.id] = normalized; 97 writeMap(storageKey, map); 98 return true; 99 } 100 101 function remove(storageKey, id, normalizer) { 102 const normalizedId = normalizeId(id); 103 if (!normalizedId) { 104 return false; 105 } 106 const map = readMap(storageKey, normalizer); 107 if (!Object.prototype.hasOwnProperty.call(map, normalizedId)) { 108 return false; 109 } 110 delete map[normalizedId]; 111 writeMap(storageKey, map); 112 return true; 113 } 114 115 function has(storageKey, id, normalizer) { 116 const normalizedId = normalizeId(id); 117 if (!normalizedId) { 118 return false; 119 } 120 const map = readMap(storageKey, normalizer); 121 return Object.prototype.hasOwnProperty.call(map, normalizedId); 122 } 123 124 function toggle(storageKey, entry, normalizer) { 125 const normalized = normalizer(entry); 126 if (!normalized.id) { 127 return false; 128 } 129 const map = readMap(storageKey, normalizer); 130 const isFavorite = Object.prototype.hasOwnProperty.call(map, normalized.id); 131 if (isFavorite) { 132 delete map[normalized.id]; 133 } else { 134 map[normalized.id] = normalized; 135 } 136 writeMap(storageKey, map); 137 return !isFavorite; 138 } 139 140 function clear(storageKey) { 141 localStorage.removeItem(storageKey); 142 } 143 144 window.FavoriteStore = { 145 FAVORITE_STREAMERS_KEY, 146 FAVORITE_GAMES_KEY, 147 getStreamerMap: () => readMap(FAVORITE_STREAMERS_KEY, normalizeStreamer), 148 getGameMap: () => readMap(FAVORITE_GAMES_KEY, normalizeGame), 149 getStreamers: () => mapToList(readMap(FAVORITE_STREAMERS_KEY, normalizeStreamer)), 150 getGames: () => mapToList(readMap(FAVORITE_GAMES_KEY, normalizeGame)), 151 getStreamerIds: () => mapKeysLimited(readMap(FAVORITE_STREAMERS_KEY, normalizeStreamer)), 152 getGameIds: () => mapKeysLimited(readMap(FAVORITE_GAMES_KEY, normalizeGame)), 153 hasStreamer: (id) => has(FAVORITE_STREAMERS_KEY, id, normalizeStreamer), 154 hasGame: (id) => has(FAVORITE_GAMES_KEY, id, normalizeGame), 155 addStreamer: (entry) => upsert(FAVORITE_STREAMERS_KEY, entry, normalizeStreamer), 156 addGame: (entry) => upsert(FAVORITE_GAMES_KEY, entry, normalizeGame), 157 removeStreamer: (id) => remove(FAVORITE_STREAMERS_KEY, id, normalizeStreamer), 158 removeGame: (id) => remove(FAVORITE_GAMES_KEY, id, normalizeGame), 159 toggleStreamer: (entry) => toggle(FAVORITE_STREAMERS_KEY, entry, normalizeStreamer), 160 toggleGame: (entry) => toggle(FAVORITE_GAMES_KEY, entry, normalizeGame), 161 clearStreamers: () => clear(FAVORITE_STREAMERS_KEY), 162 clearGames: () => clear(FAVORITE_GAMES_KEY) 163 }; 164})();
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.