Outline VPN + Telegram: bot hosted on DigitalOcean Functions (serverless)

George DanekeGeorge Daneke
4 min read

TLDR: if you want to try my free VPN bot, click this link @hidemail_vpn_bot

I decided to share what I have for my own Outline VPN Server - it simple bot where you can generate a new personal key to access this VPN server.

In the end, you will have Telegram chat id and @nickname of a user (if exist) inside a list of your outline VPN keys, like on cover for this post

Here is all code with a few comments, in general, you only need to create a telegram bot and put a token there, your chat id and outline server URL and Port.

<?php

const TELEGRAM_BOT_TOKEN = 'XXXXX:XXXXX'; // you can create new bot and generate this token with @BotFather bot - write there /newbot command
const TELEGRAM_BOT_ADMIN_CHAT_ID = 'XXXXX'; // your chat id, you can find it with @cid_bot bot

const API_PORT = 'PORT';
const API_URL = 'https://XXXXX:PORT/SOMEKEYFROMOUTLINE/';

function sendToTelegram(array $args, $url): void
{

    $curl = curl_init();

    $urlWithName = $url . urlencode('Name of your server for Outline VPN Client');

    $id = $args['message']['chat']['id'];
    $lang = $args['message']['from']['language_code'] ?? 'en';

    //if ($lang === 'en') {
    $message = 'Hello! I invite you to connect to my VPN (Outline) serverm so you will get access to the free Internet, wherever you are. Follow the instructions on the link provided in the invitation to download the app and set up your connection.

https://s3.amazonaws.com/outline-vpn/invite.html#' . $url . ' 

Have problems accessing the invitation link? Write to: @flatroy


Click and copy an access key: `' . $urlWithName . '`
';
//    } else {
    // with this IF you can add some message in different language if you want
    //  }

    $message = urlencode($message);
    curl_setopt_array($curl, [
        CURLOPT_URL => 'https://api.telegram.org/bot'.TELEGRAM_BOT_TOKEN.'/sendMessage?chat_id=' . $id . '&parse_mode=Markdown&text=' . $message,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET",
        CURLOPT_POSTFIELDS => "",
    ]);

    $response = curl_exec($curl);
    $err = curl_error($curl);

    curl_close($curl);

    // send message to admin
    $curl = curl_init();

    $id = $args['message']['chat']['id'];


    // notify admin of bot
    $message = 'new user message: ' . $args['message']['text'] . " from @" . $args['message']['chat']['username'] . ' ' . $args['message']['chat']['id'] . ' ' . $args['message']['chat']['first_name'] . ' - lang: ' . $args['message']['from']['language_code'];

    curl_setopt_array($curl, [
        CURLOPT_URL => 'https://api.telegram.org/bot'.TELEGRAM_BOT_TOKEN.'/sendMessage?chat_id='.TELEGRAM_BOT_ADMIN_CHAT_ID.'&text=' . $message,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET",
        CURLOPT_POSTFIELDS => "",
    ]);

    $response = curl_exec($curl);
    $err = curl_error($curl);

    curl_close($curl);
}


function main(array $args): array
{
    $API_PORT = API_PORT;
    $API_URL = API_URL;


    if ($args['message']['text'] === '/create' || $args['message']['text'] === '/start') {

        //check that if we already have key
        $name = "@" . $args['message']['chat']['username'] . ' ' . $args['message']['chat']['id'] . ' ' . $args['message']['chat']['first_name'] . ' - from My Bot';

        $curl = curl_init();

        curl_setopt_array($curl, [
            CURLOPT_PORT => $API_PORT,
            CURLOPT_URL => $API_URL . '/access-keys/',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_POSTFIELDS => "",
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false,
        ]);

        $response = curl_exec($curl);
        $err = curl_error($curl);
        curl_close($curl);

        if ($err) {
            return ["body" => "cURL Error #:" . $err];
        } else {

            $keys = json_decode($response, true);
            foreach ($keys['accessKeys'] as $key) {

                if ($key['name'] === $name) {
                    //return ["body" => 'ok exists'];

                    sendToTelegram($args, $key['accessUrl']);
                    return ["body" => 'ok'];
                }
            }
        }

        $curl = curl_init();

        curl_setopt_array($curl, [
            CURLOPT_PORT => $API_PORT,
            CURLOPT_URL => $API_URL . '/access-keys/',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => "",
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false,
        ]);

        $response = curl_exec($curl);
        $err = curl_error($curl);
        curl_close($curl);

        if ($err) {
            return ["body" => "cURL Error #:" . $err];

        }

        echo $response;
        $data = json_decode($response, true);
        sendToTelegram($args, $data['accessUrl']);

        $curl = curl_init();

        curl_setopt_array($curl, [
            CURLOPT_PORT => $API_PORT,
            CURLOPT_URL => $API_URL . '/access-keys/' . $data['id'] . '/name',
            CURLOPT_CUSTOMREQUEST => "PUT",
            CURLOPT_POSTFIELDS => "name=" . $name,
            CURLOPT_HTTPHEADER => [
                "Content-Type: application/x-www-form-urlencoded"
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 30,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false,
        ]);

        $response = curl_exec($curl);
        $err = curl_error($curl);
        curl_close($curl);


        // limit traffic

        $curl = curl_init();

        curl_setopt_array($curl, [
            CURLOPT_PORT => $API_PORT,
            CURLOPT_URL => $API_URL . '/access-keys/' . $data['id'] . '/data-limit',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "PUT",
            CURLOPT_POSTFIELDS => "{\"limit\":{\n\t\"bytes\": 200000000\n}}",
            CURLOPT_HTTPHEADER => [
                "Content-Type: application/json"
            ],
        ]);

        $response = curl_exec($curl);
        $err = curl_error($curl);

        curl_close($curl);

        if ($err) {
            echo "cURL Error #:" . $err;
        } else {
            echo $response;
        }

    }
    return ["body" => 'ok'];
}

This code is done only to use on DigitalOcean (serverless) functions: you can read about it here - https://www.digitalocean.com/products/functions

Hope it will help anyone, cheers!

DigitalOcean Referral Badge

1
Subscribe to my newsletter

Read articles from George Daneke directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

George Daneke
George Daneke