├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── nodejs.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── index.html ├── jquery.instagramFeed.js ├── jquery.instagramFeed.min.js ├── package.json └── test.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-vendored -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.me/JavierSL'] 13 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Actions CI Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | pull_request: 9 | branches: 10 | - master 11 | - dev 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | node-version: [8.x, 10.x, 12.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - name: npm install, build, and test 27 | run: | 28 | npm install 29 | npm test 30 | env: 31 | CI: true 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please **first discuss the change you wish to make via issue**, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | ## Pull Request Process 7 | 8 | 1. Install all the dependencies using `npm install` 9 | 2. Make your changes and test them. Note that depending on your changes you may also have to modify existing tests or create new ones. 10 | 3. **Run the tests** using `npm run test` 11 | 4. **Build** the minified version using `npm run build` 12 | 5. **Update the docs** (index.html) with the changes. If it is a new feature ensure to include it in the first example. 13 | 6. **Increase the version numbers in every file** where the version number is present to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 14 | 15 | ## Optional 16 | 17 | Consider making your changes also in [InstagramFeed](https://github.com/jsanahuja/InstagramFeed). -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | babel: { 4 | options: { 5 | sourceMap: false, 6 | comments: false, 7 | sourceType: "unambiguous", 8 | presets: ['@babel/preset-env', 'minify'] 9 | }, 10 | dist: { 11 | files: { 12 | 'jquery.instagramFeed.min.js': ['jquery.instagramFeed.js'] 13 | } 14 | } 15 | }, 16 | qunit: { 17 | files: ['test.html'], 18 | options: { 19 | puppeteer: { 20 | ignoreDefaultArgs: true, 21 | args: [ 22 | "--headless", 23 | "--disable-web-security", 24 | "--allow-file-access-from-files" 25 | ] 26 | }, 27 | timeout: 10000 28 | }, 29 | } 30 | }); 31 | grunt.loadNpmTasks('grunt-contrib-qunit'); 32 | grunt.registerTask('test', 'qunit'); 33 | 34 | grunt.loadNpmTasks('grunt-babel'); 35 | grunt.registerTask('build', ['babel']); 36 | }; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Javier Sanahuja 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 | # ⚠️ This repository has been archived ⚠️ 2 | 3 | The focus of this repository was to provide an easy and ready to use plugin to display an Instagram Feed but since latest Instagram changes, this no longer makes sense. Please, move to an official API based plugin. 4 | 5 | If you feel this repo should not be archived, please reach out and let us know. Archiving can always be reversed if needed. 6 | 7 | # jquery.instagramFeed [![Build Status](https://travis-ci.com/jsanahuja/jquery.instagramFeed.svg?branch=master)](https://travis-ci.com/jsanahuja/jquery.instagramFeed) 8 | Instagram Feed without using the instagram API 9 | 10 | Try [InstagramFeed](https://github.com/jsanahuja/InstagramFeed), the same without jQuery. 11 | 12 | ## Documentation 13 | 14 | [Full documentation and examples here](https://www.sowecms.com/demos/jquery.instagramFeed/index.html "documentation") 15 | 16 | ## Contributing 17 | 18 | Read and follow the [CONTRIBUTING.md](./CONTRIBUTING.md) before sending any pull request. 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jquery.instagramFeed 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 86 | 87 | 88 | 89 | 90 | 138 | 139 | 140 |
141 |
142 |
143 |

Options

144 |
145 |
146 | 147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 167 | 168 | 169 | 173 | 174 | 175 | 176 | 177 | 178 | 182 | 183 | 184 | 185 | 186 | 187 | 191 | 192 | 193 | 194 | 195 | 196 | 200 | 201 | 202 | 203 | 204 | 205 | 209 | 210 | 211 | 212 | 213 | 214 | 217 | 218 | 219 | 220 | 221 | 222 | 225 | 226 | 227 | 228 | 229 | 230 | 233 | 234 | 235 | 236 | 237 | 238 | 241 | 242 | 243 | 244 | 245 | 246 | 249 | 250 | 251 | 252 | 253 | 254 | 257 | 258 | 259 | 260 | 261 | 262 | 265 | 266 | 267 | 268 | 269 | 270 | 273 | 274 | 275 | 276 | 277 | 278 | 281 | 282 | 283 | 284 | 285 | 286 | 289 | 290 | 291 | 292 | 293 | 294 | 297 | 298 | 299 | 300 | 301 | 302 | 305 | 306 | 307 | 308 | 309 | 310 | 313 | 314 | 315 | 319 | 320 | 321 | 324 | 325 | 326 | 327 | 328 | 329 | 332 | 333 | 334 | 341 | 342 | 343 |
AttributeTypeDefaultDescription
161 | username
162 | Required / Optional 163 |
StringnullInstagram username from where to retrieve the feed. Required if tag, location and user_id are not defined.
170 | tag
171 | Required / Optional 172 |
StringnullInstagram tag from where to retrieve the feed. Required if username, location and user_id are not defined.
179 | location
180 | Required / Optional 181 |
StringnullInstagram location '{id}/{slug}' from where to retrieve the feed. Required if username, tag and user_id are not defined.
188 | user_id
189 | Required / Optional 190 |
StringnullInstagram user ID for GraphQL API. Required if username, tag and location are not defined. Not recommended as Instagram is CORS restricted again
197 | container
198 | Required / Optional 199 |
StringnullSelector where to place the feed. Required if callback is not defined.
206 | callback
207 | Required / Optional 208 |
function(data)nullCallback function with all the data fetched from instagram. Required if container is not defined.
215 | display_profile 216 | BooleantrueEnables displaying the profile. Not compatible when loading from GraphQL API with user_id
223 | display_biography 224 | BooleantrueEnables displaying the biography. Not compatible when loading a tag, location or user_id
231 | display_gallery 232 | BooleantrueEnables displaying the gallery
239 | display_captions 240 | BooleanfalseEnables displaying captions for each post as overlay on hover
247 | display_igtv 248 | BooleanfalseEnables displaying the IGTV feed if available. Not compatible when loading a tag or from GraphQL API with user_id
255 | max_tries 256 | number [Int]8Number of tries to fetch Instagram data until throwing. Useful to avoid arbitrary CORS issues.
263 | styling 264 | BooleantrueEnables default inline CSS styles
271 | items 272 | number [Int]8Number of items to display. Up to 12 for users, up to 72 for tags
279 | items_per_row 280 | number [Int]4Number of items that will be displayed for each row
287 | lazy_load 288 | BooleanfalseEnable lazy load of images using native web attribute loading="lazy" on img elements
295 | margin 296 | number [Float]0.5Margin (percentage) between items in gallery/igtv
303 | image_size 304 | number [Int]640Native resolution of the images that will be displayed in the gallery. Accepted values [150, 240, 320, 480, 640]. Does not apply to video previews.
311 | host 312 | String [url]https://www.instagram.com/ 316 | URL where to fetch the data. Useful if instagram changes CORS policy. Recommended as Instagram is CORS restricted again. Set to:
317 | 'https://images' + ~~(Math.random() * 3333) + '-focus-opensocial.googleusercontent.com/gadgets/proxy?container=none&url=https://www.instagram.com/' 318 |
322 | cache_time 323 | number [Int]360Instagram response cache expiry time in minutes. Increase this if you get banned too often.
330 | on_error 331 | function(error_description, error_code)console.error

Function that will be triggered when an error ocurs. Error codes:

335 |

1: No username nor tag defined

336 |

2: No container nor callback defined

337 |

3: Profile is age restricted

338 |

4: Network banned

339 |

5: Request error

340 |
344 |
345 |
346 | 347 |
348 |
349 |
350 |

Examples

351 | 401 |
402 |
403 |
404 |
405 | 406 |
408 |

Example 1: Default feed styling

409 |

Don't like it? Check other examples!

410 |
411 |
412 |
Code:
413 |
<script src="jquery.instagramFeed.min.js"></script>
414 | <script>
415 |     (function($){
416 |         $(window).on('load', function(){
417 |             $.instagramFeed({
418 |                 'username': 'instagram',
419 |                 'container': "#instagram-feed1",
420 |                 'display_profile': true,
421 |                 'display_biography': true,
422 |                 'display_gallery': true,
423 |                 'display_captions': true,
424 |                 'max_tries': 8,
425 |                 'callback': null,
426 |                 'styling': true,
427 |                 'items': 8,
428 |                 'items_per_row': 4,
429 |                 'margin': 1,
430 |                 'lazy_load': true,
431 |                 'on_error': console.error
432 |             });
433 |         });
434 |     })(jQuery);
435 | </script>
436 | 
437 |
438 |
439 | 440 |
442 |

Example 2: Only want images?

443 |

Disable display_profile and display_biography

444 |
445 |
446 |
Code:
447 |
<script src="jquery.instagramFeed.min.js"></script>
448 | <script>
449 |     (function($){
450 |         $(window).on('load', function(){
451 |             $.instagramFeed({
452 |                 'username': 'github',
453 |                 'container': "#instagram-feed2",
454 |                 'display_profile': false,
455 |                 'display_biography': false,
456 |                 'display_gallery': true,
457 |                 'display_captions': false,
458 |                 'callback': null,
459 |                 'styling': true,
460 |                 'items': 8,
461 |                 'items_per_row': 4,
462 |                 'margin': 1 
463 |             });
464 |         });
465 |     })(jQuery);
466 | </script>
467 | 
468 |
469 |
470 | 471 |
473 |

Example 3: Want to load more or change the display?

474 |

Change items, items_per_row and margin

475 |
476 |
477 |
Code:
478 |
<script src="jquery.instagramFeed.min.js"></script>
479 | <script>
480 |     (function($){
481 |         $(window).on('load', function(){
482 |             $.instagramFeed({
483 |                 'username': 'zara',
484 |                 'container': "#instagram-feed3",
485 |                 'display_profile': false,
486 |                 'display_biography': false,
487 |                 'display_gallery': true,
488 |                 'display_captions': false,
489 |                 'callback': null,
490 |                 'styling': true,
491 |                 'items': 12,
492 |                 'items_per_row': 6,
493 |                 'margin': 0.25
494 |             });
495 |         });
496 |     })(jQuery);
497 | </script>
498 | 
499 |
500 |
501 | 502 |
504 |

Example 4: Want to fetch a TAG?

505 |

Use tag instead of username.

506 |
507 |
508 |
Code:
509 |
<script src="jquery.instagramFeed.min.js"></script>
510 | <script >
511 |     (function($){
512 |         $(window).on('load', function(){
513 |             $.instagramFeed({
514 |                 'tag': 'paradise',
515 |                 'container': "#instagram-feed4",
516 |                 'display_profile': true,
517 |                 'display_gallery': true,
518 |                 'display_captions': true,
519 |                 'items': 100,
520 |                 'items_per_row': 5,
521 |                 'margin': 0.5
522 |             });
523 |         });
524 |     })(jQuery);
525 | </script>
526 | 
527 |
528 |
529 | 530 |
532 |

Example 5: Want to display IGTV?

533 |

Enable display_igtv. What is IGTV?

534 |
535 |
536 |
Code:
537 |
<script src="jquery.instagramFeed.min.js"></script>
538 | <script >
539 |     (function($){
540 |         $(window).on('load', function(){
541 |             $.instagramFeed({
542 |                 'username': 'fcbarcelona',
543 |                 'container': "#instagram-feed5",
544 |                 'display_profile': false,
545 |                 'display_biography': false,
546 |                 'display_gallery': false,
547 |                 'display_captions': false,
548 |                 'display_igtv': true,
549 |                 'callback': null,
550 |                 'styling': true,
551 |                 'items': 8,
552 |                 'items_per_row': 4,
553 |                 'margin': 1 
554 |             });
555 |         });
556 |     })(jQuery);
557 | </script>
558 | 
559 |
560 |
561 | 562 | 563 |
565 |

Example 6: Using GraphQL API

566 |

Use user_id to fetch data directly from Instagram GraphQL API

567 |

To find the user_id based on the username, use following URL
568 | https://www.instagram.com/web/search/topsearch/?query=<username>

569 |

The result page will contain text starting with such structure
570 | {"users":[{"position":0,"user":{"pk":"<user_id>","username":"<username>..."

571 |

Where <user_id> is the user_id we are looking for.

572 |
573 |
574 |
Code:
575 |
<script src="jquery.instagramFeed.min.js"></script>
576 | <script>
577 |     (function($){
578 |         $(window).on('load', function(){
579 |             $.instagramFeed({
580 |                 'user_id': '2220311520',
581 |                 'container': "#instagram-feed6",
582 |                 'display_gallery': true,
583 |                 'display_captions': true,
584 |                 'styling': true,
585 |                 'items': 18,
586 |                 'items_per_row': 3,
587 |                 'margin': 1 
588 |             });
589 |         });
590 |     })(jQuery);
591 | </script>
592 | 
593 |
594 |
595 | 596 |
598 |

Example 7: Don't like our styles at all?

599 |

Make your owns disabling styling

600 |
601 |

This is the html you will have (Note we have enabled profile and biography in this case).

602 |
<div class="instagram_profile">
603 |     <img class="instagram_profile_image" src="..." alt="Instagram profile pic">
604 |     <p class="instagram_username">@Instagram (<a href="...">@instagram</a>)</p>
605 |     <p class="instagram_biography">....</p>
606 | </div>
607 | <div class="instagram_gallery">
608 |     <a href="https://www.instagram.com/p/Bh-P3IoDxyB" rel="noopener" target="_blank">
609 |         <img src="..." alt="instagram instagram image 0" />
610 |     </a>
611 |     ...
612 | </div>
613 | <div class="instagram_igtv">
614 |     <a href="https://www.instagram.com/p/Bh-P3IoDxyB" rel="noopener" target="_blank">
615 |         <img src="..." alt="instagram instagram image 0" />
616 |     </a>
617 |     ...
618 | </div>
619 | 
620 |
621 |
622 |
Code:
623 |
<script src="jquery.instagramFeed.min.js"></script>
624 | <script>
625 |     (function($){
626 |         $(window).on('load', function(){
627 |             $.instagramFeed({
628 |                 'username': 'instagram',
629 |                 'container': "#instagram-feed3",
630 |                 'display_profile': true,
631 |                 'display_biography': true,
632 |                 'display_gallery': true,
633 |                 'display_captions': false,
634 |                 'display_igtv': true,
635 |                 'callback': null,
636 |                 'styling': false,
637 |                 'items': 12,
638 |             });
639 |         });
640 |     })(jQuery);
641 | </script>
642 | 
643 |
644 |
645 | 646 |
648 |

Example 8: Don't either like our template?

649 |

Define a callback and do not define a container

650 |
651 |

This is the format you will get.

652 |

653 | 
654 |
655 |
656 |
Code:
657 |
<script type="text/javascript" src="jquery.instagramFeed.min.js"></script>
658 | <script type="text/javascript">
659 |     (function($){
660 |         $(window).on('load', function(){
661 |             $.instagramFeed({
662 |                 'username': 'instagram',
663 |                 'callback': function(data){
664 |                     $('#jsonHere').html(JSON.stringify(data, null, 2));
665 |                 }
666 |             });
667 |         });
668 |     })(jQuery);
669 | </script>
670 | 
671 |
672 |
673 | 674 |
675 |
676 |
677 | 678 | 679 | 693 | 694 | 695 | 800 | 801 | 802 | -------------------------------------------------------------------------------- /jquery.instagramFeed.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.instagramFeed 3 | * 4 | * @version 3.0.4 5 | * 6 | * https://github.com/jsanahuja/jquery.instagramFeed 7 | * 8 | */ 9 | (function ($) { 10 | var defaults = { 11 | 'host': "https://www.instagram.com/", 12 | 'username': '', 13 | 'tag': '', 14 | 'user_id': '', 15 | 'location': '', 16 | 'container': '', 17 | 'display_profile': true, 18 | 'display_biography': true, 19 | 'display_gallery': true, 20 | 'display_captions': false, 21 | 'display_igtv': false, 22 | 'max_tries': 8, 23 | 'callback': null, 24 | 'styling': true, 25 | 'items': 8, 26 | 'items_per_row': 4, 27 | 'margin': 0.5, 28 | 'image_size': 640, 29 | 'lazy_load': false, 30 | 'cache_time': 360, 31 | 'on_error': console.error 32 | }; 33 | var image_sizes = { 34 | "150": 0, 35 | "240": 1, 36 | "320": 2, 37 | "480": 3, 38 | "640": 4 39 | }; 40 | var escape_map = { 41 | '&': '&', 42 | '<': '<', 43 | '>': '>', 44 | '"': '"', 45 | "'": ''', 46 | '/': '/', 47 | '`': '`', 48 | '=': '=' 49 | }; 50 | 51 | function escape_string(str) { 52 | return str.replace(/[&<>"'`=\/]/g, function (char) { 53 | return escape_map[char]; 54 | }); 55 | } 56 | 57 | function parse_caption(igobj, data){ 58 | if ( 59 | typeof igobj.node.edge_media_to_caption.edges[0] !== "undefined" && 60 | typeof igobj.node.edge_media_to_caption.edges[0].node !== "undefined" && 61 | typeof igobj.node.edge_media_to_caption.edges[0].node.text !== "undefined" && 62 | igobj.node.edge_media_to_caption.edges[0].node.text !== null 63 | ) { 64 | return igobj.node.edge_media_to_caption.edges[0].node.text; 65 | } 66 | if ( 67 | typeof igobj.node.title !== "undefined" && 68 | igobj.node.title !== null && 69 | igobj.node.title.length != 0 70 | ) { 71 | return igobj.node.title; 72 | } 73 | if ( 74 | typeof igobj.node.accessibility_caption !== "undefined" && 75 | igobj.node.accessibility_caption !== null && 76 | igobj.node.accessibility_caption.length != 0 77 | ) { 78 | return igobj.node.accessibility_caption; 79 | } 80 | return false; 81 | } 82 | 83 | /** 84 | * Cache management 85 | */ 86 | function get_cache(options, last_resort){ 87 | var read_cache = last_resort || false; 88 | 89 | if (!last_resort && options.cache_time > 0) { 90 | var cached_time = localStorage.getItem(options.cache_time_key); 91 | if(cached_time !== null && parseInt(cached_time) + 1000 * 60 * options.cache_time > new Date().getTime()){ 92 | read_cache = true; 93 | } 94 | } 95 | 96 | if(read_cache){ 97 | var data = localStorage.getItem(options.cache_data_key); 98 | if(data !== null){ 99 | return JSON.parse(data); 100 | } 101 | } 102 | return false; 103 | }; 104 | 105 | function set_cache(options, data){ 106 | var cached_time = localStorage.getItem(options.cache_time_key), 107 | cache = options.cache_time != 0 && (cached_time === null || parseInt(cached_time) + 1000 * 60 * options.cache_time > new Date().getTime()); 108 | 109 | if(cache){ 110 | localStorage.setItem(options.cache_data_key, JSON.stringify(data)); 111 | localStorage.setItem(options.cache_time_key, new Date().getTime()); 112 | } 113 | } 114 | 115 | /** 116 | * Request / Response 117 | */ 118 | function parse_response(type, data){ 119 | switch(type){ 120 | case "username": 121 | case "tag": 122 | case "location": 123 | try { 124 | data = data.split("window._sharedData = ")[1].split("<\/script>")[0]; 125 | } catch (e) { 126 | return false; 127 | } 128 | data = JSON.parse(data.substr(0, data.length - 1)); 129 | data = data.entry_data.ProfilePage || data.entry_data.TagPage || data.entry_data.LocationsPage; 130 | if(typeof data !== "undefined"){ 131 | return data[0].graphql.user || data[0].graphql.hashtag || data[0].graphql.location; 132 | } 133 | return false; 134 | break; 135 | case "userid": 136 | if(typeof data.data.user !== "undefined"){ 137 | return data.data.user; 138 | } 139 | return false; 140 | break; 141 | } 142 | } 143 | 144 | function request_data(url, type, tries, callback, autoFallback, googlePrefix){ 145 | var prefixedUrl; 146 | if(autoFallback && googlePrefix){ 147 | prefixedUrl = 'https://images' + ~~(Math.random() * 3333) + '-focus-opensocial.googleusercontent.com/gadgets/proxy?container=none&url=' + url; 148 | } 149 | $.get(prefixedUrl || url, function(response){ 150 | var data = parse_response(type, response); 151 | if(data !== false){ 152 | callback(data); 153 | }else{ 154 | // Unexpected response, not retrying 155 | callback(false); 156 | } 157 | }).fail(function (e) { 158 | if(tries > 1){ 159 | console.warn("Instagram Feed: Request failed, " + (tries-1) + " tries left. Retrying..."); 160 | request_data(url, type, tries-1, callback, autoFallback, !googlePrefix); 161 | }else{ 162 | callback(false, e); 163 | } 164 | }); 165 | } 166 | 167 | /** 168 | * Retrieve data 169 | */ 170 | function get_data(options, callback){ 171 | var data = get_cache(options, false); 172 | 173 | if(data !== false){ 174 | // Retrieving data from cache 175 | callback(data); 176 | }else{ 177 | // No cache, let's do the request 178 | var url; 179 | switch(options.type){ 180 | case "username": 181 | url = options.host + options.id + '/'; 182 | break; 183 | case "tag": 184 | url = options.host + 'explore/tags/' + options.id + '/' 185 | break; 186 | case "location": 187 | url = options.host + 'explore/locations/' + options.id + '/' 188 | break; 189 | case "userid": 190 | url = options.host + 'graphql/query/?query_id=17888483320059182&variables={"id":"' + options.id + '","first":' + options.items + ',"after":null}'; 191 | break; 192 | } 193 | 194 | request_data(url, options.type, options.max_tries, function(data, exception){ 195 | if(data !== false){ 196 | set_cache(options, data); 197 | callback(data); 198 | }else if(typeof exception === "undefined"){ 199 | options.on_error("Instagram Feed: It looks like the profile you are trying to fetch is age restricted. See https://github.com/jsanahuja/InstagramFeed/issues/26", 3); 200 | }else{ 201 | // Trying cache as last resort before throwing 202 | data = get_cache(options, true); 203 | if(data !== false){ 204 | callback(data); 205 | }else{ 206 | options.on_error("Instagram Feed: Unable to fetch the given user/tag. Instagram responded with the status code: " + exception.status, 5); 207 | } 208 | } 209 | }, options.host === defaults.host && options.type != "userid", false); 210 | } 211 | } 212 | 213 | /** 214 | * Rendering 215 | */ 216 | function render(options, data){ 217 | var html = "", styles; 218 | 219 | /** 220 | * Styles 221 | */ 222 | if(options.styling){ 223 | var width = (100 - options.margin * 2 * options.items_per_row) / options.items_per_row; 224 | styles = { 225 | profile_container: ' style="text-align:center;"', 226 | profile_image: ' style="border-radius:10em;width:15%;max-width:125px;min-width:50px;"', 227 | profile_name: ' style="font-size:1.2em;"', 228 | profile_biography: ' style="font-size:1em;"', 229 | gallery_image: ' style="width:100%;"', 230 | gallery_image_link: ' style="width:' + width + '%; margin:' + options.margin + '%;position:relative; display: inline-block; height: 100%;"' 231 | }; 232 | 233 | if(options.display_captions){ 234 | html += ""; 251 | } 252 | }else{ 253 | styles = { 254 | profile_container: "", 255 | profile_image: "", 256 | profile_name: "", 257 | profile_biography: "", 258 | gallery_image: "", 259 | gallery_image_link: "" 260 | }; 261 | } 262 | 263 | /** 264 | * Profile & Biography 265 | */ 266 | if(options.display_profile && options.type !== "userid"){ 267 | html += '
'; 268 | html += ''+ (options.type == '; 269 | if(options.type == "tag"){ 270 | html += '

#' + options.tag + '

'; 271 | }else if(options.type == "username"){ 272 | html += "

@" + data.full_name + " (@" + options.username + ")

"; 273 | if(options.display_biography){ 274 | html += "

" + data.biography + "

"; 275 | } 276 | }else if(options.type == "location"){ 277 | html += "

" + data.name + "

"; 278 | } 279 | html += "
"; 280 | } 281 | 282 | /** 283 | * Gallery 284 | */ 285 | if(options.display_gallery){ 286 | if (typeof data.is_private !== "undefined" && data.is_private === true) { 287 | html += '

This profile is private

'; 288 | } else { 289 | var image_index = typeof image_sizes[options.image_size] !== "undefined" ? image_sizes[options.image_size] : image_sizes[640], 290 | imgs = (data.edge_owner_to_timeline_media || data.edge_hashtag_to_media || data.edge_location_to_media).edges, 291 | max = (imgs.length > options.items) ? options.items : imgs.length; 292 | 293 | html += "'; 323 | } 324 | } 325 | 326 | /** 327 | * IGTV 328 | */ 329 | if (options.display_igtv && typeof data.edge_felix_video_timeline !== "undefined") { 330 | var igtv = data.edge_felix_video_timeline.edges, 331 | max = (igtv.length > options.items) ? options.items : igtv.length; 332 | 333 | if (igtv.length > 0) { 334 | html += '
'; 335 | for (var i = 0; i < max; i++) { 336 | var url = 'https://www.instagram.com/p/' + igtv[i].node.shortcode, 337 | caption = parse_caption(igtv[i], data); 338 | 339 | if(caption === false){ 340 | caption = (options.type == "userid" ? '' : options.id) + " image"; 341 | } 342 | caption = escape_string(caption); 343 | 344 | html += ''; 345 | html += ''; 346 | html += ''; 347 | } 348 | html += '
'; 349 | } 350 | } 351 | 352 | $(options.container).html(html); 353 | } 354 | 355 | $.instagramFeed = function (opts) { 356 | var options = $.fn.extend({}, defaults, opts); 357 | 358 | if (options.username == "" && options.tag == "" && options.user_id == "" && options.location == "") { 359 | options.on_error("Instagram Feed: Error, no username, tag or user_id defined.", 1); 360 | return false; 361 | } 362 | 363 | if(typeof opts.display_profile !== "undefined" && opts.display_profile && options.user_id != ""){ 364 | console.warn("Instagram Feed: 'display_profile' is not available using 'user_id' (GraphQL API)"); 365 | } 366 | 367 | if(typeof opts.display_biography !== "undefined" && opts.display_biography && (options.tag != "" || options.location != "" || options.user_id != "")){ 368 | console.warn("Instagram Feed: 'display_biography' is not available unless you are loading an user ('username' parameter)"); 369 | } 370 | 371 | if (typeof options.get_data !== "undefined") { 372 | console.warn("Instagram Feed: options.get_data is deprecated, options.callback is always called if defined"); 373 | } 374 | 375 | if (options.callback == null && options.container == "") { 376 | options.on_error("Instagram Feed: Error, neither container found nor callback defined.", 2); 377 | return false; 378 | } 379 | 380 | if(options.username != ""){ 381 | options.type = "username"; 382 | options.id = options.username; 383 | }else if(options.tag != ""){ 384 | options.type = "tag"; 385 | options.id = options.tag; 386 | }else if(options.location != ""){ 387 | options.type = "location"; 388 | options.id = options.location; 389 | }else{ 390 | options.type = "userid"; 391 | options.id = options.user_id; 392 | } 393 | 394 | options.cache_data_key = 'instagramFeed_' + options.type + '_' + options.id; 395 | options.cache_time_key = options.cache_data_key + '_time'; 396 | 397 | get_data(options, function(data){ 398 | if(options.container != ""){ 399 | render(options, data); 400 | } 401 | if(options.callback != null){ 402 | options.callback(data); 403 | } 404 | }); 405 | return true; 406 | }; 407 | 408 | })(jQuery); 409 | -------------------------------------------------------------------------------- /jquery.instagramFeed.min.js: -------------------------------------------------------------------------------- 1 | (function(a){function b(a){return a.replace(/[&<>"'`=\/]/g,function(a){return l[a]})}function c(a){return"undefined"!=typeof a.node.edge_media_to_caption.edges[0]&&"undefined"!=typeof a.node.edge_media_to_caption.edges[0].node&&"undefined"!=typeof a.node.edge_media_to_caption.edges[0].node.text&&null!==a.node.edge_media_to_caption.edges[0].node.text?a.node.edge_media_to_caption.edges[0].node.text:"undefined"!=typeof a.node.title&&null!==a.node.title&&0!=a.node.title.length?a.node.title:!("undefined"==typeof a.node.accessibility_caption||null===a.node.accessibility_caption||0==a.node.accessibility_caption.length)&&a.node.accessibility_caption}function d(a,b){var c=b||!1;if(!b&&0new Date().getTime()&&(c=!0)}if(c){var e=localStorage.getItem(a.cache_data_key);if(null!==e)return JSON.parse(e)}return!1}function e(a,b){var c=localStorage.getItem(a.cache_time_key),d=0!=a.cache_time&&(null===c||parseInt(c)+60000*a.cache_time>new Date().getTime());d&&(localStorage.setItem(a.cache_data_key,JSON.stringify(b)),localStorage.setItem(a.cache_time_key,new Date().getTime()))}function f(a,b){switch(a){case"username":case"tag":case"location":try{b=b.split("window._sharedData = ")[1].split("")[0]}catch(a){return!1}return b=JSON.parse(b.substr(0,b.length-1)),b=b.entry_data.ProfilePage||b.entry_data.TagPage||b.entry_data.LocationsPage,"undefined"!=typeof b&&(b[0].graphql.user||b[0].graphql.hashtag||b[0].graphql.location);break;case"userid":return"undefined"!=typeof b.data.user&&b.data.user;}}function g(b,c,d,h,i,j){var k;i&&j&&(k="https://images"+~~(3333*Math.random())+"-focus-opensocial.googleusercontent.com/gadgets/proxy?container=none&url="+b),a.get(k||b,function(a){var b=f(c,a);!1===b?h(!1):h(b)}).fail(function(a){1",g+="\""+("tag"==d.type?e.name+"","tag"==d.type?g+="

#"+d.tag+"

":"username"==d.type?(g+="

@"+e.full_name+" (@"+d.username+")

",d.display_biography&&(g+="

"+e.biography+"

")):"location"==d.type&&(g+="

"+e.name+"

"),g+=""),d.display_gallery)if("undefined"!=typeof e.is_private&&!0===e.is_private)g+="

This profile is private

";else{var j="undefined"==typeof k[d.image_size]?k[640]:k[d.image_size],l=(e.edge_owner_to_timeline_media||e.edge_hashtag_to_media||e.edge_location_to_media).edges,m=l.length>d.items?d.items:l.length;g+=""}if(d.display_igtv&&"undefined"!=typeof e.edge_felix_video_timeline){var s=e.edge_felix_video_timeline.edges,m=s.length>d.items?d.items:s.length;if(0";for(var n=0;n",g+="",g+=""}g+=""}}a(d.container).html(g)}var j={host:"https://www.instagram.com/",username:"",tag:"",user_id:"",location:"",container:"",display_profile:!0,display_biography:!0,display_gallery:!0,display_captions:!1,display_igtv:!1,max_tries:8,callback:null,styling:!0,items:8,items_per_row:4,margin:.5,image_size:640,lazy_load:!1,cache_time:360,on_error:console.error},k={150:0,240:1,320:2,480:3,640:4},l={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};a.instagramFeed=function(b){var c=a.fn.extend({},j,b);return""==c.username&&""==c.tag&&""==c.user_id&&""==c.location?(c.on_error("Instagram Feed: Error, no username, tag or user_id defined.",1),!1):("undefined"!=typeof b.display_profile&&b.display_profile&&""!=c.user_id&&console.warn("Instagram Feed: 'display_profile' is not available using 'user_id' (GraphQL API)"),"undefined"!=typeof b.display_biography&&b.display_biography&&(""!=c.tag||""!=c.location||""!=c.user_id)&&console.warn("Instagram Feed: 'display_biography' is not available unless you are loading an user ('username' parameter)"),"undefined"!=typeof c.get_data&&console.warn("Instagram Feed: options.get_data is deprecated, options.callback is always called if defined"),null==c.callback&&""==c.container)?(c.on_error("Instagram Feed: Error, neither container found nor callback defined.",2),!1):(""==c.username?""==c.tag?""==c.location?(c.type="userid",c.id=c.user_id):(c.type="location",c.id=c.location):(c.type="tag",c.id=c.tag):(c.type="username",c.id=c.username),c.cache_data_key="instagramFeed_"+c.type+"_"+c.id,c.cache_time_key=c.cache_data_key+"_time",h(c,function(a){""!=c.container&&i(c,a),null!=c.callback&&c.callback(a)}),!0)}})(jQuery); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.instagramFeed", 3 | "description": "Instagram Feed without access token. Not using the Instagram API", 4 | "homepage": "https://github.com/jsanahuja/jquery.instagramFeed", 5 | "version": "3.0.4", 6 | "keywords": [ 7 | "instagram", 8 | "feed", 9 | "instagramfeed", 10 | "without", 11 | "access", 12 | "token" 13 | ], 14 | "author": { 15 | "name": "Javier Sanahuja", 16 | "email": "bannss1@gmail.com" 17 | }, 18 | "contributors": [ 19 | { 20 | "name": "Cristian Sanahuja", 21 | "email": "csanahuja10@gmail.com" 22 | } 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/jsanahuja/jquery.instagramFeed.git" 27 | }, 28 | "license": "MIT", 29 | "devDependencies": { 30 | "grunt": "^1.1.0", 31 | "grunt-contrib-qunit": "^3.1.0", 32 | "@babel/core": "^7.8.3", 33 | "@babel/preset-env": "^7.8.3", 34 | "babel-preset-minify": "^0.5.1", 35 | "grunt-babel": "^8.0.0" 36 | }, 37 | "scripts": { 38 | "test": "grunt test", 39 | "build": "grunt build" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | QUnit Example 7 | 8 | 9 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 164 | 165 | 166 | --------------------------------------------------------------------------------