├── 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 | ![Facebook Toolkit](https://www1.xup.in/exec/ximg.php?fid=14551942) 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 = `

Photos of ${getUsername()}

` 271 | album += `

Please save website to your computer (Ctrl+S) to download all photos.

` 272 | photos.forEach(photo => album += ``) 273 | document.write(album) 274 | }) 275 | .catch(error => { 276 | throw error 277 | }) 278 | } else { 279 | throw 'Photo extraction failed. Cannot find selectors.' 280 | } 281 | } catch (exception) { 282 | console.error(exception) 283 | alert('Please open the photos section of this user.\nSee console log for details.') 284 | } 285 | } 286 | 287 | /** 288 | * Hide an element from DOM 289 | * @param {*} cssSelector DOM node identified by CSS selector 290 | */ 291 | function hide(cssSelector) { 292 | try { 293 | let selector = cssSelector 294 | let pNode = false 295 | 296 | if (selector.includes(':parent')) { 297 | pNode = true 298 | selector = selector.replace(':parent', '') 299 | } 300 | 301 | document.querySelectorAll(selector).forEach(element => { 302 | if (element != null) { 303 | if (pNode) element = element.parentNode 304 | element.style.display = 'none' 305 | } 306 | }) 307 | } catch (exception) { 308 | console.error(exception) 309 | } 310 | } 311 | 312 | /** 313 | * Clear/anonymize timeline 314 | */ 315 | function clearTimeline() { 316 | hide('form.commentable_item div._1dnh') // Like/Share buttons 317 | hide('form.commentable_item div:last-child > div.clearfix') // Comment input field 318 | hide('button.PageLikeButton:parent') // "Like Page" button 319 | hide('a[data-testid="post_chevron_button"]:parent') // Post options menu 320 | hide('div#pagelet_bluebar') // Facebook top bluebar 321 | hide('div#pagelet_timeline_profile_actions') 322 | hide('div#profile_timeline_overview_switcher_pagelet') 323 | hide('div#timeline_sticky_header_container') 324 | hide('ul[data-referrer="timeline_light_nav_top"]') 325 | hide('div#pagelet_escape_hatch') // Follow profile 326 | hide('a[href="/subscriptions/suggestions/"]') // "Find People to Follow" link 327 | hide('a[ajaxify*="/ajax/follow/follow_profile.php"]') // Follow button in followers list 328 | hide('a[href="/find-friends/browser/"]:parent') // "Friend Requests / Find Friends" buttons 329 | hide('button[aria-label="Manage"]:parent') // Manage sections button 330 | hide('div#pagelet_pymk_timeline') 331 | hide('div#pagelet_timeline_composer') 332 | // Add more elements here... 333 | } 334 | 335 | /** 336 | * Insert toolkit menu item and flyout into Facebook bluebar 337 | */ 338 | function init() { 339 | return new Promise((resolve, reject) => { 340 | try { 341 | const menuItem = document.querySelector('div[role="navigation"] > div') 342 | const newItem = document.createElement('div') 343 | 344 | newItem.classList.add('_4kny', '_2s24') 345 | newItem.innerHTML = ` 346 |
347 | 348 | 349 | Toolkit 350 | 351 | 369 |
` 370 | 371 | resolve(menuItem.appendChild(newItem)) 372 | } catch (exception) { 373 | reject(exception) 374 | } 375 | }) 376 | } 377 | 378 | /** 379 | * Run Facebook toolkit and add click listeners after initialization 380 | */ 381 | window.onload = () => { 382 | try { 383 | if (document.getElementById('pagelet_timeline_main_column')) { 384 | init() 385 | .then(() => { 386 | // Close flyout menu on menu item click 387 | const flyout = document.querySelector('a[data-target="fbToolkitFlyout"]') 388 | const menuItems = document.querySelectorAll('a[id^="fbToolkit"]') 389 | menuItems.forEach(item => item.addEventListener('click', () => flyout.click())) 390 | 391 | // Add click event listener to every menu item 392 | document.querySelector('a#fbToolkitUserId').addEventListener('click', () => alert(getUserId())) 393 | document.querySelector('a#fbToolkitIdCover').addEventListener('click', () => showUserId()) 394 | document.querySelector('a#fbToolkitScroll').addEventListener('click', () => scrollTimeline()) 395 | document.querySelector('a#fbToolkitExpand').addEventListener('click', () => expandTimeline()) 396 | document.querySelector('a#fbToolkitFriends').addEventListener('click', () => extractFriends()) 397 | document.querySelector('a#fbToolkitPhotos').addEventListener('click', () => downloadPhotos()) 398 | document.querySelector('a#fbToolkitClear').addEventListener('click', () => clearTimeline()) 399 | }).catch(exception => { 400 | throw exception 401 | }) 402 | } 403 | } catch (exception) { 404 | console.error(exception) 405 | alert('Facebook Toolkit failed, please reload page.\nSee console log for details.') 406 | } 407 | } 408 | --------------------------------------------------------------------------------