Not logged in. · Lost password · Register
Forum: agsXMPP RSS
How to implement this feature?
Page: previous  1  2  3  next
Avatar
Jabberer #16
Member since Feb 2006 · 249 posts
Group memberships: Members
Show profile · Link to this post
In reply to post ID 1450
90% correct now :)

You don't have to send the paused when you send the message. If a message is received then we assume that the sender stopped typing.
So only stop the timer and set IsComposing = false. If the sender starts composing the next message immediately you get the composing state automatically again.

in the JEP you find a compete example flow:
http://xmpp.org/extensions/xep-0085.html#example-basic
You can compare this with your results. But i think with this small change you will be OK.
Software Developer
AG-Software
This post was edited on 2013-03-08, 13:41 by Alex.
Avatar
yeawsing #17
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
Cool thanks a lot.   ;-)

I am very grateful
I am still learning..., hope I can contribute back.
YS
Avatar
yeawsing #18
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
Hi,

Now I have to work on the ChatState.Gone.  When I test on the ChatState.Gone, sometime I receive an empty message, after closing.  (Sorry again, u might not understand what I mean again).

So, what if when I closing my form, but at that same time I am still receiving a composing message.  Do u think, that is case?  If so, how can I stop that receiving composing message or other receiving message event.

YS
I am still learning..., hope I can contribute back.
YS
Avatar
Jabberer #19
Member since Feb 2006 · 249 posts
Group memberships: Members
Show profile · Link to this post
i understand what you mean.
If the chatwindow gets closed you send the gone state. The other party receives this state and should stop sending the chat states. It should not send chatstates until you opened a chat window again and sent the active state. If you get them anyway for some reason then ignore them.
You also have to discover if your chat partner supports chatestates. If he/she doesnt support them, then don't send them. if your client runs in a closed network and everybody is using your client which supports chatstates then the discovery is not necessary.
Software Developer
AG-Software
Avatar
yeawsing #20
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
In reply to post #18
Hi,

I just thought about what happen if

  • Two chat form (one is A and another is B) send the first message at the same.  Since I am using Message Thread.  What will hapen?
  • Two chat form (one is A and another is B) send the ChatState.gone message at the same time.

Do u think this will happen?  If so, how should I handle it.  Thanks
I am still learning..., hope I can contribute back.
YS
Avatar
Jabberer #21
Member since Feb 2006 · 249 posts
Group memberships: Members
Show profile · Link to this post
look at the examples in the JEP. The thread is always included with the chat state messages. So you know which chat state message belongs to which chat window. You can't have 2 chat windows with the same thread.
Software Developer
AG-Software
Avatar
yeawsing #22
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
oh.. I am sorry, I guess my sentence really misunderstanding u.  I apologize.  I will try my best to be more clear next time.

  • Two chat form (eg. One is "Me" and another is "You").  We send the first message at the same time with a different Message ThreadID. What will hapen?
  • Two chat form (eg. One is "Me" and another is "You").  We send the ChatState.gone message at the same time. What will hapen?

Cheers :)
I am still learning..., hope I can contribute back.
YS
Avatar
Jabberer #23
Member since Feb 2006 · 249 posts
Group memberships: Members
Show profile · Link to this post
in both cases your code should work.
In the first case we end up both with 2 message windows and 2 different threads.
In case 2 ignore the gone event if you receive it and the window is already closed.
Software Developer
AG-Software
Avatar
yeawsing #24
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
thanks Jabberer.

After fixing some logic stuff, now I guess I got the whole ChatState running pretty nice.
Tomorrow, I post the code for u to review again.  Hope u don't mind :).  It's getting late here.. need to catch the last train.

You are life saver.

YS
I am still learning..., hope I can contribute back.
YS
Avatar
Alex #25
Member since Feb 2003 · 4449 posts · Location: Germany
Group memberships: Administrators, Members
Show profile · Link to this post
Hi,

cool that you got it working with our help.

hey post it for all other developers, not for us  :-D
I'm sure many other developers are interested at this code.

Alex
Avatar
yeawsing #26
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
Hi guys, thanks for your guide and help.  :-)
Here are the code for other developers, who are interested with ChatState.
As Jabberer mention, may be about 90% correct.  So if anyone can correct it again, please post it.  Thanks

  1. using System;
  2. using System.Drawing;
  3. using System.ComponentModel;
  4. using System.Windows.Forms;
  5. using System.Globalization;
  6. using System.Diagnostics;
  7.  
  8. using agsXMPP;
  9. using agsXMPP.protocol;
  10. using agsXMPP.protocol.client;
  11. using agsXMPP.Collections;
  12. using agsXMPP.protocol.extensions.chatstates;
  13. using agsXMPP.util;
  14.  
  15. namespace MiniClient
  16. {
  17.     /// <summary>
  18.     ///
  19.     /// </summary>
  20.     public class frmChat : System.Windows.Forms.Form
  21.     {
  22.         private IContainer components;
  23.  
  24.         private XmppClientConnection _connection;
  25.         private Jid m_Jid;
  26.         private System.Windows.Forms.Button cmdSend;
  27.         private System.Windows.Forms.RichTextBox rtfSend;
  28.         private System.Windows.Forms.Splitter splitter1;
  29.         private System.Windows.Forms.RichTextBox rtfChat;
  30.         private string _nickname;
  31.        
  32.         private DateTime dt = DateTime.Now;
  33.         private PictureBox pictureBox1;
  34.         private StatusBar statusBar1;
  35.         private Label chatstatemsg;
  36.         internal Timer timerChat;
  37.         private bool _isMinimize;
  38.  
  39.         #region Public Property
  40.  
  41.         private String _SendLastMessageDate;
  42.         public String SendLastMessageDate
  43.         {
  44.             get { return _SendLastMessageDate; }
  45.             set { _SendLastMessageDate = value; }
  46.         }
  47.  
  48.         private String _SendLastMessageTime;
  49.         public String SendLastMessageTime
  50.         {
  51.             get { return _SendLastMessageTime; }
  52.             set { _SendLastMessageTime = value; }
  53.         }
  54.  
  55.         private String _StatusMessage;
  56.         public String StatusMessage
  57.         {
  58.             get { return _StatusMessage; }
  59.             set { _StatusMessage = value; }
  60.         }
  61.  
  62.         private String _ReceiveLastMessageDate;
  63.         public String ReceiveLastMessageDate
  64.         {
  65.             get { return _ReceiveLastMessageDate; }
  66.             set { _ReceiveLastMessageDate = value; }
  67.         }
  68.  
  69.         private String _ReceiveLastMessageTime;
  70.         public String ReceiveLastMessageTime
  71.         {
  72.             get { return _ReceiveLastMessageTime; }
  73.             set { _ReceiveLastMessageTime = value; }
  74.         }
  75.  
  76.         private String _messageThreadID;
  77.         public String MsgThreadID
  78.         {
  79.             get { return _messageThreadID; }
  80.             set { _messageThreadID = value; }
  81.         }
  82.  
  83.         private bool _isChatStateActive;
  84.         public bool IsChatStateActive
  85.         {
  86.             get { return _isChatStateActive; }
  87.             set { _isChatStateActive = value; }
  88.         }
  89.  
  90.         private bool _HasReceiveChatStateActivation;
  91.         public bool HasReceiveChatStateActivation
  92.         {
  93.             get { return _HasReceiveChatStateActivation; }
  94.             set { _HasReceiveChatStateActivation = value; }
  95.         }
  96.  
  97.         public Jid Jid
  98.         {
  99.             get { return m_Jid; }
  100.             set { m_Jid = value; }
  101.         }
  102.  
  103.         #endregion
  104.  
  105.         public frmChat(Jid jid, XmppClientConnection con, string nickname)
  106.         {
  107.             m_Jid          = jid;
  108.             _connection = con;
  109.             _nickname   = nickname;
  110.  
  111.             InitializeComponent();
  112.  
  113.             this.Text = "Chat with " + nickname;
  114.            
  115.             Util.ChatForms.Add(m_Jid.Bare.ToLower(), this);
  116.  
  117.             IsChatStateActive = false;
  118.             HasReceiveChatStateActivation = false;
  119.             MyLastChatState = String.Empty;
  120.  
  121.             this.chatstatemsg.Text = String.Empty;
  122.             ReceiveLastMessageDate = dt.ToString("D", DateTimeFormatInfo.InvariantInfo);
  123.             ReceiveLastMessageTime = dt.ToString("T", DateTimeFormatInfo.InvariantInfo);
  124.             SendLastMessageDate = dt.ToString("D", DateTimeFormatInfo.InvariantInfo);
  125.             SendLastMessageTime = dt.ToString("T", DateTimeFormatInfo.InvariantInfo);
  126.  
  127.             // Setup new Message Callback
  128.             con.MesagageGrabber.Add(jid, new BareJidComparer(), new MessageCB(MessageCallback), null);
  129.         }
  130.  
  131.         private void OutgoingMessage(agsXMPP.protocol.client.Message msg)
  132.         {
  133.             rtfChat.SelectionColor = Color.Blue;
  134.             rtfChat.AppendText("Me said: ");
  135.             rtfChat.SelectionColor = Color.Black;
  136.             rtfChat.AppendText(msg.Body);
  137.  
  138.             // Scroll down
  139.             rtfChat.SelectionStart = this.rtfChat.Text.Length - 1;
  140.             rtfChat.ScrollToCaret();
  141.             rtfChat.AppendText("\r\n");
  142.         }
  143.  
  144.         public void IncomingMessage(agsXMPP.protocol.client.Message msg)
  145.         {
  146.             #region JEP-0085 Contact's Client Sends Content Message Reply With <active/> Notification
  147.            /*
  148.                <message
  149.                    from='juliet@capulet.com/balcony'
  150.                    to='romeo@shakespeare.lit/orchard'
  151.                    type='chat'>
  152.                  <thread>act2scene2chat1</thread>
  153.                  <body>
  154.                    What man art thou that thus bescreen'd in night
  155.                     So stumblest on my counsel?
  156.                   </body>
  157.                   <active xmlns='http://jabber.org/protocol/chatstates'/>
  158.                 </message>
  159.             */
  160.             #endregion
  161.  
  162.             MsgThreadID = msg.Thread;
  163.  
  164.             // Once receive message from Contact Client we set ChatState Active
  165.             if (!HasReceiveChatStateActivation)
  166.             {
  167.                 IsChatStateActive = true;
  168.                 HasReceiveChatStateActivation = true;
  169.                 Debug.WriteLine("HasReceiveChatStateActivation");
  170.             }
  171.  
  172.             rtfChat.SelectionColor = Color.Red;
  173.             rtfChat.AppendText(_nickname + " said: ");
  174.             rtfChat.SelectionColor = Color.Black;
  175.             rtfChat.AppendText(msg.Body);
  176.  
  177.             // Scroll down
  178.             rtfChat.SelectionStart = this.rtfChat.Text.Length - 1;
  179.             rtfChat.ScrollToCaret();
  180.             rtfChat.AppendText("\r\n");
  181.  
  182.             this.chatstatemsg.Text = StatusMessage;
  183.         }
  184.  
  185.         private void cmdSend_Click(object sender, System.EventArgs e)
  186.         {
  187.             agsXMPP.protocol.client.Message msg = new agsXMPP.protocol.client.Message();
  188.  
  189.             if (!IsChatStateActive)
  190.             {
  191.                 // ******
  192.                 // Do you think this is the right way to generate the MsgThreadID?
  193.                 // ******
  194.                 _messageThreadID = Hash.Sha1Hash(Id.GetNextId() + _connection.MyJID.ToString() + m_Jid.ToString());
  195.                 IsChatStateActive = true;
  196.                 Debug.WriteLine("IsChatStateActive is FALSE. Create new MSGThreadID: " + MsgThreadID.ToString());
  197.             }
  198.  
  199.             msg.Type = MessageType.chat;
  200.             msg.To = m_Jid;
  201.             msg.From = _connection.MyJID;
  202.             msg.Thread = MsgThreadID;
  203.             msg.Body = rtfSend.Text;
  204.             msg.Chatstate = Chatstate.active;
  205.            
  206.             _connection.Send(msg);
  207.             OutgoingMessage(msg);
  208.             rtfSend.Text = "";
  209.  
  210.             if (IsComposing)
  211.             {
  212.                 IsComposing = false;
  213.                 timerChat.Stop();
  214.                 Debug.WriteLine("cmdSend_Click Send Paused");
  215.             }
  216.         }
  217.  
  218.         private void MessageCallback(object sender, agsXMPP.protocol.client.Message msg, object data)
  219.         {
  220.             if (InvokeRequired)
  221.             {
  222.                 // Windows Forms are not Thread Safe, we need to invoke this :(
  223.                 // We're not in the UI thread, so we need to call BeginInvoke              
  224.                 BeginInvoke(new MessageCB(MessageCallback), new object[] { sender, msg, data });
  225.                 return;
  226.             }
  227.  
  228.             if (msg.Body != null)
  229.             {
  230.                 DateTime dt = DateTime.Now;
  231.                 ReceiveLastMessageDate = dt.ToString("D", DateTimeFormatInfo.InvariantInfo);
  232.                 ReceiveLastMessageTime = dt.ToString("T", DateTimeFormatInfo.InvariantInfo);
  233.  
  234.                 this.chatstatemsg.Text = "Last message received at " + ReceiveLastMessageTime + " on " + ReceiveLastMessageDate;
  235.                 IncomingMessage(msg);
  236.             }
  237.             else if (msg.Body == null && msg.Chatstate == Chatstate.composing)
  238.             {
  239.                 this.chatstatemsg.Text = _nickname + " is writing a message";
  240.             }
  241.             else if (msg.Body == null && msg.Chatstate == Chatstate.gone)
  242.             {
  243.                 IsChatStateActive = false;
  244.                 HasReceiveChatStateActivation = false;
  245.                 Debug.WriteLine("------ Receive: Chatstate.gone ------\n");
  246.             }
  247.             else if (msg.Body == null && msg.Chatstate == Chatstate.paused)
  248.             {
  249.                 this.chatstatemsg.Text = "Last message received at " + ReceiveLastMessageTime + " on " + ReceiveLastMessageDate;
  250.             }
  251.             else if (msg.Body == null && msg.Chatstate == Chatstate.inactive)
  252.             {
  253.                 HasReceiveChatStateActivation = false;
  254.                 Debug.WriteLine("*** Receive: Chatstate.inactive ***\n");
  255.             }
  256.             else if (msg.Body == null && msg.Chatstate == Chatstate.active)
  257.             {
  258.                 HasReceiveChatStateActivation = true;
  259.                 Debug.WriteLine("### Receive: Chatstate.active ###\n");
  260.             }
  261.         }

continue ...
I am still learning..., hope I can contribute back.
YS
Avatar
yeawsing #27
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
  1.         private String myLastChatState;
  2.         public String MyLastChatState
  3.         {
  4.             get { return myLastChatState; }
  5.             set { myLastChatState = value; }
  6.         }
  7.  
  8.         private bool isComposing = false;
  9.         public bool IsComposing
  10.         {
  11.             get { return isComposing; }
  12.             set { isComposing = value; }
  13.         }
  14.  
  15.         private void rtfSend_TextChanged(object sender, EventArgs e)
  16.         {
  17.             this.cmdSend.Enabled = !(this.rtfSend.Text.Length == 0);
  18.  
  19.             if (IsChatStateActive && HasReceiveChatStateActivation)
  20.             {
  21.                 // As Alex mention, we need to store our time, when we start.
  22.                 DtLastInputText = DateTime.Now;
  23.  
  24.                 if (!IsComposing)
  25.                 {
  26.                     // After we have hit on the cmdSend_click, this rtfSend will receive an empty string.
  27.                     // Therefore we don't want to send another composing, we just return it.
  28.                     if (String.IsNullOrEmpty(rtfSend.Text))
  29.                         return;
  30.  
  31.                     #region JEP-0085 Example 9. User's Client Sends Standalone <composing/> Notification
  32.                    /*
  33.                            <message
  34.                                from='romeo@montague.net/orchard'
  35.                                to='juliet@capulet.com/balcony'
  36.                                type='chat'>
  37.                                <thread>act2scene2chat1</thread>
  38.                                <composing xmlns='http://jabber.org/protocol/chatstates'/>
  39.                             </message>
  40.                             */
  41.                     #endregion
  42.                    
  43.                     // Start timer every 500ms in ChatWindow
  44.                     timerChat.Enabled = true;
  45.                     this.timerChat.Start();
  46.  
  47.                     SendChatState(Chatstate.composing);
  48.                     IsComposing = true;
  49.                     Debug.WriteLine("Send Composing");
  50.                 }
  51.                 else
  52.                 {
  53.                     Debug.WriteLine("Still Composing: " + rtfSend.Text);
  54.                     return;
  55.                 }
  56.             }
  57.         }
  58.  
  59.         private DateTime dtLastInputText;
  60.         public DateTime DtLastInputText
  61.         {
  62.             get { return dtLastInputText; }
  63.             set { dtLastInputText = value; }
  64.         }
  65.    
  66.         private void frmChat_FormClosing(object sender, FormClosingEventArgs e)
  67.         {
  68.             #region JEP-0085 Example 17. Contact's Client Sends Standalone <gone/> Notification
  69.            /*
  70.                <message
  71.                    from='juliet@capulet.com/balcony'
  72.                    to='romeo@shakespeare.lit/orchard'
  73.                    type='chat'>
  74.                  <thread>act2scene2chat1</thread>
  75.                  <gone xmlns='http://jabber.org/protocol/chatstates'/>
  76.                 </message>
  77.             */
  78.             #endregion
  79.             if (IsChatStateActive && HasReceiveChatStateActivation)
  80.             {
  81.                 // Just incase we close too quick, after we have compose
  82.                 if (IsComposing)
  83.                 {
  84.                     SendChatState(Chatstate.paused); // I am not sure do I need to send this or not
  85.                     Debug.WriteLine("frmChat_FormClosing Send Paused");
  86.                     IsComposing = false;
  87.                     timerChat.Stop();
  88.                 }
  89.                 SendChatState(Chatstate.gone);
  90.                 timerChat.Dispose();
  91.                 Debug.WriteLine("frmChat_FormClosing Send Gone");
  92.             }
  93.         }
  94.  
  95.         private void frmChat_Resize(object sender, EventArgs e)
  96.         {
  97.             if (IsChatStateActive && String.IsNullOrEmpty(MyLastChatState))
  98.             {
  99.                 if (this.WindowState == FormWindowState.Minimized)
  100.                 {
  101.                     #region JEP-0085 Example 14. Contact's Client Sends Standalone <inactive/> Notification
  102.                    /*
  103.                    <message
  104.                        from='juliet@capulet.com/balcony'
  105.                        to='romeo@shakespeare.lit/orchard'
  106.                        type='chat'>
  107.                      <thread>act2scene2chat1</thread>
  108.                      <inactive xmlns='http://jabber.org/protocol/chatstates'/>
  109.                     </message>
  110.                     */
  111.                     #endregion
  112.  
  113.                     if (IsComposing)
  114.                     {
  115.                         // Just incase we close too quick, after we have compose
  116.                         SendChatState(Chatstate.paused);  // I am not sure do I need to send this or not
  117.                         Debug.WriteLine("frmChat_Resize Send Paused");
  118.                         IsComposing = false;
  119.                         timerChat.Stop();
  120.                     }
  121.                     SendChatState(Chatstate.inactive);
  122.                     _isMinimize = true;
  123.                     HasReceiveChatStateActivation = false;
  124.                     MyLastChatState = "ChatStateInactive";
  125.                     Debug.WriteLine("frmChat_Resize Send Inactive");
  126.                     return;
  127.                 }
  128.             }
  129.             else if (IsChatStateActive && (MyLastChatState == "ChatStateInactive"))
  130.             {
  131.                 if (this.WindowState == FormWindowState.Normal)
  132.                 {
  133.                     #region JEP-0085 Example 15. Contact's Client Sends Standalone <active/> Notification
  134.                    /*
  135.                    <message
  136.                        from='juliet@capulet.com/balcony'
  137.                        to='romeo@shakespeare.lit/orchard'
  138.                        type='chat'>
  139.                      <thread>act2scene2chat1</thread>
  140.                      <active xmlns='http://jabber.org/protocol/chatstates'/>
  141.                     </message>
  142.                     */
  143.                     #endregion
  144.  
  145.                     if (_isMinimize)
  146.                     {
  147.                         SendChatState(Chatstate.active);
  148.                         Debug.WriteLine("frmChat_Resize Send Active");
  149.                         HasReceiveChatStateActivation = true;
  150.                         MyLastChatState = String.Empty;
  151.                         _isMinimize = false;
  152.                         return;
  153.                     }
  154.                     else
  155.                         return;
  156.                 }
  157.             }
  158.         }
  159.  
  160.         private void SendChatState(Chatstate chatStateEnum)
  161.         {
  162.             agsXMPP.protocol.client.Message msg = new agsXMPP.protocol.client.Message();
  163.  
  164.             msg.Type = MessageType.chat;
  165.             msg.To = m_Jid;
  166.             msg.From = _connection.MyJID;
  167.             msg.Thread = MsgThreadID;
  168.             msg.Chatstate = chatStateEnum;
  169.             _connection.Send(msg);
  170.         }
  171.  
  172.         private void timerChat_Tick(object sender, EventArgs e)
  173.         {
  174.             if (IsChatStateActive && HasReceiveChatStateActivation)
  175.             {
  176.                 DateTime dt = DateTime.Now;
  177.                 TimeSpan compareTimeStamp = dt.Subtract(DtLastInputText);
  178.  
  179.                 // Jabberer recommend to use >= because of the different computer process timing
  180.                 if (compareTimeStamp.TotalSeconds >= 3)
  181.                 {
  182.                     #region JEP-0085 Example 10. User's Client Sends Standalone <paused/> Notification
  183.                    /*
  184.                    <message
  185.                        from='romeo@montague.net/orchard'
  186.                        to='juliet@capulet.com/balcony'
  187.                        type='chat'>
  188.                        <thread>act2scene2chat1</thread>
  189.                        <paused xmlns='http://jabber.org/protocol/chatstates'/>
  190.                     </message>
  191.                     */
  192.                     #endregion
  193.  
  194.                     SendChatState(Chatstate.paused);
  195.                     IsComposing = false;
  196.                     timerChat.Stop();
  197.                     Debug.WriteLine("timerChat_Tick Send Paused");
  198.                 }
  199.             }            
  200.         }
  201.  
  202.     }
  203. }
I am still learning..., hope I can contribute back.
YS
Avatar
yeawsing #28
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
hi guys,

Again, can you please check in the frmChat_FormClosing and frmChat_Resize, do u think i need to send that Chatstate.pause?
And in cmdSend_Click, do u think the way I generate the MsgThreadID is ok.

Please let me know, if I am still missing anything here... :)

I really enjoy it. ;-)
I am still learning..., hope I can contribute back.
YS
This post was edited on 2006-09-21, 06:56 by yeawsing.
Avatar
Jabberer #29
Member since Feb 2006 · 249 posts
Group memberships: Members
Show profile · Link to this post
The closing event looks good.
In the Resize (minimize) you could/should also send the inactive event. When the form gets active again you can send the active event.

look also in the definition table of the XEP here:
http://xmpp.org/extensions/xep-0085.html
Software Developer
AG-Software
This post was edited on 2013-03-08, 13:40 by Alex.
Avatar
yeawsing #30
Member since Jul 2006 · 33 posts · Location: South Korea
Group memberships: Members
Show profile · Link to this post
Hi Jabberer,

After reading that JEP-0085 defination, I guess still missing something

1. Chat Form lost/gain focus.
2. User has not interacted with the chat interface for an intermediate period of time (eg 30 seconds)
3. User has not interacted with the chat interface for long period of time (eg 2 minutes).

[1] I guess it may be important to implement.  But I am not sure about the [2] and [3].
I am still learning..., hope I can contribute back.
YS
Close Smaller – Larger + Reply to this post:
Verification code: VeriCode Please enter the word from the image into the text field below. (Type the letters only, lower case is okay.)
Smileys: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Special characters:
Page: previous  1  2  3  next
Forum: agsXMPP RSS