I have the following basic Django Channels consumer:
class EchoConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
await self.accept()
await self.send_json('Connected!')
And, in parallel, i have a normal Python script which connects to a websocket and receives some data in real time:
from binance.client import Client
import json
from binance.websockets import BinanceSocketManager
client = Client('', '')
# get all symbol prices
prices = client.get_all_tickers()
trades = client.get_recent_trades(symbol='BNBBTC')
# start aggregated trade websocket for BNBBTC
def process_message(message):
JSON1 = json.dumps(message)
JSON2 = json.loads(JSON1)
#define variables
Rate = JSON2['p']
Quantity = JSON2['q']
Symbol = JSON2['s']
Order = JSON2['m']
print(Rate, Quantity, Order)
bm = BinanceSocketManager(client)
bm.start_trade_socket('BNBBTC', process_message)
bm.start()
I would like to do the following: instead of only printing the data received, the second script should send somehow that data to the Django Channels consumer. Whenever a user opens the page, that page should receive that data. If a second user opens the page at the same time, that second user should receive the data too. Is it possible to do this? Am i supposed to use another service?
so if you want to send this data to all currently open websocket connections you can do the following.
class EchoConsumer(AsyncJsonWebsocketConsumer):
groups = ["echo_group"]
async def on_message(self, message):
await self.send_json(... something here based on the message ... )
Then in your script you need to import channels (and have django configured so its best for this to be a django command see: https://docs.djangoproject.com/en/3.0/howto/custom-management-commands/
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
... your other stuff to connect to
def process_message(message):
JSON1 = json.dumps(message)
JSON2 = json.loads(JSON1)
#define variables
Rate = JSON2['p']
Quantity = JSON2['q']
Symbol = JSON2['s']
Order = JSON2['m']
async_to_sync(channel_layer.group_send)(
"echo_group",
{"type": "on.message", "rate":Rate, "quantity": Quantity, "symbol": Symbol, "order": Order},
)
Related
I'm aware I can use this
client.send_file(receiver, '/path/to/photo.jpg')
to send an image, but how can I attach a caption to the image?
According to the documentation just pass the value of the caption with a keyword argument like so client.send_file(chat, '/my/photos/me.jpg', caption="It's me!"). You can read the documentation here
While this question on how to send an image with caption to Telegram using Telethon was already answered concisely by #matthew-barlowe, (which I used to come to my own solution, thank you), I felt that it would be helpful to include a more comprehensive example using Telethon v3's new async API.
The code is documented and type hinted so it should provide it's own explanation. In the interest of keeping the example brief, capturing of exceptions is excluded.
import logging
from random import uniform
from time import sleep
from typing import Dict, List, Union
from telethon.sync import TelegramClient
logging.basicConfig(
format="[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s", level=logging.WARNING
)
# you should use dotenv module to extract credentials from the environment or .env file; 'pip install python-dotenv'
# do not store credentials in the file in production or git commit, this is only included for example purposes
SESSION_NAME = "sqlite-session"
TG_API_ID = 1234567890
TG_API_HASH = "*****"
CHANNEL_NAME = "yourchannelname"
CHANNEL_DISPLAY_NAME = "Your Channel Name"
CHANNEL_URL = "https://t.me/your-channel-name"
# define client
client = TelegramClient(SESSION_NAME, TG_API_ID, TG_API_HASH)
def create_message(data: Dict) -> str:
"""Formats a dictionary as a Telegram message.
:param data: Dict: A dictionary containing 'title', 'subtitle', 'text', 'url' and
'image_path' keys to be formatted as a message.
:return: str: A string including Markup to be sent as a message to a Telegram channel.
"""
# generate message
message = ""
if data.get("title", None):
message += f'**{data["title"]}**\n'
message += f'{data["subtitle"]}\n' if data.get("subtitle", None) else ""
if data.get("url", None):
message += data["url"]
message += "\n\n"
message += f"[{CHANNEL_DISPLAY_NAME}]({CHANNEL_URL})"
return message
async def channel_broadcast(
messages: Union[Dict, List[Dict]],
channel: str,
min_wait: float = 25.0,
max_wait: float = 120.0
) -> None:
""" Broadcasts a message to the specified Telegram channel. There will be a humanized wait in between postings.
:param messages: Union[Dict, List[Dict, ...]]: A dictionary or list of dicts containing 'title', 'subtitle',
'text', 'url' and 'image_path' keys to be formatted as a message.
:param channel: str: The name of the channel messages are to be broadcast to. You must have permission to
broadcast to this channel. See setup in telethon docs.
:param min_wait: float: Minimum wait between messages.
:param max_wait: float: Maximum wait between messages.
:return: None
"""
# ensure list
messages = [messages] if isinstance(messages, dict) else messages
for item in messages:
# generate a properly formatted message using markup and available fields
message = create_message(item)
# connect previously defined client
async with client:
await client.connect()
# send message if image is included
if item.get("image_path", None):
await client.send_file(
channel, item["image_path"], caption=message, link_preview=True
)
# send message without image
else:
await client.send_message(channel, message, link_preview=True)
# short blocking wait for multiple messages
# non-blocking waits are not in scope of this example
if len(messages) > 1:
sleep(uniform(min_wait, max_wait))
# you can provide a single dict or list of dicts
messages = [
{
"title": "First Message",
"subtitle": "This is the first message.",
"text": "This is a paragraph of text. The main idea of the message will be included here.",
"url": "https://test.com",
"image_path": "/path/to/a/local/image.png",
},
{
"title": "Second Message",
"subtitle": None,
"text": "This is a paragraph of text. The main idea of the message will be included here.",
"url": None,
},
]
# send all messages with a humanized wait between messages
with client:
client.loop.run_until_complete(
channel_broadcast(
messages, CHANNEL_NAME
)
)
'''
I need help with the code to stary binance futures userdata stream as the package is not working.
I tried following changes in the code but it failed stating that APIERROR(code=0) Invalid Jason error message from Binance. The tweaks I made to subscribe futures userdatastream are as follows:
In my script:
bm = BinanceSocketManager(client)
def processmessage(msg):
print(msg)
conn_keys = bm.start_user_socket(processmsg)
bm.start()
In websockets.py in def start_socket: I replaced stream_url with fstream_url
In client.py in def create_api_uri: I replaced api_url with futures_url
I am in need of binance futures’s userdata stream websockets.
I am also using the package as you.
What you need is to re-write the process_data logic to get the data, and then us the code as following:
bm.start_futures_socket(process_data)
bm.start()
I was also trying to do the same. I couldn't get python-binance to work, so I switched to the unicorn_binance_websocket_api.
Quick example:
from unicorn_binance_websocket_api.unicorn_binance_websocket_api_manager import BinanceWebSocketApiManager
binance_websocket_api_manager = BinanceWebSocketApiManager(exchange="binance.com-futures")
binance_websocket_api_manager.create_stream('arr', '!userData', api_key=<Your API key>, api_secret=<Your secret API key>, output="UnicornFy")
while True:
data = binance_websocket_api_manager.pop_stream_data_from_stream_buffer()
if data:
#do something with that data
pass
Maybe that helps.
this is my code:
binance = await AsyncClient.create(config.API_KEY, config.API_SECRET)
bsm = BinanceSocketManager(binance)
async with bsm.futures_multiplex_socket(streams) as ms:
while True:
msg = await ms.recv()
...
I created a simple Django Channels consumer that should connects to an external source, retrieve data and send it to the client. So, the user opens the page > the consumer connects to the external service and gets the data > the data is sent to the websocket.
Here is my code:
import json
from channels.generic.websocket import WebsocketConsumer, AsyncConsumer, AsyncJsonWebsocketConsumer
from binance.client import Client
import json
from binance.websockets import BinanceSocketManager
import time
import asyncio
client = Client('', '')
trades = client.get_recent_trades(symbol='BNBBTC')
bm = BinanceSocketManager(client)
class EchoConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
await self.accept()
await self.send_json('test')
bm.start_trade_socket('BNBBTC', self.process_message)
bm.start()
def process_message(self, message):
JSON1 = json.dumps(message)
JSON2 = json.loads(JSON1)
#define variables
Rate = JSON2['p']
Quantity = JSON2['q']
Symbol = JSON2['s']
Order = JSON2['m']
asyncio.create_task(self.send_json(Rate))
print(Rate)
This code works when i open one page; if i try to open a new window with a new account, though, it will throw the following error:
File "C:\Users\User\Desktop\Heroku\github\master\myapp\consumers.py", line 54, in connect
bm.start()
File "C:\Users\User\lib\threading.py", line 843, in start
raise RuntimeError("threads can only be started once")
threads can only be started once
I'm new to Channels, so this is a noob question, but how can i fix this problem? What i wanted to do was: user opens the page and gets the data, another user opens the page and gets the data; is there no way to do that? Or am i simply misunderstanding how Django Channels and websockets works?
Do you really need a secondary thread ?
class EchoConsumer(AsyncJsonWebsocketConsumer):
symbol = ''
async def connect(self):
self.symbol = 'BNBBTC'
# or, more probably, retrieve the value for "symbol" from query_string
# so the client can specify which symbol he's interested into:
# socket = new WebSocket("ws://.../?symbol=BNBBTC");
await self.accept()
def process_message(self, message):
# PSEUDO-CODE BELOW !
if self.symbol == message['symbol']:
await self.send({
'type': 'websocket.send',
'text': json.dumps(message),
})
For extra flexibility, you might also accept al list of symbols from the client, instead:
//HTML
socket = new WebSocket("ws://.../?symbols=XXX,YYY,ZZZ");
then (in the consumer):
class EchoConsumer(AsyncJsonWebsocketConsumer):
symbols = []
async def connect(self):
# here we need to parse "?symbols=XXX,YYY,ZZZ" ...
# the code below has been stolen from another project of mine and should be suitably adapted
params = urllib.parse.parse_qs(self.scope.get('query_string', b'').decode('utf-8'))
try:
self.symbols = json.loads(params.get('symbols', ['[]'])[0])
except:
self.symbols = []
def process_message(self, message):
if message['symbol'] in self.symbols:
...
I'm no Django developer, but if I understand correctly, the function connect is being called more than once-- and bm.start references the same thread most likely made in bm.start_trade_socket (or somewhere else in connect). In conclusion, when bm.start is called, a thread is started, and when it is done again, you get that error.
Here start() Start the thread’s activity.
It should be called at most once per thread object. You have made a global object of BinanceSocketManager as "bm".
It will always raise a RuntimeError if called more than once on the same thread object.
Please refer the below mentioned code, it may help you
from channels.generic.websocket import WebsocketConsumer, AsyncConsumer, AsyncJsonWebsocketConsumer
from binance.client import Client
import json
from binance.websockets import BinanceSocketManager
import time
import asyncio
class EchoConsumer(AsyncJsonWebsocketConsumer):
client = Client('', '')
trades = client.get_recent_trades(symbol='BNBBTC')
bm = BinanceSocketManager(client)
async def connect(self):
await self.accept()
await self.send_json('test')
self.bm.start_trade_socket('BNBBTC', self.process_message)
self.bm.start()
def process_message(self, message):
JSON1 = json.dumps(message)
JSON2 = json.loads(JSON1)
#define variables
Rate = JSON2['p']
Quantity = JSON2['q']
Symbol = JSON2['s']
Order = JSON2['m']
asyncio.create_task(self.send_json(Rate))
print(Rate)
I'm learning Django and I'm working in a webpage where I need to offer the user the possibility to log in to an external service. I can't simply use the traditional Django views system because otherwise, I would lose the connection with a simple refresh. For that reason, I thought about using Django Channels.
My problem now is how do I send the data to the consumer class? Using the consumers.py given in the tutorial, I would like to send data from a form submission to the connect function and then make the connection if the login to the external service is ok. Then, in that scenario, I could use the clientinstance and the methods from these external services.
So, in short, is it possible to send form data the consumer? And would this be ok, with respect to security in case of sensitive data?
from channels.generic.websocket import AsyncWebsocketConsumer
import json
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
######
## login to external service
######
#get login data from form submited when the websockted is initiated
username = ...
pass = ...
self.client = Client(username, password)
if client:
await self.accept()
# Receive message from room group
async def chat_message(self, event):
message = event['message']
self.client.send(event['message'])
Update:
To clear the explanation: I can't save the user username and pass of the external service, and that I want to offer the user the possibility to use this [sms service](https://clxcommunications.github.io/sdk-xms-python/tutorial.html) with a text field and phone number.
So the problem is that even if I create a form and the username and password to log in (in the view) with
client = clx.xms.Client('myserviceplan', 'mytoken')
then in next request, I would lose the client instance. That's why I thought about Django Channels. But I'm not really sure if it is the best solution...
This will help you.
consumers.py
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json
class EventConsumer(WebsocketConsumer):
def connect(self):
# self.room_name = self.scope['url_route']['kwargs']['room_name']
# self.room_group_name = 'chat_%s' % self.room_name
self.room_name = 'event'
self.room_group_name = self.room_name+"_sharif"
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
print(self.room_group_name)
self.accept()
print("#######CONNECTED############")
def disconnect(self, code):
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
print("DISCONNECED CODE: ",code)
def receive(self, text_data=None, bytes_data=None):
print(" MESSAGE RECEIVED")
data = json.loads(text_data)
message = data['message']
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,{
"type": 'send_message_to_frontend',
"message": message
}
)
def send_message_to_frontend(self,event):
print("EVENT TRIGERED")
# Receive message from room group
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
then call the function outside/anywhere from your app like
def event_triger():
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'event_sharif',
{
'type': 'send_message_to_frontend',
'message': "event_trigered_from_views"
}
)
# here 'event_sharif' is your room_group_name as i defined before in consumer
# 'type' is like a command, for which method you wants to trigger in your consumer
for more you may take a look Send message using Django Channels from outside Consumer class
Generally, you can call a method in the consumer from an external code in the following way:
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
await self.channel_layer.send(
'<channel_name>',
{
'type': '<method_name>',
}
)
But as you can see, this requires that you specify the channel name which you can only get after the client has connected. In other words, you should not try to call connect but some other method in the consumer. Also, your client should first connect to the websocket before you eventually access it. I don't understand your use case completey but I hope this gives you an idea
I would like to download the chat history (all messages) that were posted in a public group on Telegram. How can I do this with python?
I've found this method in the API https://core.telegram.org/method/messages.getHistory which I think looks like what I'm trying to do. But how do I actually call it? It seems there's no python examples for the MTproto protocol they use.
I also looked at the Bot API, but it doesn't seem to have a method to download messages.
You can use Telethon. Telegram API is fairly complicated and with the telethon, you can start using telegram API in a very short time without any pre-knowledge about the API.
pip install telethon
Then register your app (taken from telethon):
the link is: https://my.telegram.org/
Then to obtain message history of a group (assuming you have the group id):
chat_id = YOUR_CHAT_ID
api_id=YOUR_API_ID
api_hash = 'YOUR_API_HASH'
from telethon import TelegramClient
from telethon.tl.types.input_peer_chat import InputPeerChat
client = TelegramClient('session_id', api_id=api_id, api_hash=api_hash)
client.connect()
chat = InputPeerChat(chat_id)
total_count, messages, senders = client.get_message_history(
chat, limit=10)
for msg in reversed(messages):
# Format the message content
if getattr(msg, 'media', None):
content = '<{}> {}'.format( # The media may or may not have a caption
msg.media.__class__.__name__,
getattr(msg.media, 'caption', ''))
elif hasattr(msg, 'message'):
content = msg.message
elif hasattr(msg, 'action'):
content = str(msg.action)
else:
# Unknown message, simply print its class name
content = msg.__class__.__name__
text = '[{}:{}] (ID={}) {}: {} type: {}'.format(
msg.date.hour, msg.date.minute, msg.id, "no name",
content)
print (text)
The example is taken and simplified from telethon example.
With an update (August 2018) now Telegram Desktop application supports saving chat history very conveniently.
You can store it as json or html formatted.
To use this feature, make sure you have the latest version of Telegram Desktop installed on your computer, then click Settings > Export Telegram data.
https://telegram.org/blog/export-and-more
The currently accepted answer is for very old versions of Telethon. With Telethon 1.0, the code can and should be simplified to the following:
# chat can be:
# * int id (-12345)
# * str username (#chat)
# * str phone number (+12 3456)
# * Peer (types.PeerChat(12345))
# * InputPeer (types.InputPeerChat(12345))
# * Chat object (types.Chat)
# * ...and many more types
chat = ...
api_id = ...
api_hash = ...
from telethon.sync import TelegramClient
client = TelegramClient('session_id', api_id, api_hash)
with client:
# 10 is the limit on how many messages to fetch. Remove or change for more.
for msg in client.iter_messages(chat, 10):
print(msg.sender.first_name, ':', msg.text)
Applying any formatting is still possible but hasattr is no longer needed. if msg.media for example would be enough to check if the message has media.
A note, if you're using Jupyter, you need to use async directly:
from telethon import TelegramClient
client = TelegramClient('session_id', api_id, api_hash)
# Note `async with` and `async for`
async with client:
async for msg in client.iter_messages(chat, 10):
print(msg.sender.first_name, ':', msg.text)
Now, you can use TDesktop to export chats.
Here is the blog post about Aug 2018 update.
Original Answer:
Telegram MTProto is hard to use to newbies, so I recommend telegram-cli.
You can use third-party tg-export script, but still not easy to newbies too.
You can use the Telethon library. for this you need to register your app and connect your client code to it (look at this).
Then to obtain message history of a entry (such as channel, group or chat):
from telethon.sync import TelegramClient
from telethon.errors import SessionPasswordNeededError
client = TelegramClient(username, api_id, api_hash, proxy=("socks5", proxy_ip, proxy_port)) # if in your country telegram is banned, you can use the proxy, otherwise remove it.
client.start()
# for login
if not client.is_user_authorized():
client.send_code_request(phone)
try:
client.sign_in(phone, input('Enter the code: '))
except SessionPasswordNeededError:
client.sign_in(password=input('Password: '))
async for message in client.iter_messages(chat_id, wait_time=0):
messages.append(Message(message))
# write your code