Pixiv Collection Plus

pixiv收藏拓展

  1. // ==UserScript==
  2. // @name Pixiv Collection Plus
  3. // @namespace niaier pixiv collection
  4. // @license MIT
  5. // @version 0.1
  6. // @description pixiv收藏拓展
  7. // @author niaier
  8. // @match *://www.pixiv.net/*
  9. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  10. // @grant GM_addStyle
  11. // @grant GM_getResourceText
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_download
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_setClipboard
  17. // @grant unsafeWindow
  18. // @grant window.close
  19. // @grant window.focus
  20. // @grant window.onurlchange
  21. // @require https://code.jquery.com/jquery-3.6.0.min.js
  22. // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js
  23. // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.3/cpexcel.js
  24. // @require https://cdnjs.cloudflare.com/ajax/libs/dexie/3.2.1/dexie.min.js
  25. // @resource css https://unpkg.com/element-ui/lib/theme-chalk/index.css
  26. // @require https://cdn.jsdelivr.net/npm/vue@2.6.14
  27. // @require https://unpkg.com/element-ui/lib/index.js
  28. // @require https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js
  29. // @connect *
  30. // ==/UserScript==
  31.  
  32. window.onload = function () {
  33. // 替换element字体源
  34. const styleText = GM_getResourceText("css").replace('fonts/element-icons.woff', 'https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.woff').replace('fonts/element-icons.ttf', 'https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/fonts/element-icons.ttf')
  35. // GM_addStyle(GM_getResourceText("css"));
  36. GM_addStyle(styleText);
  37.  
  38. class Database extends Dexie {
  39. constructor() {
  40. super('pixiv');
  41. this.version('1.0').stores({
  42. production: '++id,&url,title,custom_category,tag',
  43. collection: '++id,url,title,custom_category,tag,collection_list,collection_list_id',
  44. collection_class_list: '++id,list_name'
  45. });
  46.  
  47. this.collection = this.table('collection');
  48. this.collectionClassList = this.table('collection_class_list');
  49. }
  50. // 当前有关收藏
  51. async getCurCollection (url) {
  52. let curCollection = []
  53. curCollection = await this.collection.where('url').equals(url).toArray()
  54. return curCollection
  55. }
  56. // 添加收藏
  57. async addToCollection (data) {
  58. return await this.collection.add(data)
  59. }
  60. // 删除收藏
  61. async deleteCollection (id, url) {
  62. console.log(id, url);
  63. return await this.collection
  64. .where({ 'collection_list_id': id, url }).delete()
  65. }
  66. // 获取收藏列表
  67. async getCollectionList () {
  68. let collectionClassList = []
  69. collectionClassList = await this.collection_class_list.orderBy('id').toArray();
  70. return collectionClassList
  71. }
  72. // 添加收藏列表
  73. async addCollectionList (data) {
  74. return await this.collection_class_list.add(data)
  75. }
  76. //删除收藏列表
  77. async deleteCollectionList (id) {
  78. await this.collection_class_list.where('id').equals(id).delete()
  79. await this.collection.where('collection_list_id').equals(id).delete()
  80. return
  81. }
  82. //获取收藏列表下的藏品
  83. async getCollectionListContent (collection_list_id) {
  84. // 参数 收藏列表id collection_list_id
  85. let collectionListContent = []
  86. collectionListContent = await this.collection.where('collection_list_id').equals(collection_list_id).toArray()
  87. return collectionListContent
  88. }
  89. // 获取自定义标签
  90. async getCustomCategory (url) {
  91. let custom_category = []
  92. const production = await this.production.where('url').equals(url).toArray()
  93. custom_category = production[0] ? production[0].custom_category : []
  94. console.log(custom_category);
  95. return custom_category
  96. }
  97. // 添加自定义标签
  98. // 添加作品
  99. async putProduction (data) {
  100. return await this.production.put(data)
  101. }
  102. //获取作品
  103. async getProduction (url) {
  104. let arr = []
  105. arr = await this.production.where('url').equals(url).toArray()
  106. return arr[0]
  107. }
  108. // 修改自定义标签
  109. async putCustomCategory (url, custom_category) {
  110. console.log(url, custom_category);
  111. await this.production.where('url').equals(url).modify({
  112. custom_category
  113. })
  114. }
  115. async getAllData () {
  116. const production = await this.production.toArray()
  117. const collection = await this.collection.toArray()
  118. const collection_class_list = await this.collection_class_list.toArray()
  119. return { production, collection, collection_class_list }
  120.  
  121. }
  122. async importAllData (data) {
  123. // 先清空
  124. // await this.production.delete()
  125. // await this.collection.delete()
  126. // await this.collection_class_list.delete()
  127.  
  128. await this.production.bulkPut(data.production)
  129. await this.collection.bulkPut(data.collection)
  130. await this.collection_class_list.bulkPut(data.collection_class_list)
  131. }
  132.  
  133.  
  134. }
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142.  
  143. const viewCollection =
  144. `
  145. <div id="viewCollection">
  146. <el-button type="primary" size="small" :style="{
  147. 'margin-left': '10px'
  148. }" @click="showCollection">查看收藏</el-button>
  149.  
  150.  
  151. <el-dialog title="查看收藏列表" :visible.sync="collectionVisible" width="60%" center append-to-body :style="{
  152. height: '80%',
  153. }">
  154. <el-container>
  155. <el-aside width="200px">
  156. <h5>收藏列表</h5>
  157. <el-menu default-active="0" class="el-menu-vertical-demo">
  158. <el-menu-item :index="index" :key="item.id" v-for="(item, index) in collectionList">
  159. <i class="el-icon-menu"></i>
  160. <span slot="title" @click="getCollectionListContent(item)">{{ item.list_name }}</span>
  161. </el-menu-item>
  162. </el-menu>
  163. </el-aside>
  164. <el-main>
  165. <div>
  166. <div
  167. :style="{
  168. 'margin-bottom': '10px'
  169. }"
  170. >
  171. <h5>收藏展示</h5>
  172. <el-button type="primary" @click="exportJson" size="small">导出json</el-button>
  173.  
  174. <input type="file" name="file" id="importJson" v-show="false" @change="handleImport">
  175. <el-button size="small" type="primary" @click="importJson" :style="{
  176. 'margin-left': '10px',
  177. }">导入Json</el-button>
  178. </div>
  179. <el-row :gutter="10">
  180. <el-col :span="8" :key="item.id" v-for="item in collectionListContent">
  181. <div :style="{ height: 250 }">
  182. <div class="pic" :style="{ 'text-align': 'center' }">
  183. <img :src="item.preview_url" alt="" height="184" />
  184. </div>
  185. <div class="title" :style="{
  186. 'display':'flex',
  187. 'justify-content':'center'
  188. }">
  189. <h5 :style="{width:'130px'}">
  190. <el-link :href="item.url" target="_blank">
  191. {{ item.title }}
  192. </el-link>
  193. </h5>
  194. </div>
  195. </el-col>
  196. </el-row>
  197. </div>
  198. </el-main>
  199. </el-container>
  200. </el-dialog>
  201. </div>
  202. `
  203. const addCollection =
  204. ` <div id="addCollection">
  205. <el-button
  206. type="primary"
  207. size="small"
  208. @click="showCollectionList"
  209. :style="{
  210. 'margin-right': '10px'
  211. }"
  212. >
  213. 添加到收藏</el-button
  214. >
  215. <el-dialog
  216. title="收藏列表"
  217. :visible.sync="collectionListVisible"
  218. width="30%"
  219. center
  220. append-to-body
  221. >
  222. <div class="currentTag">
  223. <h5
  224. :style="{
  225. 'margin-bottom': '10px',
  226. }"
  227. >
  228. 当前标签:
  229. </h5>
  230. <span>
  231. <el-tag
  232. v-for="item in tag"
  233. :key="item"
  234. type="info"
  235. size="mimi"
  236. :style="{
  237. 'margin-left': '10px',
  238. 'margin-bottom': '10px',
  239. }"
  240. >
  241. {{ item.originTag + " | " + item.translatedTag }}
  242. </el-tag>
  243. </span>
  244. </div>
  245. <div
  246. class="customTag"
  247. :style="{
  248. 'margin-top': '10px',
  249. 'margin-bottom': '10px',
  250. }"
  251. >
  252. <h5
  253. :style="{
  254. 'margin-bottom': '10px',
  255. }"
  256. >
  257. 自定义标签:
  258. </h5>
  259.  
  260. <span>
  261. <el-tag
  262. :key="item"
  263. size="mimi"
  264. v-for="item in custom_category"
  265. closable
  266. :disable-transitions="false"
  267. @close="deleteCustomCategory(item)"
  268. :style="{
  269. 'margin-left': '10px',
  270. 'margin-button': '10px',
  271. }"
  272. >
  273. {{ item.originTag + " | " + item.translatedTag }}
  274. </el-tag>
  275. <el-input
  276. class="input-new-tag"
  277. v-if="customCategoryInputVisible"
  278. v-model="selfCategory"
  279. ref="saveTagInput"
  280. size="small"
  281. @keyup.enter.native="addSelfCategory"
  282. >
  283. </el-input>
  284. <el-button
  285. v-else
  286. class="button-new-tag"
  287. size="small"
  288. @click="showSelfCategoryInput"
  289. >+ New Tag</el-button
  290. >
  291. </span>
  292. </div>
  293.  
  294. <el-input
  295. placeholder="新建收藏列表"
  296. v-model="inputListName"
  297. class="input-with-select"
  298. :style="{
  299. 'margin-top': '10px',
  300. 'margin-button': '10px',
  301. }"
  302. >
  303. <el-button
  304. slot="append"
  305. @click="addCollectionList"
  306. size="small"
  307. type="primary"
  308. icon="el-icon-plus"
  309. ></el-button>
  310. </el-input>
  311.  
  312. <div class="collectionList">
  313. <el-row>
  314. <el-col
  315. :span="24"
  316. v-for="(item, index) in collectionList"
  317. :key="index"
  318. :style="{
  319. 'display':'flex',
  320. 'justify-content':'space-between',
  321. 'margin-top':'10px'
  322. }"
  323. >
  324. <el-checkbox
  325. v-model="item.checked"
  326. @change="changeChecked(index, item)"
  327. >{{ item.list_name }}</el-checkbox
  328. >
  329. <i
  330. class="el-icon-delete"
  331. @click="deleteCollectionList(item)"
  332. :style="{
  333. cursor:'pointer'
  334. }"
  335. ></i>
  336. </el-col>
  337. </el-row>
  338. </div>
  339. <span slot="footer" class="dialog-footer">
  340. <el-button @click="collectionListVisible = false"
  341. >取 消</el-button
  342. >
  343. <el-button
  344. type="primary"
  345. @click="collectionListVisible = false"
  346. >确 定</el-button
  347. >
  348. </span>
  349. </el-dialog>
  350. </div>`
  351.  
  352. $('#root > div:nth-child(2) > div.sc-12xjnzy-0.dIGjtZ > div:nth-child(1) > div:nth-child(1) > div > div.sc-4nj1pr-2.fDmigA').append(viewCollection)
  353. $('#root > div:nth-child(2) > div.sc-1nr368f-0.beQeCv > div > div.sc-1nr368f-3.dkdRNk > main > section > div.sc-171jvz-0.gcrJTU > div > div.sc-rsntqo-0.fPeueM > div > div.sc-ye57th-1.lbzRfC > section').append(addCollection)
  354. new Vue({
  355. el: '#viewCollection',
  356. data: () => ({
  357. collectionVisible: false,
  358. collectionList: [],
  359. collectionListContent: []
  360. }),
  361. created () {
  362. this.db = new Database()
  363. },
  364. methods: {
  365. showCollection () {
  366. this.collectionVisible = true
  367. this.getCollectionList()
  368. },
  369. async getCollectionList () {
  370. const collectionList = await this.db.getCollectionList()
  371. this.collectionList = collectionList
  372. },
  373. async getCollectionListContent (item) {
  374. const collection_list_id = item.id
  375.  
  376. const collectionListContent = await this.db.getCollectionListContent(collection_list_id)
  377. this.collectionListContent = collectionListContent
  378. console.log('collectionListContent', collectionListContent)
  379. },
  380. // 导出
  381. async exportJson () {
  382. const curDate = moment().format('YYYY_MM_hh_mm_ss');
  383. const jsonObj = await this.db.getAllData()
  384. const data = JSON.stringify(jsonObj)
  385. let blob = new Blob([data], { type: 'text/json' })
  386. let a = document.createElement('a')
  387. a.download = `pixiv收藏数据${curDate}.json`
  388. a.href = window.URL.createObjectURL(blob)
  389. document.body.appendChild(a);
  390. a.click()
  391. document.body.removeChild(a);
  392. },
  393. //导入
  394. importJson () {
  395. const input = document.querySelector('#importJson')
  396. input.click()
  397. },
  398. handleImport () {
  399. const that = this
  400. const selectedFile = document.querySelector('#importJson').files[0]
  401. const reader = new FileReader()
  402. reader.readAsText(selectedFile)
  403. let JsonObj = {}
  404. reader.onload = function () {
  405. JsonObj = JSON.parse(this.result)
  406. that.db.importAllData(JsonObj)
  407. }
  408.  
  409. }
  410. }
  411. })
  412.  
  413. new Vue({
  414. el: '#addCollection',
  415. data: () => ({
  416. collectionListVisible: false,
  417. inputListName: '',
  418. defaultChecked: false,
  419. collectionList: [],
  420. db: {},
  421. url: window.location.href,
  422. title: '',
  423. tag: [],
  424. selfCategory: '',
  425. custom_category: [],
  426. customCategoryInputVisible: false,
  427. production: {},
  428. previewUrl: ''
  429. }),
  430. created () {
  431. this.db = new Database()
  432. },
  433. mounted () {
  434. this.getTitle()
  435. this.getTag()
  436. this.initData()
  437. this.getPreviewUrl()
  438. },
  439. methods: {
  440. initData () {
  441. this.getProduction()
  442. this.getCollectionList()
  443. this.getCurCollection()
  444. },
  445. showCollectionList () {
  446. this.collectionListVisible = true
  447. this.initData()
  448. },
  449. getTitle () {
  450. const title = document.querySelector("#root > div:nth-child(2) > div.sc-1nr368f-0.beQeCv > div > div.sc-1nr368f-3.dkdRNk > main > section > div.sc-171jvz-0.gcrJTU > div > figcaption > div > div > h1").innerText
  451. this.title = title
  452. console.log(this.title);
  453. },
  454. getTag () {
  455. const tagList = []
  456. const liList = document.querySelectorAll('.sc-pj1a4x-0 .sc-h7k48h-0.lhUcKZ')
  457. liList.forEach(item => {
  458. originTag = item.querySelector('.gtm-new-work-tag-event-click').innerText
  459. translatedTag = item.querySelector('.gtm-new-work-translate-tag-event-click') ? item.querySelector('.gtm-new-work-translate-tag-event-click').innerText : ''
  460. const obj = {
  461. originTag,
  462. translatedTag
  463. }
  464. tagList.push(obj)
  465.  
  466. })
  467. this.tag = tagList
  468. console.log(this.tag);
  469.  
  470. },
  471. getCustomCategory () {
  472. console.log("this.production", this.production);
  473. this.custom_category = this.production ? this.production.custom_category : []
  474. },
  475. getPreviewUrl () {
  476. this.previewUrl = document.querySelector("a.gtm-expand-full-size-illust").href
  477. },
  478. async putCustomCategory () {
  479. console.log('custom_category', this.custom_category);
  480. await this.db.putCustomCategory(window.location.href, this.custom_category)
  481. },
  482. showSelfCategoryInput () {
  483. this.customCategoryInputVisible = true
  484. },
  485. addSelfCategory () {
  486. const categoryList = this.selfCategory.split("|")
  487. let originTag = ''
  488. let translatedTag = ''
  489. if (categoryList.length > 1) {
  490. originTag = categoryList[0]
  491. translatedTag = categoryList[1]
  492. } else if (categoryList.length == 1) {
  493. originTag = categoryList[0]
  494. translatedTag = ''
  495. }
  496. const obj = {
  497. originTag,
  498. translatedTag
  499. }
  500. this.custom_category.push(obj)
  501. this.custom_category = Array.from(new Set(this.custom_category))
  502. this.putCustomCategory()
  503. this.customCategoryInputVisible = false
  504. this.initData()
  505.  
  506. },
  507.  
  508. deleteCustomCategory (tag) {
  509. this.custom_category.splice(this.custom_category.indexOf(tag), 1);
  510. this.putCustomCategory()
  511. this.initData()
  512. },
  513.  
  514. changeChecked (index, item) {
  515. if (item.checked == true) {
  516. this.addToCollection(item)
  517. } else {
  518. this.deleteCollection(item.id, window.location.href)
  519. }
  520. },
  521. async getProduction () {
  522. this.production = await this.db.getProduction(window.location.href)
  523. this.getCustomCategory()
  524. this.production = this.production || {}
  525. },
  526. async putProduction () {
  527.  
  528. const data = {
  529. id: this.production.id,
  530. title: this.title,
  531. url: window.location.href,
  532. tag: this.tag,
  533. custom_category: this.custom_category,
  534. preview_url: this.previewUrl
  535. }
  536. console.log('putProduction', data);
  537. await this.db.putProduction(data)
  538. },
  539.  
  540. async addToCollection (item) {
  541. const data = {
  542. title: this.title,
  543. url: window.location.href,
  544. tag: this.tag,
  545. custom_category: this.custom_category,
  546. collection_list: item.list_name,
  547. collection_list_id: item.id,
  548. preview_url: this.previewUrl
  549. }
  550. await this.db.addToCollection(data);
  551. this.putProduction()
  552. this.getCurCollection()
  553. },
  554. async deleteCollection (id, url) {
  555. await this.db.deleteCollection(id, url)
  556. this.getCurCollection()
  557. },
  558. // 获取当前作品相关的收藏记录
  559. async getCurCollection () {
  560. const url = window.location.href
  561. const curCollection = await this.db.getCurCollection(url)
  562. this.curCollection = curCollection
  563. this.handleCurChecked()
  564. },
  565. async getCollectionList () {
  566. const collectionList = await this.db.getCollectionList()
  567. this.collectionList = collectionList
  568. },
  569. // 添加列表
  570. async addCollectionList () {
  571. const listName = this.inputListName
  572. const data = {
  573. list_name: listName
  574. }
  575. await this.db.addCollectionList(data)
  576. this.getCollectionList()
  577. },
  578. //删除列表
  579. async deleteCollectionList (item) {
  580. console.log('deleteCollectionList', item.id);
  581. this.db.deleteCollectionList(item.id)
  582. this.initData()
  583. this.getCurCollection()
  584. },
  585. // 处理收藏列表的显示效果
  586. handleCurChecked () {
  587. const arr = this.collectionList.map(item1 => {
  588. this.curCollection.forEach(item2 => {
  589. if (item1.id == item2.collection_list_id) {
  590. item1.checked = true
  591. }
  592. })
  593. return item1
  594. })
  595. console.log(arr);
  596. this.collectionList = arr
  597. },
  598.  
  599. }
  600. })
  601. }