import unittest
from unittest import mock

from werkzeug.exceptions import Unauthorized

from frontstage import app
from frontstage.common.authorisation import (
    EXPIRES_IN_MISSING_FROM_PAYLOAD,
    JWT_DATE_EXPIRED,
    JWT_DECODE_ERROR,
    NO_AUTHORIZATION_COOKIE,
    NO_ENCODED_JWT,
    jwt_authorization,
)
from frontstage.common.session import Session
from frontstage.exceptions.exceptions import JWTTimeoutError, JWTValidationError

valid_jwt = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0eV9pZCI6InRlc3QiLCJyb2xlIjoicmVzcG9uZGVudCIsInVucmVhZF9"
    "tZXNzYWdlX2NvdW50Ijp7InZhbHVlIjoxLCJyZWZyZXNoX2luIjozMjUwMzcyNTY4MTAwMH0sImV4cGlyZXNfaW4iOjMyNTAzNzI"
    "1NjgxMDAwfQ.8lZiYTzjFuPb6sgYJne88Qua8ozAjnSrZRgoXN6qKic"
)

in_valid_jwt = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0eV9pZCI6InRlc3QiLCJyb2xlIjoicmVzcG9uZGVudCIsInVucmVhZF9"
    "tZXNzYWdlX2NvdW50Ijp7InZhbHVlIjoxLCJyZWZyZXNoX2luIjozMjUwMzcyNTY4MTAwMH0sImV4cGlyZXNfaW4iOjMyNTAzNzI"
    "1NjgxMDAwfQ"
)

expired_jwt = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0eV9pZCI6InRlc3QiLCJyb2xlIjoicmVzcG9uZGVudCIsInVucmVhZF"
    "9tZXNzYWdlX2NvdW50Ijp7InZhbHVlIjoxLCJyZWZyZXNoX2luIjoxNTg3OTU1ODAzLjk4ODM4M30sImV4cGlyZXNfaW4iOjE1O"
    "Dc5NTkxMDMuOTI1MDg4fQ.nntdPMRSQFjCAax0J0Iez1l5BbtqE8x617yWcN_zhKY"
)

no_expires_in_jwt = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXJ0eV9pZCI6InRlc3QiLCJyb2xlIjoicmVzcG9uZGVudCIsInVucmVh"
    "ZF9tZXNzYWdlX2NvdW50Ijp7InZhbHVlIjoxLCJyZWZyZXNoX2luIjoxNjg3OTU1ODAzLjk4ODM4M319.JHUjhH4D00vIFCrC"
    "YCyUMoxQCx0cTIPdsUVglKdNajk"
)

class TestJWTAuthorization(unittest.TestCase):
    def setUp(self):
        self.app = app.test_client()
        self.party_id = "test"
        self.session = Session.from_party_id(self.party_id)

    @staticmethod
    def decorator_test(request):
        @jwt_authorization(request)
        def test_function(_):
            pass

        test_function()

    def test_jwt_authorization_success(self):
        # Given a valid JWT and authorization cookie
        self._update_session_token()
        request = mock.MagicMock(cookies={"authorization": self.session.session_key})

        # When the jwt_authorization decorator is exercised
        # Then the function runs without exceptions and the test is considered as passed
        self.decorator_test(request)

    def test_jwt_authorization_no_cookie(self):
        # Given the respondent doesnt have an authorization cookie
        request = mock.MagicMock(cookies={})

        # When the jwt_authorization decorator is exercised
        # Then the function raises an Unauthorized, NO_AUTHORIZATION_COOKIE exception
        with self.assertRaises(Unauthorized) as e:
            self.decorator_test(request)
        self.assertEqual(e.exception.description, NO_AUTHORIZATION_COOKIE)

    def test_jwt_authorization_no_encoded_jwt_for_session(self):
        # Given the respondent has an authorization cookie, but the key doesn't match a session in redis
        request = mock.MagicMock(cookies={"authorization": "incorrect_session_key"})

        # When the jwt_authorization decorator is exercised
        # Then the function raises an Unauthorized, NO_ENCODED_JWT exception
        with self.assertRaises(Unauthorized) as e:
            self.decorator_test(request)
        self.assertEqual(e.exception.description, NO_ENCODED_JWT)

    def test_jwt_authorization_expired(self):
        # Given a valid JWT and authorization cookie, but the token has expired
        self._update_session_token(expired_jwt)
        request = mock.MagicMock(cookies={"authorization": self.session.session_key})

        # When the jwt_authorization decorator is exercised
        # Then the function raises an Unauthorized, JWT_DATE_EXPIRED exception
        with self.assertRaises(JWTTimeoutError) as e:
            self.decorator_test(request)
        self.assertEqual(e.exception.message, f"{JWT_DATE_EXPIRED} {self.party_id}")

    def test_jwt_authorization_no_expiry_in_payload(self):
        # Given a valid authorization cookie, but invalid JWT where the expiry_in key is missing from the payload
        self._update_session_token(no_expires_in_jwt)
        request = mock.MagicMock(cookies={"authorization": self.session.session_key})

        # When the jwt_authorization decorator is exercised
        # Then the function raises an JWTValidationError exception
        with self.assertRaises(JWTValidationError) as e:
            self.decorator_test(request)
        self.assertEqual(e.exception.message, f"{EXPIRES_IN_MISSING_FROM_PAYLOAD} {self.party_id}")

    def test_jwt_authorization_decode_failure(self):
        # Given a valid authorization cookie, but invalid JWT where the decode step fails
        self._update_session_token(in_valid_jwt)
        request = mock.MagicMock(cookies={"authorization": self.session.session_key})

        # When the jwt_authorization decorator is exercised
        # Then the function raises an JWTValidationError exception
        with self.assertRaises(JWTValidationError) as e:
            self.decorator_test(request)
        self.assertEqual(e.exception.message, f"{JWT_DECODE_ERROR} {self.session.session_key}")

    def _update_session_token(self, token=valid_jwt):
        self.session.encoded_jwt_token = token
        self.session.save()


from django.db import models
from enumchoicefield import EnumChoiceField, ChoiceEnum

from .utils import CoreModel, TextFieldWithInputWidget

class StorageLocationCategory(ChoiceEnum):
    master = "master"
    backup = "backup"
    copy = "copy"

class DataLocation(CoreModel):
    """
    Represent a data location.
    """

    class Meta:
        app_label = "core"
        get_latest_by = "added"
        ordering = ["added"]

    backend = models.ForeignKey(
        "core.StorageResource",
        verbose_name="Storage back-end",
        on_delete=models.CASCADE,
        help_text="Select the storage or application platform that holds data.",
    )

    category = EnumChoiceField(
        StorageLocationCategory,
        default=StorageLocationCategory.master,
        blank=False,
        null=False,
        verbose_name="Nature of data copy.",
        help_text="Is this the master copy, a working copy or a backup?.",
    )

    dataset = models.ForeignKey(
        "core.Dataset", related_name="data_locations", on_delete=models.CASCADE
    )

    datatypes = models.ManyToManyField(
        "core.DataType",
        verbose_name="Stored datatypes",
        blank=True,
        related_name="storage_locations",
        help_text="The scope of this storage. Leave empty if all data types are stored in a single location.",
    )

    data_declarations = models.ManyToManyField(
        "core.DataDeclaration",
        blank=True,
        related_name="data_locations",
        verbose_name="Stored data declarations",
        help_text="The scope of this storage. Leave empty if all data declarations are stored in a single location.",
    )

    location_description = TextFieldWithInputWidget(
        blank=True,
        null=True,
        verbose_name="Location of the data",
        help_text="E.g. Laptop 1, server folder path or application URL endpoint.",
    )

    def __str__(self):
        return f"{self.category} - {self.backend.name} - {self.location_description}"


import sys
import os

_PROJECT_PATH="/".join( os.path.abspath(__file__).split("/")[0:-2] )

if __name__ == "__main__":
    from streamlit import cli as stcli
    print("#####################")
    print("# Start Streamlit ! #")
    print("#####################")
    port=int(sys.argv[1])
    sys.argv= ["streamlit", "run", os.path.join(_PROJECT_PATH,"front/main.py"), f"--server.port={port}"]
    sys.exit(stcli.main())
    print("app: server stop...")


import sys

from serial_connection import serial_ports, ReadVoltageResponse, ReadCurrentResponse, SetVoltageRequest, \
    ReadVoltageRequest, ReadCurrentRequest, SerialConnection
from ui_portselector import Ui_PortSelector
from ui_standardmode import Ui_StandardMode

from PySide6.QtCore import Qt, QTimer
from PySide6.QtWidgets import QApplication, QWidget, \
    QGridLayout, QComboBox, \
    QVBoxLayout, QLCDNumber, QMessageBox

from datetime import datetime

def timestamp(i):
    print(str(i), " ", datetime.now().time())

def serial_ports_fast_dummy():
    return ['a', 'b', 'c']

class PortSelector(QWidget):
    __disconnect_pending = False

    def __init__(self, serial_connection: SerialConnection):
        super(PortSelector, self).__init__()
        # uic.loadUi("port_selector.ui", self)
        self.ui = Ui_PortSelector()
        self.ui.setupUi(self)
        self.ui.refreshButton.clicked.connect(self.refresh_button_clicked)
        self.ui.connectButton.clicked.connect(self.connect_button_clicked)
        self.ui.connectButton.setDisabled(True)
        self.ui.portSelectorComboBox.currentTextChanged.connect(self.port_selected)
        self.refresh_button_clicked()
        self.serial_connection = serial_connection

        self.serial_connection.connection_status_change_signal.connect(self.connection_status_changed_handler)

    def refresh_button_clicked(self):
        ports = serial_ports()
        self.ui.portSelectorComboBox.clear()
        for p in ports:
            self.ui.portSelectorComboBox.addItem(p)

    def port_selected(self):
        self.ui.connectButton.setEnabled(True)

    def __display_connection_status(self, status: str):
        text = None
        if status == 'not connected':
            text = 'Nicht verbunden'
        elif status == 'connecting':
            text = 'Verbindung wird aufgestellt...'
        elif status == 'connected':
            text = 'Verbunden'
        if text is not None:
            self.ui.connectedLabel.setText(text)

    def __display_connection_error(self):
        mb = QMessageBox()
        mb.setIcon(QMessageBox.Critical)
        mb.setText("Fehler bei der Kommunikation mit dem Gerät")
        mb.setWindowTitle("Fehler")
        mb.setStandardButtons(QMessageBox.Ok)
        mb.exec()

    def connection_status_changed_handler(self, status: bool):
        if status is True:
            self.update_ui_connected()
        else:
            self.update_ui_disconnected()
            if self.__disconnect_pending:
                self.__disconnect_pending = False
            else:  # unexpected disconnect
                self.__display_connection_error()

    def update_ui_disconnected(self):
        self.__display_connection_status('not connected')
        self.ui.connectButton.setEnabled(True)
        self.ui.connectButton.setText('Verbinden')

    def update_ui_connected(self):
        self.__display_connection_status('connected')
        self.ui.connectButton.setEnabled(True)
        self.ui.connectButton.setText('Trennen')

    def update_ui_connecting(self):
        self.__display_connection_status('connecting')
        self.ui.connectButton.setDisabled(True)

    def connect_button_clicked(self):
        if self.serial_connection.is_connected():
            self.__disconnect_pending = True
            self.serial_connection.disconnect_serial()
        else:
            self.update_ui_connecting()
            self.serial_connection.connect_serial(self.ui.portSelectorComboBox.currentText())

def lcd_display(lcd: QLCDNumber, value: float):
    lcd.display('{:5.3f}'.format(value))

class StandardMode(QWidget):
    __set_voltage_req_in_queue: SetVoltageRequest = None
    __read_voltage_req_in_queue: ReadVoltageRequest = None
    __read_current_req_in_queue: ReadCurrentRequest = None

    __active = True  # indicates whether the interface is currently selected

    def __init__(self, channel_number, serial_connection: SerialConnection):
        super(StandardMode, self).__init__()
        # uic.loadUi('standard_mode.ui', self)
        self.ui = Ui_StandardMode()
        self.ui.setupUi(self)
        self.ui.dial.valueChanged.connect(self.dial_value_changed)
        self.previous_dial_value = 0
        self.target_voltage = 0
        self.coarse_increment = 100
        self.fine_increment = 1
        self.min_voltage = 0
        self.max_voltage = 12000

        self.channel_number = channel_number

        self.serial_connection = serial_connection

        if channel_number == 1:
            sv = self.serial_connection.read_voltage_signal_1
            sc = self.serial_connection.read_current_signal_1
        else:
            sv = self.serial_connection.read_voltage_signal_2
            sc = self.serial_connection.read_current_signal_2

        sv.connect(self.read_voltage_response_handler)
        sc.connect(self.read_current_response_handler)
        self.serial_connection.connection_status_change_signal.connect(self.connections_status_changed_handler)

        self.__zero_lcds()

        self.__read_values_timer = QTimer(self)
        self.__read_values_timer.timeout.connect(self.read_values)
        self.__read_values_timer.start(125)

    def dial_value_changed(self):
        val = self.ui.dial.value()
        increase = True
        if self.previous_dial_value >= 9 and val <= 1:
            increase = True
        elif self.previous_dial_value <= 1 and val >= 9:
            increase = False
        else:
            increase = val > self.previous_dial_value

        self.previous_dial_value = self.ui.dial.value()

        if increase:
            if self.ui.coarse_radio_button.isChecked():
                self.target_voltage -= self.target_voltage % self.coarse_increment
                self.target_voltage += self.coarse_increment
            else:
                self.target_voltage += self.fine_increment
            if self.target_voltage > self.max_voltage:
                self.target_voltage = self.max_voltage
        else:
            if self.ui.coarse_radio_button.isChecked():
                self.target_voltage += self.target_voltage % self.coarse_increment
                self.target_voltage -= self.coarse_increment
            else:
                self.target_voltage -= self.fine_increment
            if self.target_voltage < self.min_voltage:
                self.target_voltage = self.min_voltage

        self.set_voltage()

    def set_voltage(self):
        lcd_display(self.ui.set_voltage_lcd, self.target_voltage / 1000.0)
        if self.serial_connection.is_connected():  # self.ui.change_voltage_check_box.isChecked() and
            if self.__set_voltage_req_in_queue is not None and not self.__set_voltage_req_in_queue.sent:
                self.__set_voltage_req_in_queue.update_value(self.target_voltage)
            else:
                self.__set_voltage_req_in_queue = SetVoltageRequest(self.target_voltage, self.channel_number - 1)
                self.serial_connection.send_request(self.__set_voltage_req_in_queue)

    def read_values(self):
        if not self.serial_connection.is_connected():
            return
        if self.channel_number == 1:
            sv = self.serial_connection.read_voltage_signal_1
            sc = self.serial_connection.read_current_signal_1
        else:
            sv = self.serial_connection.read_voltage_signal_2
            sc = self.serial_connection.read_current_signal_2
        if self.__read_voltage_req_in_queue is None or self.__read_voltage_req_in_queue.sent:
            self.__read_voltage_req_in_queue = ReadVoltageRequest(self.channel_number - 1)
            self.serial_connection.send_request(self.__read_voltage_req_in_queue, sv)
        if self.__read_current_req_in_queue is None or self.__read_current_req_in_queue.sent:
            self.__read_current_req_in_queue = ReadCurrentRequest(self.channel_number - 1)
            self.serial_connection.send_request(self.__read_current_req_in_queue, sc)

    def __zero_lcds(self):
        lcd_display(self.ui.set_voltage_lcd, 0.0)
        lcd_display(self.ui.real_voltage_lcd, 0.0)
        lcd_display(self.ui.current_lcd, 0.0)

    def read_voltage_response_handler(self, r: ReadVoltageResponse):
        if not self.__active or not self.serial_connection.is_connected():
            return
        lcd_display(self.ui.real_voltage_lcd, r.voltage / 1000.0)

    def read_current_response_handler(self, r: ReadCurrentResponse):
        if not self.__active or not self.serial_connection.is_connected():
            return
        lcd_display(self.ui.current_lcd, r.current / 1000.0)

    def connections_status_changed_handler(self, status: bool):
        if status is False:
            self.__zero_lcds()

class Channel(QWidget):
    def __init__(self, channel_number: int, serial_connection: SerialConnection, *args, **kwargs):
        super(Channel, self).__init__(*args, **kwargs)
        self.vbox_layout = QVBoxLayout()
        self.setLayout(self.vbox_layout)
        self.mode_combo_box = QComboBox(self)
        self.vbox_layout.addWidget(self.mode_combo_box)

        self.channel_number = channel_number

        self.serial_connection = serial_connection

        self.standard_mode = StandardMode(channel_number, serial_connection)

        self.modes = {
            'Standard': self.load_standard_mode,
            'PWM': self.load_pwm_mode
        }

        for m in self.modes:
            self.mode_combo_box.addItem(m)

        self.load_standard_mode()

    def load_standard_mode(self):
        # self.standard_mode.__init__(self.channel_number)
        self.vbox_layout.insertWidget(1, self.standard_mode)

    def load_pwm_mode(self):
        pass

class MainWindow(QWidget):
    serial_connection = SerialConnection(2)

    def __init__(self, n_channels, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.n_channels = n_channels
        self.setWindowTitle("Spannungsquelle")
        self.grid = QGridLayout()
        self.setLayout(self.grid)
        self.port_selector = PortSelector(self.serial_connection)
        self.grid.addWidget(self.port_selector, 0, 0, 1, n_channels, Qt.AlignTop)
        self.channels = []
        for ch in range(1, n_channels + 1):
            self.channels.append(Channel(ch, self.serial_connection))
            self.grid.addWidget(self.channels[ch - 1], 1, ch - 1)

        self.setFixedSize(self.grid.sizeHint())
        self.serial_connection.start()

app = QApplication(sys.argv)

main_window = MainWindow(n_channels=2)
main_window.show()

app.exec()


import html
import os
import json
import importlib
import time
import re
import sys
import traceback

from sqlalchemy.sql.expression import text, update
import SiestaRobot.modules.sql.users_sql as sql
from sys import argv
from typing import Optional
from telegram import __version__ as peler
from platform import python_version as memek
from SiestaRobot import (
    ALLOW_EXCL,
    CERT_PATH,
    DONATION_LINK,
    LOGGER,
    OWNER_ID,
    PORT,
    SUPPORT_CHAT,
    TOKEN,
    URL,
    WEBHOOK,
    SUPPORT_CHAT,
    dispatcher,
    StartTime,
    telethn,
    pbot,
    updater,
)

# needed to dynamically load modules
# NOTE: Module order is not guaranteed, specify that in the config file!
from SiestaRobot.modules import ALL_MODULES
from SiestaRobot.modules.helper_funcs.chat_status import is_user_admin
from SiestaRobot.modules.helper_funcs.misc import paginate_modules
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ParseMode, Update
from telegram.error import (
    BadRequest,
    ChatMigrated,
    NetworkError,
    TelegramError,
    TimedOut,
    Unauthorized,
)
from telegram.ext import (
    CallbackContext,
    CallbackQueryHandler,
    CommandHandler,
    Filters,
    MessageHandler,
)
from telegram.ext.dispatcher import DispatcherHandlerStop, run_async
from telegram.utils.helpers import escape_markdown
from SiestaRobot.modules.language import gs

def get_readable_time(seconds: int) -> str:
    count = 0
    ping_time = ""
    time_list = []
    time_suffix_list = ["s", "m", "h", "days"]

    while count < 4:
        count += 1
        remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24)
        if seconds == 0 and remainder == 0:
            break
        time_list.append(int(result))
        seconds = int(remainder)

    for x in range(len(time_list)):
        time_list[x] = str(time_list[x]) + time_suffix_list[x]
    if len(time_list) == 4:
        ping_time += time_list.pop() + ", "

    time_list.reverse()
    ping_time += ":".join(time_list)

    return ping_time

SIESTA_IMG = "https://telegra.ph/file/6d3e3ef24f64fb6e35df0.jpg"

DONATE_STRING = """Heya, glad to hear you want to donate!
 You can support the project by contacting @saint_foire \
 Supporting isnt always financial! \
 Those who cannot provide monetary support are welcome to help us develop the bot at ."""

IMPORTED = {}
MIGRATEABLE = []
HELPABLE = {}
STATS = []
USER_INFO = []
DATA_IMPORT = []
DATA_EXPORT = []
CHAT_SETTINGS = {}
USER_SETTINGS = {}

for module_name in ALL_MODULES:
    imported_module = importlib.import_module("SiestaRobot.modules." + module_name)
    if not hasattr(imported_module, "__mod_name__"):
        imported_module.__mod_name__ = imported_module.__name__

    if imported_module.__mod_name__.lower() not in IMPORTED:
        IMPORTED[imported_module.__mod_name__.lower()] = imported_module
    else:
        raise Exception("Can't have two modules with the same name! Please change one")

    if hasattr(imported_module, "helps") and imported_module.helps:
        HELPABLE[imported_module.__mod_name__.lower()] = imported_module

    # Chats to migrate on chat_migrated events
    if hasattr(imported_module, "__migrate__"):
        MIGRATEABLE.append(imported_module)

    if hasattr(imported_module, "__stats__"):
        STATS.append(imported_module)

    if hasattr(imported_module, "__user_info__"):
        USER_INFO.append(imported_module)

    if hasattr(imported_module, "__import_data__"):
        DATA_IMPORT.append(imported_module)

    if hasattr(imported_module, "__export_data__"):
        DATA_EXPORT.append(imported_module)

    if hasattr(imported_module, "__chat_settings__"):
        CHAT_SETTINGS[imported_module.__mod_name__.lower()] = imported_module

    if hasattr(imported_module, "__user_settings__"):
        USER_SETTINGS[imported_module.__mod_name__.lower()] = imported_module

# do not async
def send_help(chat_id, text, keyboard=None):
    if not keyboard:
        keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help"))
    dispatcher.bot.send_message(
        chat_id=chat_id,
        text=text,
        parse_mode=ParseMode.MARKDOWN,
        disable_web_page_preview=True,
        reply_markup=keyboard,
    )

def test(update: Update, context: CallbackContext):
    # pprint(eval(str(update)))
    # update.effective_message.reply_text("Hola tester! _I_ *have* `markdown`", parse_mode=ParseMode.MARKDOWN)
    update.effective_message.reply_text("This person edited a message")
    print(update.effective_message)

def start(update: Update, context: CallbackContext):
    args = context.args
    chat = update.effective_chat
    uptime = get_readable_time((time.time() - StartTime))
    if update.effective_chat.type == "private":
        if len(args) >= 1:
            if args[0].lower() == "help":
                send_help(
                    update.effective_chat.id,
                    text=gs(
                        chat.id,
                        "pm_help_text"
                    ),
                )
            elif args[0].lower().startswith("ghelp_"):
                mod = args[0].lower().split("_", 1)[1]
                if not HELPABLE.get(mod, False):
                    return
                send_help(
                    update.effective_chat.id,
                    HELPABLE[mod].helps,
                    InlineKeyboardMarkup(
                        [
                            [
                                InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="help_back"),
                            ]
                        ]
                    ),
                )

            elif args[0].lower().startswith("stngs_"):
                match = re.match("stngs_(.*)", args[0].lower())
                chat = dispatcher.bot.getChat(match.group(1))

                if is_user_admin(chat, update.effective_user.id):
                    send_settings(match.group(1), update.effective_user.id, False)
                else:
                    send_settings(match.group(1), update.effective_user.id, True)

            elif args[0][1:].isdigit() and "rules" in IMPORTED:
                IMPORTED["rules"].send_rules(update, args[0], from_pm=True)

        else:
            first_name = update.effective_user.first_name
            update.effective_message.reply_text(
                text=gs(chat.id, "pm_start_text").format(
                    escape_markdown(first_name),
                    escape_markdown(uptime),
                    sql.num_users(),
                    sql.num_chats()),
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(text=gs(chat.id, "about_button"), callback_data="siesta_"),
                        ],
                        [
                            InlineKeyboardButton(text=gs(chat.id, "help_button"), callback_data="help_back"),
                            InlineKeyboardButton(text=gs(chat.id, "inline_button"), switch_inline_query_current_chat=""),
                        ],
                        [
                            InlineKeyboardButton(
                                text=gs(chat.id, "add_bot_to_group_button"), url="t.me/Siestaxbot?startgroup=new"),
                        ]
                    ]
                ),
                parse_mode=ParseMode.MARKDOWN,
                timeout=60,
                disable_web_page_preview=False,
            )
    else:
        update.effective_message.reply_text(
            text=gs(chat.id, "group_start_text").format(
                escape_markdown(uptime),
                ),
            parse_mode=ParseMode.MARKDOWN
       )

def error_handler(update, context):
    """Log the error and send a telegram message to notify the developer."""
    # Log the error before we do anything else, so we can see it even if something breaks.
    LOGGER.error(msg="Exception while handling an update:", exc_info=context.error)

    # traceback.format_exception returns the usual python message about an exception, but as a
    # list of strings rather than a single string, so we have to join them together.
    tb_list = traceback.format_exception(
        None, context.error, context.error.__traceback__
    )
    tb = "".join(tb_list)

    # Build the message with some markup and additional information about what happened.
    message = (
        "An exception was raised while handling an update\n"
        "
update = {}
\n\n"
        "
{}
"
    ).format(
        html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False)),
        html.escape(tb),
    )

    if len(message) >= 4096:
        message = message[:4096]
    # Finally, send the message
    context.bot.send_message(chat_id=OWNER_ID, text=message, parse_mode=ParseMode.HTML)

# for test purposes
def error_callback(update: Update, context: CallbackContext):
    error = context.error
    try:
        raise error
    except Unauthorized:
        print("no nono1")
        print(error)
        # remove update.message.chat_id from conversation list
    except BadRequest:
        print("no nono2")
        print("BadRequest caught")
        print(error)

        # handle malformed requests - read more below!
    except TimedOut:
        print("no nono3")
        # handle slow connection problems
    except NetworkError:
        print("no nono4")
        # handle other connection problems
    except ChatMigrated as err:
        print("no nono5")
        print(err)
        # the chat_id of a group has changed, use e.new_chat_id instead
    except TelegramError:
        print(error)
        # handle all other telegram related errors

def help_button(update, context):
    query = update.callback_query
    chat = update.effective_chat
    mod_match = re.match(r"help_module\((.+?)\)", query.data)
    prev_match = re.match(r"help_prev\((.+?)\)", query.data)
    next_match = re.match(r"help_next\((.+?)\)", query.data)
    back_match = re.match(r"help_back", query.data)

    print(query.message.chat.id)

    try:
        if mod_match:
            module = mod_match.group(1)

            # Convert Function To String
            module = module.replace("_", " ")
            help_list = HELPABLE[module].helps(update.effective_chat.id)
            if isinstance(help_list, list):
                help_text = help_list[0]
            elif isinstance(help_list, str):
                help_text = help_list

            # Call The Converted Module
            text = (
                gs(chat.id, "pm_help_module_text").format(
                    HELPABLE[module].__mod_name__
                )
                + help_text
            )
            query.message.edit_text(
                text=text,
                parse_mode=ParseMode.MARKDOWN,
                disable_web_page_preview=True,
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="help_back"),
                        ]
                    ]
                ),
            )

        elif prev_match:
            curr_page = int(prev_match.group(1))
            query.message.edit_text(
                text=gs(chat.id,"pm_help_text"),
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(curr_page - 1, HELPABLE, "help")
                ),
            )

        elif next_match:
            next_page = int(next_match.group(1))
            query.message.edit_text(
                text=gs(chat.id,"pm_help_text"),
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(next_page + 1, HELPABLE, "help")
                ),
            )

        elif back_match:
            query.message.edit_text(
                text=gs(chat.id,"pm_help_text"),
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(0, HELPABLE, "help")
                ),
            )

        # ensure no spinny white circle
        context.bot.answer_callback_query(query.id)
        # query.message.delete()

    except BadRequest:
        pass

def siesta_about_callback(update, context):
    query = update.callback_query
    chat = update.effective_chat
    if query.data == "siesta_":
        query.message.edit_text(
            text=gs(chat.id, "pm_about_text"),
            parse_mode=ParseMode.MARKDOWN,
            disable_web_page_preview=True,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text="Admins", callback_data="siesta_admin"),
                        InlineKeyboardButton(text=gs(chat.id, "notes_button"), callback_data="siesta_notes"),
                    ],
                    [
                        InlineKeyboardButton(text=gs(chat.id, "support_chat_link_button"), callback_data="siesta_support"),
                        InlineKeyboardButton(text="Credits", callback_data="siesta_credit"),
                    ],
                    [
                        InlineKeyboardButton(text=gs(chat.id, "source_button"), url="https://github.com/shiinobu/SiestaRobot"),
                    ],
                    [
                    InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="siesta_back"),
                    ]
                ]
            ),
        )
    elif query.data == "siesta_back":
        first_name = update.effective_user.first_name
        uptime = get_readable_time((time.time() - StartTime))
        query.message.edit_text(
                text=gs(chat.id, "pm_start_text").format(
                    escape_markdown(first_name),
                    escape_markdown(uptime),
                    sql.num_users(),
                    sql.num_chats()),
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(text=gs(chat.id, "about_button"), callback_data="siesta_"),
                        ],
                        [
                            InlineKeyboardButton(text=gs(chat.id, "help_button"), callback_data="help_back"),
                            InlineKeyboardButton(text=gs(chat.id, "inline_button"), switch_inline_query_current_chat=""),
                        ],
                        [
                            InlineKeyboardButton(text=gs(chat.id, "add_bot_to_group_button"), url="t.me/Siestaxbot?startgroup=new"),
                        ]
                    ]
                ),
                parse_mode=ParseMode.MARKDOWN,
                timeout=60,
                disable_web_page_preview=False,
        )

    elif query.data == "siesta_admin":
        query.message.edit_text(
            text=gs(chat.id, "pm_about_admin_text"),
            parse_mode=ParseMode.MARKDOWN,
            disable_web_page_preview=True,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="siesta_"),
                    ]
                ]
            ),
        )

    elif query.data == "siesta_notes":
        query.message.edit_text(
            text=gs(chat.id, "pm_about_notes_text"),
            parse_mode=ParseMode.MARKDOWN,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="siesta_"),
                    ]
                ]
            ),
        )
    elif query.data == "siesta_support":
        query.message.edit_text(
            text=gs(chat.id, "pm_about_support_text"),
            parse_mode=ParseMode.MARKDOWN,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text=gs(chat.id, "support_chat_link_button"), url="t.me/machinaxupport"),
                        InlineKeyboardButton(text=gs(chat.id, "updates_channel_link_button"), url="https://t.me/machinaxupdate"),
                    ],
                    [
                        InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="siesta_"),
                    ]
                ]
            ),
        )

    elif query.data == "siesta_credit":
        query.message.edit_text(
            text=gs(chat.id, "pm_about_credit_text"),
            parse_mode=ParseMode.MARKDOWN,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text="Vain", url="https://github.com/shiinobu"),
                    ],
                    [
                        InlineKeyboardButton(text="Paul Larsen", url="https://github.com/PaulSonOfLars"),
                        InlineKeyboardButton(text="TheHamkerCat", url="https://github.com/TheHamkerCat"),
                    ],
                    [
                        InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="siesta_"),
                    ]
                ]
            ),
        )

def Source_about_callback(update, context):
    query = update.callback_query
    chat = update.effective_chat
    uptime = get_readable_time((time.time() - StartTime))
    if query.data == "source_":
        query.message.edit_text(
            text="๏›› This advance command for Musicplayer."
            "\n\n๏ Command for admins and member can you see with command bellow."
            "\n • `/mhelp` - checking help music module (only in pm bot)"
            "\n • `/msettings` - setting your authorization music module",
            parse_mode=ParseMode.MARKDOWN,
            disable_web_page_preview=True,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="siesta_"),
                    ]
                ]
            ),
        )
    elif query.data == "source_back":
        first_name = update.effective_user.first_name
        query.message.edit_text(
                text=gs(chat.id, "pm_start_text").format(
                    escape_markdown(first_name),
                    escape_markdown(uptime),
                    sql.num_users(),
                    sql.num_chats()),
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(text=gs(chat.id, "about_button"), callback_data="siesta_"),
                        ],
                        [
                            InlineKeyboardButton(text=gs(chat.id, "help_button"), callback_data="help_back"),
                            InlineKeyboardButton(text=gs(chat.id, "inline_button"), switch_inline_query_current_chat=""),
                        ],
                        [
                            InlineKeyboardButton(text=gs(chat.id, "add_bot_to_group_button"), url="t.me/Siestaxbot?startgroup=new"),
                        ]
                    ]
                ),
                parse_mode=ParseMode.MARKDOWN,
                timeout=60,
                disable_web_page_preview=False,
        )

def get_help(update: Update, context: CallbackContext):
    chat = update.effective_chat  # type: Optional[Chat]
    args = update.effective_message.text.split(None, 1)

    # ONLY send help in PM
    if chat.type != chat.PRIVATE:
        if len(args) >= 2 and any(args[1].lower() == x for x in HELPABLE):
            module = args[1].lower()
            moduls = module.capitalize()
            update.effective_message.reply_text(
                text=gs(chat.id, "group_help_modules_text").format(
                    escape_markdown(moduls),
                    ),
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(
                                text=gs(chat.id, "group_help_button"),
                                url="t.me/{}?start=ghelp_{}".format(
                                    context.bot.username, module
                                ),
                            )
                        ]
                    ]
                ),
                parse_mode=ParseMode.MARKDOWN,
            )
            return
        update.effective_message.reply_text(
            text=gs(chat.id, "group_help_text"),
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(
                            text=gs(chat.id, "group_help_button"),
                            url="t.me/{}?start=help".format(context.bot.username),
                        )
                    ]
                ]
            ),
        )
        return

    elif len(args) >= 2 and any(args[1].lower() == x for x in HELPABLE):
        module = args[1].lower()
        text = (
            "Here is the available help for the *{}* module:\n".format(
                HELPABLE[module].__mod_name__
            )
            + HELPABLE[module].helps
        )
        send_help(
            chat.id,
            text,
            InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(text=gs(chat.id, "back_button"), callback_data="help_back"),
                    ]
                ]
            ),
        )

    else:
        send_help(chat.id, (gs(chat.id, "pm_help_text")))

def send_settings(chat_id, user_id, user=False):
    if user:
        if USER_SETTINGS:
            settings = "\n\n".join(
                "*{}*:\n{}".format(mod.__mod_name__, mod.__user_settings__(user_id))
                for mod in USER_SETTINGS.values()
            )
            dispatcher.bot.send_message(
                user_id,
                text=gs(chat_id, "pm_settings_personal_text") + "\n\n" + settings,
                parse_mode=ParseMode.MARKDOWN,
            )

        else:
            dispatcher.bot.send_message(
                user_id,
                "Seems like there aren't any user specific settings available :'(",
                parse_mode=ParseMode.MARKDOWN,
            )

    else:
        if CHAT_SETTINGS:
            chat_name = dispatcher.bot.getChat(chat_id).title
            dispatcher.bot.send_message(
                user_id,
                text=gs(chat_id, "pm_settings_group_text").format(
                    chat_name
                ),
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(0, CHAT_SETTINGS, "stngs", chat=chat_id)
                ),
                parse_mode=ParseMode.MARKDOWN,
            )
        else:
            dispatcher.bot.send_message(
                user_id,
                "Seems like there aren't any chat settings available :'(\nSend this "
                "in a group chat you're admin in to find its current settings!",
                parse_mode=ParseMode.MARKDOWN,
            )

def settings_button(update: Update, context: CallbackContext):
    query = update.callback_query
    user = update.effective_user
    chat = update.effective_chat
    bot = context.bot
    mod_match = re.match(r"stngs_module\((.+?),(.+?)\)", query.data)
    prev_match = re.match(r"stngs_prev\((.+?),(.+?)\)", query.data)
    next_match = re.match(r"stngs_next\((.+?),(.+?)\)", query.data)
    back_match = re.match(r"stngs_back\((.+?)\)", query.data)
    try:
        if mod_match:
            chat_id = mod_match.group(1)
            module = mod_match.group(2)
            chat = bot.get_chat(chat_id)
            text = gs(chat.id, "pm_settings_groups_text").format(
                escape_markdown(chat.title), CHAT_SETTINGS[module].__mod_name__
            ) + CHAT_SETTINGS[module].__chat_settings__(chat_id, user.id)
            query.message.reply_text(
                text=text,
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(
                                text=gs(chat.id, "back_button"),
                                callback_data="stngs_back({})".format(chat_id),
                            )
                        ]
                    ]
                ),
            )

        elif prev_match:
            chat_id = prev_match.group(1)
            curr_page = int(prev_match.group(2))
            chat = bot.get_chat(chat_id)
            query.message.reply_text(
                text=gs(chat.id, "pm_settings_groupss_text").format(chat.title),
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(
                        curr_page - 1, CHAT_SETTINGS, "stngs", chat=chat_id
                    )
                ),
            )

        elif next_match:
            chat_id = next_match.group(1)
            next_page = int(next_match.group(2))
            chat = bot.get_chat(chat_id)
            query.message.reply_text(
                text=gs(chat.id, "pm_settings_groupss_text").format(chat.title),
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(
                        next_page + 1, CHAT_SETTINGS, "stngs", chat=chat_id
                    )
                ),
            )

        elif back_match:
            chat_id = back_match.group(1)
            chat = bot.get_chat(chat_id)
            query.message.reply_text(
                text=gs(chat.id, "pm_settings_groupss_text").format(escape_markdown(chat.title)),
                parse_mode=ParseMode.MARKDOWN,
                reply_markup=InlineKeyboardMarkup(
                    paginate_modules(0, CHAT_SETTINGS, "stngs", chat=chat_id)
                ),
            )

        # ensure no spinny white circle
        bot.answer_callback_query(query.id)
        query.message.delete()
    except BadRequest as excp:
        if excp.message not in [
            "Message is not modified",
            "Query_id_invalid",
            "Message can't be deleted",
        ]:
            LOGGER.exception("Exception in settings buttons. %s", str(query.data))

def get_settings(update: Update, context: CallbackContext):
    chat = update.effective_chat  # type: Optional[Chat]
    user = update.effective_user  # type: Optional[User]
    msg = update.effective_message  # type: Optional[Message]

    # ONLY send settings in PM
    if chat.type != chat.PRIVATE:
        if is_user_admin(chat, user.id):
            text = gs(chat.id, "group_settings_text")
            msg.reply_text(
                text,
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(
                                text=gs(chat.id, "group_settings_button"),
                                url="t.me/{}?start=stngs_{}".format(
                                    context.bot.username, chat.id
                                ),
                            )
                        ]
                    ]
                ),
            )
        else:
            text = "Click here to check your settings."

    else:
        send_settings(chat.id, user.id, True)

def donate(update: Update, context: CallbackContext):
    user = update.effective_message.from_user
    chat = update.effective_chat  # type: Optional[Chat]
    bot = context.bot
    if chat.type == "private":
        update.effective_message.reply_text(
            DONATE_STRING, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True
        )

        if OWNER_ID != 945137470:
            update.effective_message.reply_text(
                "I'm free for everyone ❤ If you wanna make me smile, just join"
                "[My Channel]({})".format(DONATION_LINK),
                parse_mode=ParseMode.MARKDOWN,
            )
    else:
        try:
            bot.send_message(
                user.id,
                DONATE_STRING,
                parse_mode=ParseMode.MARKDOWN,
                disable_web_page_preview=True,
            )

            update.effective_message.reply_text(
                "I've PM'ed you about donating to my creator!"
            )
        except Unauthorized:
            update.effective_message.reply_text(
                "Contact me in PM first to get donation information."
            )

def migrate_chats(update: Update, context: CallbackContext):
    msg = update.effective_message  # type: Optional[Message]
    if msg.migrate_to_chat_id:
        old_chat = update.effective_chat.id
        new_chat = msg.migrate_to_chat_id
    elif msg.migrate_from_chat_id:
        old_chat = msg.migrate_from_chat_id
        new_chat = update.effective_chat.id
    else:
        return

    LOGGER.info("Migrating from %s, to %s", str(old_chat), str(new_chat))
    for mod in MIGRATEABLE:
        mod.__migrate__(old_chat, new_chat)

    LOGGER.info("Successfully migrated!")
    raise DispatcherHandlerStop

def main():

    if SUPPORT_CHAT is not None and isinstance(SUPPORT_CHAT, str):
        try:
            dispatcher.bot.sendMessage(
                f"@{SUPPORT_CHAT}",
                f"""**Yes Darling ❤, i am activated!**""",
                parse_mode=ParseMode.MARKDOWN
            )
        except Unauthorized:
            LOGGER.warning(
                "Bot isnt able to send message to support_chat, go and check!"
            )
        except BadRequest as e:
            LOGGER.warning(e.message)

    test_handler = CommandHandler("test", test, run_async=True)
    start_handler = CommandHandler("start", start, run_async=True)

    help_handler = CommandHandler("help", get_help, run_async=True)
    help_callback_handler = CallbackQueryHandler(
        help_button, pattern=r"help_.*", run_async=True
    )

    settings_handler = CommandHandler("settings", get_settings, run_async=True)
    settings_callback_handler = CallbackQueryHandler(
        settings_button, pattern=r"stngs_", run_async=True
    )

    about_callback_handler = CallbackQueryHandler(
        siesta_about_callback, pattern=r"siesta_", run_async=True
    )

    source_callback_handler = CallbackQueryHandler(
        Source_about_callback, pattern=r"source_", run_async=True
    )

    donate_handler = CommandHandler("donate", donate, run_async=True)
    migrate_handler = MessageHandler(
        Filters.status_update.migrate, migrate_chats, run_async=True
    )

    dispatcher.add_handler(test_handler)
    dispatcher.add_handler(start_handler)
    dispatcher.add_handler(help_handler)
    dispatcher.add_handler(about_callback_handler)
    dispatcher.add_handler(source_callback_handler)
    dispatcher.add_handler(settings_handler)
    dispatcher.add_handler(help_callback_handler)
    dispatcher.add_handler(settings_callback_handler)
    dispatcher.add_handler(migrate_handler)
    dispatcher.add_handler(donate_handler)

    dispatcher.add_error_handler(error_callback)

    if WEBHOOK:
        LOGGER.info("Using webhooks.")
        updater.start_webhook(listen="0.0.0.0", port=PORT, url_path=TOKEN)

        if CERT_PATH:
            updater.bot.set_webhook(url=URL + TOKEN, certificate=open(CERT_PATH, "rb"))
        else:
            updater.bot.set_webhook(url=URL + TOKEN)

    else:
        LOGGER.info("Using long polling.")
        updater.start_polling(timeout=15, read_latency=4, drop_pending_updates=True)

    if len(argv) not in (1, 3, 4):
        telethn.disconnect()
    else:
        telethn.run_until_disconnected()

    updater.idle()

if __name__ == "__main__":
    LOGGER.info("Successfully loaded modules: " + str(ALL_MODULES))
    telethn.start(bot_token=TOKEN)
    pbot.start()
    main()


import hashlib
import inspect
import threading
import types
from typing import List, Tuple, Optional, Any, Union

from streamlit import util
from streamlit.logger import get_logger
from .cache_errors import (
    UnhashableParamError,
    CacheType,
    UnhashableTypeError,
)
from .hashing import update_hash

_LOGGER = get_logger(__name__)

def make_value_key(
    cache_type: CacheType, func: types.FunctionType, *args, **kwargs
) -> str:
    """Create the key for a value within a cache.

    This key is generated from the function's arguments. All arguments
    will be hashed, except for those named with a leading "_".

    Raises
    ------
    StreamlitAPIException
        Raised (with a nicely-formatted explanation message) if we encounter
        an un-hashable arg.
    """

    # Create a (name, value) list of all *args and **kwargs passed to the
    # function.
    arg_pairs: List[Tuple[Optional[str], Any]] = []
    for arg_idx in range(len(args)):
        arg_name = _get_positional_arg_name(func, arg_idx)
        arg_pairs.append((arg_name, args[arg_idx]))

    for kw_name, kw_val in kwargs.items():
        # **kwargs ordering is preserved, per PEP 468
        # https://www.python.org/dev/peps/pep-0468/, so this iteration is
        # deterministic.
        arg_pairs.append((kw_name, kw_val))

    # Create the hash from each arg value, except for those args whose name
    # starts with "_". (Underscore-prefixed args are deliberately excluded from
    # hashing.)
    args_hasher = hashlib.new("md5")
    for arg_name, arg_value in arg_pairs:
        if arg_name is not None and arg_name.startswith("_"):
            _LOGGER.debug("Not hashing %s because it starts with _", arg_name)
            continue

        try:
            update_hash(
                (arg_name, arg_value),
                hasher=args_hasher,
                cache_type=cache_type,
            )
        except UnhashableTypeError as exc:
            raise UnhashableParamError(cache_type, func, arg_name, arg_value, exc)

    value_key = args_hasher.hexdigest()
    _LOGGER.debug("Cache key: %s", value_key)

    return value_key

def make_function_key(cache_type: CacheType, func: types.FunctionType) -> str:
    """Create the unique key for a function's cache.

    A naive implementation would involve simply creating the cache object
    right in the wrapper, which in a normal Python script would be executed
    only once. But in Streamlit, we reload all modules related to a user's
    app when the app is re-run, which means that - among other things - all
    function decorators in the app will be re-run, and so any decorator-local
    objects will be recreated.

    Furthermore, our caches can be destroyed and recreated (in response to
    cache clearing, for example), which means that retrieving the function's
    cache in the decorator (so that the wrapped function can save a lookup)
    is incorrect: the cache itself may be recreated between
    decorator-evaluation time and decorated-function-execution time. So we
    must retrieve the cache object *and* perform the cached-value lookup
    inside the decorated function.
    """
    func_hasher = hashlib.new("md5")

    # Include the function's __module__ and __qualname__ strings in the hash.
    # This means that two identical functions in different modules
    # will not share a hash; it also means that two identical *nested*
    # functions in the same module will not share a hash.
    update_hash(
        (func.__module__, func.__qualname__),
        hasher=func_hasher,
        cache_type=cache_type,
    )

    # Include the function's source code in its hash. If the source code can't
    # be retrieved, fall back to the function's bytecode instead.
    source_code: Union[str, types.CodeType]
    try:
        source_code = inspect.getsource(func)
    except OSError as e:
        _LOGGER.debug(
            "Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}",
            e,
        )
        source_code = func.__code__

    update_hash(
        source_code,
        hasher=func_hasher,
        cache_type=cache_type,
    )

    cache_key = func_hasher.hexdigest()
    return cache_key

def _get_positional_arg_name(func: types.FunctionType, arg_index: int) -> Optional[str]:
    """Return the name of a function's positional argument.

    If arg_index is out of range, or refers to a parameter that is not a
    named positional argument (e.g. an *args, **kwargs, or keyword-only param),
    return None instead.
    """
    if arg_index < 0:
        return None

    params: List[inspect.Parameter] = list(inspect.signature(func).parameters.values())
    if arg_index >= len(params):
        return None

    if params[arg_index].kind in (
        inspect.Parameter.POSITIONAL_OR_KEYWORD,
        inspect.Parameter.POSITIONAL_ONLY,
    ):
        return params[arg_index].name

    return None

class ThreadLocalCacheInfo(threading.local):
    """A thread-local counter that's incremented when we enter a cache function
    and decremented when we exit.
    """

    def __init__(self):
        self.cached_func_stack: List[types.FunctionType] = []
        self.suppress_st_function_warning = 0

    def __repr__(self) -> str:
        return util.repr_(self)


''' Test the module :py:mod:`bp.blocks`.
'''
import datetime
import unittest
import asn1
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from bp.encoding.fields import (EidField)
from bp.encoding.blocks import PrimaryBlock, CanonicalBlock
from bp.util import BundleContainer
from bp.encoding.bpsec import (TargetResultList, TypeValuePair,
                               BlockIntegrityBlock)
from bp.config import Config
from bp.agent import Agent
from bp.app.bpsec import Bpsec, BPSEC_COSE_CONTEXT_ID

class TestBpsecCoseSign(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        from dbus.mainloop.glib import DBusGMainLoop
        # Must run before connection or real main loop is constructed
        DBusGMainLoop(set_as_default=True)

    def setUp(self):
        super().setUp()

        config = Config()
        config.node_id = 'dtn://node/'
        self._bp = Agent(config)
        self._app = self._bp._app['bpsec']

    def _dummy_ca_cert(self, ca_key):
        ca_name = x509.Name([
            x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, 'CA'),
        ])
        nowtime = datetime.datetime.now(datetime.timezone.utc)
        cert = x509.CertificateBuilder().subject_name(
            ca_name
        ).issuer_name(
            ca_name
        ).public_key(
            ca_key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            nowtime
        ).not_valid_after(
            nowtime + datetime.timedelta(days=1)
        ).add_extension(
            x509.BasicConstraints(ca=True, path_length=1),
            critical=True,
        ).add_extension(
            x509.KeyUsage(
                digital_signature=False,
                content_commitment=False,
                key_encipherment=False,
                data_encipherment=False,
                key_agreement=False,
                key_cert_sign=True,
                crl_sign=True,
                encipher_only=False,
                decipher_only=False,
            ),
            critical=False,
        ).add_extension(
            x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()),
            critical=False,
        ).add_extension(
            x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
            critical=False,
        ).sign(ca_key, hashes.SHA256(), backend=default_backend())
        return cert

    def _dummy_end_cert(self, ca_key, ca_cert, end_key):
        nowtime = datetime.datetime.now(datetime.timezone.utc)
        eid_enc = asn1.Encoder()
        eid_enc.start()
        eid_enc.write(self._app._config.node_id.encode('ascii'), asn1.Numbers.IA5String)
        sans = [
            x509.OtherName(
                x509.oid.ObjectIdentifier('1.3.6.1.5.5.7.8.11'),  # id-on-bundleEID
                eid_enc.output()
            )
        ]
        cert = x509.CertificateBuilder().subject_name(
            x509.Name([
                x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, 'end-entity'),
            ])
        ).issuer_name(
            ca_cert.issuer
        ).public_key(
            end_key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            nowtime
        ).not_valid_after(
            nowtime + datetime.timedelta(days=1)
        ).add_extension(
            x509.BasicConstraints(ca=False, path_length=None),
            critical=True,
        ).add_extension(
            x509.SubjectAlternativeName(sans),
            critical=False,
        ).add_extension(
            x509.KeyUsage(
                digital_signature=True,
                content_commitment=False,
                key_encipherment=False,
                data_encipherment=False,
                key_agreement=False,
                key_cert_sign=False,
                crl_sign=False,
                encipher_only=False,
                decipher_only=False,
            ),
            critical=False,
        ).add_extension(
            x509.ExtendedKeyUsage([
                x509.oid.ObjectIdentifier('1.3.6.1.5.5.7.3.35')  # id-kp-bundleSecurity
            ]),
            critical=False,
        ).add_extension(
            x509.SubjectKeyIdentifier.from_public_key(end_key.public_key()),
            critical=False,
        ).add_extension(
            x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
            critical=False,
        ).sign(ca_key, hashes.SHA256(), backend=default_backend())
        return cert

    def _dummy_ctr(self):
        ''' Generate a dummy bundle container.
        '''
        ctr = BundleContainer()
        ctr.bundle.primary = PrimaryBlock(
            destination="dtn://other/svc",
        )
        ctr.add_block(CanonicalBlock(
            type_code=1,
            btsd=b'hi',
        ))
        self._bp._apply_primary(ctr)
        return ctr

    def test_apply_bib_ec2(self):
        ca_key = ec.generate_private_key(ec.SECP256R1, backend=default_backend())
        ca_cert = self._dummy_ca_cert(ca_key)
        end_key = ec.generate_private_key(ec.SECP256R1, backend=default_backend())  # Curve for COSE ES256
        end_cert = self._dummy_end_cert(ca_key, ca_cert, end_key)

        ctx = self._app._contexts[BPSEC_COSE_CONTEXT_ID]
        ctx._ca_certs = [ca_cert]
        ctx._cert_chain = [end_cert]
        ctx._priv_key = end_key

        ctr = self._dummy_ctr()
        self._app._apply_bib(ctr)

        bibs = ctr.block_type(BlockIntegrityBlock)
        self.assertNotEqual([], bibs)
        bib = bibs[0]
        self.assertEqual([1], bib.payload.targets)

        ctr.record_action('deliver')
        self.assertFalse(self._app._verify_bib(ctr))
        self.assertEqual({'deliver'}, ctr.actions.keys())

    def test_apply_bib_rsa(self):
        ca_key = rsa.generate_private_key(0x10001, 1024, backend=default_backend())
        ca_cert = self._dummy_ca_cert(ca_key)
        end_key = rsa.generate_private_key(0x10001, 1024, backend=default_backend())
        end_cert = self._dummy_end_cert(ca_key, ca_cert, end_key)

        ctx = self._app._contexts[BPSEC_COSE_CONTEXT_ID]
        ctx._ca_certs = [ca_cert]
        ctx._cert_chain = [end_cert]
        ctx._priv_key = end_key

        ctr = self._dummy_ctr()
        self._app._apply_bib(ctr)

        bibs = ctr.block_type(BlockIntegrityBlock)
        self.assertNotEqual([], bibs)
        bib = bibs[0]
        self.assertEqual([1], bib.payload.targets)

        ctr.record_action('deliver')
        self.assertFalse(self._app._verify_bib(ctr))
        self.assertEqual({'deliver'}, ctr.actions.keys())


import smtplib
import sys
import datetime
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_email(toaddr):
    fromaddr = "ssirisha639@gmail.com"
    msg = MIMEMultipart()
    msg['Subject'] = "GREETINGS!!!!"
    msg['From'] = fromaddr
    msg['To'] = ",".join(toaddr)

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.ehlo()
    server.starttls()
    server.login("ssirisha639@gmail.com", "YOUR PASSWORD")

    body = "WISH YOU A VERY HAPPY BIRTHDAY DEAR!!\nMAY GOD BLESS YOU WITH ABUNDANT LOVE AND HAPPINESS THIS BIRTHDAY\n" \
       "ALL THE BEST FOR YOUR FUTURE ENDEAVOURS.\n" \
       "REGARDS SAI SIRISHA :)"
    msg.attach(MIMEText(body, 'plain'))
    text = msg.as_string()
    server.sendmail(fromaddr,toaddr, text)
    server.quit()

def send_to(cy,date):
    file = open("receiver.txt",'r')
    toaddr = []
    for line in file:
        key,val = line.split(":")
        if str(str(cy)+'-'+key).strip() == str(date).strip():
            if ',' in val:
                toaddr = [i.strip() for i in val.split(",")]
            else:
                toaddr.append(val.strip())
    send_email(toaddr)

def today_time():
    now = datetime.datetime.now()
    today_date = datetime.date.today()  # today's date

    cy = now.year  # current year
    send_to(cy,today_date)

if __name__ == "__main__":
    sys.exit(today_time())


import json
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()

def append_meta(request, text, metadata_only = False):
  meta = {
    'url': request.url,
    'title': request.html.find('title', first=True).text if len(request.html.find('title')) != 0 else '',
    'description': request.html.find('meta[name="description"]', first=True).attrs.get('content') if  request.html.find('meta[name="description"]', first=True) != None else '',
    'published':request.html.find('meta[property="article:published_time"]', first=True).attrs.get('content') if request.html.find('meta[property="article:published_time"]', first=True) != None else datetime.today().strftime('%Y-%m-%d %H:%M:%S'),
    'wordCount': len(text.split(' ')),
  }
  return "Article JSON Metadata:\n"+json.dumps(meta)+"\n\n\nText Content:\n" + text if metadata_only == False else meta


# 将id对应的频次画图
import pandas as pd
from matplotlib import pyplot as plt

def plot_frequency(id_class):
	df_from = pd.read_csv(id_class+'_from_frequency.csv')
	df_to = pd.read_csv(id_class+'_to_frequency.csv')
	#
	# print(df_from['id'])
	# print(df_to['id'])
	# exit(1)
	if df_from['id'].all() == df_to['id'].all():
		X = df_from['id']
		Y_from = df_from['frequency']
		Y_to = df_to['frequency']

		plt.xlim(1000, 1500)
		plt.ylim(0, 400)

		for i in range(len(Y_to)):
			if Y_to[i] > 400:
				Y_to[i] = 400

		plt.yticks(fontsize=15)
		plt.xticks(fontsize=15)

		plt.title('from_to_frequency of '+id_class, fontsize=25)

		plt.xlabel('id', fontsize=18)
		plt.ylabel('frequency', fontsize=18)
		plt.plot(X, Y_from, color='green', linestyle='-', label='From_frequency')
		plt.plot(X, Y_to, color='red', linestyle='-', label='To_frequency')

		plt.legend(loc='higher left', fontsize=10)

		plt.show()

if __name__=='__main__':
	id_class = 'dev'
	plot_frequency(id_class)


import re
import youtube_dl
import streamlit as st
import requests
from pydub import AudioSegment
import time

# CONSTANTS and SETTINGS
ydl_opts = {
   'format': 'bestaudio/best',
   'postprocessors': [{
       'key': 'FFmpegExtractAudio',
       'preferredcodec': 'mp3',
       'preferredquality': '192',
   }],
   'ffmpeg-location': './',
   'outtmpl': "./%(id)s.%(ext)s",
   # 'nocheckcertificate': True
}

transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
upload_endpoint = 'https://api.assemblyai.com/v2/upload'

CHUNK_SIZE = 5242880

def get_vid(link):
    _id = link.strip()
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        return ydl.extract_info(_id)

def read_file(filename):
    with open(filename, 'rb') as _file:
        while True:
            data = _file.read(CHUNK_SIZE)
            if not data:
                break
            yield data

# a function to upload a given file to AssemblyAI's servers
def upload_file(file, auth):
    headers_auth_only = {'authorization': auth}
    upload_response = requests.post(
        upload_endpoint,
        headers=headers_auth_only,
        data=read_file(file)
	)
    # print('Uploaded to', upload_response.json()['upload_url'])
    return upload_response.json()['upload_url']

# a function that takes a youtube link, downloads the video, uploads it to AssemblyAI's servers and transcribes it
@st.cache
def transcribe_from_link(link, auth):
	headers_auth_only = {'authorization': auth}
    # download the audio of the YouTube video locally
	meta = get_vid(link)
	save_location = meta['id'] + ".mp3"
	# start the transcription of the audio file
	transcript_request = {
		'audio_url': upload_file(save_location, auth),
        "filter_profanity": True
	}

	transcript_response = requests.post(transcript_endpoint, json=transcript_request, headers=headers_auth_only)

	# this is the id of the file that is being transcribed in the AssemblyAI servers
	# we will use this id to access the completed transcription
	transcript_id = transcript_response.json()['id']
	polling_endpoint = transcript_endpoint + "/" + transcript_id

	return polling_endpoint, save_location

# return the status of a given transcript
def get_status(polling_endpoint, auth):
    headers_auth_only = {'authorization': auth}
    polling_response = requests.get(polling_endpoint, headers=headers_auth_only)
    return polling_response.json()['status']

# repeatedly checks the status of a given transcript until the status is completed or error
def poll(polling_endpoint, auth):
    status = get_status(polling_endpoint, auth)
    while status not in ["error", "completed"]:
        time.sleep(10)
        status = get_status(polling_endpoint, auth)

def censor_profanity(transcript, audio_file, beep):
    sound = AudioSegment.from_file(audio_file)

    # create output audio
    newsound = AudioSegment.empty()
    #time stamp of the last profanity: 0 by default
    last_vulgarity = 0
    for word in transcript['words']:
        if re.search("[a-zA-Z]\*+", word['text']):
            newsound += sound[last_vulgarity:word['start']]
            newsound += beep[:word['end']-word['start']]
            last_vulgarity = word['end']
    newsound += sound[last_vulgarity:]
    newsound.export("final.mp3", format="mp3")
    return "final.mp3"


"""
Various helpers that are based on Python tokenization

Note that the tokenize module starts the rows at 1 and prompt_toolkit starts
them at 0.
"""

import io
from itertools import tee, chain
from tokenize import tokenize, TokenError
from tokenize import (STRING, COLON, AT, ENDMARKER, DEDENT, NAME, NEWLINE,
                      ENCODING)
import ast
import re
import warnings

braces = {
    '(': ')',
    '[': ']',
    '{': '}',
    }

def tokenize_string(s, tokenizer=None):
    """
    Generator of tokens from the string s

    tokenizer should be 'tokenize', 'parso', or None (the default, which is
    the same as 'tokenize').
    """
    if tokenizer is None:
        tokenizer = 'tokenize'
    if tokenizer == 'tokenize':
        yield from tokenize(io.BytesIO(s.encode('utf-8')).readline)
    elif tokenizer == 'parso':
        from parso.utils import parse_version_string
        from parso.python.tokenize import tokenize as parso_tokenize

        for tok in parso_tokenize(s, version_info=parse_version_string()):
            # Make the parso tokens compatible with tokenize
            tok.start = tok.start_pos
            tok.end = tok.end_pos

            yield tok
    else:
        raise ValueError("The tokenizer keyword argument should be 'tokenize' or 'parso'")

def matching_parens(s, allow_intermediary_mismatches=True, tokenizer=None):
    """
    Find matching and mismatching parentheses and braces

    s should be a string of (partial) Python code.

    Returns a tuple (matching, mismatching).

    matching is a list of tuples of matching TokenInfo objects for matching
    parentheses/braces.

    mismatching is a list of TokenInfo objects for mismatching
    parentheses/braces.

    allow_intermediary_mismatches can be True (the default), or False. If it
    is True, an opening brace can still be considered matching if it is closed
    with the wrong brace but later closed with the correct brace. If it is
    False, once an opening brace is closed with the wrong brace it---and any
    unclosed braces before it---cannot be matched.

    For example, consider '[ { ] }'. If allow_intermediary_mismatches is
    False, all the braces are considered mismatched.

        >>> matching, mismatching = matching_parens('[ { ] }',
        ...     allow_intermediary_mismatches=False)
        >>> matching
        []
        >>> mismatching
        [TokenInfo(..., string='[', ...),
         TokenInfo(..., string='{', ...),
         TokenInfo(..., string=']', ...),
         TokenInfo(..., string='}', ...)]

    However, if it is True, the { and } are considered matching.

        >>> matching, mismatching = matching_parens('[ { ] }',
        ...     allow_intermediary_mismatches=True)
        >>> matching
        [(TokenInfo(..., string='{', ...), TokenInfo(..., string='}', ...))]
        >>> mismatching
        [TokenInfo(..., string='[', ...),
         TokenInfo(..., string=']', ...)]

    Furthermore, with '[ { ] } ]' only the middle ] will be considered
    mismatched (with False, all would be mismatched).

        >>> matching, mismatching = matching_parens('[ { ] } ]',
        ...     allow_intermediary_mismatches=True)
        >>> matching
        [(TokenInfo(..., string='[', ...), TokenInfo(..., string=']', start_pos=(1, 8), ...)),
         (TokenInfo(..., string='{', ...), TokenInfo(..., string='}', ...))]
        >>> mismatching
        [TokenInfo(..., string=']', start_pos=(1, 4), ...)]

        >>> matching, mismatching = matching_parens('[ { ] } ]',
        ...     allow_intermediary_mismatches=False)
        >>> matching
        []
        >>> mismatching
        [TokenInfo(..., string='[', ...),
         TokenInfo(..., string='{', ...),
         TokenInfo(..., string=']', ...),
         TokenInfo(..., string='}', ...),
         TokenInfo(..., string=']', ...)]

    allow_intermediary_mismatches=False is a more technically correct version,
    but allow_intermediary_mismatches=True may provide more useful feedback if
    mismatching braces are highlighted, as it is more likely to only highlight
    the "mistake" braces.

    tokenizer should be 'tokenize' or 'parso'. By default it is 'parso'. The
    parso tokenizer is able to handle braces inside of f-string expressions,
    whereas the 'tokenize' tokenizer is not.

        >>> matching_parens('f"{()}"', tokenizer='parso')
        ([(TokenInfo(type=OP, string='{', start_pos=(1, 2), prefix=''),
           TokenInfo(type=OP, string='}', start_pos=(1, 5), prefix='')),
          (TokenInfo(type=OP, string='(', start_pos=(1, 3), prefix=''),
           TokenInfo(type=OP, string=')', start_pos=(1, 4), prefix=''))],
        [])
        >>> matching_parens('f"{()}"', tokenizer='tokenize')
        ([], [])

    Example:

        >>> matching, mismatching = matching_parens("('a', {(1, 2)}, ]")
        >>> matching
        [(TokenInfo(..., string='{', ...), TokenInfo(..., string='}', ...)),
         (TokenInfo(..., string='(', ...), TokenInfo(..., string=')', ...))]
        >>> mismatching
        [TokenInfo(..., string='(', ...), TokenInfo(..., string=']', ...)]

    """
    if tokenizer is None:
        try:
            import parso;parso
            tokenizer = 'parso'
        except ImportError:
            warnings.warn("Could not import parso. Using tokenize for parens highlighting")
            tokenizer = 'tokenize'
    if tokenizer == 'parso':
        from parso.python.tokenize import ERRORTOKEN, OP
    else:
        from tokenize import ERRORTOKEN, OP
    stack = []
    matching = []
    mismatching = []
    try:
        for tok in tokenize_string(s, tokenizer=tokenizer):
            typ = tok.type
            string = tok.string
            if typ == ERRORTOKEN:
                # There is an unclosed string. If we do not break here,
                # tokenize will tokenize the stuff after the string delimiter.
                break
            elif typ == OP and string in braces:
                stack.append(tok)
            elif typ == OP and string in braces.values():
                if not stack:
                    mismatching.append(tok)
                    continue
                prevtok = stack.pop()
                if braces[prevtok.string] == string:
                    matching.append((prevtok, tok))
                else:
                    if allow_intermediary_mismatches:
                        stack.append(prevtok)
                    else:
                        mismatching.insert(0, prevtok)
                    mismatching.append(tok)
            else:
                continue
    except TokenError:
        pass
    except IndentationError:
        pass

    matching.reverse()

    # Anything remaining on the stack is mismatching. Keep the mismatching
    # list in order.
    stack.reverse()
    mismatching = stack + mismatching
    return matching, mismatching

def inside_string(s, row, col, include_quotes=False):
    """
    Returns True if row, col is inside a string in s, False otherwise.

    row starts at 1 and col starts at 0.

    If include_quotes=True, the quote characters and any prefix characters
    (like r, b, f, or u) are counted as part of the string. The default is
    False.

    """
    from tokenize import ERRORTOKEN
    def _offsets(string):
        # The offsets for the quote characters. The inside part of the
        # string will be tokval[start_margin: -end_margin]
        start_offset = end_offset = 0
        if not include_quotes:
            # Handle things like r'...' and rb'...'. There can be at most
            # two prefix characters. Since we are "deleting" characters,
            # shift the column that we check to the left. A negative
            # column would mean we are on a line below the opening of the
            # string, in which case start[0] will be less than row anyway.
            if string[:2].isalpha():
                start_offset += 2
            elif string[:1].isalpha():
                start_offset += 1

            # Figure out if the string is single- or triple-quoted.
            # Since tokenize only tokenizes complete valid string literals
            # as STRING, ""... must be a triple quoted string, unless it
            # is the empty string
            assert string[start_offset] in '"\'', tokval
            if tokval[start_offset] == tokval[start_offset+1] and len(string[start_offset:]) > 2:
                start_offset += 3
                end_offset += 3
            else:
                start_offset += 1
                end_offset += 1

        return (start_offset, end_offset)

    try:
        name_mark = False
        for toknum, tokval, start, end, line in tokenize_string(s):
            if toknum == NAME and include_quotes and start <= (row, col) < end:
                # Something like r'... that is unclosed will tokenize as NAME,
                # ERRORTOKEN. Mark here to check if the next token is
                # ERRORTOKEN.
                name_mark = tokval
                continue
            if toknum == ERRORTOKEN and tokval[0] in '"\'':
                if name_mark:
                    # (row, col) is on the name before the unclosed error
                    # string.
                    # Check if the previous token + string is a valid string
                    # prefix.

                    # name_mark not False implies include_quotes == True
                    try:
                        ast.literal_eval(name_mark + '""')
                    except SyntaxError:
                        return False
                    else:
                        return True
                # There is an unclosed string. We haven't gotten to the
                # position yet, so it must be inside this string
                start_offset = 0 if include_quotes else 1
                return (start[0], start[1] + start_offset) <= (row, col)
            elif name_mark:
                return False
            if start <= (row, col) < end:
                if not toknum == STRING:
                    return False

                start_offset, end_offset = _offsets(tokval)
                return (start[0], start[1] + start_offset) <= (row, col) < (end[0], end[1] - end_offset)

    except TokenError as e:
        # Uncompleted docstring or braces. If it's the former, then (row, col)
        # must be after the start of the unclosed string, or else we would
        # have seen a prior token with start <= (row, col) <= end.
        if 'string' in e.args[0] and (row, col) >= e.args[1]:
            start = e.args[1]
            tokval = s.splitlines()[start[0]-1][start[1]:]
            start_offset, _ = _offsets(tokval)
            return (start[0], start[1] + start_offset) <= (row, col)
        return False
    except IndentationError:
        return False

    return False

def parso_inside_string(s, row, col):
    from parso.utils import parse_version_string
    from parso.python.tokenize import tokenize, INDENT, STRING, ERRORTOKEN

    start = end = (0, 0)
    toknum = prev_tok = -1
    for token in tokenize(s, parse_version_string()):
        start = end
        prev_tok = toknum
        toknum, tokval, end, prefix = token
        if start <= (row, col) < end:
            if prev_tok == INDENT:
                continue
            break

    return prev_tok in [STRING, ERRORTOKEN]

# try:
#     import parso
#     inside_string = parso_inside_string
#     del parso
# except ImportError:
#     pass

# From https://docs.python.org/3/library/itertools.html
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

def nwise(iterable, n, fill=False):
    """
    n-way rolling window over iterable.

    Generalization of pairwise().

    If fill=True then the window will start like (None, ..., None, s0), (None,
    ..., None, s0, s1), .... Otherwise it will start with (s0, s1, ..., sn).
    """
    iters = tee(iterable, n)
    if fill:
        iters = [chain([None]*i, iter) for i, iter in enumerate(reversed(iters))]
        iters.reverse()
    else:
        for i, iter in enumerate(iters):
            for j in range(i):
                next(iter, None)
    return zip(*iters)

def is_multiline_python(text):
    """
    Returns True of text should be considered multiline.

    text is considered multiline if typing Enter at the end of text should add
    a newline. Returns False if the text is a single line that can be
    executed. Also returns False in some situations when text has a syntax
    error that cannot be resolved after an additional line, such as EOL in a
    non-docstring literal or an indentation error.

    """
    from tokenize import ERRORTOKEN

    # Dedent the text, otherwise, the last token will be DEDENT
    text = text.lstrip()

    try:
        error = False
        for (prevprev, prev, tok) in nwise(tokenize_string(text), 3, fill=True):
            toknum, tokval, start, end, line = tok
            # The first token is encoding
            if prev and prev.type == ENCODING and tok.exact_type == AT:
                # Decorator
                return True
            if toknum == ERRORTOKEN:
                # Error means unclosed (non doc-) string or backslash
                # continuation. We want a backslash continuation to be
                # multiline, which is caught below. Every other case shouldn't
                # be multiline.
                error = True
            if toknum in {ENDMARKER, DEDENT}:
                # In 3.6.7 and 3.7.1 the last token before ENDMARKER is always
                # NEWLINE. We want to handle both cases here.
                if prev.type == NEWLINE:
                    if prevprev.type == ERRORTOKEN and prevprev.string == '\\':
                        return True
                else:
                    if prev.type == ERRORTOKEN and prev.string == '\\':
                        return True

    except TokenError as e:
        # Uncompleted docstring or braces
        # Multiline unless there is an uncompleted non-docstring
        if ("multi-line statement" in e.args[0]
            or tok.type != ENCODING and toknum == ERRORTOKEN and tokval == '\\'):
            return True
        return not error
    except IndentationError:
        # IndentationError only occurs if the indentation cannot possibly be correct
        return False

    # Newline means it is multiline, even if there is an unclosed string
    if '\n' in text:
        return True

    # Unclosed string with no newlines
    if error:
        return False

    return prev.exact_type == COLON or (prev.exact_type == NEWLINE and prevprev.exact_type == COLON)

# Taken from standard library textwrap module

# Changes:

# - Renamed dedent() to indentation() and made it return the indentation level
#   of the text instead of dedenting it.

# - Made dedent() function that applies dedentation to text and a margin

# Copyright (C) 1999-2001 Gregory P. Ward.
# Copyright (C) 2002, 2003 Python Software Foundation.
# Written by Greg Ward <gward@python.net>

# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
#    the Individual or Organization ("Licensee") accessing and otherwise using Python
#    3.6.0 software in source or binary form and its associated documentation.
#
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby
#    grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
#    analyze, test, perform and/or display publicly, prepare derivative works,
#    distribute, and otherwise use Python 3.6.0 alone or in any derivative
#    version, provided, however, that PSF's License Agreement and PSF's notice of
#    copyright, i.e., "Copyright © 2001-2017 Python Software Foundation; All Rights
#    Reserved" are retained in Python 3.6.0 alone or in any derivative version
#    prepared by Licensee.
#
# 3. In the event Licensee prepares a derivative work that is based on or
#    incorporates Python 3.6.0 or any part thereof, and wants to make the
#    derivative work available to others as provided herein, then Licensee hereby
#    agrees to include in any such work a brief summary of the changes made to Python
#    3.6.0.
#
# 4. PSF is making Python 3.6.0 available to Licensee on an "AS IS" basis.
#    PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.  BY WAY OF
#    EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
#    WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
#    USE OF PYTHON 3.6.0 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
#
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.6.0
#    FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
#    MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.6.0, OR ANY DERIVATIVE
#    THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
#
# 6. This License Agreement will automatically terminate upon a material breach of
#    its terms and conditions.
#
# 7. Nothing in this License Agreement shall be deemed to create any relationship
#    of agency, partnership, or joint venture between PSF and Licensee.  This License
#    Agreement does not grant permission to use PSF trademarks or trade name in a
#    trademark sense to endorse or promote products or services of Licensee, or any
#    third party.
#
# 8. By copying, installing or otherwise using Python 3.6.0, Licensee agrees
#    to be bound by the terms and conditions of this License Agreement.

_whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE)
_leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE)

def indentation(text):
    """Remove any common leading whitespace from every line in `text`.

    This can be used to make triple-quoted strings line up with the left
    edge of the display, while still presenting them in the source code
    in indented form.

    Note that tabs and spaces are both treated as whitespace, but they
    are not equal: the lines "  hello" and "\\thello" are
    considered to have no common leading whitespace.  (This behaviour is
    new in Python 2.5; older versions of this module incorrectly
    expanded tabs before searching for common leading whitespace.)
    """
    # Look for the longest leading string of spaces and tabs common to
    # all lines.
    margin = None
    text = _whitespace_only_re.sub('', text)
    indents = _leading_whitespace_re.findall(text)
    for indent in indents:
        if margin is None:
            margin = indent

        # Current line more deeply indented than previous winner:
        # no change (previous winner is still on top).
        elif indent.startswith(margin):
            pass

        # Current line consistent with and no deeper than previous winner:
        # it's the new winner.
        elif margin.startswith(indent):
            margin = indent

        # Find the largest common whitespace between current line and previous
        # winner.
        else:
            for i, (x, y) in enumerate(zip(margin, indent)):
                if x != y:
                    margin = margin[:i]
                    break

    # sanity check (testing/debugging only)
    if 0 and margin:
        for line in text.split("\n"):
            assert not line or line.startswith(margin), \
                   "line = %r, margin = %r" % (line, margin)

    if margin is None:
        return ''
    return margin

def dedent(text, margin):
    if margin:
        text = re.sub(r'(?m)^' + margin, '', text)
    return text


import logging
from uuid import uuid4
import re
from telegram.ext import Updater, InlineQueryHandler
from telegram import InlineQueryResultCachedPhoto
import matplotlib.pyplot as plt
import matplotlib

# Config
botID = "xxxx" # ID of the bot
matplotlib.rcParams['text.usetex'] = True
matplotlib.rcParams['text.latex.preamble'] = r"\usepackage{{amsmath}}"
updater = Updater(token=botID,use_context=True)
bot = updater.bot
dispatcher = updater.dispatcher
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",level=logging.INFO)
upload_id = 000000 # Private channel ID for the bot to send the imgs

# Creates the image with the formula
def to_latex(formula,id):
    img_name = "image.jpeg"
    fig = plt.figure()
    fig.text(0.5,0.5,formula,fontsize=20,horizontalalignment="center",verticalalignment="center")
    fig.savefig(img_name)
    return img_name

# Handler of inline requestes
def inline_formula(update, context):
    query = update.inline_query.query
    if not query:
        return
    if (re.search(r"\^\$.*\$",query) is None):
        return
    # Create the photo
    print("TRANSFORMING: "+query+"\n")
    photoinfo = bot.send_photo(
        chat_id=upload_id,
        photo=open(to_latex(query,update.inline_query.id),"rb"),
        caption="Formula"
    )
    # Send the photo
    results = [
        InlineQueryResultCachedPhoto(
            id=uuid4(),
            photo_file_id=photoinfo["photo"][-1]["file_id"],
            title="Formula"
        )
    ]
    context.bot.answer_inline_query(update.inline_query.id, results)

# Adding the handler
inline_formula_handler = InlineQueryHandler(callback=inline_formula)
dispatcher.add_handler(inline_formula_handler)

updater.start_polling()