Search code examples
pythonhttpstelegramtelegram-botpython-telegram-bot

How to create a Python HTTPS Webserver using Let's Encrypt certificate?


I've finished my Python Bot for telegram to send HTML5 games through the chat, thanks to the help of this community! Unfortunately it seems that in order for the bot to fetch the score, I need to actually set up a HTTP server within the bot to do so. As it seems through my research, I can't seem to figure out how to create a server in python with ssl, without it being self signed (since it will give a blank page when the user clicks to play the game).

I bought a domain and it's already set up with my VPS IP adress, altough I have a ssl certificate for Apache...

Could someone help me set this up? Since sending and unsecured HTTP connection or a self-signed one will result in a blank page within the app...

Thank you very much!

Edit1: Bot Code:

import configparser, threading, requests, json, re, time, sys
from uuid import uuid4

from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram import InlineQueryResultGame, ParseMode, InputTextMessageContent
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, InlineQueryHandler, CommandHandler, CallbackContext
from http.server import HTTPServer, BaseHTTPRequestHandler


def error_callback(update, context):
    logger.warning('Update "%s" caused error "%s"', update, context.error)

class Global:
    def __init__(self):
        return

class GameHTTPRequestHandler(BaseHTTPRequestHandler):
    def __init__(self, *args):
        BaseHTTPRequestHandler.__init__(self, *args)

    def do_GET(self):
        if "#" in self.path:
            self.path = self.path.split("#")[0]
        if "?" in self.path:
            (route, params) = self.path.split("?")
        else:
            route = self.path
            params = ""
        route = route[1:]
        params = params.split("&")
        if route in Global.games:
            self.send_response(200)
            self.end_headers()
            self.wfile.write(open(route+'.html', 'rb').read())
        elif route == "setScore":
            params = {}
            for item in self.path.split("?")[1].split("&"):
                if "=" in item:
                    pair = item.split("=")
                    params[pair[0]] = pair[1]
            print(params)
            if "imid" in params:
                Global.bot.set_game_score(params["uid"], params["score"], inline_message_id=params["imid"]) 
            else:
                Global.bot.set_game_score(params["uid"], params["score"], message_id=params["mid"], chat_id=params["cid"])
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'Set score')
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b'Invalid game!')

def start(update, context):
    Global.bot.send_game(update.message.chat_id, Global.featured)

def error(update, context):
    print(update, error)

def button(update, context):
    print(update)
    query = update.callback_query
    game = query.game_short_name
    uid = str(query.from_user.id)
    if query.message:
        mid = str(query.message.message_id)
        cid = str(query.message.chat.id)
        url = "http://" + Global.host + ":"+Global.port + "/" + game + "?uid="+uid+"&mid="+mid+"&cid="+cid
    else:
        imid = update.callback_query.inline_message_id
        url = "http://" + Global.host + ":"+Global.port + "/" + game + "?uid="+uid+"&imid="+imid
    print(url)
    Global.bot.answer_callback_query(query.id, text=game, url=url)

def inlinequery(update, context):
    query = update.inline_query.query
    results = []
    for game in Global.games:
        if query.lower() in game.lower():
            results.append(InlineQueryResultGame(id=str(uuid4()),game_short_name=game))
    Global.update.inline_query.answer(results)

def main():
    config = configparser.ConfigParser()
    config.read('config.ini')
    token = config['DEFAULT']['API_KEY']
    Global.games = config['DEFAULT']['GAMES'].split(',')
    Global.host = config['DEFAULT']['HOST']
    Global.port = config['DEFAULT']['PORT']
    Global.featured = config['DEFAULT']['FEATURED']
    updater = Updater(token=token, use_context=True)

    dp = updater.dispatcher

    dp.add_handler(CommandHandler('start', start))
    dp.add_handler(InlineQueryHandler(inlinequery))
    dp.add_handler(CallbackQueryHandler(button))
    dp.add_error_handler(error)
    Global.bot = updater.bot

    print("Polling telegram")
    updater.start_polling()

    print("Starting http server")   
    http = HTTPServer((Global.host, int(Global.port)), GameHTTPRequestHandler)
    http.serve_forever()


if __name__ == '__main__':
    main()

Code inside the HTML5 Game, related to the score:

function gameOver() {
            isGameOver = true;
            clearInterval(gameInterval);

            const urlParams = new URLSearchParams(window.location.search);
            const uid = urlParams.get('uid');
            const mid = urlParams.get('mid');
            const cid = urlParams.get('cid');
            const imid = urlParams.get('imid');
            if (imid) {
                const request = new Request(`/setScore?uid=${uid}&imid=${imid}&score=${score}`);
                fetch(request).then(response => console.log("set score"));
            }
            else {
                const request = new Request(`/setScore?uid=${uid}&mid=${mid}&cid=${cid}&score=${score}`);
                fetch(request).then(response => console.log("set score"));
            }
        }

Original Bot by Mark Powers


Solution

  • After some clarifications about the question the comments, let me try to make some comments. This is probably far from a ready-to-use solution and more some hints on what to look for.

    The ultimate goal is to trigger a setGameScore request from a HTML5 + JS webpage, while having that webpage properly SSL encrypted. That page can either be hosted independently of the bot, in which case the JS code should make the request itself (method 1 below), or is hosted via a python-based webserver within the same script as the bot (methods 2 + 3 below), in which case the goals would be to

    1. have the JS code make a request to that webserver, so that the webserver does the request and
    2. ensure SSL encryption so that the website can be integrated into Telegams game setup.

    Method 1: Let the website do it directly

    Currently your JS code does something like

    const request = new Request(`/setScore?uid=${uid}&imid=${imid}&score=${score}`);
    fetch(request).then(response => console.log("set score"));
    

    My knowledge of JS is limited, but judging by sources like this it seems like

    fetch("https://api.telegram.org/botTOKEN/setGameScore?...").then(response => console.log("set score"));
    

    should already do the trick

    Method 2: Figure out SSL with BaseHTTPRequestHandler

    This seems to be a more general topic. E.g. I found these similar looking questions:

    Method 3: Use a reverse proxy to handle the SSL stuff

    I.e. make the BaseHTTPRequestHandler listen to "localhost" and have Apache/Nginx forward traffic to the correct port. Inspiration for this basically comes from here. In this case, encrypting apache/nginx with letsencrypt should be rather straightforward.