/* Jabber Protocol Plugin for Miranda IM Copyright ( C ) 2002-04 Santithorn Bunchua Copyright ( C ) 2005-12 George Hazan 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. */ #include "jabber.h" #include "jabber_iq.h" #include "jabber_caps.h" #include const TCHAR xmlnsAdmin[] = _T("http://jabber.org/protocol/muc#admin"); const TCHAR xmlnsOwner[] = _T("http://jabber.org/protocol/muc#owner"); ///////////////////////////////////////////////////////////////////////////////////////// // Global definitions enum { IDM_CANCEL, IDM_ROLE, IDM_AFFLTN, IDM_CONFIG, IDM_NICK, IDM_DESTROY, IDM_INVITE, IDM_BOOKMARKS, IDM_LEAVE, IDM_TOPIC, IDM_LST_PARTICIPANT, IDM_LST_MODERATOR, IDM_LST_MEMBER, IDM_LST_ADMIN, IDM_LST_OWNER, IDM_LST_BAN, IDM_MESSAGE, IDM_SLAP, IDM_VCARD, IDM_INFO, IDM_KICK, IDM_RJID, IDM_RJID_ADD, IDM_RJID_VCARD, IDM_RJID_COPY, IDM_SET_VISITOR, IDM_SET_PARTICIPANT, IDM_SET_MODERATOR, IDM_SET_NONE, IDM_SET_MEMBER, IDM_SET_ADMIN, IDM_SET_OWNER, IDM_SET_BAN, IDM_CPY_NICK, IDM_CPY_TOPIC, IDM_CPY_RJID, IDM_CPY_INROOMJID, IDM_LINK0, IDM_LINK1, IDM_LINK2, IDM_LINK3, IDM_LINK4, IDM_LINK5, IDM_LINK6, IDM_LINK7, IDM_LINK8, IDM_LINK9, IDM_PRESENCE_ONLINE = ID_STATUS_ONLINE, IDM_PRESENCE_AWAY = ID_STATUS_AWAY, IDM_PRESENCE_NA = ID_STATUS_NA, IDM_PRESENCE_DND = ID_STATUS_DND, IDM_PRESENCE_FREE4CHAT = ID_STATUS_FREECHAT, }; struct TRoleOrAffiliationInfo { int value; int id; TCHAR *title_en; int min_role; int min_affiliation; TCHAR *title; BOOL check(JABBER_RESOURCE_STATUS *me, JABBER_RESOURCE_STATUS *him) { if (me->affiliation == AFFILIATION_OWNER) return TRUE; if (me == him) return FALSE; if (me->affiliation <= him->affiliation) return FALSE; if (me->role < this->min_role) return FALSE; if (me->affiliation < this->min_affiliation) return FALSE; return TRUE; } void translate() { this->title = TranslateTS(this->title_en); } }; static TRoleOrAffiliationInfo sttAffiliationItems[] = { { AFFILIATION_NONE, IDM_SET_NONE, LPGENT("None"), ROLE_NONE, AFFILIATION_ADMIN }, { AFFILIATION_MEMBER, IDM_SET_MEMBER, LPGENT("Member"), ROLE_NONE, AFFILIATION_ADMIN }, { AFFILIATION_ADMIN, IDM_SET_ADMIN, LPGENT("Admin"), ROLE_NONE, AFFILIATION_OWNER }, { AFFILIATION_OWNER, IDM_SET_OWNER, LPGENT("Owner"), ROLE_NONE, AFFILIATION_OWNER }, }; static TRoleOrAffiliationInfo sttRoleItems[] = { { ROLE_VISITOR, IDM_SET_VISITOR, LPGENT("Visitor"), ROLE_MODERATOR, AFFILIATION_NONE }, { ROLE_PARTICIPANT, IDM_SET_PARTICIPANT, LPGENT("Participant"), ROLE_MODERATOR, AFFILIATION_NONE }, { ROLE_MODERATOR, IDM_SET_MODERATOR, LPGENT("Moderator"), ROLE_MODERATOR, AFFILIATION_ADMIN }, }; ///////////////////////////////////////////////////////////////////////////////////////// // JabberGcInit - initializes the new chat static const TCHAR* sttStatuses[] = { _T("Visitors"), _T("Participants"), _T("Moderators"), _T("Owners") }; int JabberGcGetStatus(JABBER_GC_AFFILIATION a, JABBER_GC_ROLE r) { switch (a) { case AFFILIATION_OWNER: return 3; default: switch (r) { case ROLE_MODERATOR: return 2; case ROLE_PARTICIPANT: return 1; } } return 0; } int JabberGcGetStatus(JABBER_RESOURCE_STATUS *r) { return JabberGcGetStatus(r->affiliation, r->role); } int CJabberProto::JabberGcInit( WPARAM wParam, LPARAM ) { int i; JABBER_LIST_ITEM* item = ( JABBER_LIST_ITEM* )wParam; GCSESSION gcw = {0}; GCEVENT gce = {0}; // translate string for menus (this can't be done in initializer) for (i = 0; i < SIZEOF(sttAffiliationItems); ++i) sttAffiliationItems[i].translate(); for (i = 0; i < SIZEOF(sttRoleItems); ++i) sttRoleItems[i].translate(); TCHAR* szNick = JabberNickFromJID( item->jid ); gcw.cbSize = sizeof(GCSESSION); gcw.iType = GCW_CHATROOM; gcw.pszModule = m_szModuleName; gcw.ptszName = szNick; gcw.ptszID = item->jid; gcw.dwFlags = GC_TCHAR; CallServiceSync( MS_GC_NEWSESSION, NULL, (LPARAM)&gcw ); HANDLE hContact = HContactFromJID( item->jid ); if ( hContact != NULL ) { DBVARIANT dbv; if ( JABBER_LIST_ITEM* bookmark = ListGetItemPtr( LIST_BOOKMARK, item->jid )) if ( bookmark->name ) { if ( !DBGetContactSettingTString( hContact, "CList", "MyHandle", &dbv )) JFreeVariant( &dbv ); else DBWriteContactSettingTString( hContact, "CList", "MyHandle", bookmark->name ); } if ( !JGetStringT( hContact, "MyNick", &dbv )) { if ( !lstrcmp( dbv.ptszVal, szNick )) JDeleteSetting( hContact, "MyNick" ); else JSetStringT( hContact, "MyNick", item->nick ); JFreeVariant( &dbv ); } else JSetStringT( hContact, "MyNick", item->nick ); TCHAR *passw = JGetStringCrypt( hContact, "LoginPassword" ); if ( lstrcmp_null( passw, item->password )) { if ( !item->password || !item->password[0] ) JDeleteSetting( hContact, "LoginPassword" ); else JSetStringCrypt( hContact, "LoginPassword", item->password ); } mir_free(passw); } mir_free( szNick ); item->bChatActive = TRUE; GCDEST gcd = { m_szModuleName, NULL, GC_EVENT_ADDGROUP }; gcd.ptszID = item->jid; gce.cbSize = sizeof(GCEVENT); gce.pDest = &gcd; gce.dwFlags = GC_TCHAR; for (i = SIZEOF(sttStatuses)-1; i >= 0; i-- ) { gce.ptszStatus = TranslateTS( sttStatuses[i] ); CallServiceSync( MS_GC_EVENT, NULL, ( LPARAM )&gce ); } gce.cbSize = sizeof(GCEVENT); gce.pDest = &gcd; gcd.iType = GC_EVENT_CONTROL; CallServiceSync( MS_GC_EVENT, (item->bAutoJoin && m_options.AutoJoinHidden) ? WINDOW_HIDDEN : SESSION_INITDONE, (LPARAM)&gce ); CallServiceSync( MS_GC_EVENT, SESSION_ONLINE, (LPARAM)&gce ); return 0; } void CJabberProto::GcLogCreate( JABBER_LIST_ITEM* item ) { if ( item->bChatActive ) return; NotifyEventHooks( m_hInitChat, (WPARAM)item, 0 ); } void CJabberProto::GcLogShowInformation( JABBER_LIST_ITEM *item, JABBER_RESOURCE_STATUS *user, TJabberGcLogInfoType type ) { if (!item || !user || (item->bChatActive != 2)) return; TCHAR buf[512] = _T(""); switch (type) { case INFO_BAN: if (m_options.GcLogBans) { mir_sntprintf(buf, SIZEOF(buf), TranslateT("User %s in now banned."), user->resourceName); } break; case INFO_STATUS: if (m_options.GcLogStatuses) { if (user->statusMessage) { mir_sntprintf(buf, SIZEOF(buf), TranslateT("User %s changed status to %s with message: %s"), user->resourceName, CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, user->status, GSMDF_TCHAR), user->statusMessage); } else { mir_sntprintf(buf, SIZEOF(buf), TranslateT("User %s changed status to %s"), user->resourceName, CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, user->status, GSMDF_TCHAR)); } } break; case INFO_CONFIG: if (m_options.GcLogConfig) { mir_sntprintf(buf, SIZEOF(buf), TranslateT("Room configuration was changed.")); } break; case INFO_AFFILIATION: if (m_options.GcLogAffiliations) { TCHAR *name = NULL; switch (user->affiliation) { case AFFILIATION_NONE: name = TranslateT("None"); break; case AFFILIATION_MEMBER: name = TranslateT("Member"); break; case AFFILIATION_ADMIN: name = TranslateT("Admin"); break; case AFFILIATION_OWNER: name = TranslateT("Owner"); break; case AFFILIATION_OUTCAST: name = TranslateT("Outcast"); break; } if (name) mir_sntprintf(buf, SIZEOF(buf), TranslateT("Affiliation of %s was changed to '%s'."), user->resourceName, name); } break; case INFO_ROLE: if (m_options.GcLogRoles) { TCHAR *name = NULL; switch (user->role) { case ROLE_NONE: name = TranslateT("None"); break; case ROLE_VISITOR: name = TranslateT("Visitor"); break; case ROLE_PARTICIPANT: name = TranslateT("Participant"); break; case ROLE_MODERATOR: name = TranslateT("Moderator"); break; } if (name) mir_sntprintf(buf, SIZEOF(buf), TranslateT("Role of %s was changed to '%s'."), user->resourceName, name); } break; } if (*buf) { GCDEST gcd = { m_szModuleName, 0, 0 }; gcd.ptszID = item->jid; GCEVENT gce = {0}; gce.cbSize = sizeof(GCEVENT); gce.ptszNick = user->resourceName; gce.ptszUID = user->resourceName; gce.ptszText = EscapeChatTags( buf ); gce.dwFlags = GC_TCHAR | GCEF_ADDTOLOG; gce.pDest = &gcd; gce.time = time(0); gcd.iType = GC_EVENT_INFORMATION; CallServiceSync( MS_GC_EVENT, NULL, ( LPARAM )&gce ); mir_free( (void*)gce.ptszText ); // Since we processed msgText and created a new string } } void CJabberProto::GcLogUpdateMemberStatus( JABBER_LIST_ITEM* item, const TCHAR* resource, const TCHAR* nick, const TCHAR* jid, int action, HXML reason, int nStatusCode ) { int statusToSet = 0; const TCHAR* szReason = NULL; if ( reason != NULL && xmlGetText( reason ) != NULL ) szReason = xmlGetText( reason ); if ( !szReason ) { if ( nStatusCode == 322 ) szReason = TranslateT( "because room is now members-only" ); else if ( nStatusCode == 301 ) szReason = TranslateT( "user banned" ); } TCHAR* myNick = (item->nick == NULL) ? NULL : mir_tstrdup( item->nick ); if ( myNick == NULL ) myNick = JabberNickFromJID( m_szJabberJID ); GCDEST gcd = { m_szModuleName, 0, 0 }; gcd.ptszID = item->jid; GCEVENT gce = {0}; gce.cbSize = sizeof(GCEVENT); gce.ptszNick = nick; gce.ptszUID = resource; if (jid != NULL) gce.ptszUserInfo = jid; gce.ptszText = szReason; gce.dwFlags = GC_TCHAR; gce.pDest = &gcd; if ( item->bChatActive == 2 ) { gce.dwFlags |= GCEF_ADDTOLOG; gce.time = time(0); } switch( gcd.iType = action ) { case GC_EVENT_PART: break; case GC_EVENT_KICK: gce.ptszStatus = TranslateT( "Moderator" ); break; default: for ( int i=0; i < item->resourceCount; i++ ) { JABBER_RESOURCE_STATUS& JS = item->resource[i]; if ( !lstrcmp( resource, JS.resourceName )) { if ( action != GC_EVENT_JOIN ) { switch( action ) { case 0: gcd.iType = GC_EVENT_ADDSTATUS; case GC_EVENT_REMOVESTATUS: gce.dwFlags &= ~GCEF_ADDTOLOG; } gce.ptszText = TranslateT( "Moderator" ); } gce.ptszStatus = TranslateTS( sttStatuses[JabberGcGetStatus(&JS)] ); gce.bIsMe = ( lstrcmp( nick, myNick ) == 0 ); statusToSet = JS.status; break; } } } CallServiceSync( MS_GC_EVENT, NULL, ( LPARAM )&gce ); if ( statusToSet != 0 ) { gce.ptszText = nick; if ( statusToSet == ID_STATUS_AWAY || statusToSet == ID_STATUS_NA || statusToSet == ID_STATUS_DND ) gce.dwItemData = 3; else gce.dwItemData = 1; gcd.iType = GC_EVENT_SETSTATUSEX; CallServiceSync( MS_GC_EVENT, NULL, ( LPARAM )&gce ); gce.ptszUID = resource; gce.dwItemData = statusToSet; gcd.iType = GC_EVENT_SETCONTACTSTATUS; CallServiceSync( MS_GC_EVENT, NULL, ( LPARAM )&gce ); } mir_free( myNick ); } void CJabberProto::GcQuit( JABBER_LIST_ITEM* item, int code, HXML reason ) { TCHAR *szMessage = NULL; const TCHAR* szReason = NULL; if ( reason != NULL && xmlGetText( reason ) != NULL ) szReason = xmlGetText( reason ); GCDEST gcd = { m_szModuleName, NULL, GC_EVENT_CONTROL }; gcd.ptszID = item->jid; GCEVENT gce = {0}; gce.cbSize = sizeof(GCEVENT); gce.ptszUID = item->jid; gce.ptszText = szReason; gce.dwFlags = GC_TCHAR; gce.pDest = &gcd; if ( code != 307 && code != 301 ) { CallServiceSync( MS_GC_EVENT, SESSION_TERMINATE, ( LPARAM )&gce ); CallServiceSync( MS_GC_EVENT, WINDOW_CLEARLOG, ( LPARAM )&gce ); DBVARIANT dbvMessage; if (!DBGetContactSettingTString( NULL, m_szModuleName, "GcMsgQuit", &dbvMessage)) { szMessage = NEWTSTR_ALLOCA(dbvMessage.ptszVal); DBFreeVariant(&dbvMessage); } else szMessage = TranslateTS(JABBER_GC_MSG_QUIT); } else { TCHAR* myNick = JabberNickFromJID( m_szJabberJID ); GcLogUpdateMemberStatus( item, myNick, myNick, NULL, GC_EVENT_KICK, reason ); mir_free( myNick ); CallServiceSync( MS_GC_EVENT, SESSION_OFFLINE, ( LPARAM )&gce ); } DBDeleteContactSetting( HContactFromJID( item->jid ), "CList", "Hidden" ); item->bChatActive = FALSE; if ( m_bJabberOnline ) { TCHAR szPresenceTo[ JABBER_MAX_JID_LEN ]; mir_sntprintf( szPresenceTo, SIZEOF( szPresenceTo ), _T("%s/%s"), item->jid, item->nick ); m_ThreadInfo->send( XmlNode( _T("presence")) << XATTR( _T("to"), szPresenceTo ) << XATTR( _T("type"), _T("unavailable")) << XCHILD( _T("status"), szMessage)); ListRemove( LIST_CHATROOM, item->jid ); } } ///////////////////////////////////////////////////////////////////////////////////////// // Context menu hooks static struct gc_item *sttFindGcMenuItem(GCMENUITEMS *items, DWORD id) { for (int i = 0; i < items->nItems; ++i) if (items->Item[i].dwID == id) return items->Item + i; return NULL; } static void sttSetupGcMenuItem(GCMENUITEMS *items, DWORD id, bool disabled) { for (int i = 0; i < items->nItems; ++i) if (!id || (items->Item[i].dwID == id)) items->Item[i].bDisabled = disabled; } static void sttShowGcMenuItem(GCMENUITEMS *items, DWORD id, int type) { for (int i = 0; i < items->nItems; ++i) if (!id || (items->Item[i].dwID == id)) items->Item[i].uType = type; } static void sttSetupGcMenuItems(GCMENUITEMS *items, DWORD *ids, bool disabled) { for ( ; *ids; ++ids) sttSetupGcMenuItem(items, *ids, disabled); } static void sttShowGcMenuItems(GCMENUITEMS *items, DWORD *ids, int type) { for ( ; *ids; ++ids) sttShowGcMenuItem(items, *ids, type); } static gc_item sttLogListItems[] = { { LPGENT("Change &nickname"), IDM_NICK, MENU_ITEM }, { LPGENT("&Invite a user"), IDM_INVITE, MENU_ITEM }, { NULL, 0, MENU_SEPARATOR }, { LPGENT("&Roles"), IDM_ROLE, MENU_NEWPOPUP }, { LPGENT("&Participant list"), IDM_LST_PARTICIPANT, MENU_POPUPITEM }, { LPGENT("&Moderator list"), IDM_LST_MODERATOR, MENU_POPUPITEM }, { LPGENT("&Affiliations"), IDM_AFFLTN, MENU_NEWPOPUP }, { LPGENT("&Member list"), IDM_LST_MEMBER, MENU_POPUPITEM }, { LPGENT("&Admin list"), IDM_LST_ADMIN, MENU_POPUPITEM }, { LPGENT("&Owner list"), IDM_LST_OWNER, MENU_POPUPITEM }, { NULL, 0, MENU_POPUPSEPARATOR }, { LPGENT("Outcast list (&ban)"), IDM_LST_BAN, MENU_POPUPITEM }, { LPGENT("&Room options"), 0, MENU_NEWPOPUP }, { LPGENT("View/change &topic"), IDM_TOPIC, MENU_POPUPITEM }, { LPGENT("Add to &bookmarks"), IDM_BOOKMARKS, MENU_POPUPITEM }, { LPGENT("&Configure..."), IDM_CONFIG, MENU_POPUPITEM }, { LPGENT("&Destroy room"), IDM_DESTROY, MENU_POPUPITEM }, { NULL, 0, MENU_SEPARATOR }, { LPGENT("Lin&ks"), 0, MENU_NEWPOPUP }, { NULL, IDM_LINK0, 0 }, { NULL, IDM_LINK1, 0 }, { NULL, IDM_LINK2, 0 }, { NULL, IDM_LINK3, 0 }, { NULL, IDM_LINK4, 0 }, { NULL, IDM_LINK5, 0 }, { NULL, IDM_LINK6, 0 }, { NULL, IDM_LINK7, 0 }, { NULL, IDM_LINK8, 0 }, { NULL, IDM_LINK9, 0 }, { LPGENT("Copy room &JID"), IDM_CPY_RJID, MENU_ITEM }, { LPGENT("Copy room topic"), IDM_CPY_TOPIC, MENU_ITEM }, { NULL, 0, MENU_SEPARATOR }, { LPGENT("&Send presence"), 0, MENU_NEWPOPUP}, { LPGENT("Online"), IDM_PRESENCE_ONLINE, MENU_POPUPITEM }, { LPGENT("Away"), IDM_PRESENCE_AWAY, MENU_POPUPITEM }, { LPGENT("NA"), IDM_PRESENCE_NA, MENU_POPUPITEM }, { LPGENT("DND"), IDM_PRESENCE_DND, MENU_POPUPITEM }, { LPGENT("Free for chat"), IDM_PRESENCE_FREE4CHAT, MENU_POPUPITEM }, { LPGENT("&Leave chat session"), IDM_LEAVE, MENU_ITEM } }; static TCHAR sttRJidBuf[JABBER_MAX_JID_LEN] = {0}; static struct gc_item sttListItems[] = { { LPGENT("&Slap"), IDM_SLAP, MENU_ITEM }, // 0 { LPGENT("&User details"), IDM_VCARD, MENU_ITEM }, // 1 { LPGENT("Member &info"), IDM_INFO, MENU_ITEM }, // 2 { sttRJidBuf, 0, MENU_NEWPOPUP }, // 3 -> accessed explicitly by index!!! { LPGENT("User &details"), IDM_RJID_VCARD, MENU_POPUPITEM }, { LPGENT("&Add to roster"), IDM_RJID_ADD, MENU_POPUPITEM }, { LPGENT("&Copy to clipboard"), IDM_RJID_COPY, MENU_POPUPITEM }, { LPGENT("Invite to room"), 0, MENU_NEWPOPUP }, { NULL, IDM_LINK0, 0 }, { NULL, IDM_LINK1, 0 }, { NULL, IDM_LINK2, 0 }, { NULL, IDM_LINK3, 0 }, { NULL, IDM_LINK4, 0 }, { NULL, IDM_LINK5, 0 }, { NULL, IDM_LINK6, 0 }, { NULL, IDM_LINK7, 0 }, { NULL, IDM_LINK8, 0 }, { NULL, IDM_LINK9, 0 }, { NULL, 0, MENU_SEPARATOR }, { LPGENT("Set &role"), IDM_ROLE, MENU_NEWPOPUP }, { LPGENT("&Visitor"), IDM_SET_VISITOR, MENU_POPUPITEM }, { LPGENT("&Participant"), IDM_SET_PARTICIPANT, MENU_POPUPITEM }, { LPGENT("&Moderator"), IDM_SET_MODERATOR, MENU_POPUPITEM }, { LPGENT("Set &affiliation"), IDM_AFFLTN, MENU_NEWPOPUP }, { LPGENT("&None"), IDM_SET_NONE, MENU_POPUPITEM }, { LPGENT("&Member"), IDM_SET_MEMBER, MENU_POPUPITEM }, { LPGENT("&Admin"), IDM_SET_ADMIN, MENU_POPUPITEM }, { LPGENT("&Owner"), IDM_SET_OWNER, MENU_POPUPITEM }, { NULL, 0, MENU_POPUPSEPARATOR }, { LPGENT("Outcast (&ban)"), IDM_SET_BAN, MENU_POPUPITEM }, { LPGENT("&Kick"), IDM_KICK, MENU_ITEM }, { NULL, 0, MENU_SEPARATOR }, { LPGENT("Copy &nickname"), IDM_CPY_NICK, MENU_ITEM }, { LPGENT("Copy real &JID"), IDM_CPY_RJID, MENU_ITEM }, { LPGENT("Copy in-room JID"), IDM_CPY_INROOMJID, MENU_ITEM } }; int CJabberProto::JabberGcMenuHook( WPARAM, LPARAM lParam ) { GCMENUITEMS* gcmi = ( GCMENUITEMS* )lParam; if ( gcmi == NULL ) return 0; if ( lstrcmpiA( gcmi->pszModule, m_szModuleName )) return 0; JABBER_LIST_ITEM* item = ListGetItemPtr( LIST_CHATROOM, gcmi->pszID ); if ( item == NULL ) return 0; JABBER_RESOURCE_STATUS *me = NULL, *him = NULL; for ( int i=0; i < item->resourceCount; i++ ) { JABBER_RESOURCE_STATUS& p = item->resource[i]; if ( !lstrcmp( p.resourceName, item->nick )) me = &p; if ( !lstrcmp( p.resourceName, gcmi->pszUID )) him = &p; } if ( gcmi->Type == MENU_ON_LOG ) { static TCHAR url_buf[1024] = {0}; gcmi->nItems = SIZEOF( sttLogListItems ); gcmi->Item = sttLogListItems; static DWORD sttModeratorItems[] = { IDM_LST_PARTICIPANT, 0 }; static DWORD sttAdminItems[] = { IDM_LST_MODERATOR, IDM_LST_MEMBER, IDM_LST_ADMIN, IDM_LST_OWNER, IDM_LST_BAN, 0 }; static DWORD sttOwnerItems[] = { IDM_CONFIG, IDM_DESTROY, 0 }; sttSetupGcMenuItem(gcmi, 0, FALSE); int idx = IDM_LINK0; if (item->itemResource.statusMessage && *item->itemResource.statusMessage) { TCHAR *bufPtr = url_buf; for (TCHAR *p = _tcsstr(item->itemResource.statusMessage, _T("http://")); p && *p; p = _tcsstr(p+1, _T("http://"))) { lstrcpyn(bufPtr, p, SIZEOF(url_buf) - (bufPtr - url_buf)); gc_item *pItem = sttFindGcMenuItem(gcmi, idx); pItem->pszDesc = bufPtr; pItem->uType = MENU_POPUPITEM; for ( ; *bufPtr && !_istspace(*bufPtr); ++bufPtr) ; *bufPtr++ = 0; if (++idx > IDM_LINK9) break; } } for ( ; idx <= IDM_LINK9; ++idx) sttFindGcMenuItem(gcmi, idx)->uType = 0; if ( !GetAsyncKeyState(VK_CONTROL)) { if (me) { sttSetupGcMenuItems(gcmi, sttModeratorItems, (me->role < ROLE_MODERATOR)); sttSetupGcMenuItems(gcmi, sttAdminItems, (me->affiliation < AFFILIATION_ADMIN)); sttSetupGcMenuItems(gcmi, sttOwnerItems, (me->affiliation < AFFILIATION_OWNER)); } if (m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PRIVATE_STORAGE) sttSetupGcMenuItem(gcmi, IDM_BOOKMARKS, FALSE); } } else if ( gcmi->Type == MENU_ON_NICKLIST ) { gcmi->nItems = SIZEOF(sttListItems); gcmi->Item = sttListItems; static DWORD sttRJidItems[] = { IDM_RJID_VCARD, IDM_RJID_ADD, IDM_RJID_COPY, 0 }; if (me && him) { int i, idx; BOOL force = GetAsyncKeyState(VK_CONTROL); sttSetupGcMenuItem(gcmi, 0, FALSE); idx = IDM_LINK0; LISTFOREACH_NODEF(i, this, LIST_CHATROOM) if (item = ListGetItemPtrFromIndex(i)) { gc_item *pItem = sttFindGcMenuItem(gcmi, idx); pItem->pszDesc = item->jid; pItem->uType = MENU_POPUPITEM; if (++idx > IDM_LINK9) break; } for ( ; idx <= IDM_LINK9; ++idx) sttFindGcMenuItem(gcmi, idx)->uType = 0; for (i = 0; i < SIZEOF(sttAffiliationItems); ++i) { struct gc_item *item = sttFindGcMenuItem(gcmi, sttAffiliationItems[i].id); item->uType = (him->affiliation == sttAffiliationItems[i].value) ? MENU_POPUPCHECK : MENU_POPUPITEM; item->bDisabled = !(force || sttAffiliationItems[i].check(me, him)); } for (i = 0; i < SIZEOF(sttRoleItems); ++i) { struct gc_item *item = sttFindGcMenuItem(gcmi, sttRoleItems[i].id); item->uType = (him->role == sttRoleItems[i].value) ? MENU_POPUPCHECK : MENU_POPUPITEM; item->bDisabled = !(force || sttRoleItems[i].check(me, him)); } if (him->szRealJid && *him->szRealJid) { mir_sntprintf(sttRJidBuf, SIZEOF(sttRJidBuf), TranslateT("Real &JID: %s"), him->szRealJid); if (TCHAR *tmp = _tcschr(sttRJidBuf, _T('/'))) *tmp = 0; if (HANDLE hContact = HContactFromJID(him->szRealJid)) { gcmi->Item[3].uType = MENU_HMENU; gcmi->Item[3].dwID = CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)hContact, 0); sttShowGcMenuItems(gcmi, sttRJidItems, 0); } else { gcmi->Item[3].uType = MENU_NEWPOPUP; sttShowGcMenuItems(gcmi, sttRJidItems, MENU_POPUPITEM); } sttSetupGcMenuItem(gcmi, IDM_CPY_RJID, FALSE); } else { gcmi->Item[3].uType = 0; sttShowGcMenuItems(gcmi, sttRJidItems, 0); sttSetupGcMenuItem(gcmi, IDM_CPY_RJID, TRUE); } if (!force) { if (me->role < ROLE_MODERATOR || (me->affiliation <= him->affiliation)) sttSetupGcMenuItem(gcmi, IDM_KICK, TRUE); if ((me->affiliation < AFFILIATION_ADMIN) || (me->affiliation == AFFILIATION_ADMIN) && (me->affiliation <= him->affiliation)) sttSetupGcMenuItem(gcmi, IDM_SET_BAN, TRUE); } } else { sttSetupGcMenuItem(gcmi, 0, TRUE); gcmi->Item[2].uType = 0; sttShowGcMenuItems(gcmi, sttRJidItems, 0); } } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // Conference invitation dialog class CGroupchatInviteDlg : public CJabberDlgBase { typedef CJabberDlgBase CSuper; struct JabberGcLogInviteDlgJidData { int hItem; TCHAR jid[JABBER_MAX_JID_LEN]; }; LIST m_newJids; TCHAR *m_room; CCtrlButton m_btnInvite; CCtrlEdit m_txtNewJid; CCtrlMButton m_btnAddJid; CCtrlEdit m_txtReason; CCtrlClc m_clc; void FilterList(CCtrlClc *) { for (HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) { char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); if (lstrcmpA(proto, m_proto->m_szModuleName) || DBGetContactSettingByte(hContact, proto, "ChatRoom", 0)) if (HANDLE hItem = m_clc.FindContact(hContact)) m_clc.DeleteItem(hItem); } } void ResetListOptions(CCtrlClc *) { m_clc.SetBkBitmap(0, NULL); m_clc.SetBkColor(GetSysColor(COLOR_WINDOW)); m_clc.SetGreyoutFlags(0); m_clc.SetLeftMargin(4); m_clc.SetIndent(10); m_clc.SetHideEmptyGroups(1); m_clc.SetHideOfflineRoot(1); for (int i=0; i <= FONTID_MAX; i++) m_clc.SetTextColor(i, GetSysColor(COLOR_WINDOWTEXT)); } void InviteUser(TCHAR *pUser, TCHAR *text) { XmlNode msg( _T("message")); HXML invite = msg << XATTR( _T("to"), m_room ) << XATTRID( m_proto->SerialNext()) << XCHILDNS( _T("x"), _T(JABBER_FEAT_MUC_USER)) << XCHILD( _T("invite")) << XATTR( _T("to"), pUser ); if ( text ) invite << XCHILD( _T("reason"), text ); m_proto->m_ThreadInfo->send( msg ); } public: CGroupchatInviteDlg(CJabberProto* ppro, TCHAR *room) : CSuper(ppro, IDD_GROUPCHAT_INVITE, NULL), m_newJids(1), m_btnInvite(this, IDC_INVITE), m_txtNewJid(this, IDC_NEWJID), m_btnAddJid(this, IDC_ADDJID, ppro->LoadIconEx("addroster"), "Add"), m_txtReason(this, IDC_REASON), m_clc(this, IDC_CLIST) { m_room = mir_tstrdup(room); m_btnAddJid.OnClick = Callback( this, &CGroupchatInviteDlg::OnCommand_AddJid ); m_btnInvite.OnClick = Callback( this, &CGroupchatInviteDlg::OnCommand_Invite ); m_clc.OnNewContact = m_clc.OnListRebuilt = Callback( this, &CGroupchatInviteDlg::FilterList ); m_clc.OnOptionsChanged = Callback( this, &CGroupchatInviteDlg::ResetListOptions ); } ~CGroupchatInviteDlg() { for (int i = 0; i < m_newJids.getCount(); ++i) mir_free(m_newJids[i]); mir_free(m_room); } void OnInitDialog() { CSuper::OnInitDialog(); TCHAR buf[256]; mir_sntprintf(buf, SIZEOF(buf), _T("%s\n%s"), m_room, TranslateT("Send groupchat invitation.")); SetDlgItemText(m_hwnd, IDC_HEADERBAR, buf); WindowSetIcon(m_hwnd, m_proto, "group"); SetWindowLongPtr(GetDlgItem(m_hwnd, IDC_CLIST), GWL_STYLE, GetWindowLongPtr(GetDlgItem(m_hwnd, IDC_CLIST), GWL_STYLE)|CLS_HIDEOFFLINE|CLS_CHECKBOXES|CLS_HIDEEMPTYGROUPS|CLS_USEGROUPS|CLS_GREYALTERNATE|CLS_GROUPCHECKBOXES); SendMessage(GetDlgItem(m_hwnd, IDC_CLIST), CLM_SETEXSTYLE, CLS_EX_DISABLEDRAGDROP|CLS_EX_TRACKSELECT, 0); ResetListOptions(&m_clc); FilterList(&m_clc); } void OnCommand_AddJid( CCtrlButton* ) { TCHAR buf[JABBER_MAX_JID_LEN]; m_txtNewJid.GetText(buf, SIZEOF(buf)); m_txtNewJid.SetTextA(""); HANDLE hContact = m_proto->HContactFromJID(buf); if ( hContact ) { int hItem = SendDlgItemMessage( m_hwnd, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0 ); if ( hItem ) SendDlgItemMessage( m_hwnd, IDC_CLIST, CLM_SETCHECKMARK, hItem, 1 ); return; } int i; for (i = 0; i < m_newJids.getCount(); ++i) if (!lstrcmp(m_newJids[i]->jid, buf)) break; if (i != m_newJids.getCount()) return; JabberGcLogInviteDlgJidData *jidData = (JabberGcLogInviteDlgJidData *)mir_alloc(sizeof(JabberGcLogInviteDlgJidData)); lstrcpy(jidData->jid, buf); CLCINFOITEM cii = {0}; cii.cbSize = sizeof(cii); cii.flags = CLCIIF_CHECKBOX; mir_sntprintf(buf, SIZEOF(buf), _T("%s (%s)"), jidData->jid, TranslateT("not on roster")); cii.pszText = buf; jidData->hItem = SendDlgItemMessage(m_hwnd,IDC_CLIST,CLM_ADDINFOITEM,0,(LPARAM)&cii); SendDlgItemMessage(m_hwnd, IDC_CLIST, CLM_SETCHECKMARK, jidData->hItem, 1); m_newJids.insert(jidData); } void OnCommand_Invite( CCtrlButton* ) { if (!m_room) return; TCHAR *text = m_txtReason.GetText(); HWND hwndList = GetDlgItem(m_hwnd, IDC_CLIST); // invite users from roster for (HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); hContact; hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0)) { char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); if (!lstrcmpA(proto, m_proto->m_szModuleName) && !DBGetContactSettingByte(hContact, proto, "ChatRoom", 0)) { if (int hItem = SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0)) { if (SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0)) { DBVARIANT dbv={0}; m_proto->JGetStringT(hContact, "jid", &dbv); if (dbv.ptszVal && ( dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_WCHAR )) InviteUser(dbv.ptszVal, text); JFreeVariant(&dbv); } } } } // invite others for (int i = 0; i < m_newJids.getCount(); ++i) if (SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)m_newJids[i]->hItem, 0)) InviteUser(m_newJids[i]->jid, text); mir_free(text); Close(); } }; ///////////////////////////////////////////////////////////////////////////////////////// // Context menu processing void CJabberProto::AdminSet( const TCHAR* to, const TCHAR* ns, const TCHAR* szItem, const TCHAR* itemVal, const TCHAR* var, const TCHAR* varVal ) { m_ThreadInfo->send( XmlNodeIq( _T("set"), SerialNext(), to ) << XQUERY( ns ) << XCHILD( _T("item")) << XATTR( szItem, itemVal ) << XATTR( var, varVal )); } void CJabberProto::AdminSetReason( const TCHAR* to, const TCHAR* ns, const TCHAR* szItem, const TCHAR* itemVal, const TCHAR* var, const TCHAR* varVal , const TCHAR* rsn) { m_ThreadInfo->send( XmlNodeIq( _T("set"), SerialNext(), to ) << XQUERY( ns ) << XCHILD( _T("item")) << XATTR( szItem, itemVal ) << XATTR( var, varVal ) << XCHILD( _T("reason"), rsn)); } void CJabberProto::AdminGet( const TCHAR* to, const TCHAR* ns, const TCHAR* var, const TCHAR* varVal, JABBER_IQ_PFUNC foo ) { int id = SerialNext(); IqAdd( id, IQ_PROC_NONE, foo ); m_ThreadInfo->send( XmlNodeIq( _T("get"), id, to ) << XQUERY( ns ) << XCHILD( _T("item")) << XATTR( var, varVal )); } // Member info dialog struct TUserInfoData { CJabberProto* ppro; JABBER_LIST_ITEM *item; JABBER_RESOURCE_STATUS *me, *him; }; static INT_PTR CALLBACK sttUserInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { TUserInfoData *dat = (TUserInfoData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); switch (msg) { case WM_INITDIALOG: { int i, idx; TCHAR buf[256]; TranslateDialogDefault(hwndDlg); SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); dat = (TUserInfoData *)lParam; WindowSetIcon( hwndDlg, dat->ppro, "group" ); LOGFONT lf; GetObject((HFONT)SendDlgItemMessage(hwndDlg, IDC_TXT_NICK, WM_GETFONT, 0, 0), sizeof(lf), &lf); lf.lfWeight = FW_BOLD; HFONT hfnt = CreateFontIndirect(&lf); SendDlgItemMessage(hwndDlg, IDC_TXT_NICK, WM_SETFONT, (WPARAM)hfnt, TRUE); SendDlgItemMessage(hwndDlg, IDC_BTN_AFFILIATION, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadSkinnedIcon(SKINICON_EVENT_FILE)); SendDlgItemMessage(hwndDlg, IDC_BTN_AFFILIATION, BUTTONSETASFLATBTN, TRUE, 0); SendDlgItemMessage(hwndDlg, IDC_BTN_AFFILIATION, BUTTONADDTOOLTIP, (WPARAM)"Apply", 0); SendDlgItemMessage(hwndDlg, IDC_BTN_ROLE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadSkinnedIcon(SKINICON_EVENT_FILE)); SendDlgItemMessage(hwndDlg, IDC_BTN_ROLE, BUTTONSETASFLATBTN, TRUE, 0); SendDlgItemMessage(hwndDlg, IDC_BTN_ROLE, BUTTONADDTOOLTIP, (WPARAM)"Apply", 0); SendDlgItemMessage(hwndDlg, IDC_ICO_STATUS, STM_SETICON, (WPARAM)LoadSkinnedProtoIcon(dat->ppro->m_szModuleName, dat->him->status), 0); mir_sntprintf(buf, SIZEOF(buf), _T("%s %s"), TranslateT("Member Info:"), dat->him->resourceName); SetWindowText(hwndDlg, buf); mir_sntprintf(buf, SIZEOF(buf), _T("%s\n%s %s %s"), TranslateT("Member Information"), dat->him->resourceName, TranslateT("from"), dat->item->jid); SetDlgItemText(hwndDlg, IDC_HEADERBAR, buf); SetDlgItemText(hwndDlg, IDC_TXT_NICK, dat->him->resourceName); SetDlgItemText(hwndDlg, IDC_TXT_JID, dat->him->szRealJid ? dat->him->szRealJid : TranslateT("Real JID not available")); SetDlgItemText(hwndDlg, IDC_TXT_STATUS, dat->him->statusMessage); for (i = 0; i < SIZEOF(sttRoleItems); ++i) { if ((sttRoleItems[i].value == dat->him->role) || sttRoleItems[i].check(dat->me, dat->him)) { SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_SETITEMDATA, idx = SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_ADDSTRING, 0, (LPARAM)sttRoleItems[i].title), sttRoleItems[i].value); if (sttRoleItems[i].value == dat->him->role) SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_SETCURSEL, idx, 0); } } for (i = 0; i < SIZEOF(sttAffiliationItems); ++i) { if ((sttAffiliationItems[i].value == dat->him->affiliation) || sttAffiliationItems[i].check(dat->me, dat->him)) { SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_SETITEMDATA, idx = SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_ADDSTRING, 0, (LPARAM)sttAffiliationItems[i].title), sttAffiliationItems[i].value); if (sttAffiliationItems[i].value == dat->him->affiliation) SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_SETCURSEL, idx, 0); } } EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_ROLE), FALSE); EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_AFFILIATION), FALSE); break; } case WM_COMMAND: if (!dat)break; switch ( LOWORD( wParam )) { case IDCANCEL: PostMessage(hwndDlg, WM_CLOSE, 0, 0); break; case IDC_TXT_AFFILIATION: if (HIWORD(wParam) == CBN_SELCHANGE) { int value = SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_GETCURSEL, 0, 0), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_AFFILIATION), dat->him->affiliation != value); } break; case IDC_BTN_AFFILIATION: { int value = SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_TXT_AFFILIATION, CB_GETCURSEL, 0, 0), 0); if (dat->him->affiliation == value) break; switch (value) { TCHAR szBareJid[ JABBER_MAX_JID_LEN ]; JabberStripJid( dat->him->szRealJid, szBareJid, SIZEOF(szBareJid)); case AFFILIATION_NONE: if (dat->him->szRealJid) dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("none")); else dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("affiliation"), _T("none")); break; case AFFILIATION_MEMBER: if (dat->him->szRealJid) dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("member")); else dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("affiliation"), _T("member")); break; case AFFILIATION_ADMIN: if (dat->him->szRealJid) dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("admin")); else dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("affiliation"), _T("admin")); break; case AFFILIATION_OWNER: if (dat->him->szRealJid) dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("owner")); else dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("affiliation"), _T("owner")); break; } } break; case IDC_TXT_ROLE: if (HIWORD(wParam) == CBN_SELCHANGE) { int value = SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_GETCURSEL, 0, 0), 0); EnableWindow(GetDlgItem(hwndDlg, IDC_BTN_ROLE), dat->him->role != value); } break; case IDC_BTN_ROLE: { int value = SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_TXT_ROLE, CB_GETCURSEL, 0, 0), 0); if (dat->him->role == value) break; switch (value) { case ROLE_VISITOR: dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("role"), _T("visitor")); break; case ROLE_PARTICIPANT: dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("role"), _T("participant")); break; case ROLE_MODERATOR: dat->ppro->AdminSet(dat->item->jid, xmlnsAdmin, _T("nick"), dat->him->resourceName, _T("role"), _T("moderator")); break; } } break; } break; case WM_CLOSE: DestroyWindow(hwndDlg); break; case WM_DESTROY: { WindowFreeIcon( hwndDlg ); g_ReleaseIcon(( HICON )SendDlgItemMessage( hwndDlg, IDC_BTN_AFFILIATION, BM_SETIMAGE, IMAGE_ICON, 0 )); g_ReleaseIcon(( HICON )SendDlgItemMessage( hwndDlg, IDC_BTN_ROLE, BM_SETIMAGE, IMAGE_ICON, 0 )); TUserInfoData *dat = (TUserInfoData *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); if (!dat)break; SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); mir_free(dat); break; } } return FALSE; } static void sttNickListHook( CJabberProto* ppro, JABBER_LIST_ITEM* item, GCHOOK* gch ) { JABBER_RESOURCE_STATUS *me = NULL, *him = NULL; for ( int i=0; i < item->resourceCount; i++ ) { JABBER_RESOURCE_STATUS& p = item->resource[i]; if ( !lstrcmp( p.resourceName, item->nick )) me = &p; if ( !lstrcmp( p.resourceName, gch->ptszUID )) him = &p; } if ( him == NULL || me == NULL ) return; // 1 kick per second, prevents crashes... enum { BAN_KICK_INTERVAL = 1000 }; static DWORD dwLastBanKickTime = 0; TCHAR szBuffer[1024]; TCHAR szTitle[256]; if ((gch->dwData >= CLISTMENUIDMIN) && (gch->dwData <= CLISTMENUIDMAX)) { if (him->szRealJid && *him->szRealJid) if (HANDLE hContact = ppro->HContactFromJID(him->szRealJid)) CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(gch->dwData, MPCF_CONTACTMENU), (LPARAM)hContact); return; } switch( gch->dwData ) { case IDM_SLAP: { if ( ppro->m_bJabberOnline ) { DBVARIANT dbv = {0}; TCHAR *szMessage = DBGetContactSettingTString( NULL, ppro->m_szModuleName, "GcMsgSlap", &dbv) ? NEWTSTR_ALLOCA(TranslateTS(JABBER_GC_MSG_SLAP)) : dbv.ptszVal; TCHAR buf[256]; // do not use snprintf to avoid possible problems with % symbol if (TCHAR *p = _tcsstr(szMessage, _T("%s"))) { *p = 0; mir_sntprintf(buf, SIZEOF(buf), _T("%s%s%s"), szMessage, him->resourceName, p+2); } else lstrcpyn(buf, szMessage, SIZEOF(buf)); UnEscapeChatTags( buf ); ppro->m_ThreadInfo->send( XmlNode( _T("message")) << XATTR( _T("to"), item->jid ) << XATTR( _T("type"), _T("groupchat")) << XCHILD( _T("body"), buf )); if (szMessage == dbv.ptszVal) DBFreeVariant(&dbv); } break; } case IDM_VCARD: { HANDLE hContact; JABBER_SEARCH_RESULT jsr = {0}; mir_sntprintf(jsr.jid, SIZEOF(jsr.jid), _T("%s/%s"), item->jid, him->resourceName ); jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT ); JABBER_LIST_ITEM* item = ppro->ListAdd( LIST_VCARD_TEMP, jsr.jid ); item->bUseResource = TRUE; ppro->ListAddResource( LIST_VCARD_TEMP, jsr.jid, him->status, him->statusMessage, him->priority ); hContact = ( HANDLE )CallProtoService( ppro->m_szModuleName, PS_ADDTOLIST, PALF_TEMPORARY, ( LPARAM )&jsr ); CallService( MS_USERINFO_SHOWDIALOG, ( WPARAM )hContact, 0 ); break; } case IDM_INFO: { TUserInfoData *dat = (TUserInfoData *)mir_alloc(sizeof(TUserInfoData)); dat->me = me; dat->him = him; dat->item = item; dat->ppro = ppro; HWND hwndInfo = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_GROUPCHAT_INFO), NULL, sttUserInfoDlgProc, (LPARAM)dat); ShowWindow(hwndInfo, SW_SHOW); break; } case IDM_KICK: { if ((GetTickCount() - dwLastBanKickTime) > BAN_KICK_INTERVAL) { dwLastBanKickTime = GetTickCount(); mir_sntprintf( szBuffer, SIZEOF(szBuffer), _T("%s: "), me->resourceName ); mir_sntprintf( szTitle, SIZEOF(szTitle), _T("%s %s"), TranslateT( "Reason to kick" ), him->resourceName ); TCHAR *resourceName_copy = mir_tstrdup(him->resourceName); // copy resource name to prevent possible crash if user list rebuilds if ( ppro->EnterString(szBuffer, SIZEOF(szBuffer), szTitle, JES_MULTINE, "gcReason_" )) ppro->m_ThreadInfo->send( XmlNodeIq( _T("set"), ppro->SerialNext(), item->jid ) << XQUERY( xmlnsAdmin ) << XCHILD( _T("item")) << XATTR( _T("nick"), resourceName_copy ) << XATTR( _T("role"), _T("none")) << XCHILD( _T("reason"), szBuffer )); mir_free(resourceName_copy); } dwLastBanKickTime = GetTickCount(); break; } case IDM_SET_VISITOR: if (him->role != ROLE_VISITOR) ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("role"), _T("visitor")); break; case IDM_SET_PARTICIPANT: if (him->role != ROLE_PARTICIPANT) ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("role"), _T("participant")); break; case IDM_SET_MODERATOR: if (him->role != ROLE_MODERATOR) ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("role"), _T("moderator")); break; case IDM_SET_NONE: if (him->affiliation != AFFILIATION_NONE) { if (him->szRealJid) { TCHAR szBareJid[ JABBER_MAX_JID_LEN ]; JabberStripJid( him->szRealJid, szBareJid, SIZEOF(szBareJid)); ppro->AdminSet(item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("none")); } else ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("affiliation"), _T("none")); } break; case IDM_SET_MEMBER: if (him->affiliation != AFFILIATION_MEMBER) { if (him->szRealJid) { TCHAR szBareJid[ JABBER_MAX_JID_LEN ]; JabberStripJid( him->szRealJid, szBareJid, SIZEOF(szBareJid)); ppro->AdminSet(item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("member")); } else ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("affiliation"), _T("member")); } break; case IDM_SET_ADMIN: if (him->affiliation != AFFILIATION_ADMIN) { if (him->szRealJid) { TCHAR szBareJid[ JABBER_MAX_JID_LEN ]; JabberStripJid( him->szRealJid, szBareJid, SIZEOF(szBareJid)); ppro->AdminSet(item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("admin")); } else ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("affiliation"), _T("admin")); } break; case IDM_SET_OWNER: if (him->affiliation != AFFILIATION_OWNER) { if (him->szRealJid) { TCHAR szBareJid[ JABBER_MAX_JID_LEN ]; JabberStripJid( him->szRealJid, szBareJid, SIZEOF(szBareJid)); ppro->AdminSet(item->jid, xmlnsAdmin, _T("jid"), szBareJid, _T("affiliation"), _T("owner")); } else ppro->AdminSet(item->jid, xmlnsAdmin, _T("nick"), him->resourceName, _T("affiliation"), _T("owner")); } break; case IDM_SET_BAN: if ((GetTickCount() - dwLastBanKickTime) > BAN_KICK_INTERVAL) { if ( him->szRealJid && *him->szRealJid ) { TCHAR szVictimBareJid[ JABBER_MAX_JID_LEN ]; JabberStripJid( him->szRealJid, szVictimBareJid, SIZEOF(szVictimBareJid)); mir_sntprintf( szBuffer, SIZEOF(szBuffer), _T("%s: "), me->resourceName ); mir_sntprintf( szTitle, SIZEOF(szTitle), _T("%s %s"), TranslateT( "Reason to ban" ), him->resourceName ); if ( ppro->EnterString(szBuffer, SIZEOF(szBuffer), szTitle, JES_MULTINE, "gcReason_" )) { ppro->m_ThreadInfo->send( XmlNodeIq( _T("set"), ppro->SerialNext(), item->jid ) << XQUERY( xmlnsAdmin ) << XCHILD( _T("item")) << XATTR( _T("jid"), szVictimBareJid ) << XATTR( _T("affiliation"), _T("outcast")) << XCHILD( _T("reason"), szBuffer )); } } } dwLastBanKickTime = GetTickCount(); break; case IDM_LINK0: case IDM_LINK1: case IDM_LINK2: case IDM_LINK3: case IDM_LINK4: case IDM_LINK5: case IDM_LINK6: case IDM_LINK7: case IDM_LINK8: case IDM_LINK9: { if ((GetTickCount() - dwLastBanKickTime) > BAN_KICK_INTERVAL) { TCHAR *resourceName_copy = NEWTSTR_ALLOCA(him->resourceName); // copy resource name to prevent possible crash if user list rebuilds TCHAR *szInviteTo = 0; int idx = gch->dwData - IDM_LINK0; LISTFOREACH(i, ppro, LIST_CHATROOM) if (JABBER_LIST_ITEM *item = ppro->ListGetItemPtrFromIndex(i)) if (!idx--) { szInviteTo = item->jid; break; } if (!szInviteTo) break; mir_sntprintf( szTitle, SIZEOF(szTitle), TranslateT("Invite %s to %s"), him->resourceName, szInviteTo ); *szBuffer = 0; if (!ppro->EnterString(szBuffer, SIZEOF(szBuffer), szTitle, JES_MULTINE)) break; mir_sntprintf(szTitle, SIZEOF(szTitle), _T("%s/%s"), item->jid, resourceName_copy); XmlNode msg( _T("message")); HXML invite = msg << XATTR( _T("to"), szTitle ) << XATTRID(ppro->SerialNext()) << XCHILD(_T("x"), szBuffer) << XATTR(_T("xmlns"), _T("jabber:x:conference")) << XATTR( _T("jid"), szInviteTo ) << XCHILD(_T("invite")) << XATTR(_T("from"), item->nick); ppro->m_ThreadInfo->send( msg ); } dwLastBanKickTime = GetTickCount(); break; } case IDM_CPY_NICK: JabberCopyText((HWND)CallService(MS_CLUI_GETHWND, 0, 0), him->resourceName); break; case IDM_RJID_COPY: case IDM_CPY_RJID: JabberCopyText((HWND)CallService(MS_CLUI_GETHWND, 0, 0), him->szRealJid); break; case IDM_CPY_INROOMJID: mir_sntprintf(szBuffer, SIZEOF(szBuffer), _T("%s/%s"), item->jid, him->resourceName); JabberCopyText((HWND)CallService(MS_CLUI_GETHWND, 0, 0), szBuffer); break; case IDM_RJID_VCARD: if (him->szRealJid && *him->szRealJid) { HANDLE hContact; JABBER_SEARCH_RESULT jsr ={0}; jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT ); mir_sntprintf(jsr.jid, SIZEOF(jsr.jid), _T("%s"), him->szRealJid); if (TCHAR *tmp = _tcschr(jsr.jid, _T('/'))) *tmp = 0; JABBER_LIST_ITEM* item = ppro->ListAdd( LIST_VCARD_TEMP, jsr.jid ); item->bUseResource = TRUE; ppro->ListAddResource( LIST_VCARD_TEMP, jsr.jid, him->status, him->statusMessage, him->priority ); hContact = ( HANDLE )CallProtoService( ppro->m_szModuleName, PS_ADDTOLIST, PALF_TEMPORARY, ( LPARAM )&jsr ); CallService( MS_USERINFO_SHOWDIALOG, ( WPARAM )hContact, 0 ); break; } case IDM_RJID_ADD: if (him->szRealJid && *him->szRealJid) { JABBER_SEARCH_RESULT jsr={0}; jsr.hdr.cbSize = sizeof( JABBER_SEARCH_RESULT ); jsr.hdr.flags = PSR_TCHAR; mir_sntprintf(jsr.jid, SIZEOF(jsr.jid), _T("%s"), him->szRealJid); if (TCHAR *tmp = _tcschr(jsr.jid, _T('/'))) *tmp = 0; jsr.hdr.nick = jsr.jid; ADDCONTACTSTRUCT acs={0}; acs.handleType = HANDLE_SEARCHRESULT; acs.szProto = ppro->m_szModuleName; acs.psr = (PROTOSEARCHRESULT *)&jsr; CallService(MS_ADDCONTACT_SHOW, (WPARAM)CallService(MS_CLUI_GETHWND, 0, 0), (LPARAM)&acs); break; } } } static void sttLogListHook( CJabberProto* ppro, JABBER_LIST_ITEM* item, GCHOOK* gch ) { TCHAR szBuffer[ 1024 ]; TCHAR szCaption[ 1024 ]; szBuffer[ 0 ] = _T('\0'); switch( gch->dwData ) { case IDM_LST_PARTICIPANT: ppro->AdminGet(gch->pDest->ptszID, xmlnsAdmin, _T("role"), _T("participant"), &CJabberProto::OnIqResultMucGetVoiceList ); break; case IDM_LST_MEMBER: ppro->AdminGet(gch->pDest->ptszID, xmlnsAdmin, _T("affiliation"), _T("member"), &CJabberProto::OnIqResultMucGetMemberList ); break; case IDM_LST_MODERATOR: ppro->AdminGet(gch->pDest->ptszID, xmlnsAdmin, _T("role"), _T("moderator"), &CJabberProto::OnIqResultMucGetModeratorList ); break; case IDM_LST_BAN: ppro->AdminGet(gch->pDest->ptszID, xmlnsAdmin, _T("affiliation"), _T("outcast"), &CJabberProto::OnIqResultMucGetBanList ); break; case IDM_LST_ADMIN: ppro->AdminGet(gch->pDest->ptszID, xmlnsAdmin, _T("affiliation"), _T("admin"), &CJabberProto::OnIqResultMucGetAdminList ); break; case IDM_LST_OWNER: ppro->AdminGet(gch->pDest->ptszID, xmlnsAdmin, _T("affiliation"), _T("owner"), &CJabberProto::OnIqResultMucGetOwnerList ); break; case IDM_TOPIC: mir_sntprintf( szCaption, SIZEOF(szCaption), _T("%s %s"), TranslateT( "Set topic for" ), gch->pDest->ptszID ); TCHAR szTmpBuff[ SIZEOF(szBuffer) * 2 ]; if ( item->itemResource.statusMessage ) { int j = 0; for ( int i = 0; i < SIZEOF(szTmpBuff); i++ ) { if ( item->itemResource.statusMessage[ i ] != _T('\n') || ( i && item->itemResource.statusMessage[ i - 1 ] == _T('\r'))) szTmpBuff[ j++ ] = item->itemResource.statusMessage[ i ]; else { szTmpBuff[ j++ ] = _T('\r'); szTmpBuff[ j++ ] = _T('\n'); } if ( !item->itemResource.statusMessage[ i ] ) break; } } else szTmpBuff[ 0 ] = _T('\0'); if ( ppro->EnterString( szTmpBuff, SIZEOF(szTmpBuff), szCaption, JES_RICHEDIT, "gcTopic_" )) ppro->m_ThreadInfo->send( XmlNode( _T("message")) << XATTR( _T("to"), gch->pDest->ptszID ) << XATTR( _T("type"), _T("groupchat")) << XCHILD( _T("subject"), szTmpBuff )); break; case IDM_NICK: mir_sntprintf( szCaption, SIZEOF(szCaption), _T("%s %s"), TranslateT( "Change nickname in" ), gch->pDest->ptszID ); if ( item->nick ) mir_sntprintf( szBuffer, SIZEOF(szBuffer), _T("%s"), item->nick ); if ( ppro->EnterString(szBuffer, SIZEOF(szBuffer), szCaption, JES_COMBO, "gcNick_" )) { JABBER_LIST_ITEM* item = ppro->ListGetItemPtr( LIST_CHATROOM, gch->pDest->ptszID ); if ( item != NULL ) { TCHAR text[ 1024 ]; mir_sntprintf( text, SIZEOF( text ), _T("%s/%s"), gch->pDest->ptszID, szBuffer ); ppro->SendPresenceTo( ppro->m_iStatus == ID_STATUS_INVISIBLE ? ID_STATUS_ONLINE : ppro->m_iStatus, text, NULL ); } } break; case IDM_INVITE: { CGroupchatInviteDlg *dlg = new CGroupchatInviteDlg( ppro, gch->pDest->ptszID ); dlg->Show(); break; } case IDM_CONFIG: { int iqId = ppro->SerialNext(); ppro->IqAdd( iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetMuc ); XmlNodeIq iq( _T("get"), iqId, gch->pDest->ptszID ); iq << XQUERY( xmlnsOwner ); ppro->m_ThreadInfo->send( iq ); break; } case IDM_BOOKMARKS: { JABBER_LIST_ITEM* item = ppro->ListGetItemPtr( LIST_BOOKMARK, gch->pDest->ptszID ); if ( item == NULL ) { item = ppro->ListGetItemPtr( LIST_CHATROOM, gch->pDest->ptszID ); if (item != NULL) { item->type = _T("conference"); HANDLE hContact = ppro->HContactFromJID( item->jid ); item->name = ( TCHAR* )JCallService( MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) hContact, GCDNF_TCHAR ); ppro->AddEditBookmark( item ); } } break; } case IDM_DESTROY: mir_sntprintf( szBuffer, SIZEOF(szBuffer), _T("%s %s"), TranslateT( "Reason to destroy" ), gch->pDest->ptszID ); if ( !ppro->EnterString(szBuffer, SIZEOF(szBuffer), NULL, JES_MULTINE, "gcReason_" )) break; ppro->m_ThreadInfo->send( XmlNodeIq( _T("set"), ppro->SerialNext(), gch->pDest->ptszID ) << XQUERY( xmlnsOwner ) << XCHILD( _T("destroy")) << XCHILD( _T("reason"), szBuffer )); case IDM_LEAVE: ppro->GcQuit( item, 0, NULL ); break; case IDM_PRESENCE_ONLINE: case IDM_PRESENCE_AWAY: case IDM_PRESENCE_NA: case IDM_PRESENCE_DND: case IDM_PRESENCE_FREE4CHAT: { if ( HANDLE h = ppro->HContactFromJID( item->jid )) ppro->OnMenuHandleDirectPresence( (WPARAM)h, 0, gch->dwData ); break; } case IDM_LINK0: case IDM_LINK1: case IDM_LINK2: case IDM_LINK3: case IDM_LINK4: case IDM_LINK5: case IDM_LINK6: case IDM_LINK7: case IDM_LINK8: case IDM_LINK9: { unsigned idx = IDM_LINK0; for (TCHAR *p = _tcsstr(item->itemResource.statusMessage, _T("http://")); p && *p; p = _tcsstr(p+1, _T("http://"))) { if (idx == gch->dwData) { char *bufPtr, *url = mir_t2a(p); for (bufPtr = url; *bufPtr && !isspace(*bufPtr); ++bufPtr) ; *bufPtr++ = 0; CallService(MS_UTILS_OPENURL, 1, (LPARAM)url); mir_free(url); break; } if (++idx > IDM_LINK9) break; } break; } case IDM_CPY_RJID: JabberCopyText((HWND)CallService(MS_CLUI_GETHWND, 0, 0), item->jid); break; case IDM_CPY_TOPIC: JabberCopyText((HWND)CallService(MS_CLUI_GETHWND, 0, 0), item->itemResource.statusMessage); break; } } ///////////////////////////////////////////////////////////////////////////////////////// // Sends a private message to a chat user static void sttSendPrivateMessage( CJabberProto* ppro, JABBER_LIST_ITEM* item, const TCHAR* nick ) { TCHAR szFullJid[ JABBER_MAX_JID_LEN ]; mir_sntprintf( szFullJid, SIZEOF(szFullJid), _T("%s/%s"), item->jid, nick ); HANDLE hContact = ppro->DBCreateContact( szFullJid, NULL, TRUE, FALSE ); if ( hContact != NULL ) { for ( int i=0; i < item->resourceCount; i++ ) { if ( _tcsicmp( item->resource[i].resourceName, nick ) == 0 ) { ppro->JSetWord( hContact, "Status", item->resource[i].status ); break; } } DBWriteContactSettingByte( hContact, "CList", "Hidden", 1 ); ppro->JSetStringT( hContact, "Nick", nick ); DBWriteContactSettingDword( hContact, "Ignore", "Mask1", 0 ); JCallService( MS_MSG_SENDMESSAGE, ( WPARAM )hContact, 0 ); } } ///////////////////////////////////////////////////////////////////////////////////////// // General chat event processing hook int CJabberProto::JabberGcEventHook(WPARAM, LPARAM lParam) { GCHOOK* gch = ( GCHOOK* )lParam; if ( gch == NULL ) return 0; if ( lstrcmpiA( gch->pDest->pszModule, m_szModuleName )) return 0; JABBER_LIST_ITEM* item = ListGetItemPtr( LIST_CHATROOM, gch->pDest->ptszID ); if ( item == NULL ) return 0; switch ( gch->pDest->iType ) { case GC_USER_MESSAGE: if ( gch->pszText && lstrlen( gch->ptszText) > 0 ) { trtrim( gch->ptszText ); if ( m_bJabberOnline ) { TCHAR* buf = NEWTSTR_ALLOCA(gch->ptszText); UnEscapeChatTags( buf ); m_ThreadInfo->send( XmlNode( _T("message")) << XATTR( _T("to"), item->jid ) << XATTR( _T("type"), _T("groupchat")) << XCHILD( _T("body"), buf )); } } break; case GC_USER_PRIVMESS: sttSendPrivateMessage( this, item, gch->ptszUID ); break; case GC_USER_LOGMENU: sttLogListHook( this, item, gch ); break; case GC_USER_NICKLISTMENU: sttNickListHook( this, item, gch ); break; case GC_USER_CHANMGR: int iqId = SerialNext(); IqAdd( iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetMuc ); m_ThreadInfo->send( XmlNodeIq( _T("get"), iqId, item->jid ) << XQUERY( xmlnsOwner )); break; } return 0; } ///////////////////////////////////////////////////////////////////////////////////////////////// void CJabberProto::AddMucListItem( JABBER_MUC_JIDLIST_INFO* jidListInfo, TCHAR* str , TCHAR* rsn) { const TCHAR* field = ( jidListInfo->type == MUC_BANLIST || _tcschr(str,'@')) ? _T("jid") : _T("nick"); TCHAR* roomJid = jidListInfo->roomJid; if ( jidListInfo->type == MUC_BANLIST ) { AdminSetReason( roomJid, xmlnsAdmin, field, str, _T("affiliation"), _T("outcast"), rsn); AdminGet( roomJid, xmlnsAdmin, _T("affiliation"), _T("outcast"), &CJabberProto::OnIqResultMucGetBanList); } } void CJabberProto::AddMucListItem( JABBER_MUC_JIDLIST_INFO* jidListInfo, TCHAR* str ) { const TCHAR* field = ( jidListInfo->type == MUC_BANLIST || _tcschr(str,'@')) ? _T("jid") : _T("nick"); TCHAR* roomJid = jidListInfo->roomJid; switch (jidListInfo->type) { case MUC_VOICELIST: AdminSet( roomJid, xmlnsAdmin, field, str, _T("role"), _T("participant")); AdminGet( roomJid, xmlnsAdmin, _T("role"), _T("participant"), &CJabberProto::OnIqResultMucGetVoiceList); break; case MUC_MEMBERLIST: AdminSet( roomJid, xmlnsAdmin, field, str, _T("affiliation"), _T("member")); AdminGet( roomJid, xmlnsAdmin, _T("affiliation"), _T("member"), &CJabberProto::OnIqResultMucGetMemberList); break; case MUC_MODERATORLIST: AdminSet( roomJid, xmlnsAdmin, field, str, _T("role"), _T("moderator")); AdminGet( roomJid, xmlnsAdmin, _T("role"), _T("moderator"), &CJabberProto::OnIqResultMucGetModeratorList); break; case MUC_BANLIST: AdminSet( roomJid, xmlnsAdmin, field, str, _T("affiliation"), _T("outcast")); AdminGet( roomJid, xmlnsAdmin, _T("affiliation"), _T("outcast"), &CJabberProto::OnIqResultMucGetBanList); break; case MUC_ADMINLIST: AdminSet( roomJid, xmlnsAdmin, field, str, _T("affiliation"), _T("admin")); AdminGet( roomJid, xmlnsAdmin, _T("affiliation"), _T("admin"), &CJabberProto::OnIqResultMucGetAdminList); break; case MUC_OWNERLIST: AdminSet( roomJid, xmlnsAdmin, field, str, _T("affiliation"), _T("owner")); AdminGet( roomJid, xmlnsAdmin, _T("affiliation"), _T("owner"), &CJabberProto::OnIqResultMucGetOwnerList); break; } } void CJabberProto::DeleteMucListItem( JABBER_MUC_JIDLIST_INFO* jidListInfo, TCHAR* jid ) { TCHAR* roomJid = jidListInfo->roomJid; switch ( jidListInfo->type ) { case MUC_VOICELIST: // change role to visitor ( from participant ) AdminSet( roomJid, xmlnsAdmin, _T("jid"), jid, _T("role"), _T("visitor")); break; case MUC_BANLIST: // change affiliation to none ( from outcast ) case MUC_MEMBERLIST: // change affiliation to none ( from member ) AdminSet( roomJid, xmlnsAdmin, _T("jid"), jid, _T("affiliation"), _T("none")); break; case MUC_MODERATORLIST: // change role to participant ( from moderator ) AdminSet( roomJid, xmlnsAdmin, _T("jid"), jid, _T("role"), _T("participant")); break; case MUC_ADMINLIST: // change affiliation to member ( from admin ) AdminSet( roomJid, xmlnsAdmin, _T("jid"), jid, _T("affiliation"), _T("member")); break; case MUC_OWNERLIST: // change affiliation to admin ( from owner ) AdminSet( roomJid, xmlnsAdmin, _T("jid"), jid, _T("affiliation"), _T("admin")); break; } }