/tgflow

A declarative-style telegram bot framework

Primary LanguagePythonMIT LicenseMIT

tgflow

A declarative-style Telegram Bot framework

Here's how you declare a vanilia counter bot:

import tgflow 

tgflow.configure(token='TOKEN',state='start') # display 'start' state by default
tgflow.start({'start':{
    'text':tgflow.paste("Hello, i'm hooray bot. Hooray %i times!", 
                   'count',default=1), # pass 'count' value to '%i' in string
    'buttons':[{
        'Say hooray':lambda count=1:
        ('start',{'count':count+1}) # display 'start' state, increment 'count' value
    }]
}
})

Getting started.

There are two ways to install the library:

pip3 install tgflow
  • Installation from source (requires git):
git clone https://github.com/DaniloZZZ
cd tgflow
python3 setup.py install

It is generally recommended to use the first option.

Writing your first bot

Tgflow is a state-based framework. Each user has a state and data, and each state corresponds to some text and buttons - generally speaking, UI. It's a good way to imagine your bot as some shema like this:

 a sample shema

Here's how you to declare states for your bot:

# States.py
from enum import Enum
class States(Enum):
	START=1
	SAY_HOORAY=2
	SET_FAV=3
	ERROR=9

Basically, you define state names in separate file and include it everywhere. You can use just strings instead on Enum class, but you can`s use some functionality then.

Then, for each state you create a dictionary that defines UI and some simple actions. To handle user input you define your functions and assign them to buttons in UI dict. You store user-specific data in a dictionary which is passed to you by 'd' argument. Here is a brief example of usage:

import tgflow
from States import States # here you defined your states
import logic # some arbitrary code with buisness logic

def show_news(input,data):
# here input will be callback_query (https://core.telegram.org/bots/api#callbackquery)
# as show_news action is used on inline keyboard
	user_id = input.message.from.id

	# don't call news if already exists.
	if not data.get('news'): # data dictionary stores all user's varibles.
		news=logic.get_news(user_id)
	else:
		news=data['news']
	if news:
		new_state=States.NEWS
	else:
		new_state=States.NO_PERMISSION
	upd_data = {'news':news} # assign user's data to pass forward and store in dict
	return new_state,upd_data

# Unnececary parameters can be omitted.
# Tgflow will automatically determine what to pass.
def show_weather(input,location=None): # you can get user's data by key like this
	user_id = input.message.from.id
	upd_data = {'weather': logic.get_weather(location)} # assign user's data to pass forward and store
	return States.WEATHER,upd_data
	
UI={States.START:{
	'text':"Hello, wanna see some news?",
	'buttons':[
		{'yes, show me news':tgflow.action(show_news)}, 
		{'no, tell me the weather':tgflow.action(show_weather)}# you can also use tgflow.a as shortcut
		]
	},
	States.NEWS:{
	# t is short for text b is for buttons
	't':tgflow.paste("here are your news:\n %s", 'news'),
	 #  tgflow.paste pastes value from user's data to string
	 'b':[{'Back':States.START}] # you can leave just state(Enum) without wrapping.
								 # this will forward user to this state.
							 # equivalent to tgflow.a(lambda(s): States.START)
							 # or tgflow.action(lambda(i,s,**d):(i,States.START,d))
	}
	States.WEATHER:{...},		
	States.NO_PERMISSION:{...},