您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Facebook, Vimeo, Youtube, Streamable, Tiktok, Instagram, Twitter, X, Dailymotion, Coub, Spotify, SoundCloud, Apple Podcasts, Amazon Music, Deezer, Tidal, Ted, Pbs, Odysee, Playeur, Bitchute, Rss - play on hover
// ==UserScript== // @name Play video on hover // @namespace https://lukaszmical.pl/ // @version 0.6.0 // @description Facebook, Vimeo, Youtube, Streamable, Tiktok, Instagram, Twitter, X, Dailymotion, Coub, Spotify, SoundCloud, Apple Podcasts, Amazon Music, Deezer, Tidal, Ted, Pbs, Odysee, Playeur, Bitchute, Rss - play on hover // @author Łukasz Micał // @match *://*/* // @icon https://static-00.iconduck.com/assets.00/cursor-hover-icon-512x439-vou7bdac.png // ==/UserScript== // libs/share/src/ui/SvgComponent.ts const SvgComponent = class { constructor(tag, props = {}) { this.element = Dom.createSvg({ tag, ...props }); } addClassName(...className) { this.element.classList.add(...className); } event(event, callback) { this.element.addEventListener(event, callback); } getElement() { return this.element; } mount(parent) { parent.appendChild(this.element); } }; // libs/share/src/ui/Dom.ts var Dom = class _Dom { static appendChildren(element, children, isSvgMode = false) { if (children) { element.append( ..._Dom.array(children).map((item) => { if (typeof item === 'string') { return document.createTextNode(item); } if (item instanceof HTMLElement || item instanceof SVGElement) { return item; } if (item instanceof Component || item instanceof SvgComponent) { return item.getElement(); } const isSvg = 'svg' === item.tag ? true : 'foreignObject' === item.tag ? false : isSvgMode; if (isSvg) { return _Dom.createSvg(item); } return _Dom.create(item); }) ); } } static applyAttrs(element, attrs) { if (attrs) { Object.entries(attrs).forEach(([key, value]) => { if (value === void 0 || value === false) { element.removeAttribute(key); } else { element.setAttribute(key, `${value}`); } }); } } static applyClass(element, classes) { if (classes) { element.classList.add(...classes.split(' ').filter(Boolean)); } } static applyEvents(element, events) { if (events) { Object.entries(events).forEach(([name, callback]) => { element.addEventListener(name, callback); }); } } static applyStyles(element, styles) { if (styles) { Object.entries(styles).forEach(([key, value]) => { const name = key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`); element.style.setProperty(name, value); }); } } static array(element) { return Array.isArray(element) ? element : [element]; } static create(data) { const element = document.createElement(data.tag); _Dom.appendChildren(element, data.children); _Dom.applyClass(element, data.classes); _Dom.applyAttrs(element, data.attrs); _Dom.applyEvents(element, data.events); _Dom.applyStyles(element, data.styles); return element; } static createSvg(data) { const element = document.createElementNS( 'http://www.w3.org/2000/svg', data.tag ); _Dom.appendChildren(element, data.children, true); _Dom.applyClass(element, data.classes); _Dom.applyAttrs(element, data.attrs); _Dom.applyEvents(element, data.events); _Dom.applyStyles(element, data.styles); return element; } static element(tag, classes, children) { return _Dom.create({ tag, children, classes }); } static elementSvg(tag, classes, children) { return _Dom.createSvg({ tag, children, classes }); } }; // libs/share/src/ui/Component.ts var Component = class { constructor(tag, props = {}) { this.element = Dom.create({ tag, ...props }); } addClassName(...className) { this.element.classList.add(...className); } event(event, callback) { this.element.addEventListener(event, callback); } getElement() { return this.element; } mount(parent) { parent.appendChild(this.element); } }; // apps/on-hover-preview/src/components/PreviewPopup.ts const PreviewPopup = class _PreviewPopup extends Component { constructor() { super('div', { attrs: { id: _PreviewPopup.ID, }, children: { tag: 'iframe', attrs: { allow: 'autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share', allowFullscreen: true, }, styles: { width: '100%', border: 'none', height: '100%', }, }, styles: { width: '500px', background: '#444', boxShadow: 'rgb(218, 218, 218) 1px 1px 5px', display: 'none', height: '300px', overflow: 'hidden', position: 'absolute', zIndex: '9999', }, }); this.iframeActive = false; this.iframe = this.element.children[0]; if (!document.querySelector(`#${_PreviewPopup.ID}`)) { this.mount(document.body); document.addEventListener('click', this.hidePopup.bind(this)); } } static { this.ID = 'play-on-hover-popup'; } hidePopup() { this.iframeActive = false; this.iframe.src = ''; this.element.style.display = 'none'; } showPopup(e, url, service) { if (!this.iframeActive) { this.iframe.src = url; this.iframeActive = true; Dom.applyStyles(this.element, { display: 'block', left: `${e.pageX}px`, top: `${e.pageY}px`, ...service.styles, }); } } }; // libs/share/src/ui/Events.ts const Events = class { static intendHover(validate, mouseover, mouseleave, timeout = 500) { let hover = false; let id = 0; const onHover = (event) => { if (!event.target || !validate(event.target)) { return; } const element = event.target; hover = true; element.addEventListener( 'mouseleave', (ev) => { mouseleave?.call(element, ev); clearTimeout(id); hover = false; }, { once: true } ); clearTimeout(id); id = window.setTimeout(() => { if (hover) { mouseover.call(element, event); } }, timeout); }; document.body.addEventListener('mouseover', onHover); } }; // apps/on-hover-preview/src/helpers/LinkHover.ts const LinkHover = class { constructor(services, onHover) { this.services = services; this.onHover = onHover; Events.intendHover( this.isValidLink.bind(this), this.onAnchorHover.bind(this) ); } anchorElement(node) { if (!(node instanceof HTMLElement)) { return void 0; } if (node instanceof HTMLAnchorElement) { return node; } const parent = node.closest('a'); if (parent instanceof HTMLElement) { return parent; } return void 0; } findService(url = '') { return this.services.find((service) => service.isValidUrl(url)); } isValidLink(node) { const anchor = this.anchorElement(node); if (!anchor || !anchor.href || anchor.href === '#') { return false; } return true; } async onAnchorHover(ev) { const anchor = this.anchorElement(ev.target); if (!anchor) { return; } const service = this.findService(anchor.href); if (!service) { return; } const previewUrl = await service.embeddedVideoUrl(anchor); if (!previewUrl) { return; } this.onHover(ev, previewUrl, service); } }; // apps/on-hover-preview/src/services/base/BaseService.ts const defaultServiceStyle = { width: '500px', height: '282px', }; const BaseService = class { createUrl(url, params) { if (params) { return `${url}?${this.params(params)}`; } return url; } extractId(url, match) { const result = this.match(url, match); return result?.id || ''; } isDarkmode() { return window.matchMedia('(prefers-color-scheme: dark)').matches; } match(url, match) { const result = url.match(match); if (result && result.groups) { return result.groups; } return void 0; } params(params) { return Object.entries(params) .map(([key, value]) => `${key}=${value}`) .join('&'); } theme(light, dark) { return window.matchMedia('(prefers-color-scheme: dark)').matches ? dark : light; } }; // apps/on-hover-preview/src/services/base/ServiceFactory.ts const ServiceFactory = class extends BaseService { constructor(config, styles = defaultServiceStyle) { super(); this.config = config; this.styles = styles; this.initialStyles = styles; } bindParams(url, params) { return Object.entries(params).reduce( (acc, [key, value]) => acc.replace(`:${key}`, value !== void 0 ? `${value}` : ''), url ); } async embeddedVideoUrl(element) { const isDarkMode = this.isDarkmode(); const patternParams = this.match(element.href, this.config.pattern) || {}; const urlParams = { ...patternParams, ...this.urlParams(element), theme: isDarkMode ? 'dark' : 'light', }; this.styles = { ...this.initialStyles, height: this.getHeight(urlParams), }; const embedUrl = this.bindParams( this.createUrl(this.config.embedUrl, this.config.queryParams), urlParams ); if (this.config.urlFunction) { return this.config.urlFunction({ ...urlParams, url: embedUrl, }); } return embedUrl; } isValidUrl(url) { return this.config.pattern.test(url); } getHeight(urlParams) { if (this.config.heightFunction) { return this.config.heightFunction(urlParams); } if (this.config.typeHeight && urlParams.type in this.config.typeHeight) { return this.config.typeHeight[urlParams.type]; } return this.initialStyles.height; } urlParams(element) { return { href: element.href, pathname: element.pathname, search: element.search, }; } }; // apps/on-hover-preview/src/services/AmazonMusic.ts const AmazonMusic = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://music.amazon.com/embed/:id', pattern: /music\.amazon\.com\/(?<type>albums|tracks|artists|playlists)\/(?<id>[^/?]+)/, typeHeight: { tracks: '250px' }, }, { width: '500px', borderRadius: '12px', height: '372px', } ); } }; // apps/on-hover-preview/src/services/AppleMusic.ts const AppleMusic = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://embed.:service.apple.com:pathname', pattern: /(?<service>music|podcasts)\.apple\.com\/.{2}\/(?<type>song|music-video|artist|album|podcast)/, typeHeight: { 'music-video': '281px', song: '175px', }, }, { width: '500px', borderRadius: '12px', height: '450px', } ); } }; // apps/on-hover-preview/src/services/Bitchute.ts const Bitchute = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://bitchute.com/embed/:id', pattern: /bitchute\.com\/video\/(?<id>[^/?]+)\/?/, }); } }; // apps/on-hover-preview/src/services/Coub.ts const Coub = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://coub.com/embed/:id', pattern: /coub\.com\/view\/(?<id>[^/]+)\/?/, queryParams: { autostart: 'true', muted: 'false', originalSize: 'false', startWithHD: 'true', }, }); } }; // apps/on-hover-preview/src/services/Dailymotion.ts const Dailymotion = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://geo.dailymotion.com/player.html?video=:id', pattern: /dailymotion\.com\/video\/(?<id>[^/?]+)/, }); } }; // apps/on-hover-preview/src/services/Deezer.ts const Deezer = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://widget.deezer.com/widget/:theme/:type/:id', pattern: /deezer\.com\/.{2}\/(?<type>album|playlist|track|artist|show|episode)\/(?<id>\d+)/, queryParams: { autoplay: 'true', radius: 'true', tracklist: 'false', }, }, { width: '500px', borderRadius: '10px', height: '300px', } ); } }; // apps/on-hover-preview/src/services/Facebook.ts const Facebook = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://www.facebook.com/plugins/video.php', pattern: /https:\/\/(www\.|m\.)?facebook\.com\/[\w\-_]+\/videos\//, queryParams: { width: '500', autoplay: 'true', href: ':href', show_text: 'false', }, }); } }; // apps/on-hover-preview/src/services/Instagram.ts const Instagram = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://www.instagram.com/p/:id/embed/', pattern: /instagram\.com\/(.+\/)?reel\/(?<id>[^/?]+)/, }, { width: '300px', height: '500px', } ); } }; // apps/on-hover-preview/src/services/Odysee.ts const Odysee = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://odysee.com/$/embed:pathname', pattern: /odysee\.com\/@/, queryParams: { autoplay: 'true', }, }); } }; // apps/on-hover-preview/src/services/Pbs.ts const Pbs = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://player.pbs.org/portalplayer/:id', pattern: /pbs\.org\/video\/(?<id>.+)?/, }); } }; // apps/on-hover-preview/src/services/Playeur.ts const Playeur = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://playeur.com/embed/:id', pattern: /playeur\.com\/(v|embed)\/(?<id>[^/]+)\/?/, }); } }; // apps/on-hover-preview/src/services/Podbean.ts const Podbean = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://www.podbean.com/player-v2', pattern: /podbean\.com\/.+\/(?<type>dir|pb)-(?<id>[^/?]+)\/?/, queryParams: { i: ':id-:type', }, }, { width: '500px', height: '150px', } ); } }; // apps/on-hover-preview/src/services/Rss.ts const Rss = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://player.rss.com/:show/:id', heightFunction: ({ id }) => (id ? '152px' : '320px'), pattern: /rss\.com\/podcasts\/(?<show>[^/]+)\/(?<id>\d*)/, queryParams: { theme: ':theme', }, }, { width: '500px', borderRadius: '8px', height: '152px', } ); } }; // apps/on-hover-preview/src/services/SoundCloud.ts const SoundCloud = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://w.soundcloud.com/player', pattern: /soundcloud\.com\/[^/]+\/[^/?]+/, queryParams: { hide_related: 'true', auto_play: 'true', show_artwork: 'true', show_comments: 'false', show_teaser: 'false', url: ':href', visual: 'false', }, }, { width: '600px', height: '166px', } ); } }; // apps/on-hover-preview/src/services/Spotify.ts const Spotify = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://open.spotify.com/embed/:type/:id', pattern: /spotify\.com\/(.+\/)?(?<type>track|album|playlist|episode|artist|show)\/(?<id>[\w-]+)/, typeHeight: { track: '152px' }, urlFunction: ({ type, url }) => ['episode', 'show'].includes(type) ? `${url}/video` : url, }, { width: '600px', borderRadius: '12px', height: '352px', } ); } }; // apps/on-hover-preview/src/services/Streamable.ts const Streamable = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://streamable.com/o/:id', pattern: /streamable\.com\/([s|o]\/)?(?<id>[^?/]+).*$/, queryParams: { autoplay: '1', }, }); } }; // apps/on-hover-preview/src/services/Ted.ts const Ted = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://embed.ted.com/talks/:id', pattern: /ted\.com\/talks\/(?<id>[^/]+)\/?/, }); } }; // apps/on-hover-preview/src/services/Tidal.ts const Tidal = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://embed.tidal.com/:types/:id', pattern: /tidal\.com\/(.+\/)?(?<type>track|album|video|playlist)\/(?<id>\d+|[\w-]+)/, typeHeight: { video: '281px', playlist: '400px', track: '120px', }, }, { width: '500px', borderRadius: '10px', height: '300px', } ); } }; // apps/on-hover-preview/src/services/Tiktok.ts const Tiktok = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://www.tiktok.com/player/v1/:id', pattern: /tiktok\.com\/.+\/video\/(?<id>\d+)/, queryParams: { autoplay: 1, rel: 0, }, }, { width: '338px', height: '575px', } ); } }; // apps/on-hover-preview/src/services/Twitter.ts const Twitter = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://platform.:platform.com/embed/Tweet.html', pattern: /(?<platform>twitter|x)\.com\/.+\/status\/(?<id>\d+)\/video/, queryParams: { id: ':id', maxWidth: 480, width: 480, theme: ':theme', }, }, { width: '500px', height: '300px', } ); } }; // apps/on-hover-preview/src/services/Vimeo.ts const Vimeo = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://player.vimeo.com/video/:id', pattern: /vimeo\.com(.+)*\/(?<id>\d+)\/?$/, }); } }; // apps/on-hover-preview/src/services/Youtube.ts const YoutubeHelper = class { static getId(search) { return new URLSearchParams(search).get('v') || ''; } static getStartTime(search) { const start = new URLSearchParams(search).get('t') || '0s'; const result = start.match(/(?:(?<h>\d+)h)?(?:(?<m>\d+)m)?(?<s>\d+)s/); if (result && result.groups) { return ( Number(result.groups.h || '0') * 3600 + Number(result.groups.m || '0') * 60 + Number(result.groups.s || '0') ); } return 0; } }; const Youtube = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://www.youtube.com/embed/:id', pattern: /youtube\.com\/watch/, queryParams: { autoplay: 1, start: ':start', }, urlFunction: ({ search, url }) => this.bindParams(url, { id: YoutubeHelper.getId(search), start: YoutubeHelper.getStartTime(search), }), }); } }; const YoutubeShortcut = class extends ServiceFactory { constructor() { super({ embedUrl: 'https://www.youtube.com/embed/:id', pattern: /youtu\.be\/(?<id>[^?/]+)/, queryParams: { autoplay: 1, start: ':start', }, urlFunction: ({ search, url }) => this.bindParams(url, { start: YoutubeHelper.getStartTime(search), }), }); } }; const YoutubeShorts = class extends ServiceFactory { constructor() { super( { embedUrl: 'https://www.youtube.com/embed/:id', pattern: /youtube\.com\/shorts\/(?<id>[^?/]+).*$/, queryParams: { autoplay: 1, }, }, { width: '256px', height: '454px', } ); } }; // apps/on-hover-preview/src/main.ts function run() { const services = [ Youtube, YoutubeShortcut, YoutubeShorts, Vimeo, Streamable, Facebook, Tiktok, Instagram, Twitter, Dailymotion, Dailymotion, Coub, Spotify, SoundCloud, AppleMusic, Deezer, Tidal, Ted, Pbs, Odysee, Playeur, Bitchute, Podbean, Rss, AmazonMusic, // Rumble, ].map((Service) => new Service()); const previewPopup = new PreviewPopup(); new LinkHover(services, previewPopup.showPopup.bind(previewPopup)); } if (window.top == window.self) { run(); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址