您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Search in Bunker @ footboom.com forum
// ==UserScript== // @name SearchMore // @namespace novhna // @description Search in Bunker @ footboom.com forum // @include https://www.footboom.com/forum* // @include http://www.footboom.com/forum* // @version 0.1.3 // @grant GM_getValue // @grant GM_setValue // ==/UserScript== // ------------------------ HELPERS --------------------------- // TODO to balance timeout and pages count const updatingTimeout = 24 * 60 * 60 * 1000 const pagesToGrab = 10 const headers = { "content-type": "application/json", "x-apikey": "5b0953381af1a2243f0b9b1c", "cache-control": "no-cache", } const db = (collection, method, data = null) => new Promise((resolve, reject) => { fetch(`https://bunker-b54a.restdb.io/rest/${collection}`, { method, headers, mode: 'cors', body: data && JSON.stringify(data), }) .then(res => res.json()) .then(resolve) .catch(console.error) }) // formatDate :: String -> String const formatDate = date => (new Date(date)).toLocaleDateString() // Should be created manually in DB at first let meta = { lastUpdate: 0, lastPage: 0, } let topics = [] const grabPage = page => new Promise((resolve, reject) => { console.log('Grab page', page) fetch(`https://www.footboom.com/forum/bunker?page=${page}`) .then(res => res.text()) .then(doc => { const rows = doc.match(/<tr> <td class="m-10">[\s\S]*?<\/tr>/g) const info = rows.map(row => ({ date: row.match(/datetime="(.+?)"/)[1], topic: row.match(/<a[\s\S]*?>([\s\S]+?)<\/a>/)[1].trim(), author: row.match(/<td[\s\S]*?>(.+?)<\/td>/g)[2].replace(/<[\s\S]+?>/g, ''), link: row.match(/href="(.+?)"/)[1], page, })) resolve(info) }) .catch(console.error) }) const updateMetadata = lastPage => { meta.lastPage = lastPage meta.lastUpdate = Date.now() db(`metadata/${meta._id}`, 'PUT', meta).then(console.log) document.querySelector('#bunker-search--last-page').textContent = meta.lastPage } const getAllTopics = (callback = () => console.warn('Please provide a callback!')) => db('topics', 'GET').then(callback) const updateDB = (count, offset = 0) => { const pages = Array(count).fill().map((_, i) => i + 1 + +offset) Promise.all(pages.map(grabPage)).then(res => { const topics = res.reduce(($, arr) => [...$, ...arr], []) console.log('Fetched topics:', topics) getAllTopics(savedTopics => { const savedLinks = savedTopics.map(({ link }) => link) const topicsToSave = topics.filter(({ link }) => !savedLinks.includes(link)) const lastPage = Math.max(...savedTopics.map(({ page }) => +page)) console.log(lastPage) db('topics', 'POST', topicsToSave).then(res => { updateMetadata(lastPage) console.log('Topics has been updated!', res) }) }) }) } // ------------------- INTERACTIONS ------------------------ // Fetching metadata and update the index DB every 24 hours const fetchMetadata = () => db('metadata', 'GET').then(docs => { meta = docs[0] document.querySelector('#bunker-search--last-page').textContent = meta.lastPage console.warn('Should update DB:', !(Date.now() - meta.lastUpdate < updatingTimeout)) if (Date.now() - meta.lastUpdate < updatingTimeout) return false console.log('Updating topics in Database!') // Shallow indexing updateDB(pagesToGrab) }) // Indexing pages in range [startPage, endPage] const indexInRange = (startPage, endPage) => { console.log('start:',endPage - startPage + 1,'offset:', startPage - 1) updateDB(endPage - startPage + 1, startPage - 1) } const searchFor = word => new Promise((resolve, reject) => { fetch(`https://bunker-b54a.restdb.io/rest/topics?q={"_tags":{"$regex":"${word}"}}`, { method: 'GET', mode: 'cors', headers, }) .then(res => res.json()) .then(resolve) .catch(console.error) }) // ============Check if need to update DB ================== fetchMetadata() // ====================== VIEW ============================= // ======================= style =========================== const style = document.createElement('style') style.type = 'text/css' style.innerHTML = ` #bunker-search { position:fixed; left:0; bottom:0; right:0; top: 0; background-color: rgba(100,100,100,0.5); color: black; z-index: -1; text-align: center; opacity: 0; transition: opacity 1s, z-index 0s 1s; } #bunker-search.expanded { opacity: 1; z-index: 9999; transition: opacity 1s, z-index 1s; } #bunker-search hr { border-top: 1px solid #fff; } #bunker-search--panel { display: inline-block; height: 100vh; width: 100vw; max-width: 700px; padding: 30px; background-color: #eee; transform: scale(0); transition: 1s; } .expanded #bunker-search--panel { transform: scale(1); } #bunker-search--results { overflow-y: auto; overflow-x: hidden; text-align: left; } #bunker-search-control { position: fixed; bottom: 0; left: 0; background-color: green; color: white; z-index: 99999; cursor: pointer; } #bunker-search-control .glass-image { height: 40px; width: 40px; background: url(https://www.footboom.net/img/new-images/icons/x2/searchx2.png) no-repeat center center; background-size: 17px 17px; } pre#bunker-search--last-page { display: inline; } .bunker-search--info { text-align: center; } ` document.querySelector('head').appendChild(style); // ================== results view ========================= const view = document.createElement('div') view.id = 'bunker-search' const submitForm = e => { e.preventDefault() view.querySelector('#bunker-search--results').innerHTML = `<li class="bunker-search--info">Шукаємо ${e.target.query.value}...</li>` searchFor(e.target.query.value) .then(res => { const links = res.map(({ link, topic, author, date }) => ` <li>[${formatDate(date)}] <a href="${link}">${topic}</a> <b>${author}</b></li> `).join('') view.querySelector('#bunker-search--results').innerHTML = links || '<li class="bunker-search--info">Нічого не знайдено</li>' }) } const toggleSearchPanel = e => { view.classList[view.classList.contains('expanded') ? 'remove' : 'add']('expanded') } view.innerHTML = ` <div id="bunker-search--panel"> <form id="bunker-search--search"> <input name="query" /> <button>Шукати в Бункері</button> </form> <ul id="bunker-search--results"></ul> <hr /> <div> Всього проіндексовано <pre id="bunker-search--last-page">...</pre> стор.<br /> <form id="bunker-search--indexing"> Індексувати з <input name="start" type="number" min="1" value="1" /> по <input name="end" type="number" min="1" value="1" /> стор. <button>Вперед</button> </form> </div> </div> ` document.body.appendChild(view) view.querySelector('form#bunker-search--search').onsubmit = submitForm // Deep indexing view.querySelector('form#bunker-search--indexing').onsubmit = e => { e.preventDefault() indexInRange(e.target.start.value, e.target.end.value) } // ==================== control ========================== const control = document.createElement('div') control.id = 'bunker-search-control' control.onclick = toggleSearchPanel; control.title = 'Search Bunker' control.innerHTML = `<div class="glass-image"></div>` document.body.appendChild(control)