Add column image exporter

This commit is contained in:
the1812 2020-10-17 22:35:04 +08:00
parent d1d024b319
commit def6ea5449
16 changed files with 264 additions and 35 deletions

View File

@ -500,6 +500,7 @@ declare global {
copyCommentLink: boolean,
unfoldFeeds: boolean,
feedsImageExporter: boolean,
columnImageExporter: boolean,
latestVersionLink: string,
currentVersion: string,
}
@ -559,5 +560,6 @@ declare global {
const getScriptVersion: () => ScriptVersion
const playerReady: () => Promise<void>
const formData: (obj: any) => string
const retrieveImageUrl: (element: Element) => { url: string; extension: string } | null
}
export { }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,7 @@
"clear-cache.min.js": "9AB1536D8278DA681831A589AD1414425BF206B36AE109FB4EC1E8A685BB2A9F",
"collapse-live-side-bar.min.css": "22348786547D2D8DB659A306AF2E9C1F2050D66488778C71545AAEF785FD57E6",
"collapse-live-side-bar.min.js": "BB1B1CF157099A70F1AEA3F154C84D9CA1A84A29B5F6E42357CA9F51FFC4965C",
"column-image-exporter.min.js": "EC98F065908736A6DA91A3F8AA68BF8DC9D4D3F2973E374BA7C36C66F5669345",
"combo-like.min.js": "368DA34768E947628099D258ED23BC5FFFCFFD25A47346E086C7975C8A52F997",
"comment.min.css": "6A72677F3DAFC1227B058C9487A00DAEE6A1E4BF9839C55888EDF7AFBD8DFF23",
"comment.min.js": "64F7B6951861C25799C48BB1ABCF2A73F79BCBBFC1E9D3BD45EC1F16F94F6451",
@ -102,7 +103,7 @@
"feeds-apis.min.js": "0A70658F6A398AC5122C4328F69EC93FC122BD19319F89C3DECF60389DC68C30",
"feeds-filter.min.js": "9E8BC1A669D97992E888A3154FCE64912CE0B7A303D6BE40C3CA0C71A9533B96",
"feeds-filter-card.vue.min.js": "D743BEF3C77C8A9AB8AA5DD24E8BD0A7AF7E638319137A103F3A9CF278F66CB4",
"feeds-image-exporter.min.js": "22A675B0FD8FFE49394686EDA2F871AE1BE91C856BA4116FE024B22244BBB721",
"feeds-image-exporter.min.js": "E92D2820FF7BD37AA4D930C9A89DF32B989B4E6A06F8AF6E0C3D9AE777343F81",
"feeds-translate.min.css": "54E959EC2AD38451B288E0DC669A269BF916CDB0F6B78497E2C315EB2811EF02",
"feeds-translate.min.js": "02C12D7A3382E185FF67C503C6287759ED18D2E9C838C2B94668953272BA09B1",
"feeds-translate-providers.min.js": "DD3B48C051ED1CD89AB79B2563723B2639D8737EF2194136D63ABAA8D4B64F6C",
@ -124,7 +125,7 @@
"gbk.min.js": "872D7A4562F13A539C4A9AF1825DE89E610C775F1AD026EA6986A80DD74E4B53",
"get-number.min.js": "7B81A45D0F7D7F68CC6EDE25E02FCD76F8A05B2CF29274F94A99980BB23D8B76",
"gui-settings.min.css": "787D487EF39F28C18815C755BB4CA336355B84778861DCC70AA5AC998FDB9CC7",
"gui-settings.min.html": "25C8D590B625A310CEE53F685286B252C7DB0B7EE8FB294B8A7A3C9A8E1D1170",
"gui-settings.min.html": "760D5BFDF0A042C1FA0D13CF978C02AC4B3EC81D6E895527C1B53DE0F199B6DC",
"gui-settings.min.js": "A092181BBAF56712E2F62B46552DE97D582FB1891B626D9D55276E577BC44D7A",
"haruna-scale.min.js": "FD079C86741F614FD63E733CF9B7B896A7D20110754815A540F3341D4DC252B2",
"hide-bangumi-reviews.min.js": "7265C0CE994FDA926355A9A4F9B207E7E68E749D47332F3C537EF93E97927A23",

Binary file not shown.

1
min/column-image-exporter.min.js vendored Normal file
View File

@ -0,0 +1 @@
(()=>{return(t,n)=>{return{widget:{condition:()=>document.URL.startsWith("https://www.bilibili.com/read/cv"),content:`\n<button class="gui-settings-flat-button column-image-export">\n<i class="mdi mdi-24px mdi-export"></i>\n<span>导出图片</span>\n</button>`,success:()=>{const t=dq(".column-image-export");const e=dq(t,"span");t.addEventListener("click",async()=>{try{t.disabled=true;e.textContent="下载中...";const o=[];const a=document.title.replace(/ - 哔哩哔哩$/,"");const i=dq(".banner-img-holder");const s=retrieveImageUrl(i);if(s){o.push({...s,name:`${a}-banner${s.extension}`})}const c=dqa('.article-holder img:not([class*="cut-off-"])');c.forEach(t=>{const n=retrieveImageUrl(t);if(n){o.push({...n,name:`${a}-${o.length-1}${n.extension}`})}});if(o.length===0){Toast.info("此专栏没有检测到任何图片.","专栏图片导出");return}const{DownloadVideoPackage:r}=await n.importAsync("download-video-package");const l=await Promise.all(o.map(({url:t})=>Ajax.getBlob(t)));const d=new r;l.forEach((t,n)=>d.add(o[n].name,t));await d.emit(`${a}.zip`)}catch(t){logError(t)}finally{e.textContent="导出图片";t.disabled=false}})}}}}})();

View File

@ -1 +1 @@
(()=>{return(e,a)=>{(async()=>{const{forEachFeedsCard:e,addMenuItem:t}=await a.importAsync("feeds-apis");const n=e=>{t(e,{className:"image-export",text:"导出图片",action:async()=>{const t=[];dqa(e.element,"img, .img-content").forEach(e=>{let a;if(e instanceof HTMLImageElement){a=e.src}else{const t=e.style.backgroundImage;if(!t){return}const n=t.match(/url\("(.+)"\)/);if(!n){return}a=n[1]}const n=a.match(/^(.+)(\..+?)(@.+)$/);if(!n){return}t.push({url:n[1]+n[2],extension:n[2]})});const{DownloadVideoPackage:n}=await a.importAsync("download-video-package");const o=await Promise.all(t.map(({url:e})=>Ajax.getBlob(e)));const s=new n;o.forEach((a,n)=>s.add(`${e.username}-${e.id}-${n}${t[n].extension}`,a));await s.emit(`${e.username}-${e.id}.zip`)}})};e({added:n})})()}})();
(()=>{return(a,e)=>{(async()=>{const{forEachFeedsCard:a,addMenuItem:t}=await e.importAsync("feeds-apis");const n=a=>{t(a,{className:"image-export",text:"导出图片",action:async()=>{const t=[];dqa(a.element,"img, .img-content").forEach(a=>{const e=retrieveImageUrl(a);if(e){t.push(e)}});if(t.length===0){Toast.info("此条动态没有检测到任何图片.","动态图片导出");return}const{DownloadVideoPackage:n}=await e.importAsync("download-video-package");const o=await Promise.all(t.map(({url:a})=>Ajax.getBlob(a)));const i=new n;o.forEach((e,n)=>i.add(`${a.username}-${a.id}-${n}${t[n].extension}`,e));await i.emit(`${a.username}-${a.id}.zip`)}})};a({added:n})})()}})();

File diff suppressed because one or more lines are too long

View File

@ -9,29 +9,15 @@ import { FeedsCard } from './feeds-apis'
action: async () => {
const imageUrls: { url: string; extension: string }[] = []
dqa(card.element, 'img, .img-content').forEach((img: HTMLImageElement | HTMLDivElement) => {
let url: string
if (img instanceof HTMLImageElement) {
url = img.src
} else {
const backgroundImage = img.style.backgroundImage
if (!backgroundImage) {
return
}
const match = backgroundImage.match(/url\("(.+)"\)/)
if (!match) {
return
}
url = match[1]
const urlData = retrieveImageUrl(img)
if (urlData) {
imageUrls.push(urlData)
}
const thumbMatch = url.match(/^(.+)(\..+?)(@.+)$/)
if (!thumbMatch) {
return
}
imageUrls.push({
url: thumbMatch[1] + thumbMatch[2],
extension: thumbMatch[2],
})
})
if (imageUrls.length === 0) {
Toast.info('此条动态没有检测到任何图片.', '动态图片导出')
return
}
const { DownloadVideoPackage } = await import('../video/download-video/download-video-package')
const imageBlobs = await Promise.all(imageUrls.map(({ url }) => Ajax.getBlob(url)))
const pack = new DownloadVideoPackage()

View File

@ -973,5 +973,11 @@ Resource.manifest = {
feedsImageExporter: '动态图片导出',
},
},
columnImageExporter: {
path: 'column-image-exporter.min.js',
displayNames: {
columnImageExporter: '专栏图片导出',
},
},
}
export const resourceManifest = Resource.manifest

View File

@ -247,6 +247,7 @@ export const settings = {
copyCommentLink: false,
unfoldFeeds: true,
feedsImageExporter: false,
columnImageExporter: false,
cache: {},
}
const fixedSettings = {

View File

@ -203,3 +203,32 @@ export const playerReady = () => {
)
}
export const formData = (obj) => Object.entries(obj).map(([k, v]) => `${k}=${v}`).join('&')
export const retrieveImageUrl = (element) => {
if (!(element instanceof HTMLElement)) {
return null
}
let url
if (element.hasAttribute('data-src')) {
url = element.getAttribute('data-src')
} else if (element instanceof HTMLImageElement) {
url = element.src
} else {
const backgroundImage = element.style.backgroundImage
if (!backgroundImage) {
return null
}
const match = backgroundImage.match(/url\("(.+)"\)/)
if (!match) {
return null
}
url = match[1]
}
const thumbMatch = url.match(/^(.+)(\..+?)(@.+)$/)
if (!thumbMatch) {
return null
}
return {
url: thumbMatch[1] + thumbMatch[2],
extension: thumbMatch[2],
}
}

View File

@ -0,0 +1,54 @@
export default {
widget: {
condition: () => document.URL.startsWith('https://www.bilibili.com/read/cv'),
content: /* html */`
<button class="gui-settings-flat-button column-image-export">
<i class="mdi mdi-24px mdi-export"></i>
<span></span>
</button>`,
success: () => {
const button = dq('.column-image-export') as HTMLButtonElement
const text = dq(button, 'span') as HTMLSpanElement
button.addEventListener('click', async () => {
try {
button.disabled = true
text.textContent = '下载中...'
const images: { name: string; extension: string; url: string }[] = []
const title = document.title.replace(/ - 哔哩哔哩$/, '')
const bannerElement = dq('.banner-img-holder') as HTMLDivElement
const bannerUrl = retrieveImageUrl(bannerElement)
if (bannerUrl) {
images.push({
...bannerUrl,
name: `${title}-banner${bannerUrl.extension}`,
})
}
const articleImages = dqa('.article-holder img:not([class*="cut-off-"])')
articleImages.forEach(image => {
const url = retrieveImageUrl(image)
if (url) {
images.push({
...url,
name: `${title}-${images.length - 1}${url.extension}`,
})
}
})
if (images.length === 0) {
Toast.info('此专栏没有检测到任何图片.', '专栏图片导出')
return
}
const { DownloadVideoPackage } = await import('../video/download-video/download-video-package')
const imageBlobs = await Promise.all(images.map(({ url }) => Ajax.getBlob(url)))
const pack = new DownloadVideoPackage()
imageBlobs.forEach((blob, index) => pack.add(images[index].name, blob))
await pack.emit(`${title}.zip`)
} catch (error) {
logError(error)
} finally {
text.textContent = '导出图片'
button.disabled = false
}
})
}
}
}

View File

@ -164,6 +164,7 @@
<checkbox indent="0" key="urlParamsClean" dependencies=""></checkbox>
<checkbox indent="0" key="preferAvUrl" dependencies=""></checkbox>
<checkbox indent="0" key="copyCommentLink" dependencies=""></checkbox>
<checkbox indent="0" key="columnImageExporter" dependencies=""></checkbox>
<!-- <checkbox indent="0" key="restoreFloors" dependencies=""></checkbox> -->
<category icon="touch">触摸</category>
<checkbox indent="0" key="touchNavBar" dependencies=""></checkbox>