Open Legend1991 opened 2 years ago
The environment means the environment in which the code will be executed. How can cloudflare be an environment? Can you please provide the full context?
@alik0211 So Cloudflare provides a serverless service called Cloudflare Workers. It behaves similar to JavaScript in the browser or in Node.js and under the hood, the Workers runtime uses the V8 engine, but there are some differences (e. g. WebSockets). You can read more how Workers works here if you interested in.
I spent a few days trying to understand and get it to work with Cloudflare Workers, thought it would be helpful for anyone looking to use mtproto with Workers.
@Legend1991 is your fork working with cloudflare worker?
@Legend1991 is your fork working with cloudflare worker?
@NumairAwan yes it is. I'm using it for my project currently. That was the main purpose.
I am trying to use in worker from last 5 days but failing. Can you please give example or your main file of mtprot-core or the worker if possible. I tried to browserify the mtproto but still its not working.
Sure. I have telegram.js file that describes telegram api that I'm using:
const MTProto = require('@mtproto/core/envs/cloudflare');
const { sleep } = require('@mtproto/core/src/utils/common');
class TelegramKVGateway {
async set(key, value) {
await TELEGRAM_KV.put(key, value);
}
async get(key) {
return TELEGRAM_KV.get(key);
}
}
class API {
constructor() {
this.mtproto = new MTProto({
api_id: TELEGRAM_API_ID,
api_hash: TELEGRAM_API_HASH,
storageOptions: {
instance: new TelegramKVGateway(),
},
});
}
async call(method, params, options = {}) {
try {
const result = await this.mtproto.call(method, params, options);
return result;
} catch (error) {
console.log(`${method} error:`, error);
const { error_code, error_message } = error;
if (error_code === 420) {
const seconds = Number(error_message.split('FLOOD_WAIT_')[1]);
const ms = seconds * 1000;
await sleep(ms);
return this.call(method, params, options);
}
if (error_code === 303) {
const [type, dcIdAsString] = error_message.split('_MIGRATE_');
const dcId = Number(dcIdAsString);
// If auth.sendCode call on incorrect DC need change default DC, because
// call auth.signIn on incorrect DC return PHONE_CODE_EXPIRED error
if (type === 'PHONE') {
await this.mtproto.setDefaultDc(dcId);
} else {
Object.assign(options, { dcId });
}
return this.call(method, params, options);
}
return Promise.reject(error);
}
}
}
const randomID = () =>
Math.ceil(Math.random() * 0xffffff) + Math.ceil(Math.random() * 0xffffff);
export async function sendMessage(user_id, access_hash, message, entities = []) {
try {
const api = new API();
await api.call('messages.sendMessage', {
clear_draft: true,
peer: {
_: 'inputPeerUser',
user_id,
access_hash,
},
message,
entities: [...entities],
random_id: randomID(),
});
} catch (error) {
console.log('error:', error.message);
}
}
export async function importContacts(phone) {
try {
const api = new API();
const result = await api.call('contacts.importContacts', {
contacts: [
{
_: 'inputPhoneContact',
client_id: randomID(),
phone,
first_name: `${randomID()}`,
},
],
});
return result;
} catch (error) {
console.log('error:', error.message);
return { users: [{}] };
}
}
Here I use Cloudflare KV storage (TELEGRAM_KV) and it looks like this:
So you need to sign in first to get those data. And then this is how I use telegram api:
import * as telegram from './telegram.js';
async function run() {
const phone = '+12025550163'; // example phone number
const otp = '123456';
const text = 'Your confirmation code is ${otp}\nDo not share this code with anyone else.\n\nThis code is valid for 2 minutes';
const entities = [{ _: 'messageEntityBold', offset: 34, length: 6 }];
const { users } = await telegram.importContacts(phone);
const { id, access_hash } = users[0];
await telegram.sendMessage(id, access_hash, text, entities);
}
Let me know if you are available. The only reason why i asked you to install it for me is, I am not understanding how you are using mtproto-core api in cloudflare worker. You cant add files and you can also not use require in cf workers. I tried to browserify the mtproto-core but its not working because mtproto-core browser using the some browser function like windows, localstorage etc that don't work in cf worker.
@NumairAwan to be able to use require in cf workers you need to use Cloudflare's CLI called Wrangler. You can read how to use it here in the cloudflare's doc: https://developers.cloudflare.com/workers/get-started/guide/
Thanks i tried with wrangler. Seems like it will work but i am getting some errors if you can address.
@NumairAwan that's because "../builder" and "../parser" are generated by npm's prepublishOnly script.
install original mtproto from npm:
npm install @mtproto/core
and then put 2 files from this PR localy (with fixed imports) nearby to telegram.js file but into mtproto-cloudflare folder:
mtproto-cloudflare/index.js content:
const makeMTProto = require('@mtproto/core/src');
const SHA1 = require('@mtproto/core/envs/browser/sha1');
const SHA256 = require('@mtproto/core/envs/browser/sha256');
const PBKDF2 = require('@mtproto/core/envs/browser/pbkdf2');
const Transport = require('./transport');
const getRandomBytes = require('@mtproto/core/envs/browser/get-random-bytes');
const getLocalStorage = require('@mtproto/core/envs/browser/get-local-storage');
function createTransport(dc, crypto) {
return new Transport(dc, crypto);
}
const MTProto = makeMTProto({
SHA1,
SHA256,
PBKDF2,
getRandomBytes,
getLocalStorage,
createTransport,
});
module.exports = MTProto;
and mtproto-cloudflare/transport.js content:
const Obfuscated = require('@mtproto/core/src/transport/obfuscated');
const subdomainsMap = {
1: 'pluto',
2: 'venus',
3: 'aurora',
4: 'vesta',
5: 'flora',
};
const readyState = {
OPEN: 1,
};
class Transport extends Obfuscated {
constructor(dc, crypto) {
super();
this.dc = dc;
this.url = `https://${subdomainsMap[this.dc.id]}.web.telegram.org${
this.dc.test ? '/apiws_test' : '/apiws'
}`;
this.crypto = crypto;
this.connect();
}
get isAvailable() {
return this.socket.readyState === readyState.OPEN;
}
async connect() {
let resp = await fetch(this.url, {
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
},
});
// If the WebSocket handshake completed successfully, then the
// response has a `webSocket` property.
this.socket = resp.webSocket;
if (!this.socket) {
throw new Error("server didn't accept WebSocket");
}
this.socket.binaryType = 'arraybuffer';
this.socket.accept();
this.socket.addEventListener('error', this.handleError.bind(this));
this.socket.addEventListener('open', this.handleOpen.bind(this));
this.socket.addEventListener('close', this.handleClose.bind(this));
this.socket.addEventListener('message', this.handleMessage.bind(this));
this.handleOpen();
}
async handleError() {
this.emit('error', {
type: 'socket',
});
}
async handleOpen() {
const initialMessage = await this.generateObfuscationKeys();
this.socket.send(initialMessage);
this.emit('open');
}
async handleClose() {
if (this.isAvailable) {
this.socket.close();
}
this.connect();
}
async handleMessage(event) {
const obfuscatedBytes = new Uint8Array(event.data);
const bytes = await this.deobfuscate(obfuscatedBytes);
const payload = this.getIntermediatePayload(bytes);
this.emit('message', payload.buffer);
}
async send(bytes) {
const intermediateBytes = this.getIntermediateBytes(bytes);
const { buffer } = await this.obfuscate(intermediateBytes);
this.socket.send(buffer);
}
}
module.exports = Transport;
Then change the first require in telegram.js file to
const MTProto = require('./mtproto-cloudflare');
Now this should work.
@Legend1991 thanks its working now.
how i can use TELEGRAM_KV ? is it possible to use kv storage outside outside the worker.js file
@Legend1991 sorry for the mention. But does this allow me use mtproto proxy on telegram on a cloudflare worker? If yes can you explain it a bit?
I ran into this PR and observed that the Cloudflare Workers runtime environment supports the implementation of MTProto within the browser environment now. Maybe this changed sometime recently? Not sure if the PR is needed anymore.
Cloudflare has its own way to work with WebSockets. Turns out to make it work you have to handle the handshaking process yourself using HTTP Upgrade mechanism. Actually it is the only difference from browser implementation.