Logging in and auth persistence actually works now
This commit is contained in:
+9
-3
@@ -25,6 +25,10 @@ bridge:
|
|||||||
|
|
||||||
command_prefix: "!tg"
|
command_prefix: "!tg"
|
||||||
|
|
||||||
|
# The key used to encrypt Telegram authentication tokens
|
||||||
|
# You can generate a new key using `pwgen 32`.
|
||||||
|
auth_key_password: long_string_to_encrypt_telegram_auth_keys
|
||||||
|
|
||||||
# Whitelist of user IDs that are allowed to use this bridge. Leave empty to disable.
|
# Whitelist of user IDs that are allowed to use this bridge. Leave empty to disable.
|
||||||
# You can enter a domain without the localpart to allow all users from that homeserver to use the bridge.
|
# You can enter a domain without the localpart to allow all users from that homeserver to use the bridge.
|
||||||
whitelist:
|
whitelist:
|
||||||
@@ -33,8 +37,10 @@ bridge:
|
|||||||
|
|
||||||
# Telegram app config. Generate your own keys at https://my.telegram.org/apps
|
# Telegram app config. Generate your own keys at https://my.telegram.org/apps
|
||||||
telegram:
|
telegram:
|
||||||
|
# Enables the !tg api ... commands for debugging.
|
||||||
|
# Do not enable this in production, it allows all whitelisted users to call any Telegram API methods freely.
|
||||||
|
allow_direct_api_calls: false
|
||||||
server_config:
|
server_config:
|
||||||
dev: false
|
dev: false
|
||||||
api_config:
|
api_id: 12345
|
||||||
api_id: 12345
|
api_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz
|
||||||
api_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"url": "https://github.com/tulir/mautrix-telegram.git"
|
"url": "https://github.com/tulir/mautrix-telegram.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"telegram-mtproto": "3.2.x",
|
"telegram-mtproto": "3.x.x",
|
||||||
"matrix-js-sdk": "0.x.x",
|
"matrix-js-sdk": "0.x.x",
|
||||||
"matrix-appservice-bridge": "1.x.x",
|
"matrix-appservice-bridge": "1.x.x",
|
||||||
"commander": "2.11.x",
|
"commander": "2.11.x",
|
||||||
|
|||||||
+37
-10
@@ -14,8 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
const {Bridge} = require("matrix-appservice-bridge")
|
const {Bridge} = require("matrix-appservice-bridge")
|
||||||
|
const crypto = require("crypto")
|
||||||
const commands = require("./commands")
|
const commands = require("./commands")
|
||||||
const MatrixUser = require("./matrix-user")
|
const MatrixUser = require("./matrix-user")
|
||||||
|
const YAML = require("yamljs")
|
||||||
|
|
||||||
class MautrixTelegram {
|
class MautrixTelegram {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
@@ -40,10 +42,18 @@ class MautrixTelegram {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
async run() {
|
||||||
console.log("Appservice listening on port %s", this.config.appservice.port)
|
console.log("Appservice listening on port %s", this.config.appservice.port)
|
||||||
this.bridge.run(this.config.appservice.port, {})
|
await this.bridge.run(this.config.appservice.port, {})
|
||||||
//this.botIntent.setDisplayName(this.config.bridge.bot_displayname)
|
const userEntries = await this.bridge.getUserStore().select({
|
||||||
|
type: "matrix",
|
||||||
|
})
|
||||||
|
for (const entry of userEntries) {
|
||||||
|
const user = MatrixUser.fromEntry(this, entry)
|
||||||
|
this.matrixUsersByID.set(entry.id, user)
|
||||||
|
}
|
||||||
|
// .then(() =>
|
||||||
|
// this.botIntent.setDisplayName(this.config.bridge.bot_displayname))
|
||||||
}
|
}
|
||||||
|
|
||||||
get bot() {
|
get bot() {
|
||||||
@@ -57,7 +67,6 @@ class MautrixTelegram {
|
|||||||
getMatrixUser(id) {
|
getMatrixUser(id) {
|
||||||
let user = this.matrixUsersByID.get(id)
|
let user = this.matrixUsersByID.get(id)
|
||||||
if (user) {
|
if (user) {
|
||||||
console.log(id, "found in cache")
|
|
||||||
return Promise.resolve(user)
|
return Promise.resolve(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,16 +76,13 @@ class MautrixTelegram {
|
|||||||
}).then(entries => {
|
}).then(entries => {
|
||||||
this.matrixUsersByID.get(id)
|
this.matrixUsersByID.get(id)
|
||||||
if (user) {
|
if (user) {
|
||||||
console.log(id, "found in cache (after race)")
|
|
||||||
return Promise.resolve(user)
|
return Promise.resolve(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entries.length) {
|
if (entries.length) {
|
||||||
user = MatrixUser.fromEntry(this, entries[0])
|
user = MatrixUser.fromEntry(this, entries[0])
|
||||||
console.log(id, "loaded from database")
|
|
||||||
} else {
|
} else {
|
||||||
user = new MatrixUser(this, id)
|
user = new MatrixUser(this, id)
|
||||||
console.log(id, "created")
|
|
||||||
}
|
}
|
||||||
this.matrixUsersByID.set(id, user)
|
this.matrixUsersByID.set(id, user)
|
||||||
return user
|
return user
|
||||||
@@ -85,10 +91,11 @@ class MautrixTelegram {
|
|||||||
|
|
||||||
putUser(user) {
|
putUser(user) {
|
||||||
const entry = user.toEntry()
|
const entry = user.toEntry()
|
||||||
return this.bridge.getUserStore().upsert({
|
const query = {
|
||||||
type: entry.type,
|
type: entry.type,
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
}, entry)
|
}
|
||||||
|
return this.bridge.getUserStore().upsert(query, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMatrixEvent(evt) {
|
handleMatrixEvent(evt) {
|
||||||
@@ -124,7 +131,8 @@ class MautrixTelegram {
|
|||||||
commands.run(user, command, args, reply =>
|
commands.run(user, command, args, reply =>
|
||||||
this.botIntent.sendText(
|
this.botIntent.sendText(
|
||||||
evt.room_id,
|
evt.room_id,
|
||||||
reply.replace("$cmdprefix", cmdprefix)))
|
reply.replace("$cmdprefix", cmdprefix)),
|
||||||
|
this)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -146,6 +154,25 @@ class MautrixTelegram {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encrypt(value) {
|
||||||
|
var cipher = crypto.createCipher("aes-256-gcm", this.config.bridge.auth_key_password);
|
||||||
|
var ret = cipher.update(Buffer.from(value), "hex", "base64");
|
||||||
|
ret += cipher.final("base64");
|
||||||
|
|
||||||
|
return [ret, cipher.getAuthTag().toString("base64")];
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt(value) {
|
||||||
|
if(!value) return value;
|
||||||
|
|
||||||
|
var decipher = crypto.createDecipher("aes-256-gcm", this.config.bridge.auth_key_password);
|
||||||
|
decipher.setAuthTag(new Buffer(value[1], "base64"));
|
||||||
|
var ret = decipher.update(value[0], "base64", "hex");
|
||||||
|
ret += decipher.final("hex");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MautrixTelegram
|
module.exports = MautrixTelegram
|
||||||
|
|||||||
+28
-18
@@ -15,22 +15,9 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
const makePasswordHash = require("telegram-mtproto").plugins.makePasswordHash
|
const makePasswordHash = require("telegram-mtproto").plugins.makePasswordHash
|
||||||
|
|
||||||
class Command {
|
|
||||||
constructor(description, usage, func) {
|
|
||||||
this.description = description
|
|
||||||
this.usage = usage
|
|
||||||
this.func = func
|
|
||||||
}
|
|
||||||
|
|
||||||
run(app, roomID, args) {
|
|
||||||
this.func(args, message =>
|
|
||||||
app.botIntent.sendText(roomID, message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const commands = {}
|
const commands = {}
|
||||||
|
|
||||||
function run(sender, command, args, reply) {
|
function run(sender, command, args, reply, app) {
|
||||||
if (sender.commandStatus) {
|
if (sender.commandStatus) {
|
||||||
if (command === "cancel") {
|
if (command === "cancel") {
|
||||||
sender.commandStatus = undefined
|
sender.commandStatus = undefined
|
||||||
@@ -38,14 +25,15 @@ function run(sender, command, args, reply) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
args.unshift(command)
|
args.unshift(command)
|
||||||
sender.commandStatus.next(sender, args, reply)
|
sender.commandStatus.next(sender, args, reply, app)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
command = this.commands[command]
|
command = this.commands[command]
|
||||||
if (!command) {
|
if (!command) {
|
||||||
reply("Unknown command. Try \"$cmdprefix help\" for help.")
|
reply("Unknown command. Try \"$cmdprefix help\" for help.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
command(sender, args, reply)
|
command(sender, args, reply, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.cancel = () => "Nothing to cancel."
|
commands.cancel = () => "Nothing to cancel."
|
||||||
@@ -60,7 +48,7 @@ const enterPassword = (sender, args, reply) => {
|
|||||||
sender.checkPassword(hash)
|
sender.checkPassword(hash)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// TODO show who the user logged in as
|
// TODO show who the user logged in as
|
||||||
reply(`Logged in successfully.`)
|
reply(`Logged in successfully as @${sender.telegramPuppet.getDisplayName()}.`)
|
||||||
sender.commandStatus = undefined
|
sender.commandStatus = undefined
|
||||||
}, err => {
|
}, err => {
|
||||||
reply(`Login failed: ${err}`)
|
reply(`Login failed: ${err}`)
|
||||||
@@ -78,7 +66,7 @@ const enterCode = (sender, args, reply) => {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status === "ok") {
|
if (data.status === "ok") {
|
||||||
// TODO show who the user logged in as
|
// TODO show who the user logged in as
|
||||||
reply(`Logged in successfully.`)
|
reply(`Logged in successfully as @${sender.telegramPuppet.getDisplayName()}.`)
|
||||||
sender.commandStatus = undefined
|
sender.commandStatus = undefined
|
||||||
} else if (data.status === "need-password") {
|
} else if (data.status === "need-password") {
|
||||||
reply(`You have two-factor authentication enabled. Password hint: ${data.hint} \nEnter your password using "$cmdprefix <password>"`)
|
reply(`You have two-factor authentication enabled. Password hint: ${data.hint} \nEnter your password using "$cmdprefix <password>"`)
|
||||||
@@ -116,6 +104,28 @@ commands.login = (sender, args, reply) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commands.api = async (sender, args, reply, app) => {
|
||||||
|
if (!app.config.telegram.allow_direct_api_calls) {
|
||||||
|
reply("Direct API calls are forbidden on this mautrix-telegram instance.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const apiMethod = args.shift()
|
||||||
|
let apiArgs
|
||||||
|
try {
|
||||||
|
apiArgs = JSON.parse(args.join(" "))
|
||||||
|
} catch (err) {
|
||||||
|
reply("Invalid API method parameters. Usage: $cmdprefix api <method> <json data>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
reply(`Calling ${apiMethod} with the following arguments:\n${JSON.stringify(apiArgs, "", " ")}`)
|
||||||
|
const response = await sender.telegramPuppet.client(apiMethod, apiArgs)
|
||||||
|
reply(`API call successful. Response:\n${JSON.stringify(response, "", " ")}`)
|
||||||
|
} catch (err) {
|
||||||
|
reply(`API call errored. Response:\n${JSON.stringify(err, "", " ")}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commands.help = (sender, args, reply) => {
|
commands.help = (sender, args, reply) => {
|
||||||
reply("Help not yet implemented 3:")
|
reply("Help not yet implemented 3:")
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-20
@@ -42,8 +42,8 @@ class MatrixUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toEntry() {
|
toEntry() {
|
||||||
if (this.puppet) {
|
if (this._telegramPuppet) {
|
||||||
this.puppetData = this.puppet.toSubentry()
|
this.puppetData = this.telegramPuppet.toSubentry()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "matrix",
|
type: "matrix",
|
||||||
@@ -72,32 +72,37 @@ class MatrixUser {
|
|||||||
throw new Error(message)
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTelegramCode(phoneNumber) {
|
async sendTelegramCode(phoneNumber) {
|
||||||
// TODO handle existing login?
|
// TODO handle existing login?
|
||||||
|
try {
|
||||||
return this.telegramPuppet.sendCode(phoneNumber)
|
const result = await this.telegramPuppet.sendCode(phoneNumber)
|
||||||
.then(result => {
|
this.phoneNumber = phoneNumber
|
||||||
this.phoneNumber = phoneNumber
|
this.phoneCodeHash = result.phone_code_hash
|
||||||
this.phoneCodeHash = result.phone_code_hash
|
await this.saveChanges()
|
||||||
this.app.putUser(this)
|
return result
|
||||||
return result
|
} catch (err) {
|
||||||
}, err => this.parseTelegramError(err))
|
return this.parseTelegramError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signInToTelegram(phoneCode) {
|
async signInToTelegram(phoneCode) {
|
||||||
if (!this.phoneNumber) throw new Error("Phone number not set")
|
if (!this.phoneNumber) throw new Error("Phone number not set")
|
||||||
if (!this.phoneCodeHash) throw new Error("Phone code not sent")
|
if (!this.phoneCodeHash) throw new Error("Phone code not sent")
|
||||||
|
|
||||||
return this.telegramPuppet.signIn(this.phoneNumber, this.phoneCodeHash, phoneCode)
|
const result = await this.telegramPuppet.signIn(this.phoneNumber, this.phoneCodeHash, phoneCode)
|
||||||
.then(result => {
|
this.phoneCodeHash = undefined
|
||||||
this.phoneCodeHash = undefined
|
await this.saveChanges()
|
||||||
return this.app.putUser(this).then(() => result)
|
return result
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPassword(password_hash) {
|
async checkPassword(password_hash) {
|
||||||
return this.telegramPuppet.checkPassword(password_hash)
|
const result = await this.telegramPuppet.checkPassword(password_hash)
|
||||||
.then(() => this.app.putUser(this))
|
await this.saveChanges()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
saveChanges() {
|
||||||
|
return this.app.putUser(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+106
-27
@@ -31,11 +31,46 @@ class TelegramPuppet {
|
|||||||
this.api_hash = opts.api_hash
|
this.api_hash = opts.api_hash
|
||||||
this.api_id = opts.api_id
|
this.api_id = opts.api_id
|
||||||
|
|
||||||
|
this.puppetStorage = {
|
||||||
|
get: async (key) => {
|
||||||
|
let value = this.data[key]
|
||||||
|
/*if (value && key.match(/_auth_key$/)) {
|
||||||
|
value = this.app.decrypt(value)
|
||||||
|
}*/
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
set: async (key, value) => {
|
||||||
|
/*if (value && key.match(/_auth_key$/)) {
|
||||||
|
value = this.app.encrypt(value)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (this.data[key] === value) return Promise.resolve()
|
||||||
|
|
||||||
|
this.data[key] = value
|
||||||
|
await this.matrixUser.saveChanges()
|
||||||
|
},
|
||||||
|
remove: async (...keys) => {
|
||||||
|
keys.forEach((key) => delete this.data[key])
|
||||||
|
await this.matrixUser.saveChanges()
|
||||||
|
},
|
||||||
|
clear: async () => {
|
||||||
|
this.data = {}
|
||||||
|
await this.matrixUser.saveChanges()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
this.apiConfig = Object.assign({}, {
|
this.apiConfig = Object.assign({}, {
|
||||||
app_version: pkg.version,
|
app_version: pkg.version,
|
||||||
lang_code: "en",
|
lang_code: "en",
|
||||||
api_id: opts.api_id,
|
api_id: opts.api_id,
|
||||||
|
initConnection : 0x69796de9,
|
||||||
|
layer: 57,
|
||||||
|
invokeWithLayer: 0xda9b0d0d,
|
||||||
}, opts.api_config)
|
}, opts.api_config)
|
||||||
|
|
||||||
|
if (this.data.dc && this.data[`dc${this.data.dc}_auth_key`]) {
|
||||||
|
this.listen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromSubentry(app, matrixUser, data) {
|
static fromSubentry(app, matrixUser, data) {
|
||||||
@@ -51,19 +86,17 @@ class TelegramPuppet {
|
|||||||
|
|
||||||
toSubentry() {
|
toSubentry() {
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
user_id: this.userID
|
user_id: this.userID,
|
||||||
}, this.data)
|
}, this.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
get datacenter() {
|
|
||||||
return { dcID: 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
get client() {
|
get client() {
|
||||||
if (!this._client) {
|
if (!this._client) {
|
||||||
|
const self = this
|
||||||
this._client = telegram.MTProto({
|
this._client = telegram.MTProto({
|
||||||
api: this.apiConfig,
|
api: this.apiConfig,
|
||||||
server: this.serverConfig,
|
server: this.serverConfig,
|
||||||
|
app: { storage: this.puppetStorage },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return this._client
|
return this._client
|
||||||
@@ -76,40 +109,86 @@ class TelegramPuppet {
|
|||||||
api_id: this.api_id,
|
api_id: this.api_id,
|
||||||
api_hash: this.api_hash,
|
api_hash: this.api_hash,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signIn(phone_number, phone_code_hash, phone_code) {
|
async signIn(phone_number, phone_code_hash, phone_code) {
|
||||||
return this.client("auth.signIn", {
|
try {
|
||||||
phone_number, phone_code, phone_code_hash
|
const result = await
|
||||||
})
|
this.client("auth.signIn", {
|
||||||
.then(
|
phone_number, phone_code, phone_code_hash,
|
||||||
result => this.signInComplete(result),
|
|
||||||
err => {
|
|
||||||
if (err.type !== "SESSION_PASSWORD_NEEDED") {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
this.client("account.getPassword", {}).then(data => {
|
|
||||||
return {
|
|
||||||
status: "need-password",
|
|
||||||
hint: data.hint,
|
|
||||||
salt: data.current_salt
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
this.signInComplete(result)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message !== "SESSION_PASSWORD_NEEDED") {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
const password = await
|
||||||
|
this.client("account.getPassword", {})
|
||||||
|
return {
|
||||||
|
status: "need-password",
|
||||||
|
hint: password.hint,
|
||||||
|
salt: password.current_salt,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPassword(password_hash) {
|
async checkPassword(password_hash) {
|
||||||
return this.client("auth.checkPassword", {password_hash})
|
const result = await this.client("auth.checkPassword", { password_hash })
|
||||||
.then((result) => this.signInComplete(result))
|
return this.signInComplete(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayName() {
|
||||||
|
if (this.data.first_name || this.data.last_name) {
|
||||||
|
return `${this.data.first_name} ${this.data.last_name}`
|
||||||
|
} else if (this.data.username) {
|
||||||
|
return this.data.username
|
||||||
|
}
|
||||||
|
return this.data.phone_number
|
||||||
}
|
}
|
||||||
|
|
||||||
signInComplete(data) {
|
signInComplete(data) {
|
||||||
this.userID = data.user.id
|
this.userID = data.user.id
|
||||||
|
this.data.username = data.user.username
|
||||||
|
this.data.first_name = data.user.first_name
|
||||||
|
this.data.last_name = data.user.last_name
|
||||||
|
this.data.phone_number = data.user.phone_number
|
||||||
|
this.matrixUser.saveChanges()
|
||||||
|
this.listen()
|
||||||
return {
|
return {
|
||||||
status: "ok"
|
status: "ok",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUpdate(data) {
|
||||||
|
console.log(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async listen() {
|
||||||
|
const client = this.client
|
||||||
|
client.on("update", data => this.handleUpdate(data))
|
||||||
|
if (client.bus) {
|
||||||
|
client.bus.untypedMessage.observe(data => this.handleUpdate(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("Updating online status...")
|
||||||
|
//const statusUpdate = await client("account.updateStatus", { offline: false })
|
||||||
|
//console.log(statusUpdate)
|
||||||
|
console.log("Fetching initial state...")
|
||||||
|
const state = await client("updates.getState", {})
|
||||||
|
console.log("Initial state:", state)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error getting initial state:", err)
|
||||||
|
}
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const state = client("updates.getState", {})
|
||||||
|
console.log("New state received")
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error updating state:", err)
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TelegramPuppet
|
module.exports = TelegramPuppet
|
||||||
|
|||||||
Reference in New Issue
Block a user