2022-03-28 12:41:28 +02:00
using Npgsql ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Threading ;
using System.Threading.Tasks ;
using TL ;
2022-09-02 23:02:44 +02:00
// This is an example userbot designed to run on a Heroku account with a PostgreSQL database for session storage
2022-03-28 12:41:28 +02:00
// This userbot simply answer "Pong" when someone sends him a "Ping" private message (or in Saved Messages)
// To use/install/deploy this userbot, follow the steps at the end of this file
// When run locally, close the window or type ALT-F4 to exit cleanly and save session (similar to Heroku SIGTERM)
namespace WTelegramClientTest
{
static class Program_Heroku
{
static WTelegram . Client Client ;
static User My ;
static readonly Dictionary < long , User > Users = new ( ) ;
static readonly Dictionary < long , ChatBase > Chats = new ( ) ;
// See steps at the end of this file to setup required Environment variables
static async Task Main ( string [ ] _ )
{
var exit = new SemaphoreSlim ( 0 ) ;
AppDomain . CurrentDomain . ProcessExit + = ( s , e ) = > exit . Release ( ) ; // detect SIGTERM to exit gracefully
2022-04-23 01:34:21 +02:00
var store = new PostgreStore ( Environment . GetEnvironmentVariable ( "DATABASE_URL" ) , Environment . GetEnvironmentVariable ( "SESSION_NAME" ) ) ;
2022-03-28 13:24:37 +02:00
// if DB does not contain a session yet, client will be run in interactive mode
2022-03-28 12:41:28 +02:00
Client = new WTelegram . Client ( store . Length = = 0 ? null : Environment . GetEnvironmentVariable , store ) ;
using ( Client )
{
2022-07-29 15:24:18 +02:00
Client . OnUpdate + = Client_OnUpdate ;
2022-03-28 12:41:28 +02:00
My = await Client . LoginUserIfNeeded ( ) ;
Console . WriteLine ( $"We are logged-in as {My.username ?? My.first_name + " " + My.last_name} (id {My.id})" ) ;
var dialogs = await Client . Messages_GetAllDialogs ( ) ;
dialogs . CollectUsersChats ( Users , Chats ) ;
await exit . WaitAsync ( ) ;
}
}
2022-07-29 15:24:18 +02:00
private static async Task Client_OnUpdate ( IObject arg )
2022-03-28 12:41:28 +02:00
{
if ( arg is not UpdatesBase updates ) return ;
updates . CollectUsersChats ( Users , Chats ) ;
foreach ( var update in updates . UpdateList )
{
Console . WriteLine ( update . GetType ( ) . Name ) ;
if ( update is UpdateNewMessage { message : Message { peer_id : PeerUser { user_id : var user_id } } msg } ) // private message
2022-06-15 19:16:11 +02:00
if ( ! msg . flags . HasFlag ( Message . Flags . out_ ) ) // ignore our own outgoing messages
if ( Users . TryGetValue ( user_id , out var user ) )
{
Console . WriteLine ( $"New message from {user}: {msg.message}" ) ;
if ( msg . message . Equals ( "Ping" , StringComparison . OrdinalIgnoreCase ) )
await Client . SendMessageAsync ( user , "Pong" ) ;
}
2022-03-28 12:41:28 +02:00
}
}
}
#region PostgreSQL session store
class PostgreStore : Stream
{
private readonly NpgsqlConnection _sql ;
2022-04-23 01:34:21 +02:00
private readonly string _sessionName ;
2022-03-28 12:41:28 +02:00
private byte [ ] _data ;
private int _dataLen ;
private DateTime _lastWrite ;
private Task _delayedWrite ;
2022-04-23 01:34:21 +02:00
/// <param name="databaseUrl">Heroku DB URL of the form "postgres://user:password@host:port/database"</param>
/// <param name="sessionName">Entry name for the session data in the WTelegram_sessions table (default: "Heroku")</param>
public PostgreStore ( string databaseUrl , string sessionName = null )
2022-03-28 12:41:28 +02:00
{
2022-04-23 01:34:21 +02:00
_sessionName = sessionName ? ? "Heroku" ;
2022-03-28 13:24:37 +02:00
var parts = databaseUrl . Split ( ':' , '/' , '@' ) ;
2022-03-28 12:41:28 +02:00
_sql = new NpgsqlConnection ( $"User ID={parts[3]};Password={parts[4]};Host={parts[5]};Port={parts[6]};Database={parts[7]};Pooling=true;SSL Mode=Require;Trust Server Certificate=True;" ) ;
_sql . Open ( ) ;
2022-04-23 01:34:21 +02:00
using ( var create = new NpgsqlCommand ( $"CREATE TABLE IF NOT EXISTS WTelegram_sessions (name text NOT NULL PRIMARY KEY, data bytea)" , _sql ) )
2022-03-28 12:41:28 +02:00
create . ExecuteNonQuery ( ) ;
2022-04-23 01:34:21 +02:00
using var cmd = new NpgsqlCommand ( $"SELECT data FROM WTelegram_sessions WHERE name = '{_sessionName}'" , _sql ) ;
2022-03-28 12:41:28 +02:00
using var rdr = cmd . ExecuteReader ( ) ;
if ( rdr . Read ( ) )
_dataLen = ( _data = rdr [ 0 ] as byte [ ] ) . Length ;
}
protected override void Dispose ( bool disposing )
{
_delayedWrite ? . Wait ( ) ;
_sql . Dispose ( ) ;
}
public override int Read ( byte [ ] buffer , int offset , int count )
{
Array . Copy ( _data , 0 , buffer , offset , count ) ;
return count ;
}
public override void Write ( byte [ ] buffer , int offset , int count ) // Write call and buffer modifications are done within a lock()
{
_data = buffer ; _dataLen = count ;
if ( _delayedWrite ! = null ) return ;
var left = 1000 - ( int ) ( DateTime . UtcNow - _lastWrite ) . TotalMilliseconds ;
if ( left < 0 )
{
2022-04-23 01:34:21 +02:00
using var cmd = new NpgsqlCommand ( $"INSERT INTO WTelegram_sessions (name, data) VALUES ('{_sessionName}', @data) ON CONFLICT (name) DO UPDATE SET data = EXCLUDED.data" , _sql ) ;
2022-03-28 12:41:28 +02:00
cmd . Parameters . AddWithValue ( "data" , count = = buffer . Length ? buffer : buffer [ offset . . ( offset + count ) ] ) ;
cmd . ExecuteNonQuery ( ) ;
_lastWrite = DateTime . UtcNow ;
}
else // delay writings for a full second
_delayedWrite = Task . Delay ( left ) . ContinueWith ( t = > { lock ( this ) { _delayedWrite = null ; Write ( _data , 0 , _dataLen ) ; } } ) ;
}
public override long Length = > _dataLen ;
public override long Position { get = > 0 ; set { } }
public override bool CanSeek = > false ;
public override bool CanRead = > true ;
public override bool CanWrite = > true ;
public override long Seek ( long offset , SeekOrigin origin ) = > 0 ;
public override void SetLength ( long value ) { }
public override void Flush ( ) { }
}
#endregion
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
HOW TO USE AND DEPLOY THIS EXAMPLE HEROKU USERBOT :
2022-09-02 23:02:44 +02:00
- From your Heroku . com account dashboard , create a new app
- Navigate to the app Resources and add the add - on "Heroku Postgres"
2022-03-28 12:41:28 +02:00
- Navigate to the app Settings , click Reveal Config Vars and save the Heroku git URL and the value of DATABASE_URL
- Add a new var named "api_hash" with your api hash obtained from https : //my.telegram.org/apps
- Add a new var named "phone_number" with the phone_number of the user this userbot will manage
- Scroll down to Buildpacks and add this URL : https : //github.com/jincod/dotnetcore-buildpack.git
- In Visual Studio , Clone the Heroku git repository and add some standard . gitignore . gitattributes files
- In this repository folder , create a new . NET Console project with this Program . cs file
- Add these Nuget packages : WTelegramClient and Npgsql
- In Project properties > Debug > Environment variables , configure the same values for DATABASE_URL , api_hash , phone_number
- Run the project in Visual Studio . The first time , it should ask you interactively for elements to complete the connection
- On the following runs , the PostgreSQL database contains the session data and it should connect automatically
- You can test the userbot by sending him "Ping" in private message ( or saved messages ) . It should respond with "Pong"
- You can now commit & push your git sources to Heroku , they will be compiled and deployed / run as a web app
- The userbot should work fine for 1 minute , but it will be taken down because it is not a web app . Let ' s fix that
- Navigate to the app Resources , copy the line starting with : web cd $ HOME / . . . . . . .
- Back in Visual Studio ( or Explorer ) , at the root of the repository create a Procfile text file ( without extension )
- Paste inside the line you copied , and replace the initial "web" with "worker:" ( don ' t forget the colon )
- Commit and push the Git changes to Heroku . Wait until the deployment is complete .
- Verify that the Resources "web" line has changed to "worker" and is enabled ( use the pencil icon if necessary )
2022-09-02 23:02:44 +02:00
- Now your userbot should be running 24 / 7.
2022-04-23 01:34:21 +02:00
- To prevent AUTH_KEY_DUPLICATED issues , set a SESSION_NAME env variable in your local VS project with a value like "PC"
2022-03-28 12:41:28 +02:00
DISCLAIMER : I ' m not affiliated nor expert with Heroku , so if you have any problem with the above I might not be able to help
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /