├── .gitignore ├── README.md ├── config ├── Config.php └── Constants.php ├── includes ├── AjaxServices.php ├── Helper.php ├── Instagram.php └── ParallelCurl.php ├── index.php └── public ├── .DS_Store ├── bootstrap-3.3.0 ├── dist │ ├── css │ │ ├── bootstrap.css.map │ │ └── bootstrap.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── js │ │ └── bootstrap.min.js └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── canvasjs └── canvasjs.min.js ├── core ├── .DS_Store ├── css │ ├── core.css │ └── core.min.css ├── images │ ├── 2.6.screen.app-I.png │ ├── 2.6.screen.app.png │ ├── Instagram.png │ ├── arrow-trans.png │ ├── bootstrap.png │ ├── browser-sessionid │ │ ├── Chrome.png │ │ ├── Firefox.png │ │ ├── IE-Edge.png │ │ └── Safari.png │ ├── canvasjs.png │ ├── downArrow.png │ ├── font-awesome.png │ ├── jQCloud.png │ ├── jQuery.png │ ├── loader.gif │ ├── loading.gif │ ├── logo.png │ ├── me.png │ ├── mustaches.png │ ├── pw.png │ ├── tnc.jpg │ ├── tnc.png │ └── typeahead.png └── js │ ├── cookies.min.js │ ├── core.dev.js │ └── core.min.js ├── font-awesome-4.7.0 ├── css │ └── font-awesome.min.css └── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── jQCloud └── dist │ ├── jqcloud.min.css │ └── jqcloud.min.js ├── jquery ├── jquery-1.10.2.min.js └── jquery-1.10.2.min.map ├── mustache └── mustache.min.js └── typeahead └── typeahead.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | /logs/* 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ Warning 2 | 3 | Dear folks, 4 | 5 | Instagram has made several modifications to their APIs and Endpoints, which have resulted in multiple issues. We are currently keeping a close eye on all the changes and will provide a permanent solution shortly. We apologize for any inconvenience caused in the meantime. 6 | 7 | --- 8 | 9 | ### :star: Instagram-Data-Scraper | Version 2.6 (Beta) | Latest Update: May 11th, 2021 10 | 11 | --- 12 | As per my promise, the search feature for @useraccount is now accessible. But, to avail this feature, you need to set up your own Instagram session ID. To obtain your session ID, log in to your Instagram account and copy the session ID from your browser. Click the 'Set session' button for further assistance. 13 | 14 | --- 15 | ### :see_no_evil: What is Instagram Data Scraper? 16 | 17 | 18 | The Instagram Data Scraper is a PHP script that allows users to enter an Instagram username or hashtag and receive data related to that account or post. The information provided includes post counts, likes, comments, images, and likes on images. Currently, the script displays basic user information such as name, biography, followers, following, posts, likes, comments, and views. However, users can access additional details by updating the Controller/Instagram Class in the PHP script code with "public $result_type = 'JSON' or 'ARRAY'." The script also uses jQuery to create Awesome Views and handle each request individually. It is a small yet powerful piece of code that we believe will be useful for you. 19 | 20 | --- 21 | 22 | ### :innocent: What's new in Instagram Data Scraper (Beta)? 23 | Following features are available for now: 24 | | Feature | Status | 25 | |-------------------------------------|----------| 26 | | Instagram Reels Search | WIP | 27 | | Instagram Reels Likes/Views/Play/Comments & Counts | WIP | 28 | | Instagram Reels Video Play | WIP | 29 | | Top 100 Posts for user | WIP | 30 | | Top trending hashtags | WIP | 31 | | Trend by location | WIP | 32 | | Most liked posts | WIP | 33 | | Hashtag Likes | ✓ | 34 | | Hashtag Comments | ✓ | 35 | | Hashtag Keywords | ✓ | 36 | | Hashtag Video Views | ✓ | 37 | | Hashtag Top Post Keywords | ✓ | 38 | | Hashtag Top Post Preview | ✓ | 39 | | Hashtag Top Post Data | ✓ | 40 | | User Account Search | ✓ | 41 | | User Account Biography | ✓ | 42 | | User Account Followers | ✓ | 43 | | User Account Following | ✓ | 44 | | User Account Posts | ✓ | 45 | | User Account Likes | ✓ | 46 | | User Account Comments | ✓ | 47 | | User Account Views | ✓ | 48 | | Download data in excel | ✓ | 49 | 50 | 51 | --- 52 | 53 | ### :scream_cat: Screenshot - [A] 2.6 (Beta) released: 54 | ![App Screenshot](https://github.com/neerajsinghsonu/Instagram-Scraper/blob/master/public/core/images/2.6.screen.app.png) 55 | 56 |

 

57 | 58 | ### :scream_cat: Screenshot - [B] 2.6 (Beta) released: 59 | ![App Screenshot](https://github.com/neerajsinghsonu/Instagram-Scraper/blob/master/public/core/images/2.6.screen.app-I.png) 60 | --- 61 | 62 | ### :pencil2: Development Notes: 63 | - The Instagram Data Scraper tool is a powerful tool that can extract various data related to a specific hashtag. It can retrieve information such as the number of likes, views, comments and the count of keywords used. In addition, it has the ability to fetch the top 10 posts associated with the hashtag. This feature is highly beneficial for marketers and social media analysts who want to track the performance of specific hashtags and analyze user engagement. By gathering data on likes, views, and comments, users can gain valuable insights into the popularity of certain hashtags and the type of content that resonates with their audience. 64 | 65 | - You have the option to customize the N Depth Level Search by editing the JavaScript code in the Instagram Data Scraper tool. The current setting is set to 4 levels, which can be found in the 'this.maxRequestNo = 4;' line of the core.js file. This depth control is currently only available for keyword searches, as account searches have no limit. However, it's possible that in the future, a Depth Controller for Account Search may be implemented as well. 66 | 67 | - Please note that the Instagram Scraper tool does not follow any standard API rules, as it is intended for proof-of-concept/demo purposes only. If you are considering using this tool for commercial purposes, you must have your own Instagram valid API access and modify the request part accordingly to comply with Instagram's API rules. We do not condone the use of the Instagram Scraper tool for any activity that violates Instagram's terms of use. 68 | 69 | - At present, the Instagram Scraper tool operates purely on a scrape method, without any API implementation. It accesses certain Instagram links, which then return JSON data that the application uses to create views based on business logic. Please note that scraping Instagram data without proper authorization or consent may violate Instagram's terms of use and could potentially result in legal consequences. 70 | 71 | - It's important to note that the Instagram links used by the Scraper tool are subject to change at any time, so it's not advisable to rely on them. To ensure a stable and reliable source of Instagram data for your application, it's recommended to have a valid Instagram API access and build your application using the API. Below are some examples of links that the Scraper tool may access: 72 | 73 | | Endpoint | Description | 74 | | --- | --- | 75 | | https://www.instagram.com/{username}/?__a=1 | Returns JSON data for a user's profile | 76 | | https://www.instagram.com/explore/tags/{hashtag}/?__a=1 | Returns JSON data for a hashtag search | 77 | | https://www.instagram.com/web/search/topsearch/?query={query} | Returns JSON data for a general search query | 78 | | https://www.instagram.com/web/search/hashtag/?query={query}&__a=1 | Returns JSON data for a hashtag search query | 79 | 80 | 81 | Please keep in mind that using unauthorized or non-approved methods to access Instagram data may be a violation of Instagram's terms of use and could result in legal consequences. 82 | 83 | - The following is a simplified flow of how the Instagram Scraper tool works: 84 | 85 | The user inputs an @username or #hashtag into the tool. 86 | The tool accesses certain Instagram links to retrieve relevant JSON data, such as user information, post counts, likes, comments, and keywords. 87 | The tool uses this data to create views, such as user profiles and posts, using business logic. 88 | The views are presented to the user for analysis and further use. 89 | It's important to note that this is a simplified flow and there may be additional steps involved in the actual implementation of the tool. Additionally, please keep in mind that unauthorized access to Instagram data may be a violation of Instagram's terms of use and could result in legal consequences. 90 | 91 | The code for the Instagram Scraper tool has been commented thoroughly to aid understanding of the flow and functionality. If there are any suggestions or improvements to be made, please let the developer know. 92 | 93 | The basic flow of the application can be summarized in two steps: 94 | 95 | (A) User Input > Account or Hashtag > Request Link > JSON Response > Create Result 96 | 97 | (B) JSON Response -> If Next Page or Has Next Page > User Input > Account or Hashtag > Request Link > JSON Response > Create Result 98 | 99 | In step (A), the user inputs an @username or #hashtag, and the tool accesses a specific Instagram link to request JSON data related to that input. Once the JSON response is received, the tool uses the data to create a result that can be presented to the user. 100 | 101 | In step (B), if the JSON response indicates that there is a next page or has more data available, the tool will prompt the user for additional inputs to request the next set of data. This process continues until all desired data has been retrieved and results can be created. 102 | 103 | 104 | --- 105 | 106 | ### :package: Data available in Instagram Data Scraper 107 | 108 | 1 - Account information - N Level Search 109 | 110 | - User Biography 111 | - User Followers Counts 112 | - User Followings Count 113 | - User Posts Count 114 | - User Likes Count 115 | - User Comments Count 116 | - User Views Count 117 | - User's Post (Likes) 118 | 119 | 2 - Search information - 4 Level Search 120 | 121 | - Unique Keyword Used and Count 122 | - No Of Posts by Hashtags 123 | - No Of Likes by Hashtags 124 | - No Of Comments by Hashtag 125 | - Top 10 Posts, Comments and Likes by Hashtag 126 | - Keyword summary, how many times a word used in Posts 127 | 128 | --- 129 | 130 | ### Instagram JSON Response Endpoints & Parameters: 131 | 132 | ```php 133 | 134 | /** 135 | * Instagram links to get JSON data 136 | * @var array 137 | */ 138 | 139 | * Next-ID = JSON Response page_info > has_next_page > end_cursor 140 | 141 | public $endpoint = array( 142 | // returns an user information - first set - html data 143 | 'account' => 'https://www.instagram.com/{user}', 144 | 145 | // returns an user information - next set - html data until has_next_page = 0 or null or false 146 | 'account_next_call' => 'https://www.instagram.com/{user}/?max_id={max_id}', 147 | 148 | // returns an user account information - first set - in json format 149 | 'account_json' => 'https://www.instagram.com/{user}/?__a=1', 150 | 151 | // returns an user account information - next set -in json format until has_next_page = 0 or null or false 152 | 'account_json_next_call' => 'https://www.instagram.com/{user}/?__a=1&max_id={max_id}', 153 | 154 | // returns json data for hashtag search or keyword search in json format - first set 155 | 'search_tags_json' => 'https://www.instagram.com/explore/tags/{tag}/?__a=1', 156 | 157 | // returns json data for hashtag search or keyword search in json format - next set until has_next_page = 0 or null or false 158 | 'search_tags_json_next_call' => 'https://www.instagram.com/explore/tags/{tag}/?__a=1&max_id={max_id}', 159 | 160 | // get all available hashtag or keyword or account name list, e.g. Instagram Search Box Auto complete list 161 | 'search_all_tags_json' => 'https://www.instagram.com/web/search/topsearch/?context=blended&query={keyword}&__a=1', 162 | 163 | // send tag code and get all user related information 164 | 'search_username_by_tagcode_json' => 'https://www.instagram.com/p/{code}/?tagged={tag}&__a=1', 165 | ); 166 | 167 | 168 | ``` 169 | --- 170 | 171 | ### For your application 172 | 173 | Use PHP Class, HTML & Core.js to tweak as per your requirement. 174 | 175 | You can see a working demo here [Click to See](https://drive.google.com/file/d/0B2Jr4ZrDD_hFbkhLdXRFb0xBQk0/view) 176 | 177 | --- 178 | 179 | ### :wrench: Requirement 180 | 181 | > Apache version => 2.4 182 | > PHP version =>5.* 183 | > PHP allow_url_fopen & openssl or cURL enable 184 | > Browser & Off-course internet Connection :) 185 | 186 | 187 | ### PHP Settings: 188 | > A-) allow_url_fopen + openssl extension OR cURL enable 189 | 190 | 191 | ### Apache Settings: 192 | > B-) Enable .htaccess (optional) 193 | 194 | 195 | ### Notes 196 | 197 | > C-) Rename dev.htaccess to .htaccess (If you are using windows just rename file dev.htaccess to .htaccess. and the window will ignore the last dot.. ) 198 | 199 | --- 200 | 201 | ### :chart_with_upwards_trend: Change logs: 202 | > Committed repo change code version 2.6 203 | - Video views count added in Table and Excel #24 204 | - User account search activated 205 | 206 | 207 | > Committed repo change code version 2.5.3 208 | - Video total view count added at top of the bar 209 | - Video view count added for each hashtag 210 | 211 | > Committed repo change code version 2.5.2 212 | - Account Information Likes has Post Preview 213 | 214 | > Committed repo change code version 2.5.1 215 | - Min or Full Account Information Toggle Button 216 | 217 | > Committed repo change code version 2.5 218 | - Now you can Search #Hashtags and @UserAccounts simultaneously - New Feature 219 | - Searching Keyword added - New Feature 220 | - Keywords Analytics - New Feature 221 | - Top Posts - New Feature 222 | - Top Post Comments - New Feature 223 | - Each request is being handled separately; so don't take a hiccup if you click somewhere during the request - Improved 224 | - Pure HTML file to make an easy template to use in PHP or ASP or any language. 225 | - Search tips: '#' to search Hashtags and '@' to search account information 226 | - Use 'Space' to search multiple hashtags and accounts 227 | - Download in Excel available 228 | 229 | > Committed repo change code version 2.1 230 | - 8 - Updated Class Instagram 231 | - 9- Updated JavaScript Code 232 | - 11- Updated Content 233 | - 12- Add MultiCurl feature [Thanks to @Pete Warden][https://github.com/petewarden/ParallelCurl] 234 | - 13- Added Error Handling 235 | 236 | > Committed repo change code version 2.0 237 | - 1 - Added data file example 238 | - 2 - Added result PHP array example 239 | - 3 - Updated navbar CSS 240 | - 4 - Update js file and CSS file and minify files 241 | - 5 - Remove htaccess [re_write apache module] dependencies 242 | - 6 - Added PHP Code to build PHP array 243 | - 7 - Some minor UI changes 244 | ` 245 | 246 | The developer encourages users to freely use and enjoy the Instagram Scraper tool. Any feedback or suggestions for enhancement are also most welcome. 247 | 248 | --- 249 | ### Request: 250 | The developer hopes that users will find the Instagram Scraper tool useful and enjoyable. While it is provided free of cost for learning purposes, it should not be used to harm anyone or for any illegal activities. It is important to respect the privacy of others and the effort they have put into their content. Please do not attempt to break anything or engage in any malicious activities. 251 | 252 | Remember that the world is beautiful and full of wonder, and we should use technology to enhance our lives and share our experiences in a positive and respectful manner. 253 | 254 | 255 | Thank you all :heart: 256 | -------------------------------------------------------------------------------- /config/Config.php: -------------------------------------------------------------------------------- 1 | true, 12 | 'environment' => 'development', 13 | 'logs' => true, 14 | 'log_file_name' => DEBUG, 15 | 'log_folder' => dirname(__FILE__) . '/../logs', 16 | 'file_folder_permission' => 0777, 17 | ]; 18 | -------------------------------------------------------------------------------- /config/Constants.php: -------------------------------------------------------------------------------- 1 | insta_account(); 99 | } 100 | // condition for hash tag search 101 | else { 102 | echo $instagram->insta_search(); 103 | } 104 | 105 | /* 106 | * Debug JSON Data Test 107 | * @var [type] 108 | */ 109 | // $json = file_get_contents('data.json'); 110 | // header('Content-Type: application/javascript'); 111 | // echo $json; 112 | -------------------------------------------------------------------------------- /includes/Helper.php: -------------------------------------------------------------------------------- 1 | ', print_r($data, true), ''; 9 | exit(4); 10 | } 11 | /** 12 | * [error_404 description] 13 | * @return [type] [description] 14 | */ 15 | function error_404() 16 | { 17 | exit("404: Request page not found"); 18 | } 19 | 20 | /** 21 | * Check if Server Supports htaccess 22 | * @return boolean [description] 23 | */ 24 | function is_htaccess_enable() 25 | { 26 | // check if mode re_write enabled or not 27 | if (!in_array('mod_rewrite', apache_get_modules()) || !isset($_SERVER['HTACCESS'])) { 28 | return false; 29 | } else { 30 | return true; 31 | } 32 | 33 | } 34 | 35 | function fetchInstaImages ($url) 36 | { 37 | // instagram only has jpeg images for now.. 38 | header("Content-type: image/jpeg"); 39 | readfile( $url ); 40 | } 41 | 42 | /** 43 | * Create log file with data 44 | * 45 | * @param [type] $file_name 46 | * @param [type] $anything 47 | * @return void 48 | */ 49 | function write_log($file_name, $anything) 50 | { 51 | global $config; 52 | if ($config['logs'] === true) { 53 | // first check if log folder exists 54 | if (is_dir($config['log_folder']) && is_writable($config['log_folder'])) { 55 | // log file name 56 | if (!isset($file_name)) { 57 | $file_name = $config['log_folder'] . "/" . $config['log_file_name']; 58 | } 59 | // catch each request data 60 | file_put_contents($config['log_folder'] . "/" . $file_name, $anything, FILE_APPEND); 61 | // separator 62 | file_put_contents($config['log_folder'] . "/" . $file_name, PHP_EOL . str_repeat('+', 25) . PHP_EOL, FILE_APPEND); 63 | } else { 64 | // Make sure the log folder is writable 65 | mkdir($config['log_folder']); 66 | chmod($config['log_folder'], $config['file_folder_permission']); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /includes/Instagram.php: -------------------------------------------------------------------------------- 1 | 'https://www.instagram.com/{user}', 31 | 'account_next_call' => 'https://www.instagram.com/{user}/?max_id={max_id}', 32 | // 'account_json' => 'https://www.instagram.com/{user}/channel/?__a=1', 33 | 'account_json' => 'https://www.instagram.com/{user}/?__a=1', 34 | 'account_json_next_call' => 'https://www.instagram.com/{user}/?__a=1&max_id={max_id}', 35 | 'account_media_json' => 'https://instagram.com/graphql/query/?query_id=17888483320059182&id={user_id}&first=12', 36 | 'account_media_json_next_call' => 'https://instagram.com/graphql/query/?query_id=17888483320059182&id={user_id}&first=12&after={max_id}', 37 | 'search_tags_json' => 'https://www.instagram.com/explore/tags/{tag}/?__a=1', 38 | 'search_tags_json_next_call' => 'https://www.instagram.com/explore/tags/{tag}/?__a=1&max_id={max_id}', 39 | 'search_all_tags_json' => 'https://www.instagram.com/web/search/topsearch/?context=blended&query={keyword}&__a=1', 40 | 'search_username_by_tagcode_json' => 'https://www.instagram.com/p/{code}/?tagged={tag}&__a=1', 41 | ); 42 | /** 43 | * Set your data scrape fetch mode 44 | * OPTIONS - PHP_ARRAY or JSON 45 | * JSON, returns data in ajax response as json object 46 | * PHP_ARRAY, returns data in ajax response as html content. 47 | * 48 | * @var string 49 | */ 50 | protected $scrap_mode = 'JSON'; 51 | /** 52 | * Default action request 53 | * OPTIONS: pull_account|pull_hashtag. 54 | * 55 | * @var array 56 | */ 57 | protected $default_request_action = array( 58 | 'pull_account', 'pull_hashtag', 'pull_media', 59 | ); 60 | /** 61 | * Collect all POST array data. 62 | * 63 | * @var array 64 | */ 65 | public $post_array = array(); 66 | /** 67 | * The request action. 68 | * 69 | * @var string 70 | */ 71 | public $request_action = 'pull_account'; 72 | /** 73 | * Response for Request. 74 | * 75 | * @var string 76 | */ 77 | public $curl_response = null; 78 | /** 79 | * Collect all inks or link or #hahtag or name. 80 | * 81 | * @var array 82 | */ 83 | private $users_input = array(); 84 | /** 85 | * Instagram link input element name. 86 | * 87 | * @var string 88 | */ 89 | private $input_key = 'keyword'; 90 | /** 91 | * Collection of all regex string and json format. 92 | * 93 | * @var string 94 | */ 95 | protected $regex_pattern = array( 96 | // Get '' tag from source code, html 97 | 'script' => '/](.*)_sharedData(.*)<\/script>/', 98 | // This is RegxPattern to extract JavaScript variables from html source code 99 | 'json' => '/(?i)([^\a]+?)<\/script>/si', 100 | // This is RegexPattern to get only json object for php 101 | 'object' => '/(window\.\_sharedData \=|\;)/', 102 | // short call json data __a=1 103 | 'filter_response' => 'window._sharedData={"entry_data":{"ProfilePage":[{source}]}};', 104 | // If in case any error, return this json in ajax response 105 | 'error_response' => 'window._sharedData = {"error": "{error}"};', 106 | // This RegexPattern to detect if response is null/empty 107 | 'empty_error' => '"entry_data": {}', 108 | ); 109 | /** 110 | * Don't change if want to save json data in mysql 111 | * Note: You have to parse PHP array and will have to make your desire output to save. 112 | * 113 | * @var string 114 | */ 115 | protected $save_as = 'mysql'; 116 | /** 117 | * Collect all data as in PHP array format. 118 | * 119 | * @var array 120 | */ 121 | protected $curl_response_array = array(); 122 | 123 | /** 124 | * Enable/Disable debug mode. 125 | */ 126 | private $debug_mode = true; 127 | 128 | /** 129 | * Class constructor function. 130 | * 131 | * @param string $request_action 132 | */ 133 | public function initialize($request_action = 'pull_account', $request_data = array()) 134 | { 135 | // collect all post array 136 | $this->post_array = $_POST; 137 | // validate request 138 | if ($this->validateRequestType() === true) { 139 | // set action 140 | $this->request_action = $request_action; 141 | // set data in class property 142 | if (!empty($request_data) && is_array($request_data)) { 143 | // set link or search array data 144 | $this->users_input = $request_data; 145 | } else { 146 | // set error response 147 | $this->exitErrorString('request'); 148 | } 149 | } else { 150 | // set error response 151 | $this->exitErrorString('request'); 152 | } 153 | } 154 | 155 | /** 156 | * Check what type of request we got 157 | * it is search or simple account info. 158 | */ 159 | private function validateRequestType() 160 | { 161 | return in_array($this->request_action, $this->default_request_action); 162 | } 163 | 164 | /** 165 | * Set action as per request 166 | * to proceed further orations. 167 | */ 168 | public function setInstagramRequest() 169 | { 170 | if (1) { 171 | // check action type & build request url according to request 172 | if ($this->request_action === 'pull_account') { 173 | // build request url 174 | if (isset($this->users_input['max_id'])) { 175 | $this->users_input = str_replace(array('{user}', '{max_id}'), array($this->users_input[$this->input_key], $this->users_input['max_id']), $this->endpoint['account_json_next_call']); 176 | } else { 177 | $this->users_input = str_replace('{user}', $this->users_input[$this->input_key], $this->endpoint['account_json']); 178 | } 179 | 180 | write_log(DEBUG, 'pull_account url: ' . $this->users_input); 181 | 182 | // user's account information requested, bulk mode, account name information, single user 183 | if ($this->sendInstagramRequest() && $this->buildResultFromCurl()) { 184 | // return response 185 | return $this->getInstagramResponse(); 186 | } else { 187 | // fall-back error data 188 | if (is_null($this->curl_response)) { 189 | $this->exitErrorString('url'); 190 | } 191 | // return response 192 | return $this->getInstagramResponse(); 193 | } 194 | } elseif ($this->request_action === 'pull_hashtag') { 195 | // check if hash tag or account name 196 | if (trim($this->users_input[$this->input_key])) { 197 | // build request url 198 | if (isset($this->users_input['next']) && $this->users_input['next'] !== 'false') { 199 | // if user send max_id as in request param next 200 | $this->users_input = str_replace(array('{tag}', '{max_id}'), array($this->users_input[$this->input_key], $this->users_input['next']), $this->endpoint['search_tags_json_next_call']); 201 | } else { 202 | // if next param has false 203 | $this->users_input = str_replace('{tag}', $this->users_input[$this->input_key], $this->endpoint['search_tags_json']); 204 | } 205 | // hash tag search 206 | if ($this->sendInstagramRequest() && $this->buildResultFromCurl()) { 207 | // return response 208 | return $this->getInstagramResponse(); 209 | } else { 210 | // fall-back error data 211 | if (is_null($this->curl_response)) { 212 | $this->exitErrorString('url'); 213 | } 214 | // return response 215 | return $this->getInstagramResponse(); 216 | } 217 | } 218 | } elseif ($this->request_action === 'pull_media') { 219 | // build request url 220 | if (isset($this->users_input['max_id'])) { 221 | $this->users_input = str_replace(array('{user_id}', '{max_id}'), array($this->users_input[$this->input_key], $this->users_input['max_id']), $this->endpoint['account_media_json_next_call']); 222 | } else { 223 | $this->users_input = str_replace('{user_id}', $this->users_input[$this->input_key], $this->endpoint['account_media_json']); 224 | } 225 | write_log(DEBUG, 'pull_media url: ' . $this->users_input); 226 | 227 | // user's media information requested, bulk mode, account name information, single user 228 | if ($this->sendInstagramRequest() && $this->buildResultFromCurl()) { 229 | // return response 230 | return $this->getInstagramResponse(); 231 | } else { 232 | // fall-back error data 233 | if (is_null($this->curl_response)) { 234 | $this->exitErrorString('default'); 235 | } 236 | // return response 237 | return $this->getInstagramResponse(); 238 | } 239 | } else { 240 | // error request 241 | $this->exitErrorString('request'); 242 | } 243 | } else { 244 | return $this->getInstagramResponse(); 245 | } 246 | } 247 | 248 | /** 249 | * Proceed Instagram Scraping. 250 | */ 251 | private function sendInstagramRequest() 252 | { 253 | write_log(DEBUG, $this->users_input . PHP_EOL); 254 | $force_openssl = true; 255 | // check allow_url_fopen setting/;'s 256 | if (ini_get('allow_url_fopen') && extension_loaded('openssl') && isset($force_openssl)) { 257 | if ($this->debug_mode === true) { 258 | write_log(DEBUG, 'debug message: running in allow_url_fopen if condition'); 259 | } 260 | // get source html data 261 | $ch = curl_init(); 262 | curl_setopt_array($ch, $this->cUrlOptionHandler()); 263 | curl_setopt($ch, CURLOPT_URL, $this->users_input); 264 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 265 | $this->curl_response = curl_exec($ch); 266 | curl_close($ch); 267 | //write_log(DEBUG, $this->curl_response); 268 | // check return data status 269 | if ($this->curl_response != false) { 270 | // return status 271 | return true; 272 | } else { 273 | $this->curl_response = null; 274 | 275 | return true; 276 | } 277 | } 278 | // make sure cURL enable 279 | elseif (function_exists('curl_version')) { 280 | if ($this->debug_mode === true) { 281 | write_log(DEBUG, 'debug message: running in curl_version elseif condition'); 282 | } 283 | // execute multi curl 284 | $parallelcurl = new \ParallelCurl(); 285 | // set curl option 286 | $parallelcurl->setOptions($this->cUrlOptionHandler()); 287 | // send link and get response by callback 288 | $parallelcurl->startRequest($this->users_input, function ($data) { 289 | // set response data 290 | $this->curl_response = $data; 291 | }); 292 | // get response 293 | $parallelcurl->finishAllRequests(); 294 | //write_log(DEBUG, $this->curl_response); 295 | //@todo MySQL Saving Configurations 296 | // collect data in array to store later in anywhere 297 | if (isset($this->save_as) && $this->save_as === 'mysql') { 298 | $this->save_as($this->curl_response); 299 | } 300 | // check return data status 301 | if ($this->curl_response) { 302 | // return status 303 | return true; 304 | } else { 305 | // show error 306 | $this->exitErrorString('empty'); 307 | } 308 | } else { 309 | if ($this->debug_mode === true) { 310 | write_log(DEBUG, 'debug message: running in else condition'); 311 | } 312 | // show error 313 | $this->exitErrorString('curl'); 314 | } 315 | } 316 | 317 | /** 318 | * Check if array has valid urls or url. 319 | * 320 | * @param array $request 321 | */ 322 | private function validateUrls($key, $request = array()) 323 | { 324 | // extract array key as php vars 325 | extract($request); 326 | // url validation & data assign 327 | if ((isset($$key) && is_array($$key) && filter_var_array($$key, FILTER_VALIDATE_URL)) || isset($$key) && filter_input(INPUT_POST, $key, FILTER_VALIDATE_URL)) { 328 | return true; 329 | } else { 330 | $this->exitErrorString('url'); 331 | } 332 | } 333 | 334 | /** 335 | * @return mixed 336 | */ 337 | private function buildResultFromCurl() 338 | { 339 | if (!is_null($this->curl_response)) { 340 | // get tag 341 | preg_match_all($this->regex_pattern['script'], $this->curl_response, $filter); 342 | // get script inside json data 343 | preg_match($this->regex_pattern['json'], array_shift($filter[0]), $output); 344 | // if source get check scrape mode 345 | if ($this->scrap_mode === 'PHP_ARRAY' && !empty($output)) { 346 | // if want to PHP array 347 | if ($this->request_action === 'pull_account') { 348 | // check empty response 349 | if (strpos($output[2], $this->regex_pattern['empty_error']) !== false) { 350 | // remove JavaScript var from data & build result array, you have to manage html view by array 351 | $this->curl_response = $this->buildJsonToPhpArray(json_decode(preg_replace($this->regex_pattern['object'], '', $output[2]), true)); 352 | } else { 353 | // url error 354 | $this->exitErrorString('url'); 355 | } 356 | } 357 | // else if filter on 358 | elseif ($this->request_action === 'pull_hashtag') { 359 | // build result array, you have to manage html view by array 360 | $this->curl_response = $this->buildJsonToPhpArray(json_decode($this->curl_response, true)); 361 | } else { 362 | // error response 363 | $this->exitErrorString('default'); 364 | } 365 | } 366 | // return as JavaScript json 367 | elseif ($this->scrap_mode === 'JSON' && !empty($output)) { 368 | // if wanna json data 369 | if ($this->request_action === 'pull_account') { 370 | // check empty response 371 | if (strpos($output[2], $this->regex_pattern['empty_error']) !== false) { 372 | $this->curl_response = $output[2]; 373 | // build result array, you have to manage html view by array 374 | $this->curl_response = str_replace('{source}', $this->curl_response, $this->regex_pattern['filter_response']); 375 | } else { 376 | // url error 377 | $this->exitErrorString('url'); 378 | } 379 | } elseif ($this->request_action === 'pull_hashtag') { 380 | // build result array, you have to manage html view by array 381 | $this->curl_response = str_replace('{source}', $this->curl_response, $this->regex_pattern['filter_response']); 382 | } else { 383 | $this->exitErrorString('default'); 384 | } 385 | } else { 386 | $this->curl_response = str_replace('{source}', $this->curl_response, $this->regex_pattern['filter_response']); 387 | 388 | return $this->curl_response; 389 | } 390 | } 391 | } 392 | 393 | /** 394 | * Build the array data from json respond. 395 | * 396 | * @param [type] $data [description] 397 | * 398 | * @return [type] [description] 399 | */ 400 | public function buildJsonToPhpArray($data) 401 | { 402 | $array_data = array(); 403 | // collect all personal information, same key as in result data 404 | $array_data['country_code'] = $data['country_code']; 405 | $array_data['language_code'] = $data['language_code']; 406 | // get 407 | extract(array_shift($data['entry_data']['ProfilePage'])); 408 | // this is total post count 409 | $array_data['count'] = $user['media']['count']; 410 | $array_data['biography'] = $user['biography']; 411 | $array_data['external_url'] = $user['external_url']; 412 | $array_data['count'] = $user['followed_by']['count']; 413 | $array_data['count'] = $user['follows']['count']; 414 | $array_data['follows_viewer'] = $user['follows_viewer']; 415 | $array_data['full_name'] = $user['full_name']; 416 | $array_data['id'] = $user['id']; 417 | $array_data['username'] = $user['username']; 418 | $array_data['external_url'] = $user['external_url']; 419 | $array_data['profile_pic_url'] = $user['profile_pic_url']; 420 | $array_data['followed_by_viewer'] = $user['followed_by_viewer']; 421 | // collect all post information 422 | if ($user['media']['nodes']) { 423 | // parse one by one 424 | foreach ($user['media']['nodes'] as $index => $array) { 425 | $array_data['post']['data'][$index]['id'] = $array['id']; 426 | $array_data['post']['data'][$index]['thumbnail_src'] = $array['thumbnail_src']; 427 | $array_data['post']['data'][$index]['is_video'] = $array['is_video']; 428 | $array_data['post']['data'][$index]['date'] = $array['date']; 429 | $array_data['post']['data'][$index]['display_src'] = $array['display_src']; 430 | $array_data['post']['data'][$index]['caption'] = isset($array['caption']) ? $array['caption'] : ''; 431 | $array_data['post']['data'][$index]['comments'] = $array['comments']['count']; 432 | $array_data['post']['data'][$index]['likes'] = $array['likes']['count']; 433 | } 434 | } 435 | 436 | return $array_data; 437 | } 438 | 439 | /** 440 | * Display the data. 441 | * 442 | * @return [type] [description] 443 | */ 444 | public function getInstagramResponse() 445 | { 446 | // set json header 447 | header('Content-type:application/javascript'); 448 | // get script 449 | //$this->curl_response = str_replace( '{source}', $this->curl_response, $this->regex_pattern['filter_response'] ); 450 | return $this->curl_response; 451 | } 452 | 453 | /** 454 | * Set User Post Key for Instagram links. 455 | * 456 | * @param string $key [description] 457 | */ 458 | public function setInputKey($key = 'keyword') 459 | { 460 | $this->input_key = $key; 461 | } 462 | 463 | /** 464 | * cURL settings. 465 | * 466 | * @return type 467 | */ 468 | private function cUrlOptionHandler() 469 | { 470 | // create a random ip address 471 | $dynamic_ip = '' . mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255); 472 | // store ip address in session incase user has valid sessionid, 473 | // sessionid depends on ip address and one session id cant be send from diffrent ip address. 474 | // if (!isset($_SESSION['insta-ip-address'])) { 475 | // $_SESSION['insta-ip-address'] = $dynamic_ip; 476 | // } 477 | // print_r($this->post_array); 478 | if ($this->validateRequestType()) { 479 | if (isset($this->post_array['sessionid']) && isset($_COOKIE[$this->post_array['sessionid']])) { 480 | $sessionid = $_COOKIE[$this->post_array['sessionid']]; 481 | } 482 | } 483 | 484 | // send headers to instagram request 485 | $http_curl_headers = array("REMOTE_ADDR: $dynamic_ip", "HTTP_X_FORWARDED_FOR: $dynamic_ip"); 486 | // if cookie set then include session id too... 487 | if (isset($sessionid)) { 488 | $http_curl_headers = array("REMOTE_ADDR: {$dynamic_ip}", "HTTP_X_FORWARDED_FOR: {$dynamic_ip}", "Cookie: sessionid={$sessionid}"); 489 | } 490 | // $http_curl_headers = array("REMOTE_ADDR: {$dynamic_ip}", "HTTP_X_FORWARDED_FOR: {$dynamic_ip}", "Cookie: sessionid={$sessionid}"); 491 | 492 | write_log(DEBUG, $http_curl_headers); 493 | // add additional curl options here 494 | return array( 495 | // return web page 496 | CURLOPT_RETURNTRANSFER => true, 497 | // don't return headers 498 | CURLOPT_HEADER => false, 499 | // follow redirects 500 | CURLOPT_FOLLOWLOCATION => true, 501 | // handle all encodings 502 | CURLOPT_ENCODING => '', 503 | // set referrer on redirect 504 | CURLOPT_AUTOREFERER => true, 505 | // timeout on connect 506 | CURLOPT_CONNECTTIMEOUT => 120, 507 | // timeout on response 508 | CURLOPT_TIMEOUT => 120, 509 | // stop after 10 redirects 510 | CURLOPT_MAXREDIRS => 10, 511 | // Disabled SSL Cert checks 512 | CURLOPT_SSL_VERIFYPEER => false, 513 | // user agent be present in the request 514 | CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 515 | // set fake ip address 516 | CURLOPT_HTTPHEADER => $http_curl_headers, 517 | ); 518 | } 519 | 520 | /** 521 | * Return all error string. 522 | * 523 | * @param string $error_key 524 | * 525 | * @return private 526 | */ 527 | private function exitErrorString($error_key = 'default') 528 | { 529 | // check request typeof error 530 | switch ($error_key) { 531 | case 'curl': 532 | $error = "You must enable Curl or 'allow_url_fopen' & 'openssl' to use this application"; 533 | break; 534 | case 'url': 535 | $error = 'Invalid or corrupt Instagram url request.'; 536 | break; 537 | case 'json': 538 | $error = "You must enable Curl or 'allow_url_fopen' & 'openssl' to use this application"; 539 | break; 540 | case 'request': 541 | $error = 'Invalid data request or bad input send...'; 542 | break; 543 | case 'empty': 544 | $error = 'getting empty data or search suspended...'; 545 | break; 546 | default: 547 | $error = 'Error, unknown error determine. Please try again.'; 548 | break; 549 | } 550 | // return error response in json format 551 | $this->curl_response = str_replace('{error}', $error, $this->regex_pattern('error_response')); 552 | // return break status 553 | // return false; 554 | } 555 | 556 | /** 557 | * [regex_pattern description]. 558 | * 559 | * @param [type] $key [description] 560 | * 561 | * @return [type] [description] 562 | */ 563 | public function regex_pattern($key) 564 | { 565 | if (isset($this->regex_pattern[$key])) { 566 | return $this->regex_pattern[$key]; 567 | } else { 568 | return false; 569 | } 570 | } 571 | } 572 | /* class end */ 573 | /** 574 | * Instagram PHPClass File 575 | * This is main Class file to get all Instagram information 576 | * The Instagram Class required open file url & openssl OR Curl 577 | * if that's requirement doesn't meet, you wont able to run this program. 578 | * 579 | * @version: 2.2 580 | * @build: PHP Class 581 | * @date: 8 August 2017 582 | * @author: Neeraj Singh 583 | */ 584 | class InstagramWrapper extends Instagram 585 | { 586 | /** 587 | * Instagram Class Vendor Object Bucket. 588 | * 589 | * @var null 590 | */ 591 | protected $objInsta = null; 592 | 593 | /** 594 | * Instagram #hashtag search. 595 | */ 596 | public function insta_search() 597 | { 598 | if (isset($_POST['keyword']) && $this->is_ajax_request()) { 599 | // this is ajax request, do something 600 | if (filter_input(INPUT_POST, 'request_action') && filter_input(INPUT_POST, 'keyword')) { 601 | // array keys to php vars 602 | extract($_POST); 603 | } else { 604 | // set request action value in class property 605 | $this->request_action = $request_action; 606 | } 607 | // parent Instagram constructor 608 | $this->initialize($request_action, $_POST); 609 | // call action 610 | $this->setInputKey('keyword'); 611 | // get and return ajax response 612 | return $this->setInstagramRequest(); 613 | } else { 614 | $this->exitErrorString(); 615 | } 616 | } 617 | 618 | /** 619 | * Instagram @user account search. 620 | */ 621 | public function insta_account() 622 | { 623 | if ($this->is_ajax_request()) { 624 | // this is ajax request, do something 625 | if (filter_input(INPUT_POST, 'request_action') && filter_input(INPUT_POST, 'keyword')) { 626 | // array keys to php vars 627 | extract($_POST); 628 | } else { 629 | // set request action value in class property 630 | $this->request_action = $request_action; 631 | } 632 | // parent Instagram constructor 633 | $this->initialize($request_action, $_POST); 634 | // call action 635 | $this->setInputKey('keyword'); 636 | // get and return ajax response 637 | return $this->setInstagramRequest(); 638 | } else { 639 | $this->exitErrorString(); 640 | } 641 | } 642 | 643 | /** 644 | * If AJAX REQUEST MADE. 645 | * 646 | * @return bool 647 | */ 648 | public function is_ajax_request() 649 | { 650 | return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; 651 | } 652 | 653 | /** 654 | * Save json data as in PHP array format to save later in MySQL. 655 | */ 656 | public function save_as($json_data) 657 | { 658 | // !session_id() ? session_start() : null; 659 | // json to php array 660 | $json_data = json_decode($json_data, true); 661 | // store each data group by keywords 662 | //$_SESSION['array_data'][$_POST['keyword']][] = $json_data; 663 | // Create your logic to store data in database 664 | //foreach ($_SESSION['array_data'] as $keyword => $data) { 665 | // other code put here... 666 | //} 667 | // Create your logic to parse array to store data in database with $json_data 668 | // for more info see sample-array-data.txt 669 | //write_log('SearchArrayData.log', print_r($_SESSION['array_data'], true) . PHP_EOL); 670 | } 671 | } 672 | -------------------------------------------------------------------------------- /includes/ParallelCurl.php: -------------------------------------------------------------------------------- 1 | startRequest('http://example.com', 'on_request_done', array('something')); 18 | // 19 | // The first argument is the address that should be fetched 20 | // The second is the callback function that will be run once the request is done 21 | // The third is a 'cookie', that can contain arbitrary data to be passed to the callback 22 | // 23 | // This startRequest call will return immediately, as long as less than the maximum number of 24 | // requests are outstanding. Once the request is done, the callback function will be called, eg: 25 | // 26 | // on_request_done($content, 'http://example.com', $ch, array('something')); 27 | // 28 | // The callback should take four arguments. The first is a string containing the content found at 29 | // the URL. The second is the original URL requested, the third is the curl handle of the request that 30 | // can be queried to get the results, and the fourth is the arbitrary 'cookie' value that you 31 | // associated with this object. This cookie contains user-defined data. 32 | // 33 | // By Pete Warden , freely reusable, see http://petewarden.typepad.com for more 34 | class ParallelCurl 35 | { 36 | public $max_requests; 37 | public $options; 38 | public $outstanding_requests; 39 | public $multi_handle; 40 | public function __construct($in_max_requests = 500, $in_options = array()) 41 | { 42 | $this->max_requests = $in_max_requests; 43 | $this->options = $in_options; 44 | $this->outstanding_requests = array(); 45 | $this->multi_handle = curl_multi_init(); 46 | } 47 | //Ensure all the requests finish nicely 48 | public function __destruct() 49 | { 50 | $this->finishAllRequests(); 51 | } 52 | // Sets how many requests can be outstanding at once before we block and wait for one to 53 | // finish before starting the next one 54 | public function setMaxRequests($in_max_requests) 55 | { 56 | $this->max_requests = $in_max_requests; 57 | } 58 | // Sets the options to pass to curl, using the format of curl_setopt_array() 59 | public function setOptions($in_options) 60 | { 61 | $this->options = $in_options; 62 | } 63 | // Start a fetch from the $url address, calling the $callback function passing the optional 64 | // $user_data value. The callback should accept 3 arguments, the url, curl handle and user 65 | // data, eg on_request_done($url, $ch, $user_data); 66 | public function startRequest($url, $callback, $user_data = array(), $post_fields = null) 67 | { 68 | if ($this->max_requests > 0) { 69 | $this->waitForOutstandingRequestsToDropBelow($this->max_requests); 70 | } 71 | $ch = curl_init(); 72 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 73 | curl_setopt_array($ch, $this->options); 74 | curl_setopt($ch, CURLOPT_URL, $url); 75 | if (isset($post_fields)) { 76 | curl_setopt($ch, CURLOPT_POST, true); 77 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields); 78 | } 79 | curl_multi_add_handle($this->multi_handle, $ch); 80 | $ch_array_key = (int) $ch; 81 | $this->outstanding_requests[$ch_array_key] = array( 82 | 'url' => $url, 83 | 'callback' => $callback, 84 | 'user_data' => $user_data, 85 | ); 86 | $this->checkForCompletedRequests(); 87 | } 88 | // You *MUST* call this function at the end of your script. It waits for any running requests 89 | // to complete, and calls their callback functions 90 | public function finishAllRequests() 91 | { 92 | $this->waitForOutstandingRequestsToDropBelow(1); 93 | } 94 | // Checks to see if any of the outstanding requests have finished 95 | private function checkForCompletedRequests() 96 | { 97 | /* 98 | // Call select to see if anything is waiting for us 99 | if (curl_multi_select($this->multi_handle, 0.0) === -1) 100 | return; 101 | // Since something's waiting, give curl a chance to process it 102 | do { 103 | $mrc = curl_multi_exec($this->multi_handle, $active); 104 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 105 | */ 106 | // fix for https://bugs.php.net/bug.php?id=63411 107 | do { 108 | $mrc = curl_multi_exec($this->multi_handle, $active); 109 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 110 | while ($active && $mrc == CURLM_OK) { 111 | if (curl_multi_select($this->multi_handle) != -1) { 112 | do { 113 | $mrc = curl_multi_exec($this->multi_handle, $active); 114 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 115 | } else { 116 | return; 117 | } 118 | } 119 | // Now grab the information about the completed requests 120 | while ($info = curl_multi_info_read($this->multi_handle)) { 121 | $ch = $info['handle']; 122 | $ch_array_key = (int) $ch; 123 | if (!isset($this->outstanding_requests[$ch_array_key])) { 124 | die("Error - handle wasn't found in requests: '$ch' in " . 125 | print_r($this->outstanding_requests, true)); 126 | } 127 | $request = $this->outstanding_requests[$ch_array_key]; 128 | $url = $request['url']; 129 | $content = curl_multi_getcontent($ch); 130 | $callback = $request['callback']; 131 | $user_data = $request['user_data']; 132 | call_user_func($callback, $content, $url, $ch, $user_data); 133 | unset($this->outstanding_requests[$ch_array_key]); 134 | curl_multi_remove_handle($this->multi_handle, $ch); 135 | } 136 | } 137 | // Blocks until there's less than the specified number of requests outstanding 138 | private function waitForOutstandingRequestsToDropBelow($max) 139 | { 140 | while (1) { 141 | $this->checkForCompletedRequests(); 142 | if (count($this->outstanding_requests) < $max) { 143 | break; 144 | } 145 | usleep(10000); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noncent/instagram-data-scraper/8b5cac344a62f02b22340f8ab8658097c69f6d9a/public/.DS_Store -------------------------------------------------------------------------------- /public/bootstrap-3.3.0/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noncent/instagram-data-scraper/8b5cac344a62f02b22340f8ab8658097c69f6d9a/public/bootstrap-3.3.0/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/bootstrap-3.3.0/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noncent/instagram-data-scraper/8b5cac344a62f02b22340f8ab8658097c69f6d9a/public/bootstrap-3.3.0/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/bootstrap-3.3.0/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noncent/instagram-data-scraper/8b5cac344a62f02b22340f8ab8658097c69f6d9a/public/bootstrap-3.3.0/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/bootstrap-3.3.0/dist/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.0",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus","focus"==b.type)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.0",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c="prev"==a?-1:1,d=this.getItemIndex(b),e=(d+c)%this.$items.length;return this.$items.eq(e)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i="next"==b?"first":"last",j=this;if(!f.length){if(!this.options.wrap)return;f=this.$element.find(".item")[i]()}if(f.hasClass("active"))return this.sliding=!1;var k=f[0],l=a.Event("slide.bs.carousel",{relatedTarget:k,direction:h});if(this.$element.trigger(l),!l.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var m=a(this.$indicators.children()[this.getItemIndex(f)]);m&&m.addClass("active")}var n=a.Event("slid.bs.carousel",{relatedTarget:k,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),j.sliding=!1,setTimeout(function(){j.$element.trigger(n)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(n)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.0",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.find("> .panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('