├── LICENSE
├── README.md
└── fbtoolkit.user.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Chris
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Facebook Toolkit
2 | JavaScript Userscript for Facebook automation.
3 |
4 | 
5 |
6 | ## Usage
7 |
8 | I recommend to use Google Chrome browser for this UserScript. Install browser addon Greasemonkey or Tampermonkey. After installation, open [fbtoolkit.user.js script file](https://github.com/RootDev4/Facebook-Toolkit/blob/master/fbtoolkit.user.js) and click the **Raw** button. The user script gets installed automatically by Greasemonkey/Tampermonkey addon. Facebook Toolkit is now accessible from the bluebar. Visit any Facebook user profile to test.
9 |
10 | ## Troubleshooting
11 |
12 | If Facebook Toolkit isn't present in the bluebar or doesn't respond, please reload the page manually by pressing Shift+F5. This will reload the whole page from the server and not from your browser's cache.
13 |
14 | Due to Facebook's regular modifications of HTML structure / DOM, some element selectors may not work correctly (especially for expanding user's timeline). If so, please visit my GitHub profile and leave me a hint over email. Thanks in advance!
15 |
16 | ## Functionality
17 |
18 | Scrolling/extracting actions are confirmed by a popup message when finished. So please be patient, if the script will run a little longer...
19 |
20 | ### Get numeric user ID
21 | Return the numeric Facebook user ID in a popup window.
22 |
23 | ### Show user ID on cover
24 | Display the numeric Facebook user ID inside the timeline cover right below the username.
25 |
26 | ### Scroll user timeline
27 | Scroll the user's timeline to the very beginning automatically.
28 |
29 | ### Expand hidden content
30 | Expand hidden content like "See more" text or covered comments.
31 |
32 | ### Clear timeline
33 | Remove boxes and buttons with unwanted content for printing the user's profile.
34 |
35 | ### Extract user's friendlist
36 | Extract all visible friends or followers and print out as CSV formatted string. After you had copied the output, reload the page manually to get back to Facebook.
37 |
38 | ### Download user's photos
39 | Download all uploaded photos. After scraping user's photos, please save page manually to your computer (Ctrl+S) to download all photos. After you had downloaded the page, reload the page manually to get back to Facebook.
40 |
41 | ### Jump to page bottom / Jump to page top
42 | Self-explaining.
43 |
44 | ### Force page reload
45 | Reload the current page from the server (not from the cache).
--------------------------------------------------------------------------------
/fbtoolkit.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Facebook Toolkit
3 | // @namespace https://github.com/RootDev4/Facebook-Toolkit
4 | // @version 0.3
5 | // @description JavaScript Userscript for Facebook automation
6 | // @author RootDev4 (Chris)
7 | // @match https://www.facebook.com/*
8 | // @grant none
9 | // @run-at document-idle
10 | // ==/UserScript==
11 |
12 | // Facebook loading image
13 | const fbLoaderImg = 'https://static.xx.fbcdn.net/rsrc.php/v3/yb/r/GsNJNwuI-UM.gif'
14 |
15 | /**
16 | * Show/hide Facebook image loader next to toolkit menu item
17 | */
18 | function toggleLoaderImg() {
19 | try {
20 | const img = document.querySelector('img#fbToolkitImg')
21 | if (img.classList.contains('hidden_elem')) {
22 | img.classList.remove('hidden_elem')
23 | } else {
24 | img.classList.add('hidden_elem')
25 | }
26 | } catch (exception) {
27 | console.error(exception)
28 | }
29 | }
30 |
31 | /**
32 | * Get numeric Facebook user ID
33 | */
34 | function getUserId() {
35 | try {
36 | const pagelet = document.getElementById('pagelet_timeline_main_column')
37 | return JSON.parse(pagelet.getAttribute('data-gt')).profile_owner
38 | } catch (exception) {
39 | console.error(exception)
40 | alert('Getting Facebook user ID failed.\nSee console log for details.')
41 | }
42 | return 0
43 | }
44 |
45 | /**
46 | * Show numeric Facebook user ID on timeline cover
47 | */
48 | function showUserId() {
49 | try {
50 | const userIdNode = document.createElement('span')
51 | userIdNode.innerHTML = `Facebook ID: ${getUserId()}`
52 | document.getElementById('fb-timeline-cover-name').parentNode.parentNode.append(userIdNode)
53 | } catch (exception) {
54 | console.error(exception)
55 | alert('Showing Facebook user ID on timeline cover failed.\nSee console log for details.')
56 | }
57 | }
58 |
59 | /**
60 | * Get user's name
61 | */
62 | function getUsername() {
63 | try {
64 | return document.querySelector('span#fb-timeline-cover-name a').innerText
65 | } catch (exception) {
66 | console.error(exception)
67 | }
68 |
69 | return null
70 | }
71 |
72 | /**
73 | * Extract Facebook vanity username out of user's profile URL
74 | * @param {*} userUrl User's profile URL
75 | */
76 | function getVanityName(userUrl) {
77 | try {
78 | const name = /facebook.com\/(.*?)\?/g.exec(userUrl)[1] || null
79 | if (name !== null && !name.contains('profile.php')) return name
80 | } catch (exception) {
81 | console.error(exception)
82 | }
83 |
84 | return null
85 | }
86 |
87 | /**
88 | * Scroll user timeline
89 | */
90 | function scrollTimeline() {
91 | try {
92 | if (document.getElementById('timeline_tab_content')) {
93 | toggleLoaderImg()
94 |
95 | let task = setInterval(() => {
96 | window.scrollBy(0, document.body.scrollHeight)
97 |
98 | if (document.querySelector('div[id^="timeline_pager_container_"] div i.img')) {
99 | clearInterval(task)
100 | window.scrollBy(0, document.body.scrollHeight)
101 | window.scrollTo(0, 0)
102 |
103 | toggleLoaderImg()
104 | alert('Auto scrolling finished')
105 | }
106 | }, 100)
107 | } else {
108 | throw 'Scrolling timeline failed. Cannot find selectors.'
109 | }
110 | } catch (exception) {
111 | console.error(exception)
112 | alert('Please open the timeline of this user.\nSee console log for details.')
113 | }
114 | }
115 |
116 | /**
117 | * Expand hidden content like comments etc.
118 | */
119 | function expandTimeline() {
120 | try {
121 | if (document.getElementById('timeline_tab_content')) {
122 | toggleLoaderImg()
123 |
124 | let expand = setInterval(() => {
125 | document.querySelectorAll('a._4sxc._42ft, a._5v47.fss, a.see_more_link').forEach(node => node.click())
126 |
127 | if (!document.querySelectorAll('a._4sxc._42ft').length) {
128 | clearInterval(expand)
129 | window.scrollTo(0, 0)
130 |
131 | toggleLoaderImg()
132 | alert('All content expanded')
133 | }
134 | }, 100)
135 | } else {
136 | throw 'Expanding hidden content on user\'s timeline failed. Cannot find selectors.'
137 | }
138 | } catch (exception) {
139 | console.error(exception)
140 | alert('Expanding hidden content failed.\nSee console log for details.')
141 | }
142 | }
143 |
144 | /**
145 | * Scrape friends/followers of an user
146 | */
147 | function friendScraper() {
148 | return new Promise((resolve, reject) => {
149 | try {
150 | let scrollContent = setInterval(() => {
151 | window.scrollBy(0, document.body.scrollHeight)
152 |
153 | const followers = document.querySelector('div#pagelet_collections_followers') ? true : false
154 | const moreMedleys = document.querySelectorAll('div[id^="pagelet_timeline_medley"]:not(#pagelet_timeline_medley_friends)')
155 |
156 | if ((!followers && moreMedleys.length) || (followers && document.querySelector('div.morePager') === null)) {
157 | clearInterval(scrollContent)
158 | window.scrollBy(0, document.body.scrollHeight)
159 | window.scrollTo(0, 0)
160 |
161 | // Fetch friends/followers
162 | const collection = (followers) ? document.querySelector('div#pagelet_collections_followers') : document.querySelector('div#pagelet_timeline_medley_friends')
163 | const friends = (followers) ? collection.querySelectorAll('li.fbProfileBrowserListItem > div') : collection.querySelectorAll('div[data-testid="friend_list_item"]')
164 | let counter = 0
165 | let friendsList = []
166 |
167 | if (!friends.length) resolve({ type: null, list: friendsList })
168 |
169 | friends.forEach(friend => {
170 | const userData = friend.querySelector('a[data-hovercard^="/ajax/hovercard/"]')
171 | const userID = /\/ajax\/hovercard\/user.php\?id=(.*?)\&/g.exec(userData.getAttribute('data-hovercard'))[1] || ''
172 | const vanityName = (userData.href.includes('profile.php')) ? '' : /facebook.com\/(.*?)\?/g.exec(userData.href)[1] || ''
173 | const userName = userData.querySelector('img[aria-label]').getAttribute('aria-label') || ''
174 |
175 | friendsList.push({ id: userID, vanity: vanityName, name: userName })
176 | counter += 1
177 | const enumType = (followers) ? 'Followers' : 'Friends'
178 |
179 | if (counter >= friendsList.length) resolve({ listtype: enumType, list: friendsList })
180 | })
181 | }
182 | }, 100)
183 | } catch (exception) {
184 | reject(exception)
185 | }
186 | })
187 | }
188 |
189 | /**
190 | * Extract friend/follower list of an user
191 | */
192 | function extractFriends() {
193 | try {
194 | if (document.getElementById('medley_header_friends')) {
195 | toggleLoaderImg()
196 |
197 | friendScraper()
198 | .then(friends => {
199 | toggleLoaderImg()
200 | if (!friends.list.length) return alert('No visible friends to extract.')
201 |
202 | let csv = 'UserID,VanityName,UserName'
203 | friends.list.forEach(friend => csv += `
${friend.id},${friend.vanity},${friend.name}`)
204 | document.write(csv)
205 |
206 | console.log(`Extracted ${friends.list.length} friends.`)
207 | })
208 | .catch(error => {
209 | throw error
210 | })
211 | } else {
212 | throw 'Friendlist extraction failed. Cannot find selectors.'
213 | }
214 | } catch (exception) {
215 | console.error(exception)
216 | alert('Please open the friends section of this user.\nSee console log for details.')
217 | }
218 | }
219 |
220 | /**
221 | * Scrape photos of an user
222 | */
223 | function photoScraper() {
224 | return new Promise((resolve, reject) => {
225 | try {
226 | let scrollContent = setInterval(() => {
227 | window.scrollBy(0, document.body.scrollHeight)
228 |
229 | if (document.querySelectorAll('div[id^="pagelet_timeline_medley"]:not(#pagelet_timeline_medley_photos').length) {
230 | clearInterval(scrollContent)
231 | window.scrollBy(0, document.body.scrollHeight)
232 | window.scrollTo(0, 0)
233 |
234 | // Fetch URL of all photos
235 | const collection = document.querySelector('div#pagelet_timeline_medley_photos')
236 | const photos = collection.querySelectorAll('li.fbPhotoStarGridElement')
237 | let counter = 0
238 | let album = []
239 |
240 | if (!photos.length) resolve(album)
241 |
242 | photos.forEach(photo => {
243 | album.push(photo.getAttribute('data-starred-src'))
244 | counter += 1
245 |
246 | if (counter >= photos.length) resolve(album)
247 | })
248 | }
249 | }, 100)
250 | } catch (exception) {
251 | reject(exception)
252 | }
253 | })
254 |
255 | }
256 |
257 | /**
258 | * Expand hidden content like comments etc.
259 | */
260 | function downloadPhotos() {
261 | try {
262 | if (document.getElementById('pagelet_timeline_medley_photos')) {
263 | toggleLoaderImg()
264 |
265 | photoScraper()
266 | .then(photos => {
267 | toggleLoaderImg()
268 | if (!photos.length) return alert('No visible photos to download.')
269 |
270 | let album = `