Add an image download button on Bing's home page.
// ==UserScript== // @namespace https://greasyfork.org/en/users/131965-levinit // @author levinit // @name Bing Image Download Button // @name:zh-CN 必应图片下载按钮 // @name:zh-TW 必應圖片下載按鈕 // @name:ko Bing 이미지 다운로드 버튼 // @name:fr Bouton de téléchargement d'image Bing // @name:ja Bing画像ダウンロードボタン // @description Add an image download button on Bing's home page. // @description:zh-CN 在必应首页添加一个图片下载按钮。 // @description:zh-TW 在必應首頁添加一个圖片下載按鈕。 // @description:ko 빙 홈페이지에 이미지 다운로드 버튼 추가 // @description:fr Ajouter le bouton de téléchargement d'image à la page d'accueil Bing. // @description:ja Bingホームページに画像ダウンロードボタンを追加する。 // @match *://cn.bing.com/* // @match *://www.bing.com/* // @run-at document-end // @version 1.3.10 // @homepageURL https://github.com/levinit/bing-image-download-button // @grant none // ==/UserScript== const bingDownloadBtnConfig = { //下载按钮css样式 btnStyles: { 'color': '', 'font-size': '1.5em', 'padding': '0.25em', 'border-radius': '0.25em', 'box-shadow': '0 0 3 px rgba(125, 125, 125, 0.25)', 'right': '20%', 'top': '12.5%', 'background': '#c3d1cf94', 'position': 'fixed', 'z-index': 9999 }, //下载按钮上的文字 btnText() { let text = 'Download Today Bing Picture' //lang en switch (navigator.language.toLowerCase()) { case 'zh': case 'zh-cn': case 'zh-sg': text = '下载今日必应图片' break; case 'zh-tw': case 'zh-hk': text = '下載今日必應圖片' break; case 'ko': case 'ko_kr': text = '오늘의 빙 이미지 다운로드' break; case 'ja': case 'ja_jp': text = '今日のBing画像をダウンロードする' break case 'fr': case 'fr_be': case 'fr_ca': case 'fr_ch': case 'fr_fr': case 'fr_lu': text = 'Téléchargez les image de bing aujourd’hui' break default: break; } return text }, //当前要下载的bing图片的信息 imgInfo: { url: '', name: '', 'name-rule': { //图片默认命名规则,true项的内容将写入到图片名中 //图片名字信息来自于图片的url 一般形如 flower_12345_1920x1080 形式 'baseName': true, //基础名字 'imgNO': false, //数字编号 'imgResolution': false, //分辨率 'dateInfo': true, //日期信息(从浏览器中获取的操作系统日期信息) 'description': true, //描述信息(bing首页右下角获取) 'copyright': false //图片版权信息(同上) }, //bing提供的图片分辨率 不设置则使用默认 默认分辨率一般和当前系统设置、显示器分辨率有关 resolution: 'UHD' //1366x768 1280x720 1920x1080 }, //设置菜单 menuInfo: { menuWrapStyles: { 'position': 'fixed', 'z-index': '9', 'right': '1%', 'top': '5%', 'font-size': '1.25em', 'display': 'none' }, //设置菜单相关标签的id值 menuWrapId: 'bing-download-settings', resetBtnId: 'reset-menu-settings', closeBtnClass: 'close-settings-menu', saveBtnId: 'save-menu-settings' }, //本项目信息 about: { github: 'https://github.com/levinit/bing-image-download-button', greasyfork: 'https://greasyfork.org/zh-TW/scripts/35070-bing-image-download-button' }, //本地存储使用的key 用于存储菜单中设置的信息 localStoreKey: 'bingImgDownload' } //当前日期偏移量 本日为0 bing可以查看前7天图片 0-7 let dateOffset = 0 //从本地存储中取得设置的信息写入到bingDownloadBtn相关项中 function getSavedSettings(info) { if (localStorage.getItem(info.localStoreKey)) { //本地存储的设置信息 const savedSettings = JSON.parse(localStorage.getItem(bingDownloadBtnConfig.localStoreKey)) const setSettings = function (settingsObj, savedSettingsObj) { //遍历本地存储的设置信息,写入到bingDownloadBtn设置菜单的各个项中 for (const item in savedSettingsObj) { if (settingsObj.hasOwnProperty(item)) { settingsObj[item] = savedSettingsObj[item] } } } //向设置菜单中写入已经保存的图片设置项的信息(图片命名规则和分辨率) setSettings(info.imgInfo, savedSettings.imgInfo) //绑定点击上一个/下一个图片时更新日期信息的事件 getDateOffset() } } function getDateOffset() { //前一天 document.getElementById("leftNav").addEventListener('click', function (e) { e.preventDefault() dateOffset = dateOffset === -7 ? -7 : dateOffset - 1 }) //后一天 document.getElementById("rightNav").addEventListener('click', function (e) { e.preventDefault() dateOffset = dateOffset === 0 ? 0 : dateOffset + 1 }) } //-----获取图片信息(根据设置规则修改) function getImgInfo(imgInfo) { let url = document.querySelector('a.downloadLink').href.split('&rf')[0] //图片地址 根据分辨率设置修改图片地址 分辨率如1920x1080 如果未设置分辨率将使用默认分辨率 url = imgInfo.resolution ? url.replace(/\d{4}x\d{3,4}/, imgInfo.resolution) : url console.log("img url is: ", url) /*图片名字 根据图片地址生成图片原始名字 原始示例 AberystwythSeafront_ZH-CN9542789062_1920x1080.jpg 原始名字分成三部分 baseName imgNO resolution */ //原始名字去掉前面的OHR.字样 使用_分割 const nameInfo = /id=.+?\.(jpg|png)/.exec(url)[0].replace('id=', '').replace(/^OHR\./, '').split('_') //图片格式 const imgFormat =nameInfo[nameInfo.length - 1].split('.')[1] //初始化图片命名相关的项 let [baseName, imgNO, resolution, description, copyright, dateInfo] = ['', '', '', '', '', ''] //根据名字生成规则修改图片名字 for (const rule in imgInfo['name-rule']) { const ruleValue = imgInfo['name-rule'][rule] if (ruleValue === true) { switch (rule) { case 'baseName': baseName = `${nameInfo[0]}` break; case 'imgNO': imgNO = `_${nameInfo[1]}` break; case 'imgResolution': resolution = `_${nameInfo[2]}`.split('.')[0] break; case 'dateInfo': //日期 先从描述信息的日期中获取,如果没有则使用系统时间 try { dateInfo = document.querySelector('.musCardCont a.title').href.match(/Date:%\d+_/)[0].slice(-9, -1) } catch (error) { console.log(error) } finally { if (dateInfo === '' || dateInfo === undefined) { const now = new Date() const imgDate = new Date(now.getTime() + dateOffset * (24 * 60 * 60 * 1000)) dateInfo = `_${imgDate.getFullYear()}-${imgDate.getMonth() + 1}-${imgDate.getDate()}` } } break; //图片描述 case 'description': description = `_${document.querySelector('.musCardCont a.title').textContent }` break; //图片版权 case 'copyright': copyright = document.querySelector('.musCardCont div.copyright').textContent break; default: break; } } } //拼接图片名字 去掉前后可能出现的_ let name = `${baseName}${imgNO}${resolution}${description}${copyright}${dateInfo}`.replace(/^_/, '').replace(/_$/, '') //如果图片没有名字只有后缀 强行给图片加上名字 if (name === `.${imgFormat}`) { name = `${nameInfo[0]}.${imgFormat}` } else { name = `${name}.${imgFormat}` } //存储图片url及名字 bingDownloadBtnConfig.imgInfo.url = url bingDownloadBtnConfig.imgInfo.name = name } //-------添加下载按钮 function addBtn(info) { const btn = document.createElement('a') btn.appendChild(document.createTextNode(info.btnText())) btn.style.cssText = (function (styles) { let btnCssText = '' for (let style in styles) { btnCssText += `${style}: ${styles[style]}; ` } return btnCssText })(info.btnStyles) btn.href = info.imgInfo.url btn.download = info.imgInfo.name btn.title = `img name: ${info.imgInfo.name} 右键打开设置菜单 | Right Click this button to open settings menu` document.body.appendChild(btn) //当光标移动到下载按钮上时立即更新图片下载信息 btn.onmouseover = function () { // 注意:点击了前一天或后一天按钮后 需要刷新图片的下载地址 getImgInfo(info.imgInfo) //将处理后的图片的url和name写入到下载按钮的属性中 this.href = info.imgInfo.url this.download = info.imgInfo.name } //在下载按钮上右键可打开设置菜单 btn.oncontextmenu = function (e) { e.preventDefault() document.getElementById(info.menuInfo.menuWrapId).style.display = 'block' } } //-----添加设置菜单 function addMenu(info) { const menuInfo = info.menuInfo //先前已经存储的图像分辨率设置信息 const savedImgResolution = info.imgInfo.resolution //先前已经存储的图像规则信息 const savedImgNameRule = info.imgInfo['name-rule'] const menuContent = ` <fieldset id="btn-settings"> <legend>settings</legend> <div class="settings-content"> <ul class="img-infos"> <header> Image Info </header> <li> <header> Image Name contains: </header> <div> <label>Base-Name</label> <input class="img-info" type="checkbox" name="name-rule" checked data-img-name-rule="baseName" /> </div> <div> <label>NO.</label> <input class="img-info" type="checkbox" name="name-rule" data-img-name-rule="imgNO" ${savedImgNameRule.imgNO ? 'checked' : ''} /> </div> <div> <label>Resolution</label> <input class="img-info" type="checkbox" name="name-rule" data-img-name-rule="imgResolution" ${savedImgNameRule.imgResolution ? 'checked' : ''} /> </div> <div> <label>Description</label> <input class="img-info" type="checkbox" name="name-rule" data-img-name-rule="description" ${savedImgNameRule.description ? 'checked' : ''} /> </div> <div> <label>CopyRight</label> <input class="img-info" type="checkbox" name="name-rule" data-img-name-rule="copyright" ${savedImgNameRule.copyright ? 'checked' : ''} /> </div> <div> <label>Date-Info</label> <input class="img-info" type="checkbox" name="name-rule" data-img-name-rule="dateInfo" ${savedImgNameRule.dateInfo ? 'checked' : ''} /> </div> </li> <li> <header> Image Resolution </header> <div> <label>UHD</label> <input class="img-info" type="radio" name="resolution" data-img-resolution="UHD" ${savedImgResolution === 'UHD' ? 'checked' : ''} /> </div> <div> <label>1920x1080</label> <input class="img-info" type="radio" name="resolution" data-img-resolution="1920x1080" ${savedImgResolution === '1920x1080' ? 'checked' : ''} /> </div> <div> <label>1366x768</label> <input class="img-info" type="radio" name="resolution" data-img-resolution="1366x768" ${savedImgResolution === '1366x768' ? 'checked' : ''} /> </div> <div> <label>1280x720</label> <input class="img-info" type="radio" name="resolution" data-img-resolution="1280x720" ${savedImgResolution === '1280x720' ? 'checked' : ''} /> </div> <div> <label>Default</label> <input class="img-info" type="radio" name="resolution" data-img-resolution="" ${savedImgResolution === '' ? 'checked' : ''} /> </div> </li> </ul> <div class="about"> About: <a href="${info.about.github}">GitHub</a> <a href="${info.about.greasyfork}">GreasyFork</a> </div> </div> <footer> <button id="${menuInfo.resetBtnId}" class="reset-btn">reset</button> <button id="${menuInfo.saveBtnId}" class="${menuInfo.closeBtnClass}">save</button> <button class="${menuInfo.closeBtnClass}">cancel</button> </footer> </fieldset> <style> #btn-settings { width: 300px; border: 1px dashed gainsboro; border-radius: 8px; box-shadow: 0 0 10px gainsboro; background-color: aliceblue; } #btn-settings legend { font-weight: bold; text-shadow: 0 0 2px gray; color: steelblue; } #btn-settings ul { padding: 0; } #btn-settings ul>header { width: 100%; border-bottom: 3px groove gainsboro; font-weight: bold; color: slategrey; text-shadow: 0 0 5px gainsboro; margin-bottom: 0.5em; } #btn-settings li { list-style-type: none; border-bottom: 1px dashed gainsboro; padding-bottom: 0.5em; } .img-infos li header { color: sienna; margin-bottom: 0.25em; } .img-infos li label { width: 80%; display: inline-block; } .img-infos .img-info { vertical-align:middle; } #btn-settings .about { text-align: right; margin-bottom: 1em; } #btn-settings .about a { margin-right: 1em; text-decoration: underline; } #btn-settings footer { text-align: right; } #btn-settings footer button { width: 88px; cursor: pointer; font-size: 1.2em; font-weight: bold; line-height: 1.25; text-align: center; padding: 0; color: teal; } #btn-settings footer .reset-btn { margin-right: 25px; color: tomato; } </style> ` const menu = document.createElement('div') menu.innerHTML = menuContent menu.id = info.menuInfo.menuWrapId let cssText = '' //设置菜单样式 for (const style in menuInfo.menuWrapStyles) { cssText += `${style}: ${menuInfo.menuWrapStyles[style]}; ` } menu.style.cssText = cssText document.body.appendChild(menu) //菜单的事件绑定:保存 重置 和 取消 menu.onclick = function (e) { if (e.target.classList.contains(menuInfo.closeBtnClass)) { //如果点击的保存或取消按钮 关闭设置菜单 menu.style.display = 'none' //如果点击的是保存按钮 存储设置的信息 if (e.target.id === menuInfo.saveBtnId) { localStorage.setItem(info.localStoreKey, JSON.stringify(getUserSettings(info))) getSavedSettings(info) getImgInfo(info.imgInfo) //刷新图片相关信息 } } //如果点击的是重置按钮 清空设置 if (e.target.id === menuInfo.resetBtnId) { localStorage.removeItem(info.localStoreKey) getSavedSettings(info) getImgInfo(info.imgInfo) //刷新图片相关信息 } } } //从本地存储获取已经保存的设置信息 function getUserSettings() { //btn-styles const btnStyles = {} for (const item of document.querySelectorAll('.btn-style')) { let value = item.value //未设置的属性 以及position设置中未选择的属性 忽略 if (item.value === "" || item.previousElementSibling.type === 'radio' && item.previousElementSibling.checked === false) { continue } const property = item.getAttribute('data-property') btnStyles[property] = value } //img-info const imgInfo = { 'name-rule': {} } for (const item of document.querySelectorAll('.img-info')) { switch (item.name) { //图片命名规则 case 'name-rule': imgInfo['name-rule'][item.getAttribute('data-img-name-rule')] = item.checked break //分辨率 case 'resolution': if (item.checked) { imgInfo.resolution = item.getAttribute('data-img-resolution') } break default: break } } return {btnStyles, imgInfo} } //+++++++++ 打开页面后的初始化 +++++++++ //从本地存储读取设置信息 getSavedSettings(bingDownloadBtnConfig) //设置图片信息 getImgInfo(bingDownloadBtnConfig.imgInfo) //添加下载按钮 addBtn(bingDownloadBtnConfig) //添加设置菜单 addMenu(bingDownloadBtnConfig)