1
0
mirror of https://github.com/fazo96/telecommander.git synced 2025-01-10 11:34:20 +01:00
telecommander/telecommander.js
2015-08-30 22:19:30 +00:00

547 lines
15 KiB
JavaScript
Executable File

#!/usr/bin/env node
var path = require('path')
var cfgDir = path.join(process.env.XDG_CONFIG_HOME || (path.join(process.env.HOME || process.env.USERPROFILE, '/.config/')), 'telecommander/')
process.env.LOGGER_FILE = cfgDir+'log'
var os = require('os')
var fs = require('fs')
var moment = require('moment')
var blessed = require('blessed')
/* IF YOU FORK THE APP PLEASE CHANGE THE ID
* AND HASH IN THE APP OBJECT! THEY IDENTIFY
* THE APPLICATION CREATOR AND YOU CAN
* OBTAIN YOURS FROM http://my.telegram.org
*/
var app = {
id: '42419',
hash: '90a3c2cdbf9b391d9ed72c0639dc0786',
version: require('./package.json').version,
lang: 'en',
deviceModel: os.type(),
systemVersion: os.platform()+'/'+os.release()
}
try { fs.makeDirSync(cfgDir,'0770') } catch (e) { }
var getLogger = require('get-log')
getLogger.PROJECT_NAME = 'telecommander'
var logger = getLogger('main')
var telegramLink = require('telegram.link')()
// Prepare blessed UI
var screen = blessed.screen({
smartCSR: true,
dockBorders: true
})
screen.title = "Telecommander"
var defaultStyle = {
fg: 'white',
border: { fg: 'gray' },
scrollbar: {
bg: 'blue',
fg: 'red'
}
}
// Contact list window
var chats = blessed.list({
left: 0,
top:0,
height: screen.height-3,
width: '20%',
border: { type: 'line' },
mouse: true,
invertSelected: false,
style: defaultStyle,
})
chats.style.selected = { bg: 'blue' }
// Function to create a log box
function mkBox(){
var box = blessed.log({
right: 0,
width: '80%',
height: screen.height - cmdline.height,
border: { type: 'line' },
scrollable: true,
//draggable: true,
style: defaultStyle
})
box.hide()
return box
}
// Command line prompt
var cmdline = blessed.textbox({
inputOnFocus: true,
bottom: 0,
left: 'center',
width: '100%',
height: 3,
border: { type: 'line' },
style: defaultStyle
})
var statusWindow = "Status"
// mgsBox holds the chat boxes for every list entry
var msgBox = { }
msgBox[statusWindow] = mkBox()
// Add stuff to the screen
screen.append(chats);
screen.append(cmdline);
screen.append(msgBox[statusWindow]);
chats.addItem(msgBox[statusWindow])
switchToBox(statusWindow)
screen.on('resize',function(){
for(i in msgBox){
item = msgBox[i]
item.height = screen.height - cmdline.height
}
chats.height = screen.height - cmdline.height
screen.render()
})
screen.render()
// Contacts holds all the contacts data
var contacts = { }
// Groups hold all the data about groups
var groups = { }
// unameToUid is used to match a name to its user id
var unameToUid = { }
// same thing for group name -> group object
var gnameToGid = { }
var state = { } // keeps track of the telegram update state
var client // used to talk with telegram
var user = { } // holds data about current user
var authKey // our authorization key to access telegram
var connected = false // keep track of wether we are good to go and logged in
var selectedWindow = statusWindow // the currently selected window
// Write something in the Status box
function log(){
args = Array.prototype.slice.call(arguments)
var msg = args.join(' ')
msgBox[statusWindow].add(msg)
logger.info(msg)
}
function command(cmd){
cmdl = cmd.split(' ')
cmdname = cmdl[0]
if(cmdname === 'phone'){ // So the user can provide his phone numbah
if(connected){
return log("Silly user, you're already connected! We don't need that phone number")
}
user.phone = cmd.split(' ')[1]
var mindate = moment()
client.auth.sendCode(user.phone,5,'en',function(result){
if(result.err_code){
return log('Errors:',result.error_code,result.error_message)
}
//log('Res:',JSON.stringify(result))
user.registered = result.phone_registered
user.phoneCodeHash = result.phone_code_hash
function gmd(){
var m = moment()
m = m.subtract(m.diff(mindate))
return 'Please use a telegram code not older than '+m.fromNow(true)
}
if(!user.registered){
log("Your number is not registered. The client will register your account with the Telegram service")
log(gmd())
log('Ready for phone code, use command: "code <code> <name> <lastname>" to register')
} else {
log("Your number is already registered with telegram. The client will log in.")
log(gmd())
log('Ready for phone code, use command: "code <code>" to login')
}
})
} else if(cmdname === 'code'){ // So the user can provide his phone code
if(connected){
return log("Silly user, you're already connected! We don't need that phone code")
}
code = cmdl[1]
name = cmdl[2]
lastname = cmdl[3]
if(((!name || !lastname) && !user.registered) || !code)
return log('insufficient arguments:',cmd)
cb = function(result){
user.id = ''+result.user.id
user.phone = result.user.phone
user.phoneCodeHash = result.phone_code_hash
user.username = result.user.username
user.first_name = result.user.first_name
user.last_name = result.user.last_name
// Done, write user data and key to disk
log('Writing key to disk...')
fs.writeFile(cfgDir+'key',authKey,function(err){
if(err)
log('Could not write key to disk:',err)
else
log('key saved to disk')
})
log('Writing user info to disk...')
fs.writeFile(cfgDir+'user_data.json',JSON.stringify(user),function(err){
if(err)
log("ERROR: couldn't write user_data.json:",err)
else
log('user_data.json saved to disk with data:',JSON.stringify(user))
})
whenReady()
}
// Log in finally
if(user.registered) client.auth.signIn(user.phone,user.phoneCodeHash,code,cb)
else client.auth.signUp(user.phone,user.phoneCodeHash,code,name,lastname,cb)
} else {
log('Command not found.')
}
}
// What happens when a different window is selected
chats.on('select',function(selected){
log('SELECT:',selected.content)
switchToBox(selected.content)
})
function switchToBox(boxname){
if(selectedWindow && msgBox[selectedWindow])
msgBox[selectedWindow].hide()
selectedWindow = boxname;
var newb = getMsgBox(selectedWindow)
newb.show()
}
// Get msgBox for given group/user NAME, create if not exists
function getMsgBox(chat,switchto){
if(chat === undefined){
log('ERROR: asked for box for "undefined"!!')
return msgBox[statusWindow]
}
if(!msgBox[chat]){
log('Generating window: "'+chat+'"')
msgBox[chat] = mkBox()
screen.append(msgBox[chat])
getMessages(chat,msgBox[chat])
} // else log('Getting window','"'+chat+'"')
if(switchto === true){
switchToBox(chat)
}
return msgBox[chat]
}
// What happens when the user submits a command in the prompt
cmdline.on('submit',function(value){
msgBox[statusWindow].add('< '+value)
if(selectedWindow === statusWindow || nameToObj(selectedWindow) === undefined){
//log('Window:',selectedWindow,'Eval cmd:',value)
command(value)
} else if(value.indexOf('//') === 0){
sendMsg(selectedWindow,value.substring(1))
} else if(value.indexOf('/') === 0){
command(value.substring(1))
} else {
sendMsg(selectedWindow,value)
}
cmdline.clearValue()
cmdline.focus()
})
cmdline.focus() // make sure prompt is focused
function quit(){
if(connected || client != undefined){
log('Closing communications and shutting down...')
client.end(function(){
process.exit(0)
})
} else process.exit(0);
}
// Catch ctrl-c or escape event and close program
cmdline.key(['escape','C-c'], function(ch,key){
quit()
});
function nameToObj(name){
var id = gnameToGid[name]
if(groups[id] && groups[id].title === name)
return groups[id]
else {
id = unameToUid[name]
return contacts[id]
}
}
function idToPeer(uid,type){
if(type === 'user')
return new telegramLink.type.InputPeerContact({ props: { user_id: ''+uid } })
else if(type === 'group')
return new telegramLink.type.InputPeerChat({ props: { chat_id: ''+uid } })
}
// Send a message
function sendMsg(name,str){
if(!connected){
return log('Error: not ready to send messages')
}
var peer = idToPeer(nameToObj(name).id,nameToObj(name).title?'group':'user')
var randid = parseInt(Math.random() * 1000000000)
log('Sending Message to:',peer.toPrintable())
client.messages.sendMessage(peer,str,randid,function(sent){
log('Sent message:','"'+str+'"','to:',selectedWindow+':',sent.toPrintable())
})
}
// Connects to telegram
function connect(){
client = telegramLink.createClient(app, telegramLink.PROD_PRIMARY_DC, function(){
if(!app.authKey){
log('Creating authkey...')
client.createAuthKey(function(auth){
authKey = auth.key.encrypt('password') // I know sorry, but I'm testing. Will add security later, I promise
log('Created key')
// Writes the new encrypted key to disk
log('ready for phone number, use command: phone <number>')
})
} else {
log('Authkey loaded from disk. Should be ready to go.')
whenReady()
}
})
client.once('dataCenter',function(dcs){
log('Datacenters:',dcs.toPrintable())
})
}
// Executed when connected and logged in
function whenReady(){
log('READY!')
connected = true
downloadData()
}
// Downloads stuff
function downloadData(){
log('Downloading data...')
client.contacts.getContacts('',function(cont){
chats.clearItems()
chats.add(statusWindow)
cont.users.list.forEach(addUser)
})
client.messages.getDialogs(0,0,10,function(dialogs){
dialogs.chats.list.forEach(addGroup)
})
client.updates.getState(function(astate){
updateState(astate)
log(state.unreadCount,'unread messages')
log('Started receiving updates')
// Can't use registerOnUpdates because it's apparently broken
//client.registerOnUpdates(onUpdate)
setTimeout(downloadUpdates,1000)
})
}
function addUser(u){
contacts[u.id] = { user: u, id: u.id}
var name = getName(u.id,'user')
unameToUid[name] = u.id
chats.addItem(name)
}
function addGroup(group){
if(groups[group.id]) return;
if(group.left === true) return;
groups[group.id] = { id: group.id, title: group.title }
gnameToGid[group.title] = group.id
chats.addItem(group.title)
log('Added group:',group.title)
}
// Updates the current state
function updateState(newstate){
state.pts = newstate.pts
state.qts = newstate.qts
state.date = newstate.date
state.sqp = newstate.seq
state.unreadCount = newstate.unread_count
}
// process an update
function onUpdate(upd){
log('Got Update:',upd.toPrintable())
// Process update
if(update.message){
// It's a chat message
log('Writing chat message to ',update.from_id)
//appendMsg(update,getName(update.from_id))
}
}
function downloadUpdates(){
client.updates.getDifference(state.pts,state.date,state.qts,function(res){
if(!res.instanceOf('api.type.updates.DifferenceEmpty')){
//log('Got Diff: ',res.toPrintable())
if(res.state){
updateState(res.state)
}
if(res.new_messages){
res.new_messages.list.forEach(function(msg){
if(!msg.message) return log('Empty message!',msg)
//log('Scheduling message: '+msg.message)
appendMsg(msg,undefined,false,true)
})
}
if(res.chats){
res.chats.list.forEach(function(c){
if(!groups[c.id]){
groups[c.id] = { id: c.id, title: c.title }
}
})
}
}
setTimeout(downloadUpdates,1000)
})
}
function getName(id,type){
if(type === undefined) throw new Error('no type')
if(type === 'group' && groups[id])
return groups[id].title
else if(type === 'user' && contacts[id]){
var u = contacts[id].user
return u.first_name + ' ' + u.last_name + (u.username?' (@'+u.username+')':'')
} else log('Failed to find name for:',id)
}
// Get message history with given name in the given box
function getMessages(name,box){
if(!connected){
return log('Uh cant get messages cuz not connected.....')
}
//log('Name to obj:',name)
var obj = nameToObj(name)
if(!obj || !obj.id){
return log("Can't get messages",obj,obj.id,obj.title)
}
var type = obj.title?'group':'user'
var peer = idToPeer(obj.id,type)
box.add('Downloading message history for '+name)
if(!peer) return log('Could not find peer:',name)
client.messages.getHistory(peer,0,-1,20,function(res){
//log(res.toPrintable())
log('Got history for: '+getName(peer.user_id||peer.chat_id,peer.chat_id?'group':'user'))
if(!res.messages){
return box.add(res.toPrintable())
}
res.messages.list.sort(function(msg1,msg2){
return msg1.date - msg2.date
})
if(res.messages.list.length === 0)
return appendToUserBox('No messages.',res)
res.messages.list.forEach(function(msg){
if(!msg.message) return log('Empty message!',msg.toPrintable())
//log('Scheduling message: '+msg.toPrintable())
appendMsg(msg)
})
})
}
function appendToUserBox(msg,context){
var goesto
if(context.messages.list.length > 0){
if(context.messages.list[0].to_id.chat_id){
// Group message
log('Chose',getName(context.messages.list[0].to_id.chat_id,'group'))
goesto = getMsgBox(getName(context.messages.list[0].to_id.chat_id))
}
}
if(goesto === undefined){
if(context.users.list[0].user_id == user.id){
goesto = getMsgBox(getName(context.users.list[1].id,'user'))
} else{
goesto = getMsgBox(getName(context.users.list[0].id,'user'))
}
}
appendMsg(msg,goesto,true)
}
// Writes given telegram.link "message" object to given boxId
function appendMsg(msg,toBoxId,bare,smartmode){
var box,param
if(toBoxId != undefined){
box = toBoxId
} else {
if(msg.to_id.chat_id != undefined){
// Is a group
param = getName(msg.to_id.chat_id,'group')
} else if(msg.from_id === msg.to_id.user_id || msg.from_id != user.id){
param = getName(msg.from_id,'user')
} else if(msg.to_id.user_id != undefined && msg.to_id.user_id != user.id) {
// don't forget dat .user_id! don't need it in from_id...
param = getName(msg.to_id.user_id,'user')
}
if(smartmode && !bare){
// Smart mode doesn't append the message to the box if it doesn't exist
// because when created, the box will download message history
if(msgBox[param] === undefined) return;
}
box = getMsgBox(param)
}
if(bare)
box.add(msg)
else {
var from = msg.from_id
var date = moment.unix(msg.date).fromNow()
name = getName(from,'user')
box.add(date+' | '+(name || from)+' > '+msg.message)
}
}
// - Entry Point -
// Load authKey and userdata from disk, then act depending on outcome
var keyPath = cfgDir+'key'
log('Checking disk for key...')
fs.exists(keyPath,function(exists){
if(exists){
log('Key found')
fs.readFile(keyPath,function(err,content){
if(err)
log('Error while reading key:',err)
else {
authKey = telegramLink.retrieveAuthKey(content,'password') // yeah sorry just testing
app.authKey = authKey
log('Key loaded')
fs.readFile(cfgDir+'user_data.json',function(err,data){
if(err)
log("FATAL: couldn't read user_data.json")
else {
try {
log("Got User Data from disk: ",data)
user = JSON.parse(data)
connect()
} catch (e) {
log("FATAL: user data corrupted:",e)
}
}
})
}
})
} else {
log('Key not found')
connect()
}
})