Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
/*
* Smuxi - Smart MUltipleXed Irc
*
* Copyright (c) 2005-2016 Mirco Bauer <meebey@meebey.net>
* Copyright (c) 2011 Tuukka Hastrup <Tuukka.Hastrup@iki.fi>
* Copyright (c) 2013-2014 Oliver Schneider <smuxi@oli-obk.de>
*
* Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
using System;
using System.IO;
using System.Net.Security;
using System.Xml;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using agsXMPP;
using agsXMPP.protocol;
using agsXMPP.protocol.client;
using agsXMPP.protocol.x.muc;
using agsXMPP.protocol.iq;
using agsXMPP.protocol.iq.roster;
using agsXMPP.protocol.iq.disco;
using agsXMPP.protocol.extensions.caps;
using agsXMPP.protocol.extensions.chatstates;
using XmppMessageType = agsXMPP.protocol.client.MessageType;
using agsXMPP.Factory;
using agsXMPP.Net;
using Starksoft.Net.Proxy;
using Smuxi.Common;
using System.Runtime.CompilerServices;
using agsXMPP.protocol.extensions.nickname;
namespace Smuxi.Engine
{
[ProtocolManagerInfo(Name = "XMPP", Description = "Extensible Messaging and Presence Protocol", Alias = "xmpp")]
public class XmppProtocolManager : ProtocolManagerBase
{
#if LOG4NET
static readonly log4net.ILog _Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endif
static readonly string LibraryTextDomain = "smuxi-engine-xmpp";
XmppClientConnection JabberClient { get; set; }
MucManager MucManager { get; set; }
DiscoManager Disco { get; set; }
string[] Nicknames { get; set; }
Dictionary<Jid, XmppPersonModel> Contacts { get; set; }
Dictionary<string, DiscoInfo> DiscoCache { get; set; }
ChatModel NetworkChat { get; set; }
GroupChatModel ContactChat { get; set; }
XmppServerModel Server { get; set; }
bool AutoReconnect { get; set; }
TimeSpan AutoReconnectDelay { get; set; }
bool IsDisposed { get; set; }
bool ShowChatStates { get; set; }
// pidgin's psychic mode
bool OpenNewChatOnChatState { get; set; }
public override string NetworkID {
get {
return Host;
}
}
public override string Protocol {
get {
return "XMPP";
}
}
public override ChatModel Chat {
get {
return NetworkChat;
}
}
public override bool IsConnected {
get {
return JabberClient.Authenticated;
}
}
public XmppProtocolManager(Session session) : base(session)
{
Trace.Call(session);
Contacts = new Dictionary<Jid, XmppPersonModel>();
DiscoCache = new Dictionary<string, DiscoInfo>();
ShowChatStates = true;
OpenNewChatOnChatState = true;
JabberClient = new XmppClientConnection();
JabberClient.AutoRoster = true;
JabberClient.AutoPresence = true;
JabberClient.OnMessage += OnMessage;
JabberClient.OnClose += OnClose;
JabberClient.OnLogin += OnLogin;
JabberClient.OnError += OnError;
JabberClient.OnStreamError += OnStreamError;
JabberClient.OnPresence += OnPresence;
JabberClient.OnRosterItem += OnRosterItem;
JabberClient.OnReadXml += OnReadXml;
JabberClient.OnWriteXml += OnWriteXml;
JabberClient.OnAuthError += OnAuthError;
JabberClient.SendingServiceUnavailable += OnSendingServiceUnavailable;
JabberClient.AutoAgents = false; // outdated feature
JabberClient.EnableCapabilities = true;
JabberClient.Capabilities.Node = "https://smuxi.im";
JabberClient.ClientVersion = Engine.VersionString;
// identify smuxi
var ident = JabberClient.DiscoInfo.AddIdentity();
ident.Category = "client";
ident.Type = "pc";
ident.Name = Engine.VersionString;
// add features here (this is just for notification of other clients)
JabberClient.DiscoInfo.AddFeature().Var = "http://jabber.org/protocol/caps";
JabberClient.DiscoInfo.AddFeature().Var = "jabber:iq:last";
JabberClient.DiscoInfo.AddFeature().Var = "http://jabber.org/protocol/muc";
JabberClient.DiscoInfo.AddFeature().Var = "http://jabber.org/protocol/disco#info";
JabberClient.DiscoInfo.AddFeature().Var = "http://jabber.org/protocol/xhtml-im";
Disco = new DiscoManager(JabberClient);
Disco.AutoAnswerDiscoInfoRequests = true;
MucManager = new MucManager(JabberClient);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnSendingServiceUnavailable(object sender, SendingServiceUnavailableEventArgs e)
{
if (e.Stanza.To == null) {
// can only be received by the server
return;
}
if (e.Stanza.To == JabberClient.MyJID.Server) {
// explicitly targeting the server
return;
}
XmppPersonModel person;
if (!Contacts.TryGetValue(e.Stanza.To.Bare, out person)) {
e.Cancel = true;
return;
}
if (person.Subscription != SubscriptionType.both &&
person.Subscription != SubscriptionType.from) {
e.Cancel = true;
return;
}
// the person already knows we are online, this does not give away our privacy
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnStreamError(object sender, agsXMPP.Xml.Dom.Element e)
{
Trace.Call(sender, e);
var error = e as agsXMPP.protocol.Error;
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
// TODO: create user readable error messages from the error.Condition
//builder.AppendErrorText(error.Condition.ToString());
switch (error.Condition) {
case StreamErrorCondition.SystemShutdown:
builder.AppendErrorText(_("The server has shut down"));
break;
case StreamErrorCondition.Conflict:
builder.AppendErrorText(_("Another client logged in with the same resource, you have been disconnected"));
break;
case StreamErrorCondition.SeeOtherHost:
Server.Hostname = e.GetTag("see-other-host");
Reconnect(null);
break;
default:
builder.AppendErrorText(error.Text ?? error.Condition.ToString());
break;
}
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnAuthError(object sender, agsXMPP.Xml.Dom.Element e)
{
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendErrorText(_("Authentication failed, either username does not exist or invalid password"));
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendMessage(_("If you want to create an account with the specified user and password, type /register now"));
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void Connect(FrontendManager fm, ServerModel server)
{
Trace.Call(fm, server);
if (server == null) {
throw new ArgumentNullException("server");
}
if (server is XmppServerModel) {
Server = (XmppServerModel) server;
} else {
Server = new XmppServerModel();
if (server.ServerID != null) {
Server.Load(Session.UserConfig, Protocol, server.ServerID);
}
// HACK: previous line overwrites any passed values with the values from config
// thus we have to copy the original values:
Server.Hostname = server.Hostname;
Server.Network = server.Network;
Server.OnConnectCommands = server.OnConnectCommands;
Server.OnStartupConnect = server.OnStartupConnect;
Server.Password = server.Password;
Server.Port = server.Port;
Server.Protocol = server.Protocol;
Server.ServerID = server.ServerID;
Server.UseEncryption = server.UseEncryption;
Server.Username = server.Username;
Server.ValidateServerCertificate = server.ValidateServerCertificate;
}
Host = Server.Hostname;
Port = Server.Port;
// TODO: use config for single network chat or once per network manager
NetworkChat = Session.CreateChat<ProtocolChatModel>(
NetworkID, String.Format("{0} {1}", Protocol, Host), this
);
Session.AddChat(NetworkChat);
Session.SyncChat(NetworkChat);
Connect();
}
[MethodImpl(MethodImplOptions.Synchronized)]
void Connect()
{
Trace.Call();
Contacts.Clear();
AutoReconnect = true;
AutoReconnectDelay = TimeSpan.FromMinutes(1);
ApplyConfig(Session.UserConfig, Server);
OpenContactChat();
#if LOG4NET
_Logger.Debug("calling JabberClient.Open()");
#endif
JabberClient.Open();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void Reconnect(FrontendManager fm)
{
Trace.Call(fm);
// IsConnected checks for a working xmpp connection
// we need to know the socket's state here
if (JabberClient.XmppConnectionState != XmppConnectionState.Disconnected) {
AutoReconnect = true;
AutoReconnectDelay = TimeSpan.Zero;
JabberClient.Close();
} else {
JabberClient.ClientSocket.OnValidateCertificate -= ValidateCertificate;
JabberClient.SocketConnectionType = SocketConnectionType.Direct;
Reconnect();
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void Disconnect(FrontendManager fm)
{
Trace.Call(fm);
AutoReconnect = false;
JabberClient.Close();
}
public override void Dispose()
{
Trace.Call();
IsDisposed = true;
base.Dispose();
AutoReconnect = false;
JabberClient.SocketDisconnect();
}
// this method is used as status / title
public override string ToString()
{
var status = String.Format("{0} ({1})", JabberClient.Server, Protocol);
if (!IsConnected) {
status += " (" + _("not connected") + ")";
}
return status;
}
DiscoItems ServerDiscoItems { get; set; }
List<Jid> CachedMucJids { get; set; }
Dictionary<Jid, DiscoInfo> CachedMucInfo { get; set; }
DateTime CachedMucJidsTimeStamp { get; set; }
// no need to synchronize this method as it only checks for null
public override IList<GroupChatModel> FindGroupChats(GroupChatModel filter)
{
Trace.Call(filter);
var list = new List<GroupChatModel>();
if (ContactChat == null) {
list.Add(new GroupChatModel("Contacts", "Contacts", this));
}
// find all transport/conference groups/whatnot
DiscoItem[] discoItems;
if (ServerDiscoItems == null) {
var reset = new AutoResetEvent(false);
lock (this) {
Disco.DiscoverItems(JabberClient.Server, (sender, e) => FindGroupChatsDiscoItems(e, reset));
}
reset.WaitOne();
}
lock (this) {
if (ServerDiscoItems == null) {
return list;
} else {
discoItems = ServerDiscoItems.GetDiscoItems();
}
}
var resetList = new List<AutoResetEvent>();
if ((CachedMucJids == null) ||
((DateTime.Now - CachedMucJidsTimeStamp) > TimeSpan.FromMinutes(5))) {
// find all conference groups
var mucList = new List<Jid>();
foreach (var discoItem in discoItems) {
var reset = new AutoResetEvent(false);
var jid = discoItem.Jid;
lock (this) {
Disco.DiscoverInformation(discoItem.Jid, (sender, e) => FindGroupChatsItemDiscoInfo(e, reset, mucList, jid));
}
resetList.Add(reset);
}
foreach (var reset in resetList) {
reset.WaitOne();
}
resetList.Clear();
// find all chats in all conference groups
var jidList = new List<Jid>();
foreach (var mucGroup in mucList) {
var reset = new AutoResetEvent(false);
lock (this) {
Disco.DiscoverItems(mucGroup, (sender, e) => FindGroupChatsDiscoMucs(e, reset, jidList));
}
resetList.Add(reset);
}
foreach (var reset in resetList) {
reset.WaitOne();
}
CachedMucJids = jidList;
CachedMucJidsTimeStamp = DateTime.Now;
CachedMucInfo = new Dictionary<Jid, DiscoInfo>();
}
// filter found items
var filteredList = new List<Jid>();
if (filter == null || String.IsNullOrEmpty(filter.Name)) {
filteredList = CachedMucJids;
} else {
string searchPattern = null;
if (!filter.Name.StartsWith("*") && !filter.Name.EndsWith("*")) {
searchPattern = String.Format("*{0}*", filter.Name);
} else {
searchPattern = filter.Name;
}
foreach (var jid in CachedMucJids) {
if (!Pattern.IsMatch(jid, searchPattern)) {
continue;
}
filteredList.Add(jid);
}
}
// get info on all chats matching the pattern
resetList.Clear();
foreach (var jid in CachedMucJids) {
bool isCached = false;
DiscoInfo info;
lock (this) {
isCached = CachedMucInfo.TryGetValue(jid, out info);
}
if (isCached) {
FindGroupChatsChatInfoParse(jid, info, list);
continue;
}
var reset = new AutoResetEvent(false);
lock (this) {
Disco.DiscoverInformation(jid, (sender, e) => FindGroupChatsChatInfo(e, reset, list));
}
resetList.Add(reset);
}
foreach (var reset in resetList) {
reset.WaitOne();
}
return list;
}
void FindGroupChatsChatInfoParse(Jid jid, DiscoInfo items, List<GroupChatModel> list)
{
var ident = items.SelectSingleElement<DiscoIdentity>();
string name;
if (ident != null && !String.IsNullOrEmpty(ident.Name)) {
name = ident.Name + " [" + jid + "]";
} else {
name = jid;
}
var chat = new GroupChatModel(jid, name, null);
chat.PersonCount = -1;
var x = items.SelectSingleElement<agsXMPP.protocol.x.data.Data>();
if (x != null) {
var users_field = x.GetField("muc#roominfo_occupants");
var topic_field = x.GetField("muc#roominfo_subject");
var desc_field = x.GetField("muc#roominfo_description");
if (users_field != null) {
chat.PersonCount = int.Parse(users_field.GetValue());
}
if (topic_field != null) {
chat.Topic = new MessageModel(topic_field.GetValue());
} else if (desc_field != null) {
chat.Topic = new MessageModel(desc_field.GetValue());
}
}
lock (list) {
list.Add(chat);
}
}
void FindGroupChatsChatInfo(IQEventArgs e, AutoResetEvent reset, List<GroupChatModel> list)
{
if (e.IQ.Error == null) {
var items = (DiscoInfo)e.IQ.Query;
lock (this) {
CachedMucInfo[e.IQ.From] = items;
}
FindGroupChatsChatInfoParse(e.IQ.From, items, list);
}
e.Handled = true;
reset.Set();
}
void FindGroupChatsDiscoMucs(IQEventArgs e, AutoResetEvent reset, List<Jid> list)
{
if (e.IQ.Error == null) {
var items = (DiscoItems)e.IQ.Query;
foreach (var item in items.GetDiscoItems()) {
// no locking required, these callbacks are sequential
list.Add(item.Jid);
}
}
e.Handled = true;
reset.Set();
}
void FindGroupChatsItemDiscoInfo(IQEventArgs e, AutoResetEvent reset, List<Jid> mucList, Jid jid)
{
if (e.IQ.Error == null) {
var discoInfo = (DiscoInfo)e.IQ.Query;
if (discoInfo.HasFeature(agsXMPP.Uri.MUC)) {
// no locking required, these callbacks are sequential
mucList.Add(jid);
}
}
e.Handled = true;
reset.Set();
}
void FindGroupChatsDiscoItems(IQEventArgs e, AutoResetEvent reset)
{
if (e.IQ.Error == null) {
lock (this) {
ServerDiscoItems = (DiscoItems)e.IQ.Query;
}
}
e.Handled = true;
reset.Set();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void OpenContactChat()
{
if (ContactChat == null) {
ContactChat = Session.CreateChat<GroupChatModel>(
"Contacts", "Contacts", this
);
Session.AddChat(ContactChat);
} else if (!ContactChat.IsEnabled) {
Session.EnableChat(ContactChat);
} else {
// already open
return;
}
foreach (var pair in Contacts) {
if (pair.Value.Resources.Count != 0) {
ContactChat.UnsafePersons.Add(pair.Key, pair.Value.ToPersonModel());
}
}
// HACK: lower probability of sync race condition during connect
ThreadPool.QueueUserWorkItem(delegate {
Thread.Sleep(5000);
lock (this) {
if (IsDisposed) {
return;
}
if (ContactChat != null) {
Session.SyncChat(ContactChat);
}
}
});
}
// no need to synchronize as no members are accessed
public override void OpenChat(FrontendManager fm, ChatModel chat)
{
Trace.Call(fm, chat);
if (chat.ID == "Contacts") {
OpenContactChat();
return;
}
CommandModel cmd = new CommandModel(fm, NetworkChat, chat.ID);
switch (chat.ChatType) {
case ChatType.Person:
CommandMessageQuery(cmd);
break;
case ChatType.Group:
CommandJoin(cmd);
break;
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void CloseChat(FrontendManager fm, ChatModel chat)
{
Trace.Call(fm, chat);
if (chat == ContactChat) {
Session.RemoveChat(chat);
ContactChat = null;
} else if (chat.ChatType == ChatType.Group) {
if (IsConnected) {
var groupchat = (XmppGroupChatModel)chat;
if (!groupchat.IsSynced) {
Session.RemoveChat(chat);
} else {
MucManager.LeaveRoom(chat.ID, ((XmppGroupChatModel)chat).OwnNickname);
}
} else {
Session.RemoveChat(chat);
}
} else if (chat.ChatType == ChatType.Person) {
Session.RemoveChat(chat);
} else {
#if LOG4NET
_Logger.Error("CloseChat(): Invalid chat type");
#endif
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override void SetPresenceStatus(PresenceStatus status,
string message)
{
Trace.Call(status, message);
if (!IsConnected) {
return;
}
switch (status) {
case PresenceStatus.Online:
JabberClient.Show = ShowType.NONE;
JabberClient.Priority = Server.Priorities[status];
JabberClient.Status = message;
break;
case PresenceStatus.Away:
JabberClient.Priority = Server.Priorities[status];
JabberClient.Show = ShowType.away;
JabberClient.Status = message;
break;
}
JabberClient.SendMyPresence();
// send presence update to all MUCs, see XEP-0045:
// http://xmpp.org/extensions/xep-0045.html#changepres
foreach (var chat in Chats) {
if (!(chat is XmppGroupChatModel)) {
continue;
}
var muc = (XmppGroupChatModel) chat;
var to = new Jid(muc.ID) {
Resource = muc.OwnNickname
};
var presence = new Presence() {
Show = JabberClient.Show,
Status = JabberClient.Status,
From = JabberClient.MyJID,
To = to
};
if (JabberClient.EnableCapabilities) {
presence.AddChild(JabberClient.Capabilities);
}
JabberClient.Send(presence);
}
base.SetPresenceStatus(status, message);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void CommandRegister(CommandModel command)
{
Trace.Call(command);
Connect();
JabberClient.RegisterAccount = true;
// TODO: add callbacks to process in case of error or success
}
public override bool Command(CommandModel command)
{
bool handled = false;
if (IsConnected) {
if (command.IsCommand) {
switch (command.Command) {
case "help":
CommandHelp(command);
handled = true;
break;
case "msg":
case "query":
CommandMessageQuery(command);
handled = true;
break;
case "me":
CommandMe(command);
handled = true;
break;
case "say":
CommandSay(command);
handled = true;
break;
case "joinas":
CommandJoinAs(command);
handled = true;
break;
case "join":
CommandJoin(command);
handled = true;
break;
case "invite":
CommandInvite(command);
handled = true;
break;
case "part":
case "leave":
CommandPart(command);
handled = true;
break;
case "away":
CommandAway(command);
handled = true;
break;
case "roster":
CommandRoster(command);
handled = true;
break;
case "contact":
CommandContact(command);
handled = true;
break;
case "priority":
CommandPriority(command);
handled = true;
break;
case "whois":
CommandWhoIs(command);
handled = true;
break;
case "register":
CommandRegister(command);
handled = true;
break;
}
} else {
_Say(command.Chat, command.Data);
handled = true;
}
} else {
if (command.IsCommand) {
// commands which work even without being connected
switch (command.Command) {
case "help":
CommandHelp(command);
handled = true;
break;
case "connect":
CommandConnect(command);
handled = true;
break;
}
} else {
// normal text, without connection
NotConnected(command);
handled = true;
}
}
return handled;
}
public void CommandMe(CommandModel command)
{
if (command.Data.Length <= 4) {
return;
}
string actionstring = command.Data.Substring(3);
// http://xmpp.org/extensions/xep-0245.html
// says we should append "/me " no matter what our command char is
_Say(command.Chat, "/me" + actionstring, true, false);
// groupchat echos messages anyway
if (command.Chat.ChatType == ChatType.Person) {
var builder = CreateMessageBuilder();
builder.AppendActionPrefix();
builder.AppendIdendityName(Me);
builder.AppendText(actionstring);
Session.AddMessageToChat(command.Chat, builder.ToMessage());
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void printResource(MessageBuilder builder, XmppResourceModel res)
{
builder.AppendText("\n\tName: {0}", res.Name);
var pres = res.Presence;
builder.AppendText("\n\tPresence:");
builder.AppendText("\n\t\tShow:\t{0}", pres.Show);
builder.AppendText("\n\t\tStatus:\t{0}", pres.Status);
builder.AppendText("\n\t\tLast:\t{0}", (pres.Last!=null)?pres.Last.Seconds.ToString():"");
builder.AppendText("\n\t\tPriority:\t{0}", pres.Priority);
builder.AppendText("\n\t\tType:\t{0}", pres.Type);
builder.AppendText("\n\t\tXDelay:\t{0}", (pres.XDelay!=null)?pres.XDelay.Stamp.ToString():"");
if (res.Disco != null) {
builder.AppendText("\n\tFeatures:");
foreach(var feat in res.Disco.GetFeatures()) {
builder.AppendText("\n\t\t{0}", feat.Var);
}
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void CommandWhoIs(CommandModel cmd)
{
Jid jid;
if (cmd.DataArray.Length < 2) {
if ((cmd.DataArray.Length == 1)
&& (cmd.Chat is PersonChatModel)) {
jid = (cmd.Chat as PersonChatModel).Person.ID;
} else {
NotEnoughParameters(cmd);
return;
}
} else {
jid = GetJidFromNickname(cmd.DataArray[1]);
}
XmppPersonModel person;
var builder = CreateMessageBuilder();
if (!Contacts.TryGetValue(jid.Bare, out person)) {
builder.AppendErrorText(_("Could not find contact {0}"), jid);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
return;
}
if (!String.IsNullOrEmpty(jid.Resource)) {
if (person.Resources.Count > 1) {
builder.AppendText(_("Contact {0} has {1} known resources"), jid.Bare, person.Resources.Count);
}
XmppResourceModel res;
if (!person.Resources.TryGetValue(jid.Resource??"", out res)) {
builder.AppendErrorText(_("{0} is not a known resource"), jid.Resource);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
return;
}
printResource(builder, res);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
return;
}
builder.AppendText(_("Contact's JID: {0}"), person.Jid);
builder.AppendText("\n");
switch (person.Subscription) {
case SubscriptionType.both:
builder.AppendText(_("You have a mutual subscription with this contact"));
break;
case SubscriptionType.none:
builder.AppendText(_("You have no subscription with this contact and this contact is not subscribed to you"));
break;
case SubscriptionType.to:
builder.AppendText(_("You are subscribed to this contact, but the contact is not subscribed to you"));
break;
case SubscriptionType.from:
builder.AppendText(_("You are not subscribed to this contact, but the contact is subscribed to you"));
break;
case SubscriptionType.remove:
#if LOG4NET
_Logger.Debug("a contact with SubscriptionType remove has been found");
#endif
break;
}
int i = 0;
foreach(var res in person.Resources) {
builder.AppendText("\nResource({0}):", i);
printResource(builder, res.Value);
i++;
}
Session.AddMessageToFrontend(cmd, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void CommandContact(CommandModel cd)
{
FrontendManager fm = cd.FrontendManager;
// todo: allow length of 2 in private chat windows
if (cd.DataArray.Length < 3) {
NotEnoughParameters(cd);
return;
}
Jid jid = GetJidFromNickname(cd.DataArray[2]);
string cmd = cd.DataArray[1];
// the logic here is taken from
// http://xmpp.org/rfcs/rfc3921.html#int
switch (cmd) {
case "addgroup":
if (cd.DataArray.Length < 4) {
NotEnoughParameters(cd);
return;
}
JabberClient.RosterManager.AddRosterItem(jid, null, cd.DataArray[3]);
break;
case "addonly":
JabberClient.RosterManager.AddRosterItem(jid);
break;
case "add":
XmppPersonModel person;
if (Contacts.TryGetValue(jid.Bare, out person)) {
if (person.Subscription == SubscriptionType.both) break;
if (person.Subscription != SubscriptionType.to) {
JabberClient.PresenceManager.Subscribe(jid);
}
if (person.Subscription != SubscriptionType.from) {
// in case we already know this contact… but he can't see us
JabberClient.PresenceManager.ApproveSubscriptionRequest(jid);
}
} else {
JabberClient.RosterManager.AddRosterItem(jid);
JabberClient.PresenceManager.Subscribe(jid);
JabberClient.PresenceManager.ApproveSubscriptionRequest(jid);
}
break;
case "subscribe":
JabberClient.PresenceManager.Subscribe(jid);
break;
case "unsubscribe":
// stop receiving status updates from this contact
// that contact will still receive your updates
JabberClient.PresenceManager.Unsubscribe(jid);
break;
case "remove":
case "rm":
case "del":
case "delete":
JabberClient.RosterManager.RemoveRosterItem(jid);
// unsubscribing is unnecessary, the server is required to do this
break;
case "accept":
case "allow":
case "approve":
case "auth":
case "authorize":
JabberClient.PresenceManager.ApproveSubscriptionRequest(jid);
break;
case "deny":
case "refuse":
// stop the contact from receiving your updates
// you will still receive the contact's status updates
JabberClient.PresenceManager.RefuseSubscriptionRequest(jid);
break;
case "rename":
if (cd.DataArray.Length < 4) {
JabberClient.RosterManager.UpdateRosterItem(jid, "");
} else {
var newNick = String.Join(" ", cd.DataArray.Skip(3).ToArray());
JabberClient.RosterManager.UpdateRosterItem(jid, newNick);
}
break;
default:
var builder = CreateMessageBuilder();
builder.AppendText(_("Invalid contact command: {0}"), cmd);
Session.AddMessageToFrontend(cd, builder.ToMessage());
return;
}
}
public void CommandHelp(CommandModel cmd)
{
var builder = CreateMessageBuilder();
// TRANSLATOR: this line is used as a label / category for a
// list of commands below
builder.AppendHeader(_("{0} Commands"), Protocol);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
string[] help = {
"connect xmpp/jabber server port username password [resource]",
"msg/query jid/nick message",
"say message",
"join muc-jid [password]",
"part/leave [muc-jid]",
"away [away-message]",
"roster [full]",
"contact add/remove jid/nick",
"contact rename jid/nick [newnick]"
};
foreach (string line in help) {
builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText(line);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
}
// TRANSLATOR: this line is used as a label / category for a
// list of commands below
builder = CreateMessageBuilder();
builder.AppendHeader(_("Advanced {0} Commands"), Protocol);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
string[] help2 = {
"contact addonly/subscribe/unsubscribe/approve/deny",
"whois jid",
"joinas muc-jid nickname [password]",
"priority away/online/temp priority-value"
};
foreach (string line in help2) {
builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText(line);
Session.AddMessageToFrontend(cmd, builder.ToMessage());
}
}
public void CommandConnect(CommandModel cd)
{
FrontendManager fm = cd.FrontendManager;
var server = new XmppServerModel();
if (cd.DataArray.Length >= 3) {
server.Hostname = cd.DataArray[2];
} else {
NotEnoughParameters(cd);
return;
}
if (cd.DataArray.Length >= 4) {
try {
server.Port = Int32.Parse(cd.DataArray[3]);
} catch (FormatException) {
var builder = CreateMessageBuilder();
builder.AppendText(_("Invalid port: {0}"), cd.DataArray[3]);
Session.AddMessageToFrontend(cd, builder.ToMessage());
return;
}
} else {
NotEnoughParameters(cd);
return;
}
if (cd.DataArray.Length >= 5) {
server.Username = cd.DataArray[4];
} else {
NotEnoughParameters(cd);
return;
}
if (cd.DataArray.Length >= 6) {
server.Password = cd.DataArray[5];
} else {
NotEnoughParameters(cd);
return;
}
if (cd.DataArray.Length >= 7) {
server.Resource = cd.DataArray[6];
}
Connect(fm, server);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void CommandPriority(CommandModel command)
{
if (command.DataArray.Length < 3) {
var builder = CreateMessageBuilder();
builder.AppendText(_("Priority for Available is: {0}"), Server.Priorities[PresenceStatus.Online]);
Session.AddMessageToFrontend(command, builder.ToMessage());
builder = CreateMessageBuilder();
builder.AppendText(_("Priority for Away is: {0}"), Server.Priorities[PresenceStatus.Away]);
Session.AddMessageToFrontend(command, builder.ToMessage());
return;
}
string subcmd = command.DataArray[1];
int prio;
if (!int.TryParse(command.DataArray[2], out prio) || prio < -128 || prio > 127) {
var builder = CreateMessageBuilder();
builder.AppendText(_("Invalid priority: {0} (valid priorities are between -128 and 127 inclusive)"), command.DataArray[2]);
Session.AddMessageToFrontend(command, builder.ToMessage());
return;
}
JabberClient.Priority = prio;
bool change_current_prio = false;
switch (subcmd) {
case "temp":
case "temporary":
change_current_prio = true;
// only set priority
break;
case "away":
Server.Priorities[PresenceStatus.Away] = prio;
change_current_prio = (JabberClient.Show == ShowType.away);
JabberClient.Priority = prio;
break;
case "online":
case "available":
Server.Priorities[PresenceStatus.Online] = prio;
change_current_prio = (JabberClient.Show == ShowType.NONE);
JabberClient.Priority = prio;
break;
default:
return;
}
if (change_current_prio) {
// set priority and keep all other presence info
JabberClient.SendMyPresence();
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
Jid GetJidFromNickname(string nickname)
{
XmppPersonModel it;
Jid jid = nickname;
if (Contacts.TryGetValue(jid, out it)) {
// nickname is a jid we know
return jid;
}
if (Contacts.TryGetValue(jid.Bare, out it)) {
// is a jid with resource
return jid;
}
// arg is not a jid in our rostermanager
// find a jid to which the nickname belongs
foreach (var pair in Contacts) {
if (pair.Value.IdentityName != null &&
pair.Value.IdentityName.Replace(" ", "_") == nickname) {
return pair.Key;
}
}
// not found in roster, message directly to jid
// TODO: check jid for validity
return jid;
}
[MethodImpl(MethodImplOptions.Synchronized)]
void MessageQuery(Jid jid, string message)
{
var chat = GetOrCreatePersonChat(jid);
if (message != null && message.Trim().Length > 0) {
_Say(chat, message);
}
}
public void CommandMessageQuery(CommandModel cd)
{
if (cd.DataArray.Length < 2) {
NotEnoughParameters(cd);
return;
}
Jid jid = GetJidFromNickname(cd.DataArray[1]);
if (cd.DataArray.Length >= 3) {
// we have a message
string message = String.Join(" ", cd.DataArray, 2, cd.DataArray.Length-2);
MessageQuery(jid, message);
} else {
MessageQuery(jid, null);
}
}
public void CommandJoin(CommandModel cd)
{
if (cd.DataArray.Length < 2) {
NotEnoughParameters(cd);
return;
}
string password = null;
if (cd.DataArray.Length > 2) {
password = cd.DataArray[2];
}
JoinRoom(cd.DataArray[1], null, password);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void JoinRoom(Jid jid, string nickname, string password)
{
XmppGroupChatModel chat = (XmppGroupChatModel)GetChat(jid, ChatType.Group);
if (nickname == null) {
nickname = Nicknames[0];
}
if (chat == null) {
chat = Session.CreateChat<XmppGroupChatModel>(jid, jid, this);
Session.AddChat(chat);
}
if (chat.IsJoining) {
// double call to JoinRoom
return;
}
chat.IsJoining = true;
MucManager.JoinRoom(jid, nickname, password);
if (password != null) {
chat.Password = password;
}
chat.IsSynced = false;
chat.OwnNickname = nickname;
}
public void CommandJoinAs(CommandModel cd)
{
if (cd.DataArray.Length < 3) {
NotEnoughParameters(cd);
return;
}
string password = null;
if (cd.DataArray.Length > 3) {
password = cd.DataArray[3];
}
JoinRoom(cd.DataArray[1], cd.DataArray[2], password);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void CommandPart(CommandModel cd)
{
string jid;
if (cd.DataArray.Length >= 2)
jid = cd.DataArray[1];
else
jid = cd.Chat.ID;
XmppGroupChatModel chat = (XmppGroupChatModel)GetChat(jid, ChatType.Group);
if (chat != null) {
MucManager.LeaveRoom(jid, chat.OwnNickname);
}
}
public void CommandInvite(CommandModel cd)
{
if (cd.DataArray.Length < 3) {
NotEnoughParameters(cd);
return;
}
string password = null;
if (cd.DataArray.Length > 3) {
password = cd.DataArray[3];
}
Invite(cd.DataArray[2], cd.DataArray[1], null, password);
}
void Invite(Jid jid, Jid room, string reason, string password)
{
Invite(new Jid[]{jid}, room, reason, password);
}
void Invite(string[] jids_string, string room, string reason, string password)
{
var jids = new Jid[jids_string.Length];
for (int i = 0; i < jids.Length; i++) {
jids[i] = jids_string[i];
}
Invite(jids, room, reason, password);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void Invite(Jid[] jid, Jid room, string reason, string password)
{
JoinRoom(room, null, password);
XmppGroupChatModel chat = (XmppGroupChatModel)GetChat(room, ChatType.Group);
// if no password is passed, but we are already in the chatroom and know
// about a password, use that password
if (password == null && chat != null) {
password = chat.Password;
}
MucManager.Invite(jid, room, reason, password);
}
public void CommandAway(CommandModel cd)
{
if (cd.DataArray.Length >= 2) {
SetPresenceStatus(PresenceStatus.Away, cd.Parameter);
} else {
SetPresenceStatus(PresenceStatus.Online, null);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void CommandRoster(CommandModel cd)
{
bool full = false;
if (cd.Parameter == "full") {
full = true;
}
MessageBuilder builder = CreateMessageBuilder();
builder.AppendHeader("Roster");
Session.AddMessageToFrontend(cd, builder.ToMessage());
foreach (var pair in Contacts) {
string status = "+";
var contact = pair.Value;
if (contact.Resources.Count == 0) {
if (!full) {
continue;
}
status = "-";
}
builder = CreateMessageBuilder();
builder.AppendText("{0} {1}\t({2}): {3},{4}",
status,
contact.IdentityName,
pair.Key,
contact.Subscription,
contact.Ask
);
foreach (var p in contact.Resources) {
builder.AppendText("\t|\t{0}:{1}:{2}",
p.Key,
p.Value.Presence.Type.ToString(),
p.Value.Presence.Priority
);
if (!String.IsNullOrEmpty(p.Value.Presence.Status)) {
builder.AppendText(":\"{0}\"", p.Value.Presence.Status);
}
}
Session.AddMessageToFrontend(cd, builder.ToMessage());
}
}
public void CommandSay(CommandModel cd)
{
_Say(cd.Chat, cd.Parameter);
}
void _Say(ChatModel chat, string text)
{
_Say(chat, text, true);
}
void _Say(ChatModel chat, string text, bool send)
{
_Say(chat, text, send, true);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void _Say(ChatModel chat, string text, bool send, bool display)
{
if (!chat.IsEnabled) {
return;
}
if (chat == ContactChat) {
return;
}
if (send) {
if (chat.ChatType == ChatType.Person) {
var _person = (chat as PersonChatModel).Person as PersonModel;
XmppPersonModel person = GetOrCreateContact(_person.ID, _person.IdentityName);
SendPrivateMessage(person, _person.ID, text);
} else if (chat.ChatType == ChatType.Group) {
JabberClient.Send(new Message(chat.ID, XmppMessageType.groupchat, text));
return; // don't show now. the message will be echoed back if it's sent successfully
}
}
var builder = CreateMessageBuilder();
builder.AppendSenderPrefix(Me);
builder.AppendMessage(text);
var msg = builder.ToMessage();
if (display) {
Session.AddMessageToChat(chat, msg);
}
OnMessageSent(
new MessageEventArgs(chat, msg, null, chat.ID)
);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void _SendPrivateMessage(XmppPersonModel person, Jid jid, string text)
{
var mesg = new Message(jid, XmppMessageType.chat, text);
XmppResourceModel res;
if (person.Resources.TryGetValue(jid.Resource ?? "", out res)) {
if (res.NicknameContactKnowsFromMe != Nicknames[0]) {
res.NicknameContactKnowsFromMe = Nicknames[0];
mesg.Nickname = new Nickname(Nicknames[0]);
}
}
JabberClient.Send(mesg);
}
void SendPrivateMessage(XmppPersonModel person, string text)
{
SendPrivateMessage(person, null, text);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void SendPrivateMessage(XmppPersonModel person, Jid jid, string text)
{
if (jid == null) {
jid = person.Jid;
}
if ((jid.Server == "gmail.com") ||
(jid.Server == "googlemail.com")) {
// don't send to all high prio resources or to specific resources
// because gtalk clones any message to all resources anyway
_SendPrivateMessage(person, jid.Bare, text);
} else if (!String.IsNullOrEmpty(jid.Resource)) {
_SendPrivateMessage(person, jid, text);
} else {
var resources = person.GetResourcesWithHighestPriority();
if (resources.Count == 0) {
// no connected resource, send to bare jid
_SendPrivateMessage(person, jid.Bare, text);
} else {
foreach (var res in resources) {
if (String.IsNullOrEmpty(res.Name)) {
// don't send messages to empty resources
continue;
}
Jid j = new Jid(jid);
j.Resource = res.Name;
_SendPrivateMessage(person, j, text);
}
}
}
}
void OnReadXml(object sender, string text)
{
if (!DebugProtocol) {
return;
}
try {
var strWriter = new StringWriter();
var xmlWriter = new XmlTextWriter(strWriter);
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.Indentation = 2;
xmlWriter.IndentChar = ' ';
var document = new XmlDocument();
document.LoadXml(text);
document.WriteContentTo(xmlWriter);
DebugRead("\n" + strWriter.ToString());
} catch (XmlException) {
DebugRead("\n" + text);
} catch (Exception ex) {
#if LOG4NET
_Logger.Error("OnProtocol(): Exception", ex);
#endif
}
}
void OnWriteXml(object sender, string text)
{
if (!DebugProtocol) {
return;
}
try {
if (text == null || text.Trim().Length == 0) {
// suppress logging keep-alive messages
return;
}
var strWriter = new StringWriter();
var xmlWriter = new XmlTextWriter(strWriter);
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.Indentation = 2;
xmlWriter.IndentChar = ' ';
var document = new XmlDocument();
document.LoadXml(text);
document.WriteContentTo(xmlWriter);
DebugWrite("\n" + strWriter.ToString());
} catch (XmlException) {
// HACK: in case of an invalid doucment fallback to
// plain string logging
DebugWrite("\n" + text);
} catch (Exception ex) {
#if LOG4NET
_Logger.Error("OnWriteText(): Exception", ex);
#endif
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
XmppPersonModel GetOrCreateContact(Jid jid, string name)
{
XmppPersonModel p;
if (!Contacts.TryGetValue(jid.Bare, out p)) {
p = new XmppPersonModel(jid, name, this);
Contacts[jid.Bare] = p;
}
return p;
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnRosterItem(object sender, RosterItem rosterItem)
{
// setting to none also removes the person from chat, as we'd never get an offline message anymore
if (rosterItem.Subscription == SubscriptionType.none
|| rosterItem.Subscription == SubscriptionType.remove) {
if (rosterItem.Subscription == SubscriptionType.remove) {
Contacts.Remove(rosterItem.Jid);
}
if (ContactChat == null) {
return;
}
PersonModel oldp = ContactChat.GetPerson(rosterItem.Jid);
if (oldp == null) {
// doesn't exist, don't need to do anything
return;
}
Session.RemovePersonFromGroupChat(ContactChat, oldp);
return;
}
// create or update a roster item
var contact = GetOrCreateContact(rosterItem.Jid.Bare, rosterItem.Name ?? rosterItem.Jid);
contact.Temporary = false;
contact.Subscription = rosterItem.Subscription;
contact.Ask = rosterItem.Ask;
string oldIdentityName = contact.IdentityName;
var oldIdentityNameColored = contact.IdentityNameColored;
contact.IdentityName = rosterItem.Name ?? rosterItem.Jid;
if (oldIdentityName == contact.IdentityName) {
// identity name didn't change
// the rest of this function only handles changed identity names
return;
}
contact.IdentityNameColored = null; // uncache
ProcessIdentityNameChanged(contact, oldIdentityNameColored, oldIdentityName);
}
void ProcessIdentityNameChanged(XmppPersonModel contact, TextMessagePartModel oldIdentityNameColored, string oldIdentityName)
{
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
string idstring = (oldIdentityName == contact.Jid.Bare)?"":GenerateIdString(contact);
oldIdentityNameColored.BackgroundColor = TextColor.None;
builder.AppendFormat("{2}{1} is now known as {0}", contact, idstring, oldIdentityNameColored);
if (ContactChat != null) {
PersonModel oldp = ContactChat.GetPerson(contact.ID);
if (oldp != null) {
Session.UpdatePersonInGroupChat(ContactChat, oldp, contact.ToPersonModel());
Session.AddMessageToChat(ContactChat, new MessageModel(builder.ToMessage()));
}
}
var chat = Session.GetChat(contact.ID, ChatType.Person, this) as PersonChatModel;
if (chat != null) {
chat.Name = contact.IdentityName;
builder.MessageType = MessageType.ChatNameChanged;
var msg = builder.ToMessage();
Session.AddMessageToChat(chat, msg);
chat.Person = contact.ToPersonModel();
var msg2 = new MessageModel(msg);
msg2.MessageType = MessageType.PersonChatPersonChanged;
Session.AddMessageToChat(chat, msg2);
}
}
protected virtual string GenerateIdString(PersonModel contact)
{
if (contact.ID == contact.IdentityName) {
return "";
}
var jid = new Jid(contact.ID);
return String.Format(" [{0}]", jid.Bare);
}
void RequestCapabilities(Jid jid, Capabilities caps)
{
string hash = caps.Node + "#" + caps.Version;
RequestCapabilities(jid, hash);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void RequestCapabilities(Jid jid, string hash)
{
// already in cache?
DiscoInfo info;
if (DiscoCache.TryGetValue(hash, out info)) {
AddCapabilityToResource(jid, info);
return;
}
// prevent duplicate requests
DiscoCache[hash] = null;
// request it
Disco.DiscoverInformation(jid,
(object sender, IQEventArgs e) =>
OnDiscoInfo(e, hash)
);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void AddCapabilityToResource(Jid jid, DiscoInfo info)
{
XmppPersonModel contact;
if (!Contacts.TryGetValue(jid.Bare, out contact)) {
return;
}
XmppResourceModel res;
if (!contact.Resources.TryGetValue(jid.Resource??"", out res)) {
return;
}
res.Disco = info;
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnDiscoInfo(IQEventArgs e, string hash)
{
if (e.IQ.Error != null) {
#if LOG4NET
_Logger.DebugFormat("An error happened during service discovery: {0}", e.IQ);
#endif
// clear item from cache so the request is done again some time
DiscoCache.Remove(hash);
e.Handled = true;
return;
}
if (e.IQ.Type != IqType.result) {
#if LOG4NET
_Logger.Debug("OnDiscoInfo(): iq is not a result");
#endif
return;
}
if (!(e.IQ.Query is DiscoInfo)) {
#if LOG4NET
_Logger.Debug("OnDiscoInfo(): query is not a DiscoInfo");
#endif
return;
}
var info = (DiscoInfo)e.IQ.Query;
DiscoCache[hash] = info;
e.Handled = true;
if (String.IsNullOrEmpty(e.IQ.From.User)) {
// server capabilities
var builder = CreateMessageBuilder();
builder.AppendText("The Server supports the following features: ");
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
foreach (var feature in info.GetFeatures()) {
builder = CreateMessageBuilder();
builder.AppendText(feature.Var);
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
} else {
AddCapabilityToResource(e.IQ.From, info);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
MessageModel CreatePresenceUpdateMessage(Jid jid, PersonModel person, Presence pres)
{
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
string idstring = GenerateIdString(person);
// print the type (and in case of available detailed type)
switch (pres.Type) {
case PresenceType.available:
switch(pres.Show) {
case ShowType.NONE:
builder.AppendFormat(_("{0}{1} is available"), person, idstring);
builder.AppendPresenceState(person, MessageType.PresenceStateOnline);
break;
case ShowType.away:
builder.AppendFormat(_("{0}{1} is away"), person, idstring);
builder.AppendPresenceState(person, MessageType.PresenceStateAway);
break;
case ShowType.xa:
builder.AppendFormat(_("{0}{1} is extended away"), person, idstring);
builder.AppendPresenceState(person, MessageType.PresenceStateAway);
break;
case ShowType.dnd:
builder.AppendFormat(_("{0}{1} wishes not to be disturbed"), person, idstring);
builder.AppendPresenceState(person, MessageType.PresenceStateAway);
break;
case ShowType.chat:
builder.AppendFormat(_("{0}{1} wants to chat"), person, idstring);
builder.AppendPresenceState(person, MessageType.PresenceStateOnline);
break;
}
break;
case PresenceType.unavailable:
builder.AppendPresenceState(person, MessageType.PresenceStateOffline);
builder.AppendFormat(_("{0}{1} is offline"), person, idstring);
break;
case PresenceType.subscribe:
if ((person as XmppPersonModel).Ask == AskType.subscribe) {
builder = CreateMessageBuilder();
builder.AppendActionPrefix();
builder.AppendFormat(_("Automatically allowed {0} to subscribe to you, since you are already asking to subscribe"),
person
);
} else {
builder.AppendFormat(_("{0}{1} wishes to subscribe to you"),
person, idstring);
// you have to respond
builder.MarkAsHighlight();
}
break;
case PresenceType.subscribed:
// you can now see their presences
builder.AppendFormat(_("{0}{1} allowed you to subscribe"), person, idstring);
break;
case PresenceType.unsubscribed:
if ((person as XmppPersonModel).Subscription == SubscriptionType.from) {
builder = CreateMessageBuilder();
builder.AppendActionPrefix();
builder.AppendFormat(
_("Automatically removed {0}'s subscription to " +
"your presences after losing the subscription " +
"to theirs"),
person
);
} else {
// you cannot (anymore?) see their presences
builder.AppendFormat(_("{0}{1} denied/removed your subscription"), person, idstring);
}
break;
case PresenceType.unsubscribe:
// you might still be able to see their presences
builder.AppendFormat(_("{0}{1} unsubscribed from you"), person, idstring);
break;
case PresenceType.error:
if (pres.Error == null) {
builder.AppendErrorText(_("received a malformed error message: {0}"), pres);
break;
}
switch (pres.Error.Type) {
case ErrorType.cancel:
switch (pres.Error.Condition) {
case ErrorCondition.RemoteServerNotFound:
builder.AppendErrorText(_("{0}{1}'s server could not be found"), person.IdentityName, idstring);
break;
case ErrorCondition.Conflict:
builder.AppendErrorText(_("{0}{1} is already using your requested resource"), person.IdentityName, idstring);
break;
default:
if (!String.IsNullOrEmpty(pres.Error.ErrorText)) {
builder.AppendErrorText(pres.Error.ErrorText);
} else {
builder.AppendErrorText(
_("There is currently no useful error message for {0}, {1}, {2}{3}"),
pres.Error.Type,
pres.Error.Condition,
person.IdentityName,
idstring);
}
break;
}
break;
case ErrorType.auth:
switch (pres.Error.Condition) {
case ErrorCondition.Forbidden:
builder.AppendErrorText(
_("You do not have permission to access {0}{1}")
, person.IdentityName,
idstring);
break;
default:
if (!String.IsNullOrEmpty(pres.Error.ErrorText)) {
builder.AppendErrorText(pres.Error.ErrorText);
} else {
builder.AppendErrorText(
_("There is currently no useful error message for {0}, {1}, {2}{3}"),
pres.Error.Type,
pres.Error.Condition,
person.IdentityName,
idstring);
}
break;
}
break;
default:
if (!String.IsNullOrEmpty(pres.Error.ErrorText)) {
builder.AppendErrorText(pres.Error.ErrorText);
} else {
builder.AppendErrorText(
_("There is currently no useful error message for {0}, {1}, {2}{3}"),
pres.Error.Type,
pres.Error.Condition,
person.IdentityName,
idstring);
}
break;
}
break;
}
// print timestamp of presence
if (pres.XDelay != null || pres.Last != null) {
DateTime stamp = DateTime.MinValue;
TimeSpan span = TimeSpan.MinValue;
if (pres.XDelay != null) {
stamp = pres.XDelay.Stamp;
span = DateTime.Now.Subtract(stamp);
} else if (pres.Last != null) {
span = TimeSpan.FromSeconds(pres.Last.Seconds);
stamp = DateTime.Now.Subtract(span);
}
string spanstr;
if (span > TimeSpan.FromDays(1)) {
spanstr = String.Format(
"{0:00}:{1:00}:{2:00}:{3:00}",
span.TotalDays, span.Hours, span.Minutes, span.Seconds
);
spanstr = String.Format(_("{0} days"), spanstr);
} else if (span > TimeSpan.FromHours(1)) {
spanstr = String.Format(
"{0:00}:{1:00}:{2:00}",
span.Hours, span.Minutes, span.Seconds
);
spanstr = String.Format(_("{0} hours"), spanstr);
} else if (span > TimeSpan.FromMinutes(1)) {
spanstr = String.Format("{0:00}:{1:00}",
span.Minutes, span.Seconds);
spanstr = String.Format(_("{0} minutes"), spanstr);
} else {
spanstr = String.Format("{0:00}", span.Seconds);
spanstr = String.Format(_("{0} seconds"), spanstr);
}
string timestamp = null;
try {
string format = Session.UserConfig["Interface/Notebook/TimestampFormat"] as string;
if (!String.IsNullOrEmpty(format)) {
timestamp = stamp.ToString(format);
}
} catch (FormatException e) {
timestamp = "Timestamp Format ERROR: " + e.Message;
}
builder.AppendText(_(" since {0} ({1})"), timestamp, spanstr);
}
// print user defined message
if (pres.Status != null && pres.Status.Trim().Length > 0) {
builder.AppendText(": {0}", pres.Status);
}
return builder.ToMessage();
}
[MethodImpl(MethodImplOptions.Synchronized)]
void PrintGroupChatPresence(XmppGroupChatModel chat, PersonModel person, Presence pres)
{
Jid jid = pres.From;
var msg = CreatePresenceUpdateMessage(person.ID, person, pres);
Session.AddMessageToChat(chat, msg);
// clone directly to muc person chat
// don't care about real jid, that has its own presence packets
var personChat = Session.GetChat(jid, ChatType.Person, this);
if (personChat != null) {
Session.AddMessageToChat(personChat, msg);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnGroupChatPresence(XmppGroupChatModel chat, Presence pres)
{
Jid jid = pres.From;
var person = new PersonModel(jid, pres.From.Resource ?? pres.From, NetworkID, Protocol, this);
PrintGroupChatPresence(chat, person, pres);
switch (pres.Type) {
case PresenceType.available:
// don't do anything if the contact already exists
if (chat.UnsafePersons.ContainsKey(person.ID)) {
return;
}
// is the chat synced? add the new contact the regular way
if (chat.IsSynced) {
Session.AddPersonToGroupChat(chat, person);
return;
}
chat.UnsafePersons.Add(person.ID, person);
// did I join? then the chat roster is fully received
if (pres.From.Resource == chat.OwnNickname ||
pres.MucUser.StatusCodes.Exists(x => x.Code == StatusCode.SelfPresence)) {
if (pres.MucUser.StatusCodes.Exists(x => x.Code == StatusCode.ModifiedNick)) {
// as per XEP-0045 7.2.3 Example 24 the server is
// allowed to give us a different nick than we requested
chat.OwnNickname = pres.From.Resource;
}
chat.IsJoining = false;
// HACK: lower probability of sync race condition swallowing messages
ThreadPool.QueueUserWorkItem(delegate {
Thread.Sleep(1000);
lock (this) {
if (IsDisposed) {
return;
}
chat.IsSynced = true;
Session.SyncChat(chat);
Session.EnableChat(chat);
}
});
}
break;
case PresenceType.unavailable:
Session.RemovePersonFromGroupChat(chat, person);
// did I leave? then I "probably" left the room
if (pres.From.Resource == chat.OwnNickname) {
Session.RemoveChat(chat);
}
break;
case PresenceType.error:
OnGroupChatPresenceError(chat, pres);
break;
}
}
MessageModel CreateGroupChatPresenceErrorMessage(Presence pres)
{
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
if (pres.Error == null) {
builder.AppendErrorText(_("An unknown groupchat error occurred: {0}"), pres);
return builder.ToMessage();
}
switch (pres.Error.Type) {
case ErrorType.cancel:
switch (pres.Error.Condition) {
case ErrorCondition.RemoteServerNotFound:
builder.AppendErrorText(_("Server of groupchat \"{0}\" not found."), pres.From.Bare);
break;
case ErrorCondition.ServiceUnavailable:
builder.AppendErrorText(_("MUC service is not available for \"{0}\""), pres.From.Bare);
break;
}
break;
case ErrorType.auth:
switch (pres.Error.Condition) {
case ErrorCondition.NotAuthorized:
builder.AppendErrorText(_("You do not have permission to join \"{0}\""), pres.From.Bare);
break;
}
break;
}
if (String.IsNullOrEmpty(pres.Error.ErrorText) && builder.IsEmpty) {
builder.AppendErrorText(_("An unhandled groupchat error occurred: {0}"), pres.From.Bare);
} else {
builder.AppendErrorText(": {0}", pres.Error.ErrorText);
}
return builder.ToMessage();
}
void OnGroupChatPresenceError(XmppGroupChatModel chat, Presence pres)
{
if (pres.Error != null &&
pres.Error.Type == ErrorType.cancel &&
pres.Error.Condition == ErrorCondition.Conflict) {
// nickname already in use; autorejoin with _ appended
JoinRoom(chat.ID, chat.OwnNickname + "_", chat.Password);
return;
}
var msg = CreateGroupChatPresenceErrorMessage(pres);
Session.AddMessageToChat(NetworkChat, msg);
Session.RemoveChat(chat);
}
[MethodImpl(MethodImplOptions.Synchronized)]
void PrintPrivateChatPresence(XmppPersonModel person, Presence pres)
{
Jid jid = pres.From;
XmppResourceModel resource;
if (person.Resources.TryGetValue(jid.Resource??"", out resource)) {
if (resource.Presence.Show == pres.Show
&& resource.Presence.Status == pres.Status
&& resource.Presence.Last == pres.Last
&& resource.Presence.XDelay == pres.XDelay
&& resource.Presence.Priority == pres.Priority
&& resource.Presence.Type == pres.Type
) {
// presence didn't change enough to warrent a display message -> abort
return;
}
}
MessageModel msg = CreatePresenceUpdateMessage(jid, person, pres);
if (!String.IsNullOrEmpty(jid.Resource)) {
var directchat = Session.GetChat(jid, ChatType.Person, this);
if (directchat != null) {
// in case of direct chat we still send this message
Session.AddMessageToChat(directchat, msg);
}
}
// a nonexisting resource going offline?
if (pres.Type == PresenceType.unavailable) {
if (!person.Resources.ContainsKey(jid.Resource??"")) {
return;
}
}
var res = person.GetOrCreateResource(jid);
var oldpres = res.Presence;
res.Presence = pres;
// highest pres
Jid hjid = jid;
Jid nextjid = jid;
// 2nd highest pres
Presence hpres = pres;
Presence nextpres = null;
bool amHighest = true;
bool wasHighest = true;
foreach (var pair in person.Resources) {
if (pair.Value == res) continue;
if (nextpres == null || pair.Value.Presence.Priority > nextpres.Priority) {
nextjid.Resource = pair.Key;
nextpres = pair.Value.Presence;
}
if (pair.Value.Presence.Priority > hpres.Priority) {
// someone has a higher priority than I do
// print the status of that resource
hjid.Resource = pair.Key;
hpres = pair.Value.Presence;
amHighest = false;
}
if (oldpres != null && pair.Value.Presence.Priority > oldpres.Priority) {
wasHighest = false;
}
}
if (pres.Type == PresenceType.available) {
// wasn't and isn't highiest prio -> ignore
if (!wasHighest && !amHighest) return;
// just another below zero prio -> ignore
if (amHighest && pres.Priority < 0) return;
// was highest, isn't anymore -> show presence of new highest
if (wasHighest && !amHighest) {
msg = CreatePresenceUpdateMessage(hjid, person, hpres);
}
} else if (pres.Type == PresenceType.unavailable) {
// still a resource left with positive priority
if (nextpres != null && nextpres.Priority >= 0) {
msg = CreatePresenceUpdateMessage(nextjid, person, nextpres);
}
}
var chat = Session.GetChat(jid.Bare, ChatType.Person, this);
if (chat != null) {
Session.AddMessageToChat(chat, msg);
}
if (ContactChat != null) {
Session.AddMessageToChat(ContactChat, msg);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnPrivateChatPresence(Presence pres)
{
Jid jid = pres.From;
if (jid.Bare == JabberClient.MyJID.Bare) {
// don't process any of my own resources
return;
}
var person = GetOrCreateContact(jid.Bare, jid);
PrintPrivateChatPresence(person, pres);
switch (pres.Type) {
case PresenceType.available:
if (pres.Priority < 0) break;
if (ContactChat == null) break;
if (ContactChat.UnsafePersons.ContainsKey(jid.Bare)) break;
Session.AddPersonToGroupChat(ContactChat, person.ToPersonModel());
break;
case PresenceType.unavailable:
person.RemoveResource(jid);
if (pres.Priority < 0) break;
if (ContactChat == null) break;
if (!ContactChat.UnsafePersons.ContainsKey(jid.Bare)) break;
var pers = ContactChat.GetPerson(jid.Bare);
Session.RemovePersonFromGroupChat(ContactChat, pers);
break;
case PresenceType.subscribe:
if (person.Ask == AskType.subscribe) {
// we are currently asking the contact OR are subscribed to him
// so we allow the contact to subscribe
// TODO: make the following dependent on some user setable boolean
JabberClient.PresenceManager.ApproveSubscriptionRequest(jid);
}
break;
case PresenceType.subscribed:
// we are now able to see that contact's presences
break;
case PresenceType.unsubscribed:
// the contact does not wish us to see his presences anymore
if (person.Subscription == SubscriptionType.from) {
// but the contact can still see us
// TODO: make the following dependent on some user setable boolean
JabberClient.PresenceManager.RefuseSubscriptionRequest(jid);
} else {
// TODO: this contact was just created in OnPresence… prevent it from doing that?
// TODO: this can happen when a subscription=none contact sends a deny…
Contacts.Remove(jid.Bare);
}
break;
case PresenceType.unsubscribe:
// the contact does not wish to see our presence anymore?
// we could care less
break;
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnPresence(object sender, Presence pres)
{
Trace.Call(sender, pres);
Jid jid = pres.From;
if (jid == JabberClient.MyJID) return; // we don't care about ourself
if (pres.Capabilities != null && pres.Type == PresenceType.available) {
// only test capabilities of users going online or changing something in their online state
RequestCapabilities(jid, pres.Capabilities);
}
if (pres.MucUser != null || pres.Muc != null) {
var groupChat = (XmppGroupChatModel) Session.GetChat(jid.Bare, ChatType.Group, this);
if (groupChat == null) {
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendErrorText(_("Received a presence update from {0}, but there's no corresponding chat window"), pres.From.Bare);
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
if (pres.Type == PresenceType.error) {
var msg = CreateGroupChatPresenceErrorMessage(pres);
Session.AddMessageToChat(NetworkChat, msg);
} else {
MucManager.LeaveRoom(jid.Bare, jid.Resource);
}
} else {
OnGroupChatPresence(groupChat, pres);
}
} else {
OnPrivateChatPresence(pres);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnGroupChatMessage(Message msg)
{
string group_jid = msg.From.Bare;
XmppGroupChatModel groupChat = (XmppGroupChatModel) Session.GetChat(group_jid, ChatType.Group, this);
if (groupChat == null) {
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendErrorText(_("Received a groupchat message from {0} but there's no corresponding chat window: {1}"), msg.From, msg.Body);
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
return;
}
// resource can be empty for room messages
var sender_id = msg.From.Resource ?? msg.From.Bare;
var person = groupChat.GetPerson(sender_id);
if (person == null) {
// happens in case of a delayed message if the participant has left meanwhile
// TODO: or in case of a room message?
person = new PersonModel(sender_id,
sender_id,
NetworkID, Protocol, this);
}
// XXX maybe only a Google Talk bug requires this:
if (msg.XDelay != null) {
var stamp = msg.XDelay.Stamp;
if (stamp > groupChat.LatestSeenStamp) {
groupChat.LatestSeenStamp = stamp;
} else {
return; // already seen newer delayed message
}
if (groupChat.SeenNewMessages) {
return; // already seen newer messages
}
} else {
groupChat.SeenNewMessages = true;
}
// mark highlights only for received messages
MessageModel message;
if (person.ID == groupChat.OwnNickname) {
message = CreateEchoGroupChatMessage(groupChat, msg);
} else {
message = CreateGroupChatMessage(groupChat, person, msg);
}
Session.AddMessageToChat(groupChat, message);
OnMessageReceived(
new MessageEventArgs(groupChat, message, msg.From, groupChat.ID)
);
}
void AddMessageToChatIfNotFiltered(MessageModel msg, ChatModel chat, bool isNew)
{
if (Session.IsFilteredMessage(chat, msg)) {
Session.LogMessage(chat, msg, true);
return;
}
if (isNew) {
Session.AddChat(chat);
}
Session.AddMessageToChat(chat, msg, true);
if (isNew) {
Session.SyncChat(chat);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void ProcessNickname(XmppPersonModel person, Nickname nick)
{
if (String.IsNullOrEmpty(nick.Value)) {
return;
}
// only rename person if it doesn't have a preset name
if (person.IdentityName == person.ID) {
var oldIdentityNameColored = person.IdentityNameColored;
var oldIdentityName = person.IdentityName;
person.IdentityName = nick.Value;
ProcessIdentityNameChanged(person, oldIdentityNameColored, oldIdentityName);
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnPrivateChatMessage(Message msg)
{
var chat = Session.GetChat(msg.From, ChatType.Person, this) as PersonChatModel;
bool isNew = false;
if (chat == null) {
// in case full jid doesn't have a chat window, use bare jid
chat = GetOrCreatePersonChat(msg.From.Bare, out isNew);
}
if (msg.Nickname != null) {
ProcessNickname(GetOrCreateContact(msg.From, msg.Nickname.Value), msg.Nickname);
}
var message = CreatePrivateChatMessage(chat.Person, msg);
AddMessageToChatIfNotFiltered(message, chat, isNew);
OnMessageReceived(
new MessageEventArgs(chat, message, msg.From, null)
);
}
MessageModel CreateEchoGroupChatMessage(XmppGroupChatModel groupChat, Message msg)
{
var builder = CreateMessageBuilder();
string msgstring;
if (msg.Html != null) {
msgstring = msg.Html.ToString();
} else {
msgstring = msg.Body.Trim();
}
var self = new PersonModel(
groupChat.OwnNickname,
groupChat.OwnNickname,
NetworkID, Protocol, this);
if (msgstring.StartsWith("/me ")) {
// leave the " " intact
msgstring = msgstring.Substring(3);
builder.AppendActionPrefix();
builder.AppendIdendityName(self);
} else {
builder.AppendSenderPrefix(self);
}
if (msg.Html != null) {
builder.AppendHtmlMessage(msgstring);
} else {
builder.AppendMessage(msgstring);
}
if (msg.XDelay != null) {
builder.TimeStamp = msg.XDelay.Stamp;
}
return builder.ToMessage();
}
MessageModel CreateGroupChatMessage(XmppGroupChatModel groupChat, PersonModel person, Message msg)
{
var builder = CreateMessageBuilder();
string msgstring;
if (msg.Html != null) {
msgstring = msg.Html.ToString();
} else {
msgstring = msg.Body.Trim();
}
if (msgstring.StartsWith("/me ")) {
// leave the " " intact
msgstring = msgstring.Substring(3);
builder.AppendActionPrefix();
builder.AppendIdendityName(person);
} else {
builder.AppendSenderPrefix(person);
}
if (msg.Html != null) {
builder.AppendHtmlMessage(msgstring);
} else {
builder.AppendMessage(msgstring);
}
// mark hilights only for OwnNickname, too
builder.Me = new PersonModel(
groupChat.OwnNickname,
groupChat.OwnNickname,
NetworkID, Protocol, this);
builder.MarkHighlights();
if (msg.XDelay != null) {
builder.TimeStamp = msg.XDelay.Stamp;
}
return builder.ToMessage();
}
MessageModel CreatePrivateChatMessage(PersonModel person, Message msg)
{
var builder = CreateMessageBuilder();
string msgstring;
if (msg.Html != null) {
msgstring = msg.Html.ToString();
} else {
msgstring = msg.Body.Trim();
}
if (msgstring.StartsWith("/me ")) {
// leave the " " intact
msgstring = msgstring.Substring(3);
builder.AppendActionPrefix();
builder.AppendIdendityName(person, true);
} else {
builder.AppendSenderPrefix(person, true);
}
if (msg.Html != null) {
builder.AppendHtmlMessage(msgstring);
} else {
builder.AppendMessage(msgstring);
}
if (msg.XDelay != null) {
builder.TimeStamp = msg.XDelay.Stamp;
}
return builder.ToMessage();
}
void OnGroupChatMessageError(Message msg, XmppGroupChatModel chat)
{
var builder = CreateMessageBuilder();
// TODO: nicer formatting
if (msg.Error.ErrorText != null) {
builder.AppendErrorText(msg.Error.ErrorText);
} else {
builder.AppendErrorText(msg.Error.ToString());
}
Session.AddMessageToChat(chat, builder.ToMessage());
}
void OnPrivateChatMessageError(Message msg, PersonChatModel chat)
{
var builder = CreateMessageBuilder();
// TODO: nicer formatting
if (msg.Error.ErrorText != null) {
builder.AppendErrorText(msg.Error.ErrorText);
} else {
builder.AppendErrorText(msg.Error.ToString());
}
Session.AddMessageToChat(chat, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnMessage(object sender, Message msg)
{
// process chatstates
if (msg.Chatstate != agsXMPP.protocol.extensions.chatstates.Chatstate.None) {
OnChatState(msg);
}
if (String.IsNullOrEmpty(msg.Body)) {
// TODO: capture events and stuff
return;
}
switch (msg.Type) {
case XmppMessageType.groupchat:
OnGroupChatMessage(msg);
break;
case XmppMessageType.chat:
case XmppMessageType.headline:
case XmppMessageType.normal:
if (String.IsNullOrEmpty(msg.From.User)) {
OnServerMessage(msg);
} else if (msg.MucUser != null) {
OnMucMessage(msg);
} else {
OnPrivateChatMessage(msg);
}
break;
case XmppMessageType.error:
{
var chat = Session.GetChat(msg.From, ChatType.Group, this);
if (chat != null) {
OnGroupChatMessageError(msg, chat as XmppGroupChatModel);
break;
}
chat = Session.GetChat(msg.From, ChatType.Person, this);
if (chat != null) {
OnPrivateChatMessageError(msg, chat as PersonChatModel);
break;
}
// no person and no groupchat open? -> dump in networkchat
var builder = CreateMessageBuilder();
// TODO: nicer formatting
if (msg.Error.ErrorText != null) {
builder.AppendErrorText(msg.Error.ErrorText);
} else {
builder.AppendErrorText(msg.Error.ToString());
}
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
break;
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnMucMessage (Message msg)
{
User user = msg.MucUser;
string text;
if (user.Invite != null) {
if (user.Invite.Reason != null && user.Invite.Reason.Trim().Length > 0) {
text = String.Format(_("You have been invited to {2} by {0} because {1}"),
user.Invite.From,
user.Invite.Reason,
msg.From
);
} else {
text = String.Format(_("You have been invited to {1} by {0}"),
user.Invite.From,
msg.From
);
}
} else {
text = msg.ToString();
}
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
var txt = builder.CreateText(text);
txt.IsHighlight = true;
builder.AppendText(txt);
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
builder = CreateMessageBuilder();
string url;
if (!String.IsNullOrEmpty(user.Password)) {
url = String.Format("xmpp:{0}?join;password={1}", msg.From, user.Password);
} else {
url = String.Format("xmpp:{0}?join", msg.From);
}
builder.AppendUrl(url, _("Accept invite (join room)"));
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnChatState(Message msg)
{
if (!ShowChatStates) {
return;
}
if (msg.Body != null) {
return;
}
switch (msg.Type) {
case XmppMessageType.chat:
case XmppMessageType.headline:
case XmppMessageType.normal:
{
var chat = GetChat(msg.From, ChatType.Person) as PersonChatModel;
bool isNew = false;
// no full jid chat
if (chat == null) {
// create chat
chat = GetOrCreatePersonChat(msg.From.Bare, out isNew);
if (isNew) {
if (!OpenNewChatOnChatState) {
return;
}
if (msg.Chatstate != Chatstate.composing) {
// there is NO reason to open a new chat window for
// a chatstate other than composing
return;
}
Session.AddChat(chat);
}
}
var builder = CreateMessageBuilder();
switch (msg.Chatstate) {
case Chatstate.composing:
builder.AppendChatState(chat.Person, MessageType.ChatStateComposing);
break;
case Chatstate.paused:
builder.AppendChatState(chat.Person, MessageType.ChatStatePaused);
break;
default:
builder.AppendChatState(chat.Person, MessageType.ChatStateReset);
break;
}
Session.AddMessageToChat(chat, builder.ToMessage());
if (isNew) {
Session.SyncChat(chat);
}
}
break;
default:
break;
}
}
void OnServerMessage(Message msg)
{
var builder = CreateMessageBuilder();
builder.AppendText("<{0}> {1}", msg.From, msg.Body);
builder.MarkHighlights();
// todo: can server messages have an xdelay?
if (msg.XDelay != null) {
builder.TimeStamp = msg.XDelay.Stamp;
}
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
PersonChatModel GetOrCreatePersonChat(Jid jid)
{
bool isNew;
var chat = GetOrCreatePersonChat(jid, out isNew);
if (isNew) {
Session.AddChat(chat);
Session.SyncChat(chat);
}
return chat;
}
[MethodImpl(MethodImplOptions.Synchronized)]
PersonChatModel GetOrCreatePersonChat(Jid jid, out bool isNew)
{
var chat = (PersonChatModel) Session.GetChat(jid, ChatType.Person, this);
isNew = false;
if (chat != null) return chat;
var person = GetOrCreateContact(jid.Bare, jid);
PersonModel pers;
if (!String.IsNullOrEmpty(jid.Resource)) {
pers = new PersonModel(jid, person.IdentityName, NetworkID, Protocol, this);
} else {
pers = person.ToPersonModel();
}
isNew = true;
chat = Session.CreatePersonChat(pers, this);
if (jid == JabberClient.MyJID || jid == JabberClient.MyJID.Bare) {
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText("Note: you are now talking to yourself");
Session.AddMessageToChat(chat, builder.ToMessage());
}
return chat;
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnClose(object sender)
{
Trace.Call(sender);
foreach (var chat in Chats) {
// don't disable the protocol chat, else the user loses all
// control for the protocol manager! e.g. after a manual
// reconnect or server-side disconnect
if (chat.ChatType == ChatType.Protocol) {
continue;
}
Session.DisableChat(chat);
}
OnDisconnected(EventArgs.Empty);
// reset socket
JabberClient.ClientSocket.OnValidateCertificate -= ValidateCertificate;
JabberClient.SocketConnectionType = SocketConnectionType.Direct;
if (AutoReconnect) {
Reconnect(AutoReconnectDelay);
}
}
void Reconnect()
{
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText(_("Reconnecting to {0}"),
JabberClient.Server);
Session.AddMessageToChat(Chat, builder.ToMessage());
Connect();
}
void Reconnect(TimeSpan span)
{
int delay = (int)span.TotalMilliseconds;
if (delay <= 0) {
Reconnect();
}
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText(_("Reconnecting to {0} in {1} seconds"),
JabberClient.Server, span.TotalSeconds);
Session.AddMessageToChat(Chat, builder.ToMessage());
ThreadPool.QueueUserWorkItem(delegate {
Thread.Sleep(delay);
lock (this) {
// prevent this timer from calling connect after it has been closed
if (IsDisposed) {
return;
}
// prevent this timer from calling connect if during the timout
// some other event already began a connect
if (JabberClient.XmppConnectionState != XmppConnectionState.Disconnected) {
return;
}
Connect();
}
});
}
void OnError(object sender, Exception ex)
{
Trace.Call(sender);
#if LOG4NET
_Logger.Error("OnError(): Exception", ex);
#endif
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendErrorText(_("Error: {0}"), String.Empty);
builder.AppendMessage(ex.Message);
Session.AddMessageToChat(NetworkChat, builder.ToMessage());
}
[MethodImpl(MethodImplOptions.Synchronized)]
void OnLogin(object sender)
{
Trace.Call(sender);
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText(_("Authenticated"));
Session.AddMessageToChat(Chat, builder.ToMessage());
RequestCapabilities(JabberClient.Server, JabberClient.Server);
OnConnected(EventArgs.Empty);
foreach (var chat in Chats) {
if (chat is PersonChatModel) {
Session.EnableChat(chat);
Session.SyncChat(chat);
} else if (chat is XmppGroupChatModel) {
var muc = (XmppGroupChatModel)chat;
JoinRoom(muc.ID, muc.OwnNickname, muc.Password);
}
}
}
[MethodImpl(MethodImplOptions.Synchronized)]
void ApplyConfig(UserConfig config, XmppServerModel server)
{
if (String.IsNullOrEmpty(server.Nickname)) {
Nicknames = (string[]) config["Connection/Nicknames"];
} else {
Nicknames = new string[] { server.Nickname };
}
if (server.Username.Contains("@")) {
var jid_user = server.Username.Split('@')[0];
var jid_host = server.Username.Split('@')[1];
JabberClient.ConnectServer = server.Hostname;
JabberClient.AutoResolveConnectServer = false;
JabberClient.Username = jid_user;
JabberClient.Server = jid_host;
} else {
JabberClient.Server = server.Hostname;
JabberClient.Username = server.Username;
}
JabberClient.Port = server.Port;
JabberClient.Password = server.Password;
var proxySettings = new ProxySettings();
proxySettings.ApplyConfig(config);
var protocol = Server.UseEncryption ? "xmpps" : "xmpp";
var serverUri = String.Format("{0}://{1}:{2}", protocol,
Server.Hostname, Server.Port);
var proxy = proxySettings.GetWebProxy(serverUri);
var socket = JabberClient.ClientSocket as ClientSocket;
if (proxy == null) {
socket.Proxy = null;
} else {
var builder = CreateMessageBuilder();
builder.AppendEventPrefix();
builder.AppendText(_("Using proxy: {0}:{1}"),
proxy.Address.Host,
proxy.Address.Port);
Session.AddMessageToChat(Chat, builder.ToMessage());
var proxyScheme = proxy.Address.Scheme;
var proxyType = Starksoft.Net.Proxy.ProxyType.None;
try {
proxyType = (Starksoft.Net.Proxy.ProxyType) Enum.Parse(
typeof(Starksoft.Net.Proxy.ProxyType),
proxy.Address.Scheme,
true
);
} catch (ArgumentException ex) {
#if LOG4NET
_Logger.Error("ApplyConfig(): Couldn't parse proxy type: " +
proxyScheme, ex);
#endif
}
var proxyFactory = new ProxyClientFactory();
if (String.IsNullOrEmpty(proxySettings.ProxyUsername) &&
String.IsNullOrEmpty(proxySettings.ProxyPassword)) {
socket.Proxy = proxyFactory.CreateProxyClient(
proxyType,
proxy.Address.Host,
proxy.Address.Port
);
} else {
socket.Proxy = proxyFactory.CreateProxyClient(
proxyType,
proxy.Address.Host,
proxy.Address.Port,
proxySettings.ProxyUsername,
proxySettings.ProxyPassword
);
}
}
Me = new PersonModel(
JabberClient.MyJID.Bare,
Nicknames[0],
NetworkID, Protocol, this
);
Me.IdentityNameColored.ForegroundColor = new TextColor(0, 0, 255);
Me.IdentityNameColored.BackgroundColor = TextColor.None;
Me.IdentityNameColored.Bold = true;
// XMPP specific settings
JabberClient.Resource = server.Resource;
if (server.UseEncryption) {
// HACK: Google Talk doesn't support StartTLS :(
if (server.Hostname == "talk.google.com" &&
server.Port == 5223) {
JabberClient.ForceStartTls = false;
JabberClient.UseSSL = true;
} else {
JabberClient.ForceStartTls = true;
}
} else {
JabberClient.ForceStartTls = false;
JabberClient.UseStartTLS = true;
}
if (!server.ValidateServerCertificate) {
JabberClient.ClientSocket.OnValidateCertificate += ValidateCertificate;
}
}
static bool ValidateCertificate(object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
}
static string _(string msg)
{
return LibraryCatalog.GetString(msg, LibraryTextDomain);
}
}
}