├── .gitignore
├── content-script.js
├── img
└── download.png
├── interceptor.js
├── manifest.json
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/content-script.js:
--------------------------------------------------------------------------------
1 | const insertScript = (pathOrCode, isPath = true) => {
2 | let newScript = window.document.createElement('script');
3 | if (isPath) newScript.src = chrome.extension.getURL(pathOrCode);
4 | if (!isPath) newScript.innerText = pathOrCode;
5 | newScript.onload = function () {
6 | window.document.body.removeChild(newScript);
7 | };
8 | let doc = window.document.body || window.document.head || window.document.documentElement;
9 | doc.appendChild(newScript);
10 | }
11 |
12 | insertScript('./interceptor.js');
13 |
14 | (() => {
15 | const downloadIconUrl = chrome.extension.getURL("img/download.png");
16 | let interval = setInterval(() => {
17 | const controlsContainer = document.querySelector('.miomc0xe.pmk7jnqg.cgat1ltu.n7fi1qx3.j83agx80');
18 | if (!controlsContainer) return;
19 | if (controlsContainer.querySelector('.injected-controls')) return;
20 | const button = document.createElement('div');
21 | button.className = 'tojvnm2t a6sixzi8 k5wvi7nf q3lfd5jv pk4s997a bipmatt0 cebpdrjk qowsmv63 owwhemhu dp1hu0rb dhp61c6y l9j0dhe7 iyyx5f41 a8s20v7p injected-controls';
22 | button.innerHTML = `
`;
27 | button.onclick = () => {
28 | const e = new CustomEvent('request-download-story', {});
29 | window.dispatchEvent(e);
30 | };
31 | controlsContainer.prepend(button);
32 | }, 100);
33 | })()
34 |
--------------------------------------------------------------------------------
/img/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monokaijs/fb-story-downloader/1f3e8883acc3f4cdfa63ddf3a3ea92a668f17073/img/download.png
--------------------------------------------------------------------------------
/interceptor.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | console.log("Interceptor loaded...");
3 | const serialize = function(obj, prefix) {
4 | let str = [],
5 | p;
6 | for (p in obj) {
7 | if (obj.hasOwnProperty(p)) {
8 | let k = prefix ? prefix + "[" + p + "]" : p,
9 | v = obj[p];
10 | str.push((v !== null && typeof v === "object") ?
11 | serialize(v, k) :
12 | encodeURIComponent(k) + "=" + encodeURIComponent(v));
13 | }
14 | }
15 | return str.join("&");
16 | }
17 | const getQueryValue = (name, query) => {
18 | let raw = query.includes('?') ? query.split('?')[1] : query;
19 | let parts = raw.split('&');
20 | let parsedObject = {};
21 | parts.forEach(part => {
22 | if (part.includes('=')) parsedObject[part.split('=')[0]] = part.split('=')[1];
23 | });
24 | // console.log(parsedObject);
25 | return parsedObject[name];
26 | }
27 |
28 | let lastStorySeenRequestData = null;
29 |
30 | (function (xhr) {
31 | let
32 | proto = xhr.prototype,
33 | _send = proto.send,
34 | _open = proto.open;
35 |
36 | // overload open() to access url and request method
37 | proto.open = function () {
38 | // store type and url to use in other methods
39 | this._method = arguments[0];
40 | this._url = arguments[1];
41 | if (this._method.toLowerCase() === 'post' && this._url === '/api/graphql/') {
42 | this._isGraphQL = true;
43 | }
44 | _open.apply(this, arguments);
45 | };
46 |
47 | proto.send = function () {
48 | if (this._isGraphQL) {
49 | const friendlyName = getQueryValue('fb_api_req_friendly_name', arguments[0]);
50 | if (friendlyName === 'storiesUpdateSeenStateMutation') lastStorySeenRequestData = arguments[0];
51 | }
52 | _send.apply(this, arguments);
53 | };
54 | })(XMLHttpRequest);
55 |
56 | window.addEventListener('request-download-story', e => {
57 | const variables = getQueryValue('variables', lastStorySeenRequestData);
58 | if (variables) {
59 | const object = JSON.parse(decodeURIComponent(variables));
60 | const bucketId = object.input['bucket_id'];
61 | const storyId = object.input['story_id'];
62 | fetch('https://www.facebook.com/api/graphql/', {
63 | method: 'POST',
64 | headers: {
65 | 'content-type': 'application/x-www-form-urlencoded'
66 | },
67 | body: serialize({
68 | doc_id: '2913003758722672',
69 | variables: JSON.stringify({
70 | 'bucketIDs': [bucketId],
71 | 'scale': 1,
72 | 'prefetchPhotoUri': false,
73 | }),
74 | fb_dtsg: require('DTSGInitialData').token,
75 | server_timestamps: true,
76 | }),
77 | }).then(r => r.json()).then(response => {
78 | if (response && response.data && response.data.nodes && response.data.nodes[0]) {
79 | const bucket = response.data.nodes[0];
80 | const stories = bucket['unified_stories']['edges'];
81 | const storyToDownload = stories.find(story => {
82 | return story.node['id'] === storyId
83 | });
84 | if (storyToDownload) {
85 | const story = storyToDownload.node;
86 | const attachments = story['attachments'];
87 | attachments.forEach(attachment => {
88 | let url;
89 | if (attachment.media['__typename'] === "Photo") {
90 | url = attachment.media['image']['uri'];
91 | } else if (attachment.media['__typename'] === "Video") {
92 | url = attachment.media['playable_url_quality_hd'] || attachment.media['playable_url'];
93 | }
94 | const anchor = document.createElement('a');
95 | anchor.href = url;
96 | anchor.download = url.split('#').shift().split('?').shift().split('/').pop();
97 | anchor.target = "_blank";
98 | document.body.appendChild(anchor);
99 | anchor.click();
100 | document.body.removeChild(anchor);
101 | });
102 | } else {
103 | console.log("Failed to find your wanted story.")
104 | }
105 | }
106 | });
107 | }
108 | });
109 |
110 | })();
111 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FB Story Downloader",
3 | "version": "1.0",
4 | "description": "Download your story on ease.",
5 | "content_scripts": [
6 | {
7 | "matches": [
8 | "https://*.facebook.com/*"
9 | ],
10 | "js": [
11 | "content-script.js"
12 | ]
13 | }
14 | ],
15 | "web_accessible_resources": [
16 | "img/*.png",
17 | "interceptor.js"
18 | ],
19 | "permissions": [
20 | "webRequest"
21 | ],
22 | "manifest_version": 2
23 | }
24 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Story Downloader for FB Users
2 |
3 | This extension helps you easily download any Facebook Story.
4 |
5 | ---
6 |
7 |
--------------------------------------------------------------------------------