Last active
January 23, 2026 13:55
-
-
Save mimonelu/f0e35ce16631c13179df0124baff14eb to your computer and use it in GitHub Desktop.
Nicosky - Blueskyのポストをニコニコ風に表示するブックマークレット
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| const JETSTREAM_URL = "wss://jetstream1.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post" | |
| const NUMBER_OF_SLOTS = 15 | |
| const TARGET_LANG = "ja" | |
| const FONT_SIZE = 5 | |
| const SLOT_HEIGHT = 6.25 | |
| const TRANSITION_DURATIONS = [100, 2500] | |
| const CONTAINER_CLASS_NAME = "nicosky" | |
| const slots = Array(NUMBER_OF_SLOTS).fill(false) | |
| let numberOfMessages = 0 | |
| let numberOfLangPosts = 0 | |
| let numberOfDisplayPosts = 0 | |
| let container = document.querySelector(`.${CONTAINER_CLASS_NAME}`) | |
| if (container) { | |
| if (container.__nicoskySocket) { | |
| try { | |
| container.__nicoskySocket.close() | |
| } catch {} | |
| } | |
| container.parentNode.removeChild(container) | |
| return | |
| } else { | |
| container = document.createElement("div") | |
| container.style.cssText = ` | |
| all: initial; | |
| contain: layout paint style; | |
| overflow: hidden; | |
| pointer-events: none; | |
| position: fixed; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100vh; | |
| z-index: 65535; | |
| ` | |
| container.classList.add(CONTAINER_CLASS_NAME) | |
| document.body.appendChild(container) | |
| } | |
| const infoContainer = document.createElement("div") | |
| infoContainer.style.cssText = ` | |
| ${makeTextStyles()} | |
| font-size: ${FONT_SIZE / 2}vh; | |
| color: #c0c0c0; | |
| position: absolute; | |
| right: 0.5em; | |
| bottom: 0.5em; | |
| ` | |
| container.appendChild(infoContainer) | |
| const socket = new WebSocket(JETSTREAM_URL) | |
| container.__nicoskySocket = socket | |
| socket.addEventListener("message", event => { | |
| numberOfMessages ++ | |
| updateInfo() | |
| if (event.data == null) { | |
| return | |
| } | |
| const record = JSON.parse(event.data)?.commit?.record | |
| if ( | |
| !record || | |
| !record.langs?.length || | |
| !record.text | |
| ) { | |
| return | |
| } | |
| const hasTargetLang = record.langs.includes(TARGET_LANG) | |
| if (!hasTargetLang) { | |
| return | |
| } | |
| numberOfLangPosts ++ | |
| const hasFacets = record.facets != null | |
| if (hasFacets) { | |
| return | |
| } | |
| const hasEmbed = record.embed != null | |
| if (hasEmbed) { | |
| return | |
| } | |
| const slotIndex = slots.findIndex(slot => slot === false) | |
| if (slotIndex === - 1) { | |
| return | |
| } | |
| updateInfo() | |
| slots[slotIndex] = true | |
| const textContainer = document.createElement("div") | |
| textContainer.addEventListener("transitionend", () => { | |
| if (container && textContainer) { | |
| container.removeChild(textContainer) | |
| slots[slotIndex] = false | |
| } | |
| }) | |
| const textNode = document.createTextNode(record.text) | |
| textContainer.appendChild(textNode) | |
| container.appendChild(textContainer) | |
| const isReply = record.reply != null | |
| const duration = record.text.length * TRANSITION_DURATIONS[0] + TRANSITION_DURATIONS[1] | |
| textContainer.style.cssText = ` | |
| ${makeTextStyles()} | |
| color: ${isReply ? "#c0e0ff" : "#ffffff" }; | |
| font-size: ${FONT_SIZE}vh; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| transform: translate3d(100vw, 0, 0); | |
| transition: transform ${duration}ms linear; | |
| ` | |
| requestAnimationFrame(() => { | |
| const width = textContainer.clientWidth | |
| const y = slotIndex * SLOT_HEIGHT | |
| textContainer.style.cssText += ` | |
| top: ${y}vh; | |
| transform: translate3d(-${width}px, 0, 0); | |
| ` | |
| }) | |
| if (++ numberOfDisplayPosts >= 1000) { | |
| socket.close() | |
| } | |
| }) | |
| let lastInfo = 0 | |
| function updateInfo () { | |
| const now = performance.now() | |
| if (now - lastInfo < 125) { | |
| return | |
| } | |
| lastInfo = now | |
| infoContainer.innerText = `${numberOfDisplayPosts} shown (${numberOfLangPosts} "${TARGET_LANG}" / ${numberOfMessages} total)` | |
| } | |
| function makeTextStyles () { | |
| return ` | |
| font-family: monospace; | |
| -webkit-font-smoothing: none; | |
| font-weight: bold; | |
| text-shadow: -1px -1px 0 #000000, 1px -1px 0 #000000, -1px 1px 0 #000000, 1px 1px 0 #000000; | |
| user-select: none; | |
| white-space: nowrap; | |
| ` | |
| } | |
| })() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
説明
Blueskyのポストを某動画サイト風に表示するブックマークレットです。
jaを含むポストfacetsのないポストembedのないポスト