Перейти к публикации
[ {"link":"https://topdeck.ru/apps/toptrade/member/32/promo/1", "image":"https://topdeck.ru/apps/toptrade/member/32/promo/1/image"}, {"link":"https://topdeck.ru/apps/toptrade/member/32/promo/2", "image":"https://topdeck.ru/apps/toptrade/member/32/promo/2/image"} ]

GrannyCola

Пользователи
  • Публикации

    22
  • Зарегистрирован

  • Посещение

1 подписчик

О GrannyCola

  • День рождения 17.09.1997

Информация

  • Пол
    Мужчина
  • Город
    Волгоград
  • Ник MTGO
    GrannyCola
  • ФИО
    Ярослав

Посетители профиля

3 057 просмотров профиля

Достижения GrannyCola

Новичок

Новичок (1/14)

  • Неофит колобков
  • Сотник колобков
  • Десять лет с нами
  • Целеустремлённый
  • Первый пост

Недавние значки

  1. Открытое ПО Тут все открыто, пусть люди напишут, где тут уязвимость, если она вообще есть)
  2. Сделал небольшую утилиту. Мб кому-то пригодится. Вам нужно расширение для браузера TamperMonkey. Работает так, что для размеченных карт (которые подсвечиваются после "Разметить Карты", например Opt) в темах выводит минимальную/среднюю/макс. цену на карту по Scryfall. Скрипт, который надо туда добавить. П.с. Запускать на свое усмотрение. Ответственности за неполадки не несу. // ==UserScript== // @name Topdeck card prices tooltip (hover left of cursor) // @namespace http://tampermonkey.net/ // @version 0.3 // @description Show min/avg/max prices in tooltip on hover // @author GrannyCola // @match https://topdeck.ru/* // @grant GM_xmlhttpRequest // @connect api.scryfall.com // ==/UserScript== (function () { 'use strict'; const TARGET_SELECTOR = 'a.topdeck_tooltipCard'; // If empty, tooltip shows for all cards. // If not empty, only for names from this array. const ALLOWED_CARD_NAMES = [ // 'Spare Supplies' ]; const SOURCES = [ { name: 'usd', extract: json => { if (!json || !json.prices) return null; const v = parseFloat(json.prices.usd); return Number.isFinite(v) ? v : null; } }, { name: 'eur', extract: json => { if (!json || !json.prices) return null; const v = parseFloat(json.prices.eur); return Number.isFinite(v) ? v : null; } }, { name: 'usd_foil', extract: json => { if (!json || !json.prices) return null; const v = parseFloat(json.prices.usd_foil); return Number.isFinite(v) ? v : null; } } ]; const cardCache = new Map(); let tooltipEl = null; let currentLink = null; function gmGetJson(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, onload: response => { try { const data = JSON.parse(response.responseText); resolve(data); } catch (e) { console.error('Failed to parse JSON', e); reject(e); } }, onerror: err => { console.error('Request failed', err); reject(err); }, ontimeout: () => { reject(new Error('Request timeout')); } }); }); } async function fetchCardData(cardName) { const url = 'https://api.scryfall.com/cards/named?exact=' + encodeURIComponent(cardName); return gmGetJson(url); } async function getCardPrices(cardName) { if (cardCache.has(cardName)) { return cardCache.get(cardName); } let json; try { json = await fetchCardData(cardName); } catch (e) { console.warn('Cannot fetch card data for', cardName, e); cardCache.set(cardName, null); return null; } const prices = SOURCES .map(source => source.extract(json)) .filter(v => v !== null); if (!prices.length) { cardCache.set(cardName, null); return null; } const minPrice = Math.min.apply(null, prices); const maxPrice = Math.max.apply(null, prices); const avgPrice = prices.reduce((acc, v) => acc + v, 0) / prices.length; const result = { min: minPrice, avg: avgPrice, max: maxPrice }; cardCache.set(cardName, result); return result; } function formatPrice(value) { return '$' + value.toFixed(2); } function createTooltip() { if (tooltipEl) return; tooltipEl = document.createElement('div'); tooltipEl.id = 'tm-card-price-tooltip'; tooltipEl.style.position = 'absolute'; tooltipEl.style.zIndex = '9999'; tooltipEl.style.background = 'rgba(20, 20, 20, 0.95)'; tooltipEl.style.color = '#fff'; tooltipEl.style.padding = '4px 6px'; tooltipEl.style.borderRadius = '4px'; tooltipEl.style.fontSize = '11px'; tooltipEl.style.whiteSpace = 'nowrap'; tooltipEl.style.pointerEvents = 'none'; tooltipEl.style.display = 'none'; document.body.appendChild(tooltipEl); } function showTooltipAt(mouseX, mouseY, text) { if (!tooltipEl) createTooltip(); if (!tooltipEl) return; tooltipEl.textContent = text; tooltipEl.style.display = 'block'; const rect = tooltipEl.getBoundingClientRect(); const tooltipWidth = rect.width; const tooltipHeight = rect.height; const offset = 10; const top = mouseY - tooltipHeight / 2; const left = mouseX - tooltipWidth - offset; tooltipEl.style.top = top + 'px'; tooltipEl.style.left = left + 'px'; } function moveTooltip(mouseX, mouseY) { if (!tooltipEl || tooltipEl.style.display === 'none') return; const rect = tooltipEl.getBoundingClientRect(); const tooltipWidth = rect.width; const tooltipHeight = rect.height; const offset = 10; const top = mouseY - tooltipHeight / 2; const left = mouseX - tooltipWidth - offset; tooltipEl.style.top = top + 'px'; tooltipEl.style.left = left + 'px'; } function hideTooltip() { if (tooltipEl) tooltipEl.style.display = 'none'; currentLink = null; } function isAllowedCardName(cardName) { if (!ALLOWED_CARD_NAMES.length) return true; return ALLOWED_CARD_NAMES.includes(cardName); } function attachHandlersToLink(link) { if (link.dataset.tmPriceBound === '1') return; link.dataset.tmPriceBound = '1'; link.addEventListener('mouseenter', async event => { const cardName = link.textContent.trim(); if (!cardName || !isAllowedCardName(cardName)) return; currentLink = link; showTooltipAt(event.pageX, event.pageY, 'Loading prices...'); try { const prices = await getCardPrices(cardName); if (!prices) { if (currentLink === link) { showTooltipAt(event.pageX, event.pageY, 'No prices'); } return; } const text = 'min ' + formatPrice(prices.min) + ' / avg ' + formatPrice(prices.avg) + ' / max ' + formatPrice(prices.max); if (currentLink === link) { showTooltipAt(event.pageX, event.pageY, text); } } catch (e) { console.error('Error processing card', cardName, e); if (currentLink === link) { showTooltipAt(event.pageX, event.pageY, 'Error'); } } }); link.addEventListener('mousemove', event => { if (currentLink === link) { moveTooltip(event.pageX, event.pageY); } }); link.addEventListener('mouseleave', () => { if (currentLink === link) { hideTooltip(); } }); } function processAllLinks(root) { const links = (root || document).querySelectorAll(TARGET_SELECTOR); links.forEach(attachHandlersToLink); } function init() { createTooltip(); processAllLinks(document); const observer = new MutationObserver(mutations => { for (const mutation of mutations) { mutation.addedNodes.forEach(node => { if (!(node instanceof HTMLElement)) return; if (node.matches && node.matches(TARGET_SELECTOR)) { attachHandlersToLink(node); } processAllLinks(node); }); } }); observer.observe(document.body, { childList: true, subtree: true }); } init(); })();
  3. В теории можно синхронизировать с какой-нибудь Google таблицей.
  4. Согласен. Пока так, костыльно сделал. Можно использовать crontab и просто запускать этот скрипт раз в сутки, в одно и то же время
  5. from playwright.sync_api import sync_playwright import logging import time TOPIC_URL = ( "<URL вашей торговой темы>" ) COOKIES = [ { "name": "ips4_device_key", "value": "<ПОДСТАВИТЬ СВОЕ ЗНАЧЕНИЕ>", "domain": "topdeck.ru", "path": "/", }, { "name": "ips4_member_id", "value": "<ПОДСТАВИТЬ СВОЕ ЗНАЧЕНИЕ>", "domain": "topdeck.ru", "path": "/", }, { "name": "ips4_login_key", "value": "<ПОДСТАВИТЬ СВОЕ ЗНАЧЕНИЕ>", "domain": "topdeck.ru", "path": "/", }, { "name": "ips4_IPSSessionFront", "value": "<ПОДСТАВИТЬ СВОЕ ЗНАЧЕНИЕ>", "domain": "topdeck.ru", "path": "/", }, { "name": "ips4_loggedIn", "value": "<ПОДСТАВИТЬ СВОЕ ЗНАЧЕНИЕ>", "domain": "topdeck.ru", "path": "/", }, ] LOG_FILE = "topdeck_bump_playwright.log" def setup_logging() -> None: logging.basicConfig( filename=LOG_FILE, level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", ) def bump_via_browser() -> None: logging.info("Starting bump attempt via Playwright") with sync_playwright() as p: browser = p.chromium.launch(headless=True) try: context = browser.new_context() context.add_cookies(COOKIES) page = context.new_page() logging.info("Navigating to topic URL") page.goto(TOPIC_URL, wait_until="networkidle") page.wait_for_timeout(3000) li_selector = 'li[data-controller*="BumpUpTopics"]' li_loc = page.locator(li_selector) li_count = li_loc.count() logging.info("BumpUpTopics <li> count: %s", li_count) if li_count == 0: logging.warning("No <li> with BumpUpTopics found on page") with open("last_page.html", "w", encoding="utf-8") as f: f.write(page.content()) return li_first = li_loc.first spans_in_li = li_first.locator("span") span_count = spans_in_li.count() logging.info("Span count inside BumpUpTopics li: %s", span_count) # Пробуем найти именно disabled / enabled по id bump_span = page.locator("#elBumpDisabled, #elBumpEnabled").first bump_span_count = bump_span.count() logging.info("Bump span by id count: %s", bump_span_count) if bump_span_count == 0: logging.info("Bump span with id not found") with open("last_page.html", "w", encoding="utf-8") as f: f.write(page.content()) return if not bump_span.is_visible(): logging.info( "Bump span found by id, but not visible " "(cooldown or CSS-hidden)" ) return logging.info("Clicking bump span by id...") bump_span.click() page.wait_for_timeout(3000) logging.info("Bump span click finished") finally: browser.close() if __name__ == "__main__": setup_logging() while True: try: logging.info("=== New hourly bump cycle started ===") bump_via_browser() except Exception as exc: logging.exception("Unhandled error in main loop: %s", exc) logging.info("Sleeping for 1 hour before next attempt") time.sleep(3600) Пока сделал так. Каждый час скрипт пытается через эмуляцию браузера нажать на кнопку. Разворачивать на VDS сервере. Предварительно вы должны узнать свои куки (подставить соответствующие значения в места, где написано """<ПОДСТАВИТЬ СВОЕ ЗНАЧЕНИЕ>", через пункт Сеть в Панели разработчика браузера, которая открывается через F12. Потом как раз блокируется кнопка, можно будет сделать и без эмуляции браузера, когда подсмотрю элемент. Если надо подробнее - пишите, постараюсь ответить.
  6. Привет! Добро пожаловать в мою торговую тему. Здесь я продаю карты в разные форматы. Минимальная сумма заказа — 500 ₽. Отправка Почтой России — +130 ₽ к заказу. Перед оформлением, пожалуйста, уточняйте: состояние интересующих карт, язык (по умолчанию все карты на английском), выпуск / издание карты. Если состояние не указано отдельно — считаем, что карта в состоянии SP. Большинство карт NM. Состояние всех карт в процессе уточнения. Не забывайте пользоваться поиском по странице (CTRL+F), чтобы быстрее найти нужные карты. Формат таблицы: Кол-во, Название, Цена в рублях, Состояние и язык, Выпуск с номером карты и др. инфа 1 Suspicious Stowaway // Seafaring Werewolf 10 NM MID 80 1 Indulging Patrician 20 NM TDC 292 1 Inspiring Veteran 10 NM ELD 194 1 Inferno Titan 15 SP M12 147 1 Lutri, the Spellchaser 15 NM IKO 227 1 Kefnet the Mindful 30 NM AKH 59 1 Linden, the Steadfast Queen 30 NM ELD 20 (foil) 1 Command the Dreadhorde 10 WAR 82 1 Hour of Revelation 25 SP ZNC 17 1 Plargg, Dean of Chaos // Augusta, Dean of Order 15 NM STX 155 1 Finale of Promise 25 NM WAR 127 1 Nimble Obstructionist 15 C20 121 1 Tectonic Edge 23 C14 313 1 Brushfire Elemental 10 ZNR 221 2 Domri's Ambush 10 WAR 192 1 Jared Carthalion, True Heir 17 CMR 281 1 Treacherous Greed 20 MKM 237 3 Shifting Ceratops 14 M20 194 1 Masked Vandal 40 KHM 184 (foil) 1 Allure of the Unknown 10 THB 207 1 Glorybringer 25 AKH 134 1 Sulfuric Vortex 30 DDK 68 1 Flametongue Kavu 20 PLS 60 2 Breath of Darigaaz 16 INV 138 (ENG, CN) 1 Monastery Swiftspear 20 KTK 118 1 Mishra's Factory 10 2XM 323 1 Moon-Circuit Hacker 25 NEO 67 1 Ice Out 10 WOE 54 1 Not Dead After All 50 WOE 101 2 Distress 10 M12 94 1 Scoured Barrens 3 NEO 274 1 Slagwoods Bridge 10 MH2 256 1 Rustvale Bridge 10 MH2 253 2 Whisper Agent 10 GRN 220 4 Shrivel 5 MM2 95 2 Sign in Blood 10 MM2 97 1 Liliana's Specter 10 CNS 116 1 Spare Supplies 10 ZNR 254 4 Gray Merchant of Asphodel 10 THS 89 4 Sovereign's Bite 12 M19 120 2 Everflame Eidolon 8 BNG 92 3 Disfigure 10 PM20 95 4 Phyrexian Rager 5 EMA 102 1 Serum Visions 47 5DN 36 1 Forked Bolt 40 ROE 146 1 Galazeth Prismari 90 STX 189 1 God-Eternal Kefnet 90 WAR 53 1 Eerie Ultimatum 150 IKO 184 1 High Market 125 C15 289 1 Restless Prairie 60 LCI 281 1 Vanquish the Horde 48 MID 41 1 Ayara, First of Locthwain 30 ELD 75 1 Chasm Skulker 50 M15 46 1 Reflections of Littjara 50 KHM 73 3 Stormbreath Dragon 45 THS 143 4 Tenth District Legionnaire 10 WAR 222 4 Gingerbrute 10 ELD 219 4 Favored Hoplite 15 J25 196 4 Toolcraft Exemplar 10 KLD 32 1 Angelic Ascension 10 M21 3 1 Seal Away 10 DOM 31 1 Seasoned Hallowblade 25 M21 34 (foil) 2 Blisterpod 10 BFZ 163 1 Syr Faren, the Hengehammer 10 ELD 177 1 Dimir Charm 12 GTC 154 2 Stormchaser Mage 14 OGW 159 1 Indulging Patrician 20 M21 219 4 Cast Out 10 C20 79 3 Baffling End 12 RIX 1 3 Skyclave Cleric // Skyclave Basilica 15 ZNR 40 1 Miscast 50 M21 57 1 Infernal Grasp 25 MID 107 3 Expedite 10 BBD 177 3 Grasp of Darkness 9 GN2 30 2 Order of Midnight // Alter Fate 10 ELD 99 1 Glimpse of Freedom 10 THB 50 4 Hieroglyphic Illumination 5 AKH 57 1 Psychic Rebuttal 10 ORI 67 1 Pteramander 10 RNA 47 2 Teferi's Tutelage 10 M21 78 1 Call of the Death-Dweller 20 IKO 78 2 Mystical Dispute 20 ELD 58 2 Damping Sphere 25 DOM 213 1 Soul-Guide Lantern 10 THB 237 1 Chain Lightning 35 MB2 55 1 Jolrael, Mwonvuli Recluse 10 PLST M21-191 1 Weathered Runestone 14 KHM 247 3 Lucky Clover 10 PLST ELD-226 1 Lucky Clover 50 PLST ELD-226 (foil) 4 Enigmatic Incarnation 10 THB 215 4 Fires of Invention 14 ELD 125 4 Skilled Animator 8 M19 73 2 The Blackstaff of Waterdeep 15 AFR 48 1 Remorseful Cleric 15 M19 33 4 Ingenious Smith 10 AFR 21 4 Thraben Inspector 15 2XM 35 1 Oath of Teferi 15 CMM 934 3 Crash Through 10 M19 133 1 Jegantha, the Wellspring 25 IKO 222 1 Thrashing Brontodon 5 M21 209 2 Selfless Savior 10 M21 36 2 Dauntless Bodyguard 10 DOM 14 4 Gods Willing 10 M20 19 1 Light of Hope 10 IKO 20 1 Fight as One 10 IKO 12 1 Defiant Strike 10 STA 3 2 Defiant Strike 20 M21 15 (foil) 2 Defiant Strike 10 WAR 9 2 Swift Justice 10 RTR 26 2 Rile 10 XLN 158 1 Thassa's Intervention 14 THB 72 1 Merfolk Secretkeeper // Venture Deeper 10 ELD 284 1 Merfolk Secretkeeper // Venture Deeper 10 ELD 53 1 Shrapnel Blast 8 MMA 129 1 Luminarch Aspirant 10 ZNR 24 4 Ghostfire Blade 15 KTK 220 1 Bomat Courier 15 KLD 199 4 Ornithopter 10 M11 211 4 Izzet Charm 10 RTR 172 1 Birgi, God of Storytelling 1100 NM RU KHM 123 2 Agent of Treachery 210 NM RU M20 43 1 Grim Tutor 1100 NM RU М21 103 1 Jorn, God of Winter 150 Mint(заводская упаковка) RU Kaldheim Prerelease Promos 1 Bloodline Culling 50 NM RU MID Prerealese Promo 1 Atraxa, Grand Unifier 2300 NM EN ONE 196 3 Up the Beanstalk 200 NM FR WOE 3 Eidolon of the Great Revel 400 SP-NP 93 1 Embercleave 350 NM RU ELD 120 1 Embercleave 350 NM EN ELD 120 1 Tergrid, God of Fright 700 NM RU KHM 112 1 Sublime Epiphany 400 NM EN M21 355 Borderless 1 Teferi, Time Raveler 400 NM RU WAR 221 1 Teferi, Time Raveler 400 NM RU WAR 221 вилка 1 Ludevic, Necro-Alchemist 118 C16 Foil 1 Chandra, Awakened Inferno 400 NM RU M20 Foil 1 Lord Skitter, Sewer King 220 WOE 2 Mechagodzilla, Battle Fortress // Hangarback Walker 200 PLG20 Promo 2 Teferi, Who Slows the Sunset 400 MID Borderless 1 Emeria's Call // Emeria, Shattered Skyclave 300 ZNR 1 Gush 100 DD2 2 Valakut Awakening // Valakut Stoneforge 350 NM RU ZNR 1 Kiora, the Crashing Wave 157 BNG 1 Void Winnower 1458 BFZ 1 Wandering Archaic // Explore the Vastlands 707 STX Foil 2 Reckoner Bankbuster 55 NEO 1 Chandra, Torch of Defiance 219 KLD 1 Ox of Agonas 55 THB 1 Fiery Temper 28 UMA Foil 1 Beastmaster Ascension 371 C14 1 Eruth, Tormented Prophet 39 VOW Foil 1 Kraum, Ludevic's Opus 550 NM JP C16 Foil 1 Vineglimmer Snarl 150 STX 1 Insidious Roots 130 NM MKM 1 Siege Rhino 50 SP CP3 Promo 5 1 Increasing Vengeance 500 NM JP STA 103 4 Tithing Blade 90 NM LCI 128 4 Khalni Garden 90 SP-NM RU EN JP WWK 138 4 Cast Down 40 NM CMR 112 2 Lembas 70 NM LTR 243 3 Crypt Rats 40 NM RU MH1 084 4 Ichor Wellspring 35 NM 2XM 261;MBS 110 4 Deadly Dispute 30 NM RU AFR 094 1 Witch's Cottage 15 NM RU ELD 249 1 Blood Fountain 20 NM RU VOW 095 1 Candy Trail 30 NM WOE 243 4 Defile 10 NM RU EN MH1 086 2 Golgari Rot Farm 20 NM 2 Thorn of the Black Rose 15 NM 3 Omen of the Sun 12 NM 1 Clever Lumimancer 10 NM RU 4 Whirlwind Denial 10 NM RU 4 Jwari Disruption 15 NM RU 1 Geth's Verdict 20 SP RU 1 Sign in Blood 10 NM 3 Avenging Hunter 150 NM CLB 215 2 Spinning Darkness 150 NM-SP 4 Fanatical Offering 250(комплектом 900) NM LCI 105 2 Reckoner's Bargain 100 NM NEO 120 1 Bojuka Bog 180 NM C14 285
  7. Вообще я уже сделал, я думал, просто кто-то конкретно элемент HTML напишет, по которому тыкать надо, а то у меня кнопка заблокирована на поднятие)
  8. Привет. Кто-нибудь знает как сделать авто поднятие темы? С программированием дружу
  9. Люди, бросившие окончательно магию, вряд ли оставят комментарий здесь)
  10. @Strein К сожалению пока да, по причине отсутствия свободного времени...
  11. Такой возможности пока нет. Думаю эту проблему на первых парах решит вывод количества карт (слава богу такой параметр есть), не додумался его добавить что-то сразу. Спасибо за идею.
×
×
  • Создать...