MediaWiki:ChatLinkSearch.js
De Guild Wars 2 Wiki
Ir a la navegaciónIr a la búsqueda
Nota: Después de publicar, quizás necesite actualizar la caché de su navegador para ver los cambios.
- Firefox/Safari: Mantenga presionada la tecla Shift mientras pulsa el botón Actualizar, o presiona Ctrl+F5 o Ctrl+R (⌘+R en Mac)
- Google Chrome: presione Ctrl+Shift+R (⌘+Shift+R en Mac)
- Internet Explorer/Edge: mantenga presionada Ctrl mientras pulsa Actualizar, o presione Ctrl+F5
- Opera: Presiona Ctrl+F5.
/* <nowiki> */ /** * GW2W Chat link search * * Decodes Guild Wars 2 chat links in the search panel, and tries to find the * corresponding article using the SMW property "Has game id". (Saelyth note: which is why we didn't translate that one) * * Original by Patrick Westerhoff [User:Poke]. 2022 modifications by Chieftain Alex. */ /** * Entry point, slightly different to EN wiki since does not need to handle interwikis. */ (function chatLinkSearch_wrapper() { var searchBar = document.querySelector('#searchText input'); if (!searchBar) { return; } // Check for an interwiki prefix chatLinkSearch(searchBar) })(); function getCookie(k) { var v = document.cookie.match('(^|;) ?' + k + '=([^;]*)(;|$)'); return v ? v[2] : null } function chatLinkSearch(searchBar) { var mwApi; // Helper function: Convert item mask into options (upgrades, sigils/runes, skins) function itemChoices(mask){ var option = {}; // Bitmask meanings: 0 = no upgrades, 64 (or 32) = 1 sigil, 96 = 2 sigils, 128 = skinned, 192 (or 160) = skinned + 1 sigil, 224 = skinned + 2 sigils switch (mask) { case 0: option.name = 'no upgrades'; option.arr = ['','','']; break; case 32: case 64: option.name = 'one sigil/rune'; option.arr = ['objeto','','']; break; case 96: option.name = 'two sigils/runes'; option.arr = ['objeto','objeto','']; break; case 128: option.name = 'skin applied'; option.arr = ['diseño','','']; break; case 160: case 192: option.name = 'one sigil/rune and a skin applied'; option.arr = ['diseño','objeto','']; break; case 224: option.name = 'two sigils/runes and a skin applied'; option.arr = ['diseño','objeto','objeto']; break; default: option.name = 'unknown'; option.arr = ['','','']; break; } return option; } // Helper function: Convert specialization mask into options (top/middle/bottom) function specializationChoices(mask){ // Convert to binary var binary = mask.toString(2).padStart(8,'0'); // Split into pairs var binary_pairs = binary.match(/../g); // Remove the useless 1st pair binary_pairs.shift(); // Reverse the order and convert back into decimals var positions = $.map(binary_pairs.reverse(), function(v) { return parseInt(v,2) - 1; }); var traits = $.map(positions, function(v,k) { var pos = ['Top','Middle','Bottom']; return pos[v]; }); return traits; } function decodeChatLink2(input) { /** Example usage: decodeChatLink('[&AdsnAAA=]') * * Some examples that can be decoded: * '[&AdsnAAA=]'; // Coin - 1g 02s 03c * '[&AgGqtgAA]'; // Item - Zojja's Claymore * '[&AgGqtgDgfQ4AAP9fAAAnYAAA]'; // Item - Zojja's Claymore (item 46762), bitmask 224 (skin + two upgrades) skinned as Dreamthistle Greatsword (skin 3709), with Superior Bloodlust (item 24575), Superior Force (24615) * '[&AxcnAAA=]'; // Text: "Fight what cannot be fought" - id 10007 * '[&DGYAAABOBAAA]'; // WvW objective: [[Y'lan Academy]] --> map id 1102, objective 102 * '[&DAYAAAAmAAAA]'; // WvW objective: [[Speldan Clearcut]] --> map id 38, objective 6 * '[&DQQIByEANzZ5AHgAqwEAALUApQEAALwA7QDtABg9AAEAAAAAAAAAAAAAAAA=]'; // Ranger build * '[&DQEqHhAaPj1LFwAAFRcAAEgBAAAxAQAANwEAAAAAAAAAAAAAAAAAAAAAAAA=]'; // Burn firebrand */ // HELPER FUNCTION #1 // Reads a string like AAB60000, break into pairs AA-B6-00-00, reverses pairs 00-00-B6-AA, joins, and converts HEX (radix 16) to DECIMAL (radix 10). // Note: prefixing a number with 0x would allow you to skip specifying the 16 bit. // https://www.binaryhexconverter.com/hex-to-decimal-converter function parseHexLittleEndian(text){ if (text == undefined || !(text.match(/../g)) ){ return ''; } return parseInt(text.match(/../g).reverse().join(''),16); } // Input cleanup - remove "[&" and rear "]" var code = input.replace(/^\[\&+|\]+$/g, ''); // Split characters into array var textArray = code.split(''); // Convert from Text to an Array of decimal numbers (0, 1, 2, 3, 4, 5, 6, 7, 8, 9). var AtoB_lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var decimals = new Array(textArray.length); for (var i = 0; i < code.length; i++) { decimals[i] = AtoB_lookup.indexOf(textArray[i]); } // Convert from numbers to blocks of 6 binary bits [aka digits] // Needs 6 digits 32-16-8-4-2-1 to represent 0-63 for a base 64 number var binaries = new Array(code.length); for (var i = 0; i < code.length; i++) { binaries[i] = decimals[i].toString(2).padStart(6,'0'); } // Join var binary_stream = binaries.join(''); // Split into blocks, but this time in groups of 4 binary bits var binary_quads = binary_stream.match(/..../g); // Interpret as HEX - one hex character = 4 bits. Therefore two hex characters = 8 bits == 1 byte. var hex_quads = new Array(binary_quads.length); for (var i = 0; i < binary_quads.length; i++) { hex_quads[i] = parseInt(binary_quads[i], 2).toString(16).toUpperCase(); } // Join var hex_stream = hex_quads.join(''); // Layout specifications change depending on the header number var specification = { 1: { name: 'coin', searchflag: 'n', format: [ { name: 'header', bytes: 2 }, { name: 'copper_qty', bytes: 8 } ] }, 2: { name: 'objeto', // item searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'quantity', bytes: 2 }, { name: 'id', bytes: 6 }, { name: 'bitmask', bytes: 2 }, // Bitmask meanings: 0 = no upgrades, 64 (or 32) = 1 sigil, 96 = 2 sigils, 128 = skinned, 192 (or 160) = skinned + 1 sigil, 224 = skinned + 2 sigils { name: 'upgrade1', bytes: 6 }, { name: 'padding1', bytes: 2 }, { name: 'upgrade2', bytes: 6 }, { name: 'padding2', bytes: 2 }, { name: 'upgrade3', bytes: 6 } ] }, 3: { name: 'text', searchflag: 'n', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 4: { name: 'localización', // location searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 6: { name: 'habilidad', // skill searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 7: { name: 'rasgo', // trait searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 9: { name: 'receta', // recipe searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 10: { name: 'diseño', // skin searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 11: { name: 'atuendo', // outfit searchflag: 'y', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 } ] }, 12: { name: 'wvw objective', searchflag: 'n', format: [ { name: 'header', bytes: 2 }, { name: 'id', bytes: 8 }, { name: 'map_id', bytes: 8 } ] }, 13: { name: 'build template', searchflag: 'n', format: [ { name: 'header', bytes: 2 }, // 2 hex digits (each capable of 0-15 permutations) = 2*4 binary digits = 1 byte { name: 'prof', bytes: 2 }, // 1 byte { name: 'spec1', bytes: 2 }, // 1oo6 bytes { name: 'spec1_choices', bytes: 2 }, // 2oo6 bytes { name: 'spec2', bytes: 2 }, // 3oo6 bytes { name: 'spec2_choices', bytes: 2 }, // 4oo6 bytes { name: 'spec3', bytes: 2 }, // 5oo6 bytes { name: 'spec3_choices', bytes: 2 }, // 6oo6 bytes { name: 'heal', bytes: 4 }, // 2oo20 bytes { name: 'aquatic_heal', bytes: 4 }, // 4oo20 { name: 'utility1', bytes: 4 }, // 6oo20 { name: 'aquatic_utility1', bytes: 4 }, // 8oo20 { name: 'utility2', bytes: 4 }, // 10oo20 { name: 'aquatic_utility2', bytes: 4 }, // 12oo20 { name: 'utility3', bytes: 4 }, // 14oo20 { name: 'aquatic_utility3', bytes: 4 }, // 16oo20 { name: 'elite', bytes: 4 }, // 18oo20 { name: 'aquatic_elite', bytes: 4 }, // 20oo20 { name: 'pet1ORrevlegend1', bytes: 2 }, // 1oo4 bytes { name: 'pet2ORrevlegend2', bytes: 2 }, // 2oo4 { name: 'aquatic_pet1ORarevlegend1', bytes: 2 }, // 3oo4 { name: 'aquatic_pet2ORarevlegend2', bytes: 2 } // 4oo4 ] } }; // Examine the header - this informs the structure of the rest of the chatlink var headerTypeNum = parseHexLittleEndian( hex_stream.slice(0,2) ); if (!(headerTypeNum in specification)){ // Chatlink header type not supported return { headername: 'unsupported', header: headerTypeNum, searchflag: 'n' }; } // Convert hex stream into blocks of decimals var hex_spec = {}, dec_spec = { 'headername': '' }, offset = 0; $.each( specification[headerTypeNum].format, function(i,v){ hex_spec[v.name] = hex_stream.slice(offset, offset + v.bytes); dec_spec[v.name] = parseHexLittleEndian( hex_spec[v.name] ); offset += v.bytes; }); // Push the header name and wiki seach true/false flag dec_spec.headername = specification[headerTypeNum].name; dec_spec.searchflag = specification[headerTypeNum].searchflag; // Extra sanitization due to printing the json blob if (dec_spec.headername == 'objeto') { // Upgrades var i_temp = itemChoices(dec_spec.bitmask); // Name dec_spec.enhancements = i_temp.name; // Rename remaining variables var i_temp_upgrades_array = [dec_spec.upgrade1,dec_spec.upgrade2,dec_spec.upgrade3]; var i_count = 0; $.each(i_temp.arr, function(i,v) { if (v !== '') { if (v == 'skin') { dec_spec.skin = i_temp_upgrades_array[i]; } else { i_count += 1; dec_spec['item_upgrade_id' + i_count] = i_temp_upgrades_array[i]; } } }); delete dec_spec.bitmask; delete dec_spec.upgrade1; delete dec_spec.upgrade2; delete dec_spec.upgrade3; delete dec_spec.padding1; delete dec_spec.padding2; delete dec_spec.padding3; } if (dec_spec.headername == 'build template') { // Specializations dec_spec.spec1_choices = specializationChoices(dec_spec.spec1_choices).join('-'); dec_spec.spec2_choices = specializationChoices(dec_spec.spec2_choices).join('-'); dec_spec.spec3_choices = specializationChoices(dec_spec.spec3_choices).join('-'); // Ranger and Revenant specific // Ranger if (dec_spec.prof == 4) { dec_spec.pet1 = dec_spec.pet1ORrevlegend1; dec_spec.pet2 = dec_spec.pet2ORrevlegend2; dec_spec.aquatic_pet1 = dec_spec.aquatic_pet1ORarevlegend1; dec_spec.aquatic_pet2 = dec_spec.aquatic_pet2ORarevlegend2; } // Revenant if (dec_spec.prof == 9) { dec_spec.revlegend1 = dec_spec.pet1ORrevlegend1; dec_spec.revlegend2 = dec_spec.pet2ORrevlegend2; dec_spec.aquatic_revlegend1 = dec_spec.aquatic_pet1ORarevlegend1; dec_spec.aquatic_revlegend2 = dec_spec.aquatic_pet2ORarevlegend2; } delete dec_spec.pet1ORrevlegend1; delete dec_spec.pet2ORrevlegend2; delete dec_spec.aquatic_pet1ORarevlegend1; delete dec_spec.aquatic_pet2ORarevlegend2; } return dec_spec; } function smwAskArticle (type, id, callback) { var apiData = { action: 'ask', query: '?Tiene canónico nombre|?Tiene contexto|limit=1|' }; var query = '[[:+]] [[Has game id::' + id + ']]'; if (type == 'objeto') { query += '[[Tiene contexto::Objeto]]'; } else if (type == 'localización') { query += '[[Tiene contexto::Localización]]'; } else if (type == 'habilidad') { query = query + '[[Tiene contexto::Habilidad]] OR ' + query + '[[Tiene contexto::Efecto]]'; } else if (type == 'rasgo') { query += '[[Tiene contexto::Rasgo]]'; } else if (type == 'diseño') { query += '[[Tiene contexto::Diseño]]'; } else if (type == 'receta') { query = '[[:+]] [[Tiene id de receta::' + id + ']]'; } else if (type == 'atuendo') { query += '[[:+]] [[Tiene id de atuendo::' + id + ']]'; } apiData.query += query; mwApi.get(apiData) .done(function (data) { if (data.query.results.length === 0) { callback(null); } else { for (var title in data.query.results) { var canonicalName = data.query.results[title].printouts['Tiene canónico nombre'][0]; var gameContexts = data.query.results[title].printouts['Tiene contexto'] callback(title, canonicalName, gameContexts.length ? gameContexts[0] : null); return; } } }) .fail(function (data) { callback(null); }); } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function sanitizeTitle(obj) { delete obj.searchflag; // Remove emtpy key-value pairs $.each(obj, function(i,v){ if (v == "") { delete obj[i]; } }); // Replace quote marks, newlines and double spaces return JSON.stringify(obj, null, 2) .replace(/"/g,"") .replace(/\n/g,"") .replace(/ /g," "); } function display (code, listItem) { var data = decodeChatLink2(code); var type = data.headername; var id; var searchflag = data.searchflag; if (searchflag == 'n') { if (type == 'unsupported') { var span = document.createElement('span'); span.innerHTML = 'Este tipo de enlace de chat no está soportado de momento (Chat link header #' + data.header + ')'; span.title = sanitizeTitle(data); $(span).fadeIn(1000).appendTo(listItem); return; } else { var span = document.createElement('span'); span.innerHTML = capitalizeFirstLetter(type) + ' enlace de chat. Actualmente no se admite la búsqueda de este tipo de enlace de chat, pero se ha decodificado. Pase el cursor sobre esta línea para obtener más detalles.' if ('id' in data ) { span.innerHTML += ' (' + type + ' #' + data.id + ')'; } span.title = sanitizeTitle(data); $(span).fadeIn(1000).appendTo(listItem); return; } } else { id = data.id; smwAskArticle(type, id, function (title, canonicalName, gameContext) { var span = document.createElement('span'); span.title = sanitizeTitle(data); if (title) { // If a single chatlink returns a single result (single li element), redirect to that page // but don't redirect if it contains anything except a chatlink, e.g. interwiki prefix or text following if (searchBar.value.match(/^\[&[A-Za-z0-9+/=]+\]$/)) { // Redirect only once for the current browsing session for that precise result var key = 'searchredirected-' + searchBar.value; try { if (!sessionStorage.getItem(key)) { sessionStorage.setItem(key, 'true'); document.location = '/index.php?title=' + encodeURIComponent(title.replace(/ /g, '_')); } } catch(e) { // This might throw if session storage is disabled or unsupported. Just don't redirect if so. } } var link = document.createElement('a'); link.href = '/wiki/' + $.map(title.split('/'), function(v){ return encodeURIComponent(v.replace(/ /g, '_')); }).join('/'); link.title = title; link.innerHTML = canonicalName || title; span.appendChild(link); if (type == 'habilidad' && gameContext == 'Efecto') { type = 'efecto'; } span.appendChild(document.createTextNode(' (' + type + ' #' + id + ')')); } else { var msg = 'No hay ningún articulo asociado con esta (' + id + ') todavía.'; msg += ' Si sabes a qué <i>' + (type == 'habilidad' ? 'habilidad o efecto' : type) + '</i> este código enlaza, por favor añade la ID al articulo o crealo si todavía no existe.'; span.innerHTML = msg; } $(span).fadeIn(1000).appendTo(listItem); $(listItem).attr('data-gameid', id) }); } } window.mw.loader.using('mediawiki.api', function() { mwApi = new window.mw.Api(); // Find chat links var ul = document.createElement('ul'); var expr = /\[&([A-Za-z0-9+/]+=*)\]/g; var match; while ((match = expr.exec(searchBar.value))) { var li = document.createElement('li'); li.innerHTML = '<tt>' + match[0] + '</tt>'; ul.appendChild(li); display(match[1], li); } // Display results if (ul.children.length) { var div = document.createElement('div'); div.className = 'gw2w-chat-link-search'; div.innerHTML = 'Los siguiente <a href="/wiki/Chat_link" title="Enlace de chat">enlaces de chat</a> fueron incluidos en tu consulta de busqueda:'; div.appendChild(ul); var topTable = document.getElementById('mw-search-top-table'); $(div).hide().insertAfter(topTable).show('fast'); } }); } /* </nowiki> */