Limit the access of telegram bot through Chat ID - python-3.x

I have attached the code. I want chat_id to be accessed from the text file located at my local machine.
#constants.py
chatid_list = []
file1 = open(r'D:\\folder1\\chatids.txt', "r")
for chat_id in file1:
chatid_list.append(chat_id)
#main.py
def start(update, context):
chat_id = update.message.chat_id
first_name =update.message.chat.first_name
last_name = update.message.chat.last_name
username = update.message.chat.username
if chat_id in cp.chatid_list:
print("chat_id : {} and firstname : {} lastname : {} username {}". format(chat_id, first_name, last_name , username))
context.bot.send_message(chat_id, 'Hi ' + first_name + ' Whats up?')
update.message.reply_text( text=main_menu_message(),reply_markup=main_menu_keyboard())
else:
print("WARNING: Unauthorized access denied for {}.".format(chat_id))
update.message.reply_text('User disallowed.')

My guess would be that the elements of chatid_list are strings because you read them from a text file, but chat_id is an integer. So you'd either have to convert chat_id to a string or the elements of chatid_list to integers.

As a cleaner alternative, you could consider using environment variables (e.g. with dotenv) to add configuration elements like the chat IDs (as strings, as noted by #CallMeStag).
That would remove the necessity of having to format and read a file for that purpose. In this case, you can have a .env file in the same directory with:
#.env file
# allowed ids - users allowed to use bot
ALLOWED_IDS = 12345678, 24567896
I'm assuming you're using python 3 and can use f-strings.
So, you can throw your constants.py away and your main.py would look like below:
#main.py file
import os
from dotenv import load_dotenv
load_dotenv()
def start(update, context):
chat_id = update.message.chat_id
first_name = update.message.chat.first_name
last_name = update.message.chat.last_name
username = update.message.chat.username
if chat_id in os.getenv('ALLOWED_IDS'):
print(f'chat_id : {chat_id } and firstname : {first_name } lastname : {last_name } username {username }')
context.bot.send_message(chat_id, f'Hi {first_name}, Whats up?')
update.message.reply_text(text=main_menu_message(),reply_markup=main_menu_keyboard())
else:
print(f'WARNING: Unauthorized access denied for {chat_id}.')
update.message.reply_text('User disallowed.')
Finally, in case you want multiple functions to be "protected", you can use a wrapper as shown here.
Edit: added alternative local file types after OP's comment.
You could also store your allowed users in a JSON (ids.json):
{
"allowed_users": [
{
"name": "john wick",
"id": 1234567
},
{
"name": "rick wick",
"id": 2345738
}
]
}
And then read the allowed IDs as follows:
import json
with open(r'./ids.json', 'r') as in_file:
allowed_users = json.load(in_file)['allowed_users']
allowed_ids = [user['id'] for user in allowed_users]
OR, you could simply drop all the IDs into a text file (ids.txt), with one ID per line:
1234567
2345738
And then read them as follows:
allowed_ids = []
with open(r'./ids.txt', 'r') as in_file:
for row in in_file:
allowed_ids.append(int(row.strip()))
Finally, replace os.getenv('ALLOWED_IDS') from the above code snippet with allowed_ids.

Related

How can i fix AWS python event key problem

I have a problem with my AWS python code.
I am trying to send a post request to my code on AWS but I have key problems
My code on AWS
import json
import random
def lambda_handler(event, context):
name = surname = birthDate = favoriteFilm = password = ""
indexList = keysArray = []
setParams(event, name, surname, birthDate, favoriteFilm)
fillArray(keysArray, name, surname, birthDate, favoriteFilm)
arrayLength = len(keysArray)
while len(password)<6:
index = getRandomRangeIndex(arrayLength)
if index in indexList:
continue
password = password + keysArray[index]
indexList.append(index)
return {
'statusCode': 200,
'body': json.dumps(password)
}
def setParams(event, name, surname, birthDate, favoriteFilm):
name = event['first_name']
surname = event['last_name']
birthDate = event['d_o_b']
favoriteFilm = event['favorite_film']
def fillArray(keysArray, name, surname, birthDate, favoriteFilm):
for names in name.split():
keysArray.append(names)
keysArray.append(surname)
for dates in birthDate.split('-'):
keysArray.append(dates)
for films in favoriteFilm.split():
keysArray.append(films)
def getRandomRangeIndex(arrayLength):
return random.randint(0, arrayLength-1)
My Postman request header
{
"first_name": "John",
"last_name": "Smith",
"d_o_b": "1985-12-04",
"favorite_film": "Back to the Future"
}
My problem log
[ERROR] KeyError: 'first_name'
Traceback (most recent call last):
File "/var/task/password.py", line 7, in lambda_handler
setParams(event, name, surname, birthDate, favoriteFilm)
File "/var/task/password.py", line 24, in setParams
name = event['first_name']
I am not able to find any solution. How can I fix this problem? Thank you.
When you submit your json though api gateway, the event object that your function receives is different than what you submit. The format is shown in aws docs.
In your case the event will be something like this:
{
"resource": "/test",
"path": "/test",
"httpMethod": "POST",
#
# a banch of data removed for length
#
"body": "{\n \"first_name\": \"John\",\n \"last_name\": \"Smith\",\n \"d_o_b\": \"1985-12-04\",\n \"favorite_film\": \"Back to the Future\"\n}",
"isBase64Encoded": false
}
Thus, to get your actual data and you have to parse body you can use python's ast. You body is not json string, thus need to use ast:
import json
import random
import ast
def lambda_handler(event, context):
# overwrite event to keep using event in later parts of your code
event = ast.literal_eval(event['body'])
#....

Creating several DB instances from a single POST request

I have a table like this:
class Mapping(db.Model):
map_id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
bike_id = db.Column(db.String(255))
is_active_data = db.Column(db.Boolean, default=True)
created_by = db.Column(db.String(150))
updated_by = db.Column(db.String(150))
My POST method:
def save_MapperM(adddata):
create_data = Mapping(**adddata)
db.session.add(create_data)
db.session.commit()
return dict(Successful="Successfully Created")
Route:
#test.route("/addmapper"))
class MapperV(Resource):
#staticmethod
def post():
if request.method == 'POST':
save_data = request.get_json()
try:
return jsonify(save_MapperM(save_data))
except Exception:
return jsonify({'Unsuccessful': 'Looks like you missed something !!'})
Current Code :
The current code will take only one bike_id for every request.
Requirements:
I want to take multiple bike_id's as for one user id and store it as multiple records in the table level.
Example data format coming from UI:
{ user_id: 1, bike_id: 1,2,3,4 }
The easiest solution is to modify your save_MapperM function with a cycle:
def save_MapperM(adddata):
for bike_id in adddata["bike_id"]:
item_data = adddata.copy()
item_data["bike_id"] = bike_id
create_data = Mapping(**item_data)
db.session.add(create_data)
db.session.commit()
return dict(Successful="Successfully Created")
But be careful with this function as it allows to create Mapping instances with any parameters received from the POST request. It looks like it is intended but it can lead to security issues if your Mapping class has some private attributes which should be filled only on the server side. For example the user "Mike" can send a request:
{ "user_id": 1, "bike_id": [1, 2], "created_by": "Alex", "updated_by": "Alex" }
It will cause the save_MapperM function to create instances with created_by and updated_by values set to "Alex" which may not be true. It is better to get such attributes from the session data.
So your post method may look like this (post and save_MapperM functionality combined):
def post():
request_data = request.get_json()
for bike_id in request_data.get("bike_id", []):
item = Mapping(
user_id=request_data.get("user_id"),
bike_id=bike_id,
created_by=session.get("username"),
updated_by=session.get("username"),
)
db.session.add(item)
try:
db.session.commit()
except Exception:
return jsonify({"success": False})
return jsonify({"success": True})
The next step may be implementing request JSON data validation. It is OK when you have a couple of JSON keys with a simple structure but when you need to pass lots of data you need to be sure it is correct. You can use some of the serialization/ODM libraries for this Marshmallow for example.

Issue storing user id's in a json file

So what I'm trying to do is to store user id's and add their score when they do a command. But the issue I'm getting is the user gets logged twice and it doesn't add the score. This is what happens in the json file:
{"272356126391238536": {"experience": 1}, "272356126391238536": {"experience": 1}}
This is a part of my code that defines the update and add:
async def update(amount, user):
if not user.id in amount:
amount[user.id] = {}
amount[user.id]['experience'] = 0
async def add(amount, user, exp):
amount[user.id]['experience'] += exp
This is the part in the command that the score updates from:
await update(buffer, ctx.message.author)
await add(buffer, ctx.message.author, 1)
When I change the ".id" to a ".name" It works fine but the issue with name is if a user changes his name his old score will be lost.
Make sure you use a ctx variable, so:
async def update(ctx, amount):
if not str(ctx.message.author.id) in amount:
amount[str(ctx.message.author.id)] = {}
amount[str(ctx.message.author.id)]['experience'] = 0
async def add(amount, user : discord.Member, exp):
amount[user.id]['experience'] += exp
This should work.

Send messages to telegram group without user input

I'm trying to build a bot which automatically sends a message whenever there is an update in the latest news using python. Following is what I did.
companies = {
"name_1": {
"rss": "name_1 rss link",
"link": "name_1 link"
}
}
import feedparser as fp
import time, telebot
token = <TOKEN>
bot = telebot.TeleBot(token)
LIMIT = 1
while True:
def get_news():
count = 1
news = []
for company, value in companies.items():
count = 1
if 'rss' in value:
d = fp.parse(value['rss'])
for entry in d.entries:
if hasattr(entry, 'published'):
if count > LIMIT:
break
news.append(entry.link)
count = count + 1
return (news)
val = get_news()
time.sleep(10)
val2 = get_news()
try:
if val[0]!=val2[0]:
bot.send_message(chat_id= "Hardcoded chat_id", text=val2[0])
except Exception:
pass
How can I update my code so that the bot publishes the latest news to all the groups to which it is added?
I got the chat_id using:
bot.get_updates()[-1].message.chat.id
Any suggestions on how to automate this?
Using the python-telegram-bot api, you can send a message like this
bot.send_message(id, text='Message')
you need the "bot" and "id"
I keep these in a dictionary called "mybots" which I fill/update when people interact with the bot for the first time / or on later communication with the bot. It's possible to pickle this dictionary to keep it persistant.
mybots = {}
def start(bot, update):
"""Send a message when the command /start is issued."""
mybots[update.message.chat_id] = bot
update.message.reply_text('Hello{}!'.format(
update.effective_chat.first_name))
def send_later():
for id, bot in mybots.items():
bot.send_message(id, text='Beep!')
In short, you can use sendMessage() to send message to a specific group or user.
bot.sendMessage(chat_id=chat_id, text=msg)
the complete code,
import telegram
#token that can be generated talking with #BotFather on telegram
my_token = ''
def send(msg, chat_id, token=my_token):
"""
Send a message to a telegram user or group specified on chatId
chat_id must be a number!
"""
bot = telegram.Bot(token=token)
bot.sendMessage(chat_id=chat_id, text=msg)

Fill out online form

I'm trying to fill out the form located at https://idp.ncedcloud.org/idp/AuthnEngine#/authn with a username and password. I want to know if went through successfully or not. I tried it ith python2, but I couldn't get it to work.
#!/usr/bin/env python
import urllib
import urllib2
name = "username"
name2 = "password"
data = {
"description" : name,
"ember501": name2
}
encoded_data = urllib.urlencode(data)
content = urllib2.urlopen("https://idp.ncedcloud.org/idp/AuthnEngine#/authn",
encoded_data)
print(content)
error:
It prints the content of the same webpage, and I want it to print the new webpage content.
desired behavior:
python3 solution that goes to the next webpage, or why my code isn't working
Basic example of posting data to a webpage like this using requests library. I would suggest using session, so that your login info is saved for any following requests:
import requests
url = "http://example.com"
name = "username"
name2 = "password"
data = {
"description" : name,
"ember501": name2
}
s = requests.Session()
# If it supports basic auth you could just do
# s.auth(username, password) here before a request
req = s.post(url, data=data)
print(req.text)
Should then be able to do following requests with the s session object

Resources