├── screen.png
├── error.svg
├── README.md
└── Pinterest.com_Backup_Original_Files.user.js
/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cvzi/pinterest-Backup-Original-Files/HEAD/screen.png
--------------------------------------------------------------------------------
/error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pinterest-Backup-Original-Files
2 |
3 | [**Click here for install**](https://github.com/cvzi/pinterest-Backup-Original-Files/raw/master/Pinterest.com_Backup_Original_Files.user.js)
4 |
5 | This is a [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) script.
6 | You can find information about Greasemonkey scripts over at [wiki.greasespot.net](https://wiki.greasespot.net/Greasemonkey_Manual:Installing_Scripts).
7 |
8 | The scripts lets you download all original images from your Pinterest.com profile.
9 |
10 | It adds a red download button at the top of your boards. The script also creates an entry in the Greasemonkey menu.
11 |
12 | Simply go to one of your boards, scroll down to the last image and click the download button or option in the menu.
13 |
14 | The images will be downloaded and packed into a single Zip file file for you.
15 |
16 | [](https://saythanks.io/to/cvzi)
17 |
18 |
19 | 
20 |
21 | Works with Firefox and Chrome (via [Tampermonkey](http://tampermonkey.net/))
22 |
23 | The Zip file contains all original photos. If there were errors, the thumbnails of the missing photos will be added to a special folder "error_thumbnails" in the Zip file.
24 |
--------------------------------------------------------------------------------
/Pinterest.com_Backup_Original_Files.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Pinterest.com Backup Original Files
3 | // @description Download all original images from your Pinterest.com profile. Creates an entry in the Greasemonkey menu, just go to one of your boards, scroll down to the last image and click the option in the menu.
4 | // @namespace cuzi
5 | // @license MIT
6 | // @version 19.0.4
7 | // @match https://*.pinterest.com/*
8 | // @match https://*.pinterest.at/*
9 | // @match https://*.pinterest.ca/*
10 | // @match https://*.pinterest.ch/*
11 | // @match https://*.pinterest.cl/*
12 | // @match https://*.pinterest.co.kr/*
13 | // @match https://*.pinterest.co.uk/*
14 | // @match https://*.pinterest.com.au/*
15 | // @match https://*.pinterest.com.mx/*
16 | // @match https://*.pinterest.de/*
17 | // @match https://*.pinterest.dk/*
18 | // @match https://*.pinterest.es/*
19 | // @match https://*.pinterest.fr/*
20 | // @match https://*.pinterest.ie/*
21 | // @match https://*.pinterest.info/*
22 | // @match https://*.pinterest.it/*
23 | // @match https://*.pinterest.jp/*
24 | // @match https://*.pinterest.net/*
25 | // @match https://*.pinterest.nz/*
26 | // @match https://*.pinterest.ph/*
27 | // @match https://*.pinterest.pt/*
28 | // @match https://*.pinterest.ru/*
29 | // @match https://*.pinterest.se/*
30 | // @grant GM_xmlhttpRequest
31 | // @grant GM_registerMenuCommand
32 | // @grant GM.xmlHttpRequest
33 | // @grant GM.registerMenuCommand
34 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
35 | // @require https://cdn.jsdelivr.net/npm/jszip@3.9.1/dist/jszip.min.js
36 | // @require https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js
37 | // @connect pinterest.com
38 | // @connect pinterest.de
39 | // @connect pinimg.com
40 | // @icon https://s.pinimg.com/webapp/logo_trans_144x144-5e37c0c6.png
41 | // ==/UserScript==
42 |
43 | /* globals JSZip, saveAs, GM, MouseEvent */
44 |
45 | // Time to wait between every scroll to the bottom (in milliseconds)
46 | const scrollPause = 1000
47 |
48 | let scrollIV = null
49 | let lastScrollY = null
50 | let noChangesFor = 0
51 | let lastImageListLength = -1
52 | let noImageListLengthChangesFor = 0
53 |
54 | function prepareForDownloading () {
55 | if (scrollIV !== null) {
56 | return
57 | }
58 |
59 | document.scrollingElement.scrollTo(0, 0)
60 | collectActive = true
61 | scrollIV = true
62 | collectImages()
63 |
64 | if (!window.confirm('The script needs to scroll down to the end of the page. It will start downloading once the end is reached.\n\nOnly images that are already visible can be downloaded.\n\n\u2757 Keep this tab open (visible) \u2757')) {
65 | return
66 | }
67 |
68 | const div = document.querySelector('.downloadoriginal123button')
69 | div.style.position = 'fixed'
70 | div.style.top = '30%'
71 | div.style.zIndex = 100
72 | div.innerHTML = 'Collecting images... (keep this tab visible)
'
73 |
74 | const startDownloadButton = div.appendChild(document.createElement('button'))
75 | startDownloadButton.appendChild(document.createTextNode('Stop scrolling & start downloading'))
76 | startDownloadButton.addEventListener('click', function () {
77 | window.clearInterval(scrollIV)
78 | downloadOriginals()
79 | })
80 |
81 | const statusImageCollector = div.appendChild(document.createElement('div'))
82 | statusImageCollector.setAttribute('id', 'statusImageCollector')
83 |
84 | document.scrollingElement.scrollTo(0, document.scrollingElement.scrollHeight)
85 |
86 | window.setTimeout(function () {
87 | scrollIV = window.setInterval(scrollDown, scrollPause)
88 | }, 1000)
89 | }
90 |
91 | function scrollDown () {
92 | if (document.hidden) {
93 | // Tab is hidden, don't do anyhting
94 | return
95 | }
96 | if (noChangesFor > 2) {
97 | console.log('noChangesFor > 2')
98 | window.clearInterval(scrollIV)
99 | window.setTimeout(downloadOriginals, 1000)
100 | } else {
101 | console.log('noChangesFor <= 2')
102 | document.scrollingElement.scrollTo(0, document.scrollingElement.scrollTop + 500)
103 | if (document.scrollingElement.scrollTop === lastScrollY) {
104 | noChangesFor++
105 | console.log('noChangesFor++')
106 | } else {
107 | noChangesFor = 0
108 | console.log('noChangesFor = 0')
109 | }
110 | if (entryList.length !== lastImageListLength) {
111 | lastImageListLength = entryList.length
112 | noImageListLengthChangesFor = 0
113 | } else {
114 | console.log('noImageListLengthChangesFor =', noImageListLengthChangesFor)
115 | noImageListLengthChangesFor++
116 | if (noImageListLengthChangesFor > 5) {
117 | window.clearInterval(scrollIV)
118 | window.setTimeout(downloadOriginals, 1000)
119 | }
120 | }
121 | }
122 | lastScrollY = document.scrollingElement.scrollTop
123 | }
124 |
125 | let entryList = []
126 | let url = document.location.href
127 | let collectActive = false
128 | let boardName = ''
129 | let boardNameEscaped = ''
130 | let userName = ''
131 | let userNameEscaped = ''
132 | const startTime = new Date()
133 | const entryTemplate = {
134 | images: [],
135 | title: null,
136 | link: null,
137 | description: null,
138 | note: null,
139 | sourceLink: null
140 | }
141 |
142 | function collectImages () {
143 | if (!collectActive) return
144 | if (url !== document.location.href) {
145 | // Reset on new page
146 | url = document.location.href
147 | entryList = []
148 | }
149 |
150 | const masonry = document.querySelector('[data-test-id="board-feed"] .masonryContainer')
151 | if (!masonry) {
152 | return
153 | }
154 | const imgs = masonry.querySelectorAll('a[href^="/pin/"] img')
155 | for (let i = 0; i < imgs.length; i++) {
156 | if (imgs[i].clientWidth < 100) {
157 | // Skip small images, these are user profile photos
158 | continue
159 | }
160 | if (!('mouseOver' in imgs[i].dataset)) {
161 | // Fake mouse over to load source link
162 | const mouseOverEvent = new MouseEvent('mouseover', {
163 | bubbles: true,
164 | cancelable: true
165 | })
166 |
167 | imgs[i].dispatchEvent(mouseOverEvent)
168 | imgs[i].dataset.mouseOver = true
169 | }
170 |
171 | const entry = Object.assign({}, entryTemplate)
172 | entry.images = [imgs[i].src.replace(/\/\d+x\//, '/originals/'), imgs[i].src]
173 |
174 | if (imgs[i].alt) {
175 | entry.description = imgs[i].alt
176 | }
177 |
178 | const pinWrapper = parentQuery(imgs[i], '[data-test-id="pinWrapper"]') || parentQuery(imgs[i], '[role="listitem"]') || parentQuery(imgs[i], '[draggable="true"]')
179 | if (pinWrapper) {
180 | // find metadata
181 | const aText = Array.from(pinWrapper.querySelectorAll('a[href*="/pin/"]')).filter(a => a.firstChild.nodeType === a.TEXT_NODE)
182 | if (aText.length > 0 && aText[0]) {
183 | entry.title = aText[0].textContent.trim()
184 | entry.link = aText[0].href.toString()
185 | } else if (pinWrapper.querySelector('a[href*="/pin/"]')) {
186 | entry.link = pinWrapper.querySelector('a[href*="/pin/"]').href.toString()
187 | }
188 | const aNotes = Array.from(pinWrapper.querySelectorAll('a[href*="/pin/"]')).filter(a => a.querySelector('div[title]'))
189 | if (aNotes.length > 0 && aNotes[0]) {
190 | entry.note = aNotes[0].textContent.trim()
191 | }
192 |
193 | if (pinWrapper.querySelector('[data-test-id="pinrep-source-link"] a')) {
194 | entry.sourceLink = pinWrapper.querySelector('[data-test-id="pinrep-source-link"] a').href.toString()
195 | }
196 | }
197 |
198 | if (imgs[i].srcset) {
199 | // e.g. srcset="https://i-h2.pinimg.com/236x/15/87/ae/abcdefg1234.jpg 1x, https://i-h2.pinimg.com/474x/15/87/ae/abcdefg1234.jpg 2x, https://i-h2.pinimg.com/736x/15/87/ae/abcdefg1234.jpg 3x, https://i-h2.pinimg.com/originals/15/87/ae/abcdefg1234.png 4x"
200 |
201 | let goodUrl = false
202 | let quality = -1
203 | const srcset = imgs[i].srcset.split(', ')
204 | for (let j = 0; j < srcset.length; j++) {
205 | const pair = srcset[j].split(' ')
206 | const q = parseInt(pair[1].replace('x'))
207 | if (q > quality) {
208 | goodUrl = pair[0]
209 | quality = q
210 | }
211 | if (pair[0].indexOf('/originals/') !== -1) {
212 | break
213 | }
214 | }
215 | if (goodUrl && quality !== -1) {
216 | entry.images[0] = goodUrl
217 | }
218 | }
219 |
220 | let exists = false
221 | for (let j = 0; j < entryList.length; j++) {
222 | if (entryList[j].images[0] === entry.images[0] && entryList[j].images[1] === entry.images[1]) {
223 | exists = true
224 | entryList[j] = entry // replace with newer entry
225 | break
226 | }
227 | }
228 | if (!exists) {
229 | entryList.push(entry)
230 | console.debug(imgs[i].parentNode)
231 | console.debug(entry)
232 | }
233 | }
234 | const statusImageCollector = document.getElementById('statusImageCollector')
235 | if (statusImageCollector) {
236 | statusImageCollector.innerHTML = `Collected ${entryList.length} images`
237 | }
238 | }
239 |
240 | function addButton () {
241 | if (document.querySelector('.downloadoriginal123button')) {
242 | return
243 | }
244 |
245 | if (document.querySelector('[data-test-id="board-tools"],[data-test-id="board-header"]') && document.querySelectorAll('[data-test-id="board-feed"] a[href^="/pin/"] img').length) {
246 | const button = document.createElement('div')
247 | button.type = 'button'
248 | button.classList.add('downloadoriginal123button')
249 | button.setAttribute('style', `
250 | position: absolute;
251 | display: block;
252 | background: white;
253 | border: none;
254 | padding: 5px;
255 | text-align: center;
256 | cursor:pointer;
257 | `)
258 | button.innerHTML = `
259 |
| Title | 452 |Image | 453 |Source | 455 |Description | 456 |Notes | 457 |
|---|