Open Digital Guide for iOS API configuration

To make the iOS platform emulation possible we need to do some changes in Nextpost files. It is not so hard but it’s required changes.

Also, I will show you how to change the emulated device, iOS version, and use a “switch platform” feature.

Instruction

Suggestion: use Microsoft Visual Studio Code for code editing and profiling. All your syntax error will be highlighted.

Before we start making changes to the code, I strongly urge you to backup the source files so that you do not have any problems if something goes wrong. The list of files that we have to change:

/app/vendor/mgp25/instagram-php (all folder)

/app/helpers/common.helper.php

/app/controllers/InstagramController.php
/app/controllers/AccountController.php
/app/controllers/AccountsController.php
/assets/js/core.js
/assets/css/core.css

/app/controllers/PMAccountController.php (only if you are using Proxy Manager by Nextpass.io)
/app/controllers/PMAccountsController.php (only if you are using Proxy Manager by Nextpass.io)

/app/views/fragments/accounts.fragment.php
/app/views/accounts.php

/app/controllers/UserController.php
/app/controllers/PackageController.php
/app/controllers/TrialPackageController.php

/app/views/fragments/user.fragment.php
/app/views/fragments/package.fragment.php
/app/views/fragments/package-trial.fragment.php

Changelog

Version 5.0.1 - 2.05.2021

- UPDATE: IPV6 support for Nextpost API request.

Changed files:
/src/Instagram.php

Version 5.0 - 1.05.2021

- URGENT: Please open file /src/Constants.php and add a valid license key instead of YOUR-LICENSE-KEY text at the end of the file. You can find your key here: https://nextpost.tech/dashboard/
- UPDATE: query_hash for getReelsMediaFeedGraph() deprecated. Instagram not using web API endpoint anymore.
- UPDATE: Constants for API updated.
- UPDATE: Story analytics request added.
- UPDATE: Requests for People section updated.

Changed files:
/src/Instagram.php
/src/Constants.php
/src/Settings/StorageHandler.php
/src/Request/Business.php
/src/Request/People.php

License configuration

Please open file /src/Constants.php and add a valid license key instead of YOUR-LICENSE-KEY text at the end of the file. You can find your key here.

Step 1

To begin with, you’ll have to check the Ubuntu, PHP and CURL versions that’s currently installed in your system by typing the following commands:

lsb_release -a
php -v
curl -V

Minimum server requirements:

  • Ubuntu: min. v.20.4
  • PHP: min. v.8.0
  • CURL: min. v.7.73

1.1. Ubuntu 20.04 is required for the correct work of Zstd compression in API.

IMPORTANT: Create a full server back-up before update!

Upgrade Ubuntu 18.04 to 20.04 LTS using command line:

https://www.cyberciti.biz/faq/upgrade-ubuntu-18-04-to-20-04-lts-using-command-line/

Notice that you cannot run both Apache and Nginx on the same port.

Don’t forget to disable old PHP versions and keep working only PHP 7.4. It’s important to not running more than one PHP version in the same moment.

You’ll need to disable Apache webserver using command line:

sudo systemctl disable --now apache2

1.2. Curl update.

Upgrade Curl to 7.73 using command line:

apt-get update
apt-get install build-essential libcurl4 openssl libssl-dev libssh-dev zlib1g-dev zlib1g libbrotli-dev brotli libkrb5-dev libldap2-dev librtmp-dev libpsl-dev libnghttp2-dev zstd libzstd-dev
cd /usr/local/src
rm -rf curl*
wget https://curl.haxx.se/download/curl-7.75.0.tar.gz
tar -xvf curl-7.75.0.tar.gz && cd curl-7.75.0
./configure --with-libzstd --with-libssh2 --with-libmetalink --with-ngtcp2 
make 
make install
mv /usr/bin/curl /usr/bin/curl.bak
cp /usr/local/bin/curl /usr/bin/curl
curl -V
sudo ldconfig

1.3. Zstd extension for PHP.

If you are using PHP 8.0 you can skip this step. This version of PHP has Ztsd compression in the box by default.

Installation of Zstd Extension for PHP is required. Without that PHP extension API will not work correctly.

git clone --recursive --depth=1 https://github.com/kjdev/php-ext-zstd.git
cd php-ext-zstd
phpize
./configure
make
make install
cd modules
cp zstd.so /usr/local/

Open php.ini and add this line anywhere after “[PHP]”:

extension = "/usr/local/zstd.so"

Then just save the file and restart server using:

sudo service nginx restart
sudo service php8.0-fpm restart

php8.x-fpm – depends on which php you use.

To check if everything is working create a file called phpinfo.php into your root web directory and put these lines of code:

<?php phpinfo(); ?>

And then visit http://yoursite.com/phpinfo.php and search “zstd”.

If that is installed correctly, you will see the latest version is showing up.

1.4. API files update.

  • Unzip file api-files-XXX.zip, which attached to your order in Dashboard.
  • Replace /app/vendor/mgp25/instagram-php folder with the folder from api-files-XXX.zip.

Step 2 (optional)

In this step I will show you how you can select different iOS device than iPhone X.

First of all open file /app/vendor/mgp25/instagram-php/src/Constants.php and scroll page down to section iOS API constants. In the current case, we emulate iPhone X with iOS 13.3.1.

The User-Agent string is:

Instagram 133.0.0.20.118 (iPhone10,2; iOS 13_3_1; en_US; en-US; scale=2.61; 1080×1920; 203469221)

For example you want to change emulated device to iPhone 11 Pro.

In that case, you should go to website with common User-Agents and select the best one for you.

I found that one:

All what I should to do is update Constants.php file like that:

  • iOS version is 13_3_1 because I want to use the latest version;
  • iPhone12,5 means that it’s an iPhone 11 Pro;
  • I don’t touch other constants because I’m already using the latest values.
const IOS_VERSION = '13_3_1';
const IOS_MODEL   = 'iPhone12,5';
const IOS_DPI     = '1242x2688';
const IOS_SCALE   = '3.00';

Step 3

Open file /app/helpers/common.helper.php and to the end of file add new function:

/**
 * Check is it iOS or Android session
 * @param  string $user_id  User ID
 * @param  string $username Account username
 * @return bool        
 */
function is_android_session($user_id, $username) {
    $base_folder = SESSIONS_PATH."/".$user_id;
    $devicePath = $base_folder."/".$username."/".$username."-settings.dat";
    if (file_exists($devicePath)) {
        $parts = explode(';', file_get_contents($devicePath));
        $deviceString = json_decode($parts[1], true);
        if (isset($deviceString["devicestring"])) {
            if ($deviceString["devicestring"] == "ios") {
                return false;
            } else {
                return true;
            }
        } else {
            return true;
        }
    } else {
        return true;
    }
}

Step 4

Open file /app/controllers/InstagramController.php and find that lines:

$Instagram = new \InstagramAPI\Instagram(false, false, $storage_config);

Change previous code to that:

// Platform detection
if (is_android_session($Account->get("user_id"), $Account->get("username"))) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

$Instagram = new \InstagramAPI\Instagram(false, false, $storage_config, $platform);

Step 4.1

Open file /app/controllers/AccountController.php and find that lines in private function save():

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// iOS API modification
if (Input::post("old-session")) {
    if (is_android_session($AuthUser->get("id"), $this->username)) {
        $platform = "android";
    } else {
        $platform = "ios";
    }
} else {
    $platform = "android";
    if ($AuthUser->get("settings.ios_api_enabled")) {
        $platform = "ios";
    }
}
$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 4.2

Open file /app/controllers/AccountController.php and find that lines in protected function twofa():

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// Platform detection
if (is_android_session($AuthUser->get("id"), $username)) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 4.3

Open file /app/controllers/AccountController.php and find that lines in protected function resend2FA():

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// Platform detection
if (is_android_session($AuthUser->get("id"), $username)) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 4.4

Open file /app/controllers/AccountController.php and find that lines in protected function challenge():

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// Platform detection
if (is_android_session($AuthUser->get("id"), $username)) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 4.5

Open file /app/controllers/AccountController.php and find that lines in protected function resendChallenge():

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// Platform detection
if (is_android_session($AuthUser->get("id"), $username)) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 4.6 (optional)

Only if you are using Cookie Downloader extension and implemented our modification for that.

Open file /app/controllers/AccountController.php and find that lines in protected function upload_cookie_and_login():

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// Platform detection
if (is_android_session($AuthUser->get("id"), $Account->get("username"))) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 5

Repeat Steps 4.1 – 4.6 for /app/controllers/PMAccountController.php file if you are using Proxy Manager module by Joe Cramer (Nextpass).

Step 6.1

Open file /app/controllers/AccountsController.php and find that lines in public function process():

$this->setVariable("Accounts", $Accounts);

Change previous code to that:

$this->setVariable("Accounts", $Accounts);

if (Input::post("action") == "switch-platform") {
    $this->switchPlatform();
}

Step 6.2

Open file /app/controllers/AccountsController.php and to the end of that file after private function remove() add new code (don’t forget about the closing tag “}”).

/**
 * Reconnect Instagram Account
 * @return void
 */
public function reconnect($self_info = true, $update_avatar = true)
{
    ini_set('default_socket_timeout', 300);

    $this->resp->result = 0;
    $AuthUser = $this->getVariable("AuthUser"); 

    if (!Input::post("id")) {
        $this->resp->title = __("Error");
        $this->resp->msg = __("ID is requred!");
        $this->jsonecho();
    }

    $Account = Controller::model("Account", Input::post("id"));

    // Check Account ID and Account Status
    if (!$Account->isAvailable() ||
        $Account->get("user_id") != $AuthUser->get("id")) 
    {
        $this->resp->title = __("Error");
        $this->resp->msg = __("Invalid ID");
        $this->jsonecho();
    }

    // Encrypt the password
    // try {
    //    $passhash = Defuse\Crypto\Crypto::encrypt($Account->get("password"), 
    //                Defuse\Crypto\Key::loadFromAsciiSafeString(CRYPTO_KEY));
    //    $this->resp->msg = $passhash;
    //    $this->jsonecho();
    // } catch (\Exception $e) {
    //    $this->resp->msg = $passhash;
    //    $this->jsonecho();
    // }

    // // Force delete Instagram-cookie file on re-connect
    // $cookie_filename = APPPATH . "/sessions/" . $Account->get("user_id") . "/" . $Account->get("username") . "/" . $Account->get("username") . "-cookies.dat";
    // if (file_exists($cookie_filename)) {
    //     @unlink($cookie_filename);
    //     $this->resp->cookies_deleted = true;
    // } else {
    //     $this->resp->cookies_deleted = false;
    // }

    // Set login_required to 0 before logining to Instagram
    // login() function will not work if login_required = 1
    $Account->set("login_required", 0)
            ->save();

    // Get self account info 7 times and skip this process, if this 7 retries unsuccessful
    // Mobile proxy connection break adaptation
    $reconnect_get = 0;
    $reconnect_get_count = 0;
    $reconnect_get_status = 0;

    do { 
        $reconnect_get_count += 1;
        if ($reconnect_get_count == 7) {
            $reconnect_get = 1;
            $reconnect_get_status = 0;
        }
        // Try login to Instagram
        try {
            $login_resp = \InstagramController::login($Account);

            if ($self_info) {
                // Get and save self account info
                $this->resp->data = $this->selfinfo($login_resp);
            }
    
            if ($update_avatar) {
                // Update account avatar
                $this->resp->avatar_url = $this->updateavatar($login_resp);
            }

            // Logging client events
            $login_resp->event->forceSendBatch();

            $reconnect_get = 1;
            $reconnect_get_status = 1;
        } catch (\InstagramAPI\Exception\NetworkException $e) {
            // Couldn't connect to Instagram account because of network or connection error
            // We will try 10 times otherwise we will show error user message
            if ($reconnect_get_count == 7) {
                $this->resp->msg = $e->getMessage();
                $this->jsonecho();
            }
            sleep(7);
        } catch (\InstagramAPI\Exception\EmptyResponseException $e) {
            // Instagram send us empty response 
            // We will try 10 times otherwise we will show error user message
            if ($reconnect_get_count == 7) {
                $this->resp->msg = $e->getMessage();
                $this->jsonecho();
            }
            sleep(7);
        } catch (\Exception $e) {
            $separated = $e->getMessage();
            $text = explode(" | ", $separated, 2);
            $this->resp->title = isset($text[0]) ? $text[0] : __("Oops...");
            $this->resp->msg = isset($text[1]) ? $text[1] : $e->getMessage();

            if ($text[0] == __("Challenge Required") ||
                $text[0] == __("Login Required") ||
                $text[0] == __("Invalid Username") ||
                $text[0] == __("2FA Required") ||
                $text[0] == __("Incorrect Password")) {
                // Redirect user to account settings
                $account_id = Input::post("id"); 
                if (isset($account_id)) {
                    $this->resp->redirect = APPURL."/accounts/".$account_id; 
                } else {
                    $this->resp->redirect = APPURL."/accounts"; 
                }
            }
            
            $this->jsonecho();
        }
    } while (!$reconnect_get);

    if (!$reconnect_get_status) {
        $this->resp->title = __("Couldn't connect to Instagram");
        $this->resp->msg= __("Account not re-connected. Please try again later or contact to Support.");
        $this->jsonecho();
    }

    // Check is account have active Reactions Pro task
    $sql = "SELECT * FROM `".TABLE_PREFIX."reactions_schedule` WHERE `account_id` = " . $Account->get("id") . " AND `is_active` = " . 1;
    $pdo = \DB::pdo();
    $stmt = $pdo->prepare($sql);
    try {
        $stmt->execute();
        $row = $stmt->fetch();
        if ($row) {
            $AuthUser->set("data.is_reactions_pro_task_active." . $Account->get("id"), true)->save();
        } else {
            $AuthUser->set("data.is_reactions_pro_task_active." . $Account->get("id"), false)->save();
        }
    } catch (\Exception $e) {
        $AuthUser->set("data.is_reactions_pro_task_active." . $Account->get("id"), false)->save();
    }

    // Check is account have active Leads Finder task
    $sql = "SELECT * FROM `".TABLE_PREFIX."parser_schedule` WHERE `account_id` = " . $Account->get("id") . " AND `is_active` = " . 1;
    $pdo = \DB::pdo();
    $stmt = $pdo->prepare($sql);
    try {
        $stmt->execute();
        $row = $stmt->fetch();
        if ($row) {
            $AuthUser->set("data.is_parser_task_active." . $Account->get("id"), true)->save();
        } else {
            $AuthUser->set("data.is_parser_task_active." . $Account->get("id"), false)->save();
        }
    } catch (\Exception $e) {
        $AuthUser->set("data.is_parser_task_active." . $Account->get("id"), false)->save();
    }

    $this->resp->result = 1;  
    $this->jsonecho();
} 

/**
 * Get self account info and save this data to database
 * @return void
 */
public function selfinfo($Instagram) 
{
    $AuthUser = $this->getVariable("AuthUser"); 

    if (!Input::post("id")) {
        $this->resp->title = __("Error");
        $this->resp->msg = __("ID is requred!");
        $this->jsonecho();
    }

    $Account = Controller::model("Account", Input::post("id"));

    // Check Account ID and Account Status
    if (!$Account->isAvailable() ||
        $Account->get("user_id") != $AuthUser->get("id")) 
    {
        $this->resp->title = __("Error");
        $this->resp->msg = __("Invalid ID");
        $this->jsonecho();
    }

    // Get self account info 7 times and skip this process, if this 7 retries unsuccessful
    // Mobile proxy connection break adaptation
    $selfinfo_get = 0;
    $selfinfo_get_count = 0;
    $selfinfo_get_status = 0;

    do { 
        $selfinfo_get_count += 1;
        if ($selfinfo_get_count == 7) {
            $selfinfo_get = 1;
            $selfinfo_get_status = 0;
        }
        try {
            // Logging client events
            $Instagram->event->sendNavigation('main_profile', 'feed_timeline', 'self_profile');

            $Instagram->people->getFriendship($Instagram->account_id);
            $self_feed = $Instagram->timeline->getSelfUserFeed();
            $Instagram->highlight->getUserFeed($Instagram->account_id);

            $resp = $Instagram->people->getSelfInfo();
            $this->resp->self_info = $resp;
            $selfinfo_get = 1;
            $selfinfo_get_status = 1;

            $Instagram->story->getUserStoryFeed($Instagram->account_id);

            // Logging client events
            $Instagram->event->sendProfileView($Instagram->account_id);
            if (isset($self_feed)) {
                $feed_items = $self_feed->getItems();
                if (count($feed_items) > 0) {
                    foreach ($feed_items as $key => $f) {
                        // Send a thumbnail impression
                        $Instagram->event->sendThumbnailImpression('instagram_thumbnail_impression', $f, 'self_profile');
                    }
                }
            }
            $Instagram->event->sendNavigation('main_home', 'self_profile', 'feed_timeline');
        } catch (\InstagramAPI\Exception\NetworkException $e) { 
            // Couldn't connect to Instagram account because of network or connection error
            // Do nothing, just try again
            sleep(7);
        } catch (\InstagramAPI\Exception\EmptyResponseException $e) {
            // Instagram send us empty response
            // Do nothing, just try again
            sleep(7);
        } catch (\InstagramAPI\Exception\FeedbackRequiredException $e) {
            $this->resp->title = __("Action blocked by Instagram");
            $response = $e->getResponse();
            $response = json_decode($response);
            $this->resp->msg = isset($response->feedback_message) ? $response->feedback_message : $e->getMessage();
            $this->jsonecho();
        } catch (\Exception $e) {
            if ($e->hasResponse()) {
                $msg = $e->getResponse()->getMessage();
            } else {
                $msg = explode(":", $e->getMessage(), 2);
                $msg = end($msg);
            }
            if ($msg == "login_required") {
                $Account->set("login_required", 1)
                        ->save();
            }
            $this->resp->msg = $msg;
            $this->resp->title = __("Oops...");
            $this->jsonecho();
        }
    } while (!$selfinfo_get);

    if (!$selfinfo_get_status) {
        $this->resp->title = __("Couldn't connect to Instagram");
        $this->resp->msg= __("Account self info not updated. Please try again later or contact to Support.");
        $this->jsonecho();
    }

    // Save current account basic numbers
    $u_data = $resp->getUser();
    $account_data = "data.".$Account->get("username");

    // Check is prev account type is equal to current, don't 
    $pev_account_type = $AuthUser->get($account_data . ".pev.account_type");
    $account_type = $u_data->getAccountType();
    if ($pev_account_type == $account_type) {
        $AuthUser->set($account_data . ".prev", "")
                    ->save();
    }

    // Check is Insights Basic data available for today
    if (class_exists('InsightBasicModel')) {
        if ($Account->isAvailable() && $Account->get("user_id") == $AuthUser->get("id")) {
            $now = new \Moment\Moment("now", $AuthUser->get("preferences.timezone"));
            $now = $now->format("Y-m-d");
            $InsightBasic = new InsightBasicModel([
                "user_id" => $Account->get("user_id"),
                "instagram_id" => $Account->get("instagram_id"),
                "date" => $now
            ]);
            if ($InsightBasic->isAvailable()) {
                // Update data
                $InsightBasic->set("data.follower_count", $u_data->getFollowerCount())
                                ->save();
                $this->resp->insights_basic_now  = $now;
                $this->resp->insights_basic_new = false;
            } else {
                // Create new database note
                $InsightNew = new InsightBasicModel;
                $InsightNew->set("user_id", $Account->get("user_id"))
                            ->set("instagram_id", $Account->get("instagram_id"))
                            ->set("data.follower_count", $u_data->getFollowerCount())
                            ->set("date", $now)
                            ->save();
                $this->resp->insights_basic_now  = $now;
                $this->resp->insights_basic_new_note = true;
            }
            $Settings = \Controller::model("GeneralData", "settings");
            $interval = $Settings->get("data.insight_basic_interval") ? $Settings->get("data.insight_basic_interval") : 3;
            // Update last check time
            $Account->set("insights_last_check", date("Y-m-d H:i:s", time() + $interval * 3600))
                    ->save();
        }
    }

    // Check is username should be updated
    if ($u_data->getUsername()) {
        if ($Account->get("username") !==  $u_data->getUsername()) {
            $storage_config = [
                "storage" => "file",
                "basefolder" => SESSIONS_PATH."/".$Account->get("user_id")."/",
            ];
            $Instagram = new \InstagramAPI\Instagram(false, false, $storage_config);
            $Instagram->client->moveUser($Account->get("username"), $u_data->getUsername());

            // Remove statistics data 
            $account_data = "data.".$Account->get("username");
            $User->set($account_data, "")
                    ->save();

            // Delete Instagram Account avatar from User Library
            delete(ROOTPATH . "/assets/uploads/" . $User->get("id") . "/profile-pic-" . $Account->get("username") . ".jpg");

            $Account->set("username", $u_data->getUsername())
                    ->save();
        }
    }

    $AuthUser->set($account_data . ".media_count", $u_data->getMediaCount())
                ->set($account_data . ".following_count", $u_data->getFollowingCount())
                ->set($account_data . ".follower_count", $u_data->getFollowerCount())
                ->set($account_data . ".full_name", $u_data->getFullName())
                ->set($account_data . ".bio", $u_data->getBiography())
                ->set($account_data . ".link", $u_data->getExternalUrl())
                ->set($account_data . ".category", $u_data->getCategory())
                ->set($account_data . ".city", $u_data->getCityName())
                ->set($account_data . ".address_street", $u_data->getAddressStreet())
                ->set($account_data . ".city_id", $u_data->getCityId())
                ->set($account_data . ".is_private", $u_data->getIsPrivate())
                ->set($account_data . ".is_business", $u_data->getIsBusiness())
                ->set($account_data . ".account_type", $u_data->getAccountType())
                ->set($account_data . ".should_show_category", $u_data->getShouldShowCategory())
                ->set($account_data . ".should_show_public_contacts", $u_data->getShouldShowPublicContacts())
                ->set($account_data . ".public_email", $u_data->getPublicEmail())
                ->set($account_data . ".public_phone_country_code", $u_data->getPublicPhoneCountryCode())
                ->set($account_data . ".public_phone_number", $u_data->getPublicPhoneNumber())
                ->set($account_data . ".business_contact_method", $u_data->getBusinessContactMethod())
                ->save();
    
    $this->resp->data = [
        "media_count" => $u_data->getMediaCount(),
        "following_count" => $u_data->getFollowingCount(),
        "follower_count" => $u_data->getFollowerCount(),
        "full_name" => $u_data->getFullName(),
        "bio" => $u_data->getBiography(),
        "link" => $u_data->getExternalUrl(),
        "category" => $u_data->getCategory(),
        "city" => $u_data->getCityName(),
        "address_street" => $u_data->getAddressStreet(),
        "city_id" => $u_data->getCityId(),
        "is_private" => $u_data->getIsPrivate(),
        "is_business" => $u_data->getIsBusiness(),
        "account_type" => $u_data->getAccountType(),
        "should_show_category" => $u_data->getShouldShowCategory(),
        "should_show_public_contacts" => $u_data->getShouldShowPublicContacts(),
        "public_email" => $u_data->getPublicEmail(),
        "public_phone_country_code" => $u_data->getPublicPhoneCountryCode(),
        "public_phone_number" => $u_data->getPublicPhoneNumber(),
        "business_contact_method" => $u_data->getBusinessContactMethod()
    ];

    return $this->resp->data;
}

/**
 * Get account avatar and save him to user folder
 * @return void
 */
public function updateavatar($Instagram) 
{
    $AuthUser = $this->getVariable("AuthUser"); 

    if (!Input::post("id")) {
        $this->resp->title = __("Error");
        $this->resp->msg = __("ID is requred!");
        $this->jsonecho();
    }

    $Account = Controller::model("Account", Input::post("id"));

    // Check Account ID and Account Status
    if (!$Account->isAvailable() ||
        $Account->get("user_id") != $AuthUser->get("id")) 
    {
        $this->resp->title = __("Error");
        $this->resp->msg = __("Invalid ID");
        $this->jsonecho();
    }

    // Default redirect
    $this->resp->redirect = APPURL."/accounts";

    // Get account avatar 7 times and skip this process, if this 7 retries unsuccessful
    // Mobile proxy connection break adaptation
    $avatar_get = 0;
    $avatar_get_count = 0;
    $avatar_get_status = 0;

    do { 
        $avatar_get_count += 1;
        if ($avatar_get_count == 7) {
            $avatar_get = 1;
            $avatar_get_status = 0;
        }
        try {
            $user_resp = $Instagram->account->getCurrentUser()->getUser();
            $avatar_get = 1;
            $avatar_get_status = 1;
        } catch (\InstagramAPI\Exception\NetworkException $e) { 
            // Couldn't connect to Instagram account because of network or connection error
            // Do nothing, just try again
            sleep(7);
        } catch (\InstagramAPI\Exception\EmptyResponseException $e) {
            // Instagram send us empty response
            // Do nothing, just try again
            sleep(7);
        } catch (\Exception $e) {
            if ($e->hasResponse()) {
                $msg = $e->getResponse()->getMessage();
            } else {
                $msg = explode(":", $e->getMessage(), 2);
                $msg = end($msg);
            }
            if ($msg == "login_required") {
                $Account->set("login_required", 1)
                        ->save();
            }
            $this->resp->msg = $msg;
            $this->resp->title = __("Oops...");
            $this->jsonecho();
        }
    } while (!$avatar_get);

    if (!$avatar_get_status) {
        $this->resp->title = __("Couldn't connect to Instagram");
        $this->resp->msg= __("Account avatar not updated. Please try again later or contact to Support.");
        $this->jsonecho();
    }

    // Download profile picture 
    // Set path to user directory   
    $user_dir_path = ROOTPATH."/assets/uploads/".$AuthUser->get("id")."/";
    if (!file_exists($user_dir_path)) {
        mkdir($user_dir_path);
    } 

    // Set user directory URL
    $user_dir_url = APPURL."/assets/uploads/".$AuthUser->get("id")."/";

    $ig_pic_url = $user_resp->getProfilePicUrl(); 
    $url_parts = parse_url($ig_pic_url);   
    $ext = strtolower(pathinfo($url_parts['path'], PATHINFO_EXTENSION));
    $filename = "profile-pic-".$user_resp->getUsername().".".$ext;
    
    try {
        stream_context_set_default(
            array(
                'http' => array(
                    'method' => "GET",
                    'header' => "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.79 Safari/537.1\r\n"
                )
            )
        );
        $ig_pic = file_get_contents($ig_pic_url);
        if (!empty($ig_pic)) {
            $downres = file_put_contents($user_dir_path.$filename, $ig_pic); 
        }
    } catch (\Exception $e) {
        // Skip avatar updating because of empty response
    }

    $this->resp->avatar_url = APPURL."/assets/uploads/".$AuthUser->get("id")."/profile-pic-".$user_resp->getUsername().".".$ext;

    return $this->resp->avatar_url;
}

/**
 * Switch emulated platform
 */
public function switchPlatform()
{
    ini_set('default_socket_timeout', 300);

    $this->resp->result = 0;
    $AuthUser = $this->getVariable("AuthUser"); 

    if (!Input::post("id")) {
        $this->resp->title = __("Error");
        $this->resp->msg = __("ID is requred!");
        $this->jsonecho();
    }

    $Account = Controller::model("Account", Input::post("id"));

    // Check Account ID and Account Status
    if (!$Account->isAvailable() ||
        $Account->get("user_id") != $AuthUser->get("id")) 
    {
        $this->resp->title = __("Error");
        $this->resp->msg = __("Invalid ID");
        $this->jsonecho();
    }

    // Check is user have access to switcher or not
    // Switcher automatically enabled when checkbox "Enable iOS API emulation" selected in User/Package settings
    if (!$AuthUser->get("settings.ios_api_enabled")) { 
        $this->resp->title = __("Upgrade your package");
        $this->resp->msg = __("You don't have access to that feature. Platform switching available only for users with specific packages where enabled iOS API emulation.");
        $this->jsonecho();
    }

    $username = $Account->get("username");

    // Detect current platform and set new
    if (is_android_session($Account->get("user_id"), $Account->get("username"))) {
        $new_platform = "ios";
    } else {
        $new_platform = "android";
    }

    $this->resp->platform_detected = $new_platform;

    // Delete device settings file
    $device_file = APPPATH . "/sessions/" . $AuthUser->get("id") . "/" . $Account->get("username") . "/" . $Account->get("username") . "-settings.dat";
    delete($device_file);

    $this->resp->device_settings_deleted = true;

    // Create and configure new instagram client
    // Allow web usage
    \InstagramAPI\Instagram::$allowDangerousWebUsageAtMyOwnRisk = true;
    $st_config = [
        "storage" => "file",
        "basefolder" => SESSIONS_PATH."/".$Account->get("user_id")."/",
    ];
    $Instagram = new \InstagramAPI\Instagram(false, false, $st_config, $new_platform);
    $Instagram->setVerifySSL(SSL_ENABLED);

    $proxy = $Account->get("proxy");
    if ($proxy) {
        $Instagram->setProxy($proxy);
    }

    // Decrypt pass.
    try {
        $password = \Defuse\Crypto\Crypto::decrypt($Account->get("password"), 
                    \Defuse\Crypto\Key::loadFromAsciiSafeString(CRYPTO_KEY));
    } catch (Exception $e) {
        throw new Exception(__("Encryption error"));
    }

    $Instagram->changeUser($username, $password);

    $this->resp->user_changed = true;

    $this->reconnect(false, false);
}

Step 7

Repeat Steps 6.1 – 6.2 for /app/controllers/PMAccountsController.php file if you are using the Proxy Manager module by Joe Cramer (Nextpass).

Step 8

Open file /assets/js/core.js and to the end of that file add new code:

/**
 * Switch platform
 */
NextPost.SwitchPlatform = function() {
    $("body").on("click", "a.js-switch-platform", function() {
        var id = $(this).data("id"); 
        var url = $(this).data("url");
        var account_box = $(".box-list-item[data-id='" + id + "']");
        var device_description = account_box.find(".device-type-description[data-account-id='" + id + "']");
        var device_type = device_description.find(".device-type");

        account_box.addClass("onprogress");

        $.ajax({
            url: url,
            type: 'POST',
            dataType: 'jsonp',
            data: {
                action: "switch-platform", 
                id: id   
            },

            error: function() {
                account_box.removeClass("onprogress");

                NextPost.Alert({ 
                    title:  __("Oops!"),
                    content:  __("An error occured. Please try again later!"),
                    confirmText: __("Close")
                });
            },

            success: function(resp) {
                if (device_type.hasClass("android")) {
                    device_type.replaceWith("<span class='device-type ios'><span class='mdi mdi-apple mr-3'></span><span>" + __('iOS') + "</span>");
                } else {
                    device_type.replaceWith("<span class='device-type android'><span class='mdi mdi-android mr-3'></span><span>" + __('Android') + "</span>");
                }

                account_box.removeClass("onprogress");

                if (resp.result == 0) {
                    NextPost.Alert({ 
                        title: resp.title,
                        content: resp.msg,
                        confirmText: __("Close"),

                        confirm: function() {
                            if (resp.redirect) {
                                window.location.href = resp.redirect; 
                            }
                        }
                    });
                }
            }
        });
    });
}

Step 9.1

Open file /app/views/fragments/accounts.fragment.php and find line with that code:

<div class="box-list-item text-c js-list-item">

Replace with this code:

<div class="box-list-item text-c js-list-item" data-id="<?= $a->get("id") ?>">

Step 9.2

Open file /app/views/fragments/accounts.fragment.php and after <div class=”quick-info”></div> section insert new code.

<div class="device-type-section mt-10 mb-10">
    <?php 
        if (is_android_session($a->get("user_id"), $a->get("username"))):
    ?>
        <span class="tooltip tippy device-type-description" 
            data-account-id="<?= $a->get("id") ?>"
            data-position="top"
            data-size="small"
            title="<?= __('We emulating in real-time device with selected platform. All made actions are similar to real app actions.') ?>">
            <span class="device-type android">
                <span class="mdi mdi-android"></span>
                <span><?= __('Android') ?></span>
            </span>
        </span>
    <?php else: ?>
        <span class="tooltip tippy device-type-description" 
            data-account-id="<?= $a->get("id") ?>"
            data-position="top"
            data-size="small"
            title="<?= __('We emulating in real-time device with selected platform. All made actions are similar to real app actions.') ?>">
            <span class="device-type ios">
                <span class="mdi mdi-apple"></span>
                <span><?= __('iOS') ?></span>
            </span>
        </span>
    <?php endif ?>
</div>

Step 9.3

Open file /app/views/fragments/accounts.fragment.php and find that code:

<li>
    <a href="javascript:void(0)" 
        class="js-remove-list-item" 
        data-id="<?= $a->get("id") ?>" 
        data-url="<?= APPURL."/accounts" ?>">
        <?= __("Delete") ?>
    </a>
</li>

Change previous code to that:

<li>
    <a href="javascript:void(0)" 
        class="js-switch-platform"
        data-id="<?= $a->get("id") ?>" 
        data-url="<?= APPURL."/accounts" ?>">
        <?= __("Switch platform") ?>
    </a>
</li>

<li>
    <a href="javascript:void(0)" 
        class="js-remove-list-item" 
        data-id="<?= $a->get("id") ?>" 
        data-url="<?= APPURL."/accounts" ?>">
        <?= __("Delete") ?>
    </a>
</li>

Step 10

Open file /app/views/accounts.php and find that code:

<script type="text/javascript" charset="utf-8">
    $(function(){
    })
</script>

Change previous code to that:

<script type="text/javascript" charset="utf-8">
    $(function(){
        NextPost.SwitchPlatform();
    })
</script>

Step 11

Open file /app/controllers/PackageController.php and in private function save() find that code:

// File pickers
$options["file_pickers"] = [
    "dropbox" => (boolean)Input::post("dropbox"),
    "onedrive" => (boolean)Input::post("onedrive"),
    "google_drive" => (boolean)Input::post("google-drive")
];

Change previous code to that:

// Enable iOS API
$options["ios_api_enabled"] = (boolean)Input::post("ios-api-enabled");

// File pickers
$options["file_pickers"] = [
    "dropbox" => (boolean)Input::post("dropbox"),
    "onedrive" => (boolean)Input::post("onedrive"),
    "google_drive" => (boolean)Input::post("google-drive")
];

Step 12

Open file /app/controllers/TrialPackageController.php and in public function save() find that code:

// File pickers
$options["file_pickers"] = [
    "dropbox" => (boolean)Input::post("dropbox"),
    "onedrive" => (boolean)Input::post("onedrive"),
    "google_drive" => (boolean)Input::post("google-drive")
];

Change previous code to that:

// Enable iOS API
$options["ios_api_enabled"] = (boolean)Input::post("ios-api-enabled");

// File pickers
$options["file_pickers"] = [
    "dropbox" => (boolean)Input::post("dropbox"),
    "onedrive" => (boolean)Input::post("onedrive"),
    "google_drive" => (boolean)Input::post("google-drive")
];

Step 13

Open file /app/controllers/UserController.php and in private function save() find that code:

// File pickers
$settings["file_pickers"] = [
    "dropbox" => (boolean)Input::post("dropbox"),
    "onedrive" => (boolean)Input::post("onedrive"),
    "google_drive" => (boolean)Input::post("google-drive")
];

Change previous code to that:

// Enable iOS API
$settings["ios_api_enabled"] = (boolean)Input::post("ios-api-enabled");

// File pickers
$settings["file_pickers"] = [
    "dropbox" => (boolean)Input::post("dropbox"),
    "onedrive" => (boolean)Input::post("onedrive"),
    "google_drive" => (boolean)Input::post("google-drive")
];

Step 14

Open file /app/views/fragments/package.fragment.php and find that code:

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("File Pickers") ?></label>
    <div class="clearfix mt-15">
        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="dropbox" 
                        value="1" 
                        <?= $Package->get("settings.file_pickers.dropbox") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Dropbox') ?>
                </span>
            </label>
        </div>

        <div class="col s6 s-last m6 m-last l6 l-last mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="onedrive" 
                        value="1" 
                        <?= $Package->get("settings.file_pickers.onedrive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('OneDrive') ?>
                </span>
            </label>
        </div>

        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="google-drive" 
                        value="1" 
                        <?= $Package->get("settings.file_pickers.google_drive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Google Drive') ?>
                </span>
            </label>
        </div>
    </div>
</div>

Change previous code to that:

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("Instagram API Settings") ?></label>
    <div class="clearfix mt-15">
        <div class="mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="ios-api-enabled" 
                        value="1" 
                        <?= $Package->get("settings.ios_api_enabled") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Enable iOS API') ?>
                </span>
            </label>
        </div>
    </div>
</div>

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("File Pickers") ?></label>
    <div class="clearfix mt-15">
        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="dropbox" 
                        value="1" 
                        <?= $Package->get("settings.file_pickers.dropbox") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Dropbox') ?>
                </span>
            </label>
        </div>

        <div class="col s6 s-last m6 m-last l6 l-last mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="onedrive" 
                        value="1" 
                        <?= $Package->get("settings.file_pickers.onedrive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('OneDrive') ?>
                </span>
            </label>
        </div>

        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="google-drive" 
                        value="1" 
                        <?= $Package->get("settings.file_pickers.google_drive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Google Drive') ?>
                </span>
            </label>
        </div>
    </div>
</div>

Step 15

Open file /app/views/fragments/package-trial.fragment.php and find that code:

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("File Pickers") ?></label>
    <div class="clearfix mt-15">
        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="dropbox" 
                        value="1" 
                        <?= $TrialPackage->get("data.file_pickers.dropbox") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Dropbox') ?>
                </span>
            </label>
        </div>

        <div class="col s6 s-last m6 m-last l6 l-last mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="onedrive" 
                        value="1" 
                        <?= $TrialPackage->get("data.file_pickers.onedrive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('OneDrive') ?>
                </span>
            </label>
        </div>

        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="google-drive" 
                        value="1" 
                        <?= $TrialPackage->get("data.file_pickers.google_drive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Google Drive') ?>
                </span>
            </label>
        </div>
    </div>
</div>

Change previous code to that:

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("Instagram API Settings") ?></label>
    <div class="clearfix mt-15">
        <div class="mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="ios-api-enabled" 
                        value="1" 
                        <?= $TrialPackage->get("data.ios_api_enabled") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Enable iOS API emulation') ?>
                </span>
            </label>
        </div>
    </div>
</div>

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("File Pickers") ?></label>
    <div class="clearfix mt-15">
        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="dropbox" 
                        value="1" 
                        <?= $TrialPackage->get("data.file_pickers.dropbox") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Dropbox') ?>
                </span>
            </label>
        </div>

        <div class="col s6 s-last m6 m-last l6 l-last mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="onedrive" 
                        value="1" 
                        <?= $TrialPackage->get("data.file_pickers.onedrive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('OneDrive') ?>
                </span>
            </label>
        </div>

        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="google-drive" 
                        value="1" 
                        <?= $TrialPackage->get("data.file_pickers.google_drive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Google Drive') ?>
                </span>
            </label>
        </div>
    </div>
</div>

Step 16

Open file /app/views/fragments/user.fragment.php and find that code:

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("File Pickers") ?></label>
    <div class="clearfix mt-15">
        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="dropbox" 
                        value="1" 
                        <?= $User->get("settings.file_pickers.dropbox") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Dropbox') ?>
                </span>
            </label>
        </div>

        <div class="col s6 s-last m6 m-last l6 l-last mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="onedrive" 
                        value="1" 
                        <?= $User->get("settings.file_pickers.onedrive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('OneDrive') ?>
                </span>
            </label>
        </div>

        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="google-drive" 
                        value="1" 
                        <?= $User->get("settings.file_pickers.google_drive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Google Drive') ?>
                </span>
            </label>
        </div>
    </div>
</div>

Change previous code to that:

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("Instagram API Settings") ?></label>
    <div class="clearfix mt-15">
        <div class="mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="ios-api-enabled" 
                        value="1" 
                        <?= $User->get("settings.ios_api_enabled") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Enable iOS API emulation') ?>
                </span>
            </label>
        </div>
    </div>
</div>

<div class="mb-30">
    <label class="form-label form-label--secondary"><?= __("File Pickers") ?></label>
    <div class="clearfix mt-15">
        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="dropbox" 
                        value="1" 
                        <?= $User->get("settings.file_pickers.dropbox") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Dropbox') ?>
                </span>
            </label>
        </div>

        <div class="col s6 s-last m6 m-last l6 l-last mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="onedrive" 
                        value="1" 
                        <?= $User->get("settings.file_pickers.onedrive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('OneDrive') ?>
                </span>
            </label>
        </div>

        <div class="col s6 m6 l6 mb-10">
            <label>
                <input type="checkbox" 
                        class="checkbox" 
                        name="google-drive" 
                        value="1" 
                        <?= $User->get("settings.file_pickers.google_drive") ? "checked" : "" ?>>
                <span>
                    <span class="icon unchecked">
                        <span class="mdi mdi-check"></span>
                    </span>
                    <?= __('Google Drive') ?>
                </span>
            </label>
        </div>
    </div>
</div>

Step 17 (compatibility with Quick Source Search module)

Open file /inc/plugins/quick-source-search/quick-source-search.php and find that code:

$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);

Change previous code to that:

// Platform detection
if (is_android_session($Account->get("user_id"), $Account->get("username"))) {
    $platform = "android"; 
} else {
    $platform = "ios";
}

// Create and configure new instagram client
$Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);

Step 18 (compatibility with Reactions Pro & Hypervoter module targets lists feature)

Open file /inc/plugins/reactions/controllers/ScheduleController.php and update code at lines 745-757 to that:

$IGDevice = new \InstagramAPI\Instagram(false, false, $storageConfig);
if (method_exists($IGDevice, 'getIsAndroidSession')){
    if (is_android_session($Account->get("user_id"), $Account->get("username"))) {
        $platform = "android";
    } else {
        $platform = "ios";
    }
    // Create and configure new Instagram client
    $Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig, $platform);
} else {
    // Create and configure new Instagram client
    $Instagram = new \InstagramAPI\Instagram(false, false, $storageConfig);
}

Make similar changes to Hypervoter module.

Step 19

Open file /assets/css/core.css and add that lines to the end of the file:

/**
* iOS mod styles
*/

.box-list-item.onprogress:after {
  visibility: visible;
  opacity: 1;
  border-radius: 5px;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  -o-border-radius: 5px;
}

.darkside .box-list-item:after {
  background-color: rgba(0, 0, 0, 0.75);
}

.box-list-item:after {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 50;
  visibility: hidden;
  width: 100%;
  height: 100%;
  content: "";
  opacity: 0;
  background-color: rgba(255, 255, 255, 0.75);
  background-image: url(../img/round-loading.svg);
  background-repeat: no-repeat;
  background-position: center;
  -webkit-transition: all ease 0.2s;
  -moz-transition: all ease 0.2s;
  transition: all ease 0.2s;
}

.device-type-section {
  display: inline-block;
}

.device-type {
  border: 1px solid #e6e6e6;
  border-radius: 5px;
  color: #9b9b9b;
  background: transparent;
  padding: 5px 10px 4px 10px;
  font-size: 12px;
  text-align: center;
  display: table-cell;
  font-weight: normal;
  line-height: 12px;
  min-width: 50px;
}

.darkside .device-type {
  border: 1px solid #171717;
}

.device-type-description {
  cursor: pointer;
}

.mr-3 {
  margin-right: 3px;
}

Step 20

You also need a ramsey/uuid is a PHP library for generating and working with universally unique identifiers (UUIDs).

If you don’t have a knowledge how to work with composer, you can skip ramsey/uuid library installation and change ramsey/uuid code in file /app/vendor/mgp25/instagram-php/src/Signatures.php.

Remove that code from header of file:

And change that code:

To that code:

public static function generateUUID(
    $keepDashes = true)
{
    $uuid = sprintf(
        '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
        mt_rand(0, 0xffff),
        mt_rand(0, 0xffff),
        mt_rand(0, 0xffff),
        mt_rand(0, 0x0fff) | 0x4000,
        mt_rand(0, 0x3fff) | 0x8000,
        mt_rand(0, 0xffff),
        mt_rand(0, 0xffff),
        mt_rand(0, 0xffff)
    );

    return $keepDashes ? $uuid : str_replace('-', '', $uuid);
}

If you can, follow ramsey/uuid library installation guide.

Open file /app/composer.json and check the PHP version. It’s should be equal to your current built. It’s very important.

Use command php -v if you want to know your current PHP version.

To install ramsey/uuid library fist of all go to main nextpost folder using this command:

  • /var/www/main-app/app/ – is your unique path to Nextpost /app/ folder
cd /var/www/main-app/app

Once you in /app/ folder run command:

composer require ramsey/uuid

If you see something like this at installation of ramsey/uuid library:

Your requirements could not be resolved to an installable set of packages.

Open server command line and execute following code:

apt install php7.4-bcmath

Where php7.4 is your current PHP built.

If you don’t have Composer install him.

Notice: Contact with Sergey Komlev in Telegram if you have problems with this step.

Step 21

Open file /app/controllers/InstagramController.php and find that code in public static function publish($Post):

// Check spintax permission
if ($User->get("settings.spintax")) {
    $caption = Spintax::process($caption);
    $first_comment = Spintax::process($first_comment);
}

Replace with that:

// Check spintax permission
if ($User->get("settings.spintax")) {
    $caption = Spintax::process($caption);
    $first_comment = Spintax::process($first_comment);
}

// Logging client events
try {
    // Click on the camera icon and move to the gallery pick
    $Instagram->event->sendNavigation('main_camera', 'feed_timeline', 'tabbed_gallery_camera');
    $startTime = round(microtime(true) * 1000);
    $waterfallId = \InstagramAPI\Signatures::generateUUID();
    // Open Photo camera tab
    $Instagram->event->sendOpenPhotoCameraTab($waterfallId, $startTime, round(microtime(true) * 1000));
    // Start a new session ID for the gallery edit.
    $editSessionId = \InstagramAPI\Signatures::generateUUID();
    $Instagram->event->sendStartGalleryEditSession($editSessionId);
    // End edit gallery session.
    $Instagram->event->sendEndGalleryEditSession($editSessionId);
    // Start share session. Now the image will be processed,
    $Instagram->event->sendStartShareSession($editSessionId);
    // And now we tell Instagram we will start uploading the media.
    $Instagram->event->sendShareMedia($waterfallId, $startTime, round(microtime(true) * 1000));
} catch (Exception $e) {}

Step 22

Open file /app/controllers/InstagramController.php and add that code after $metadata definition in public static function publish($Post):

// Logging client events
if (isset($waterfallId)) {
    $metadata["waterfall_id"] = $waterfallId;
}
$type == “timeline”
$type == “story”
$type == “album”

Step 23

Open file /app/controllers/InstagramController.php and find that code in public static function publish($Post):

return $ig_media_code;  

Replace with that code:

// Logging client events
try { $Instagram->event->forceSendBatch(); } catch (\Exception $e) {}

return $ig_media_code;   

Step 24

Open file /app/controllers/InstagramController.php and find that code in public static function login():

return $Instagram;

Replace with that code:

// Logging client events
try { $Instagram->event->forceSendBatch(); } catch (\Exception $e) {}

return $Instagram;  

Step 25

Open file /app/vendor/mgp25/instagram-php/src/Constants.php and find that lines:

Replace with your proxies location language and country. In my case it’s looks like that.

That’s all.

Thank you!

Now you can enable iOS emulation for user or package:

And use a platform switcher function to change platform in one click :

Changelog

Version 1.0 - 10.08.2020

- Initial release.