BingImageCreator カスタムダウンロードボタン

ダウンロードボタンをカスタムし、ファイル名に作成日時を、ExifのUserCommentに作成プロンプトを埋め込みます。

// ==UserScript==
// @name        BingImageCreator カスタムダウンロードボタン
// @description ダウンロードボタンをカスタムし、ファイル名に作成日時を、ExifのUserCommentに作成プロンプトを埋め込みます。
// @namespace   https://gf.qytechs.cn/ja/users/1189800-ggg-niya-to
// @version     1.1
// @include     /https://www\.bing\.com/images/create/.+\?(.+&)?view=detailv2(&|$)/
// @compatible  edge on PC / manager : Tampermonkey
// @author      ggg.niya.to
// @license     MIT
// ==/UserScript==
////////////////////////
// loadイベントが何故か発行されない場合の対策
(()=>{
	let executed;
	function startup(){
		if(executed){ return; }
		executed=true;
		main();
	}
	addEventListener("load",startup);
	setTimeout(startup,1000);
})();
////////////////////////
if(!Blob.prototype.bytes){
	Blob.prototype.bytes=function(){ return this.arrayBuffer().then(b => new Uint8Array(b)); };
}
////////////////////////
function main(){
// 日時文字列
	const TIME=(()=>{
		function d2d(d){ return d<10 ? "0"+d : d; }
		let t=location.pathname.match("/create/[^/]+/\\d-([0-9a-f]{8})[0-9a-f]{24}");
		return t ? ""+((t=new Date(parseInt(t[1],16)*1000)).getFullYear())+d2d(t.getMonth()+1)+d2d(t.getDate())+"-"+d2d(t.getHours())+d2d(t.getMinutes())+d2d(t.getSeconds()) : "unknown";
	})();
// セレクター定数
	const UL_SELECTOR=".actn_container UL",IMG_SELECTOR="#mainImageWindow .mainImage.current IMG";
// 監視とタイムアウト
	const OBS=new MutationObserver(setup);
	let timeout=true;
	setTimeout(()=>{
		if(timeout){
			OBS.disconnect();
			alert("\""+UL_SELECTOR+"\" not found");
		}
	},3000);
	OBS.observe(document.body,{ subtree:true, childList:true });
	setup();
// 構築処理関数
	function setup(){
		// 追加場所を取得
		const UL=document.querySelector(UL_SELECTOR);
		if(!UL){ return; }
		timeout=false;
		OBS.disconnect();
		// 元のダウンロードボタンとカスタムボタン
		const M=UL.querySelector(".dldc"),O = M ? (M.style.display="none",M.children[0]) : null,
			B=UL.insertBefore(document.createElement("LI"),(()=>{
				let e;
				return (e=M) || (e=UL.querySelector(".c")) || (e=UL.querySelector(".sharec")) ? e.nextSibling : UL.firstChild;
			})()).appendChild(document.createElement("DIV"));
		B.className="action dld nofocus";
		B.role="button";
		B.tabIndex=0;
		B.innerHTML="<SPAN><SPAN class=\"icon\"></SPAN><SPAN class=\"text\">ダウンロード</SPAN></SPAN>";
		// ダウンロードリンク本体
		const A=document.createElement("A");
		document.body.appendChild(A).style.display="none";
		// 本処理
		let last;
		B.onclick=(event)=>{
			// 前回のメモリがあれば解放
			if(last){
				URL.revokeObjectURL(last);
				last=null;
			}
			// Ctrlと一緒に押したら元のボタン(あれば)を起動
			if(O && event.ctrlKey){
				O.click();
				return;
			}
			// ダウンロードする画像
			const IMG=document.querySelector(IMG_SELECTOR);
			if(!IMG){
				alert("\""+IMG_SELECTOR+"\" not found");
				return;
			}
			// 基URLとそこから生成した識別コード
			const SRC=IMG.src.replace(/\?.+$/,""),CODE=(()=>{
				var sum=0,a=1,s,i;
				for(s=SRC.replace(/^.+\/(.{4}\.)?/,""),i=0;i<s.length;i++,a*=2){ sum+=s.charCodeAt(i)*a; }
				return (new Uint8Array([sum/256,sum])).toHex();
			})();
			// 画像をロードしてBlobを取得
			fetch(SRC+"?pid=ImgGn").then((RESPONSE)=>{	// そのままのパラメータだとwebpが返ってくる場合があるので
				if(!RESPONSE.ok){
					alert("http-error : "+RESPONSE.status);
					return;
				}
				RESPONSE.blob().then((BLOB_I)=>{
					// 生成プロンプトから APP1<EXIF<TIFF<コメントデータ のBlobを作成
					(new Blob([IMG.alt],{ type:"text/plain" })).bytes().then((BA)=>{
						// コメントバイトデータの整形(念のため)
						const com=Array.from(BA),S_A1D=52,S_CET=8,MAX_COM=256*256-1-S_A1D-S_CET-1-1;	// USHRT_MAX-APP1_DATA-COM_ENC_TAG-末尾NUL-偶数整形マージン
						if(MAX_COM < com.length){ com.length=MAX_COM; }	// 念のため1
						com.push(0);	// 念のため2
						if(com.length%2){ com.push(0); }	// 念のため3
						while(com.length<8){ com.push(0); }	// 念のため4
						// データ構成(短いのでベタ書き)
						const CC=S_CET+com.length,SS=S_A1D+CC;
						return new Blob([
							// APP1
							new Uint8Array([0xFF,0xE1,SS/256,SS]),
								// EXIF
								"Exif",new Uint8Array([0x00,0x00]),
									// TIFF
									"MM",new Uint8Array([0x00,0x2A,0x00,0x00,0x00,0x08,
										// 0th-IFD
										0x00,0x01,0x87,0x69,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x00,
										// EXIF-IFD
										0x00,0x01,0x92,0x86,0x00,0x07,0x00,0x00,CC/256,CC,0x00,0x00,0x00,0x2C,0x00,0x00,0x00,0x00,
										// コメントデータ
										0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]),new Uint8Array(com)]);
					}).then((BLOB_C)=>{
						// 画像BlobにコメントBlobを挿入したBlobからオブジェクトURLを生成し、それをリンク先に設定してダウンロード
						A.href=last=URL.createObjectURL(new Blob([BLOB_I.slice(0,2),BLOB_C,BLOB_I.slice(2)],{ type:BLOB_I.type }));
						A.setAttribute("download",TIME+"_"+CODE);
						A.click();
					});
				});
			});
		};
	}
}
////////////////////////
/* 作成メモ
先頭2バイト(FF,D8)の後に挿入

-- APP1 構造 --
セグメントマーカー(FF,E1)
セグメントサイズ(BE、2バイト、2+58+n)
EXIFデータ本体(58+nバイト)

-- EXIF 構造 -- 58+nバイト
EXIF識別コード(6バイト、"Exif\0\0")
TIFFデータ本体(52+nバイト)

-- TIFF 構造 -- 52+nバイト
TIFFヘッダー(8バイト)
	エンディアン指定(2バイト、BE="MM"=4D,4D)
	マジックナンバー(2バイト、00,2A)
	0th-IFDへのポインタ(4バイト、8=00,00,00,08)
0th-IFD(18バイト)
	エントリ数(2バイト、EXIF-IFDのみなら1=00,01)
	エントリ(12バイト)
		タグ(2バイト、EXIF-IFDへのポインタ=34665=87,69)
		タイプ(2バイト、LONG=4=00,04)
		カウント(4バイト、1=00,00,00,01)
		ポインタ(4バイト、8+0th-IFDのサイズ(18)=26=00,00,00,1A)
	1st-IFDへのポインタ(4バイト、存在しないので0=00,00,00,00)
		↑ 一部サイトで2バイトと書いてあるが大嘘っぽい
EXIF-IFD(26+nバイト)
	エントリ数(2バイト、1=00,01)
	エントリ(12バイト)
		タグ(2バイト、ユーザーコメント=37510=92,86)
		タイプ(2バイト、UNDEFINED=7=00,07)
		カウント(4バイト、コメントデータのバイト数=8+n)
		オフセット(4バイト、8+0th-IFDのサイズ(18)+2+12+4=44=00,00,00,2C)
	不使用のポインタ(4バイト、00,00,00,00)
	コメントデータ(8+nバイト)
		エンコードタグ(8バイト、未定義=0=00,00,00,00,00,00,00,00)
		コメント本体(nバイト)
*/

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址