bridgin.c 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. /*\
  2. |*| This file is part of the Bridgin program
  3. |*| Copyright 2013-2014 bill-auger <https://github.com/bill-auger/bridgin/issues>
  4. |*|
  5. |*| Bridgin is free software: you can redistribute it and/or modify
  6. |*| it under the terms of the GNU Affero General Public License as published by
  7. |*| the Free Software Foundation, either version 3 of the License, or
  8. |*| (at your option) any later version.
  9. |*|
  10. |*| Bridgin is distributed in the hope that it will be useful,
  11. |*| but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. |*| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. |*| GNU Affero General Public License for more details.
  14. |*|
  15. |*| You should have received a copy of the GNU Affero General Public License
  16. |*| along with Bridgin. If not, see <http://www.gnu.org/licenses/>.
  17. \*/
  18. #include "bridgin.h"
  19. #include "bridgin.dbg.h"
  20. /* globals */
  21. static char* Commands[N_UNIQ_CMDS] = COMMANDS ;
  22. static unsigned int NRelayChannels = 0 ;
  23. static PurplePluginInfo PluginInfo =
  24. {
  25. PURPLE_PLUGIN_MAGIC , PURPLE_MAJOR_VERSION , PURPLE_MINOR_VERSION ,
  26. PLUGIN_TYPE , PLUGIN_GUI_TYPE , 0 , NULL , PURPLE_PRIORITY_DEFAULT ,
  27. PLUGIN_ID , PLUGIN_NAME , PLUGIN_VERSION , PLUGIN_SHORT_DESC , PLUGIN_LONG_DESC ,
  28. PLUGIN_AUTHOR , PLUGIN_WEBSITE , PLUGIN_ONLOAD_CB , PLUGIN_ONUNLOAD_CB ,
  29. NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL
  30. } ;
  31. /* purple helpers */
  32. PurpleCmdId registerCmd(const char* command , const char* format ,
  33. PurpleCmdRet (*callback)() , const char* help)
  34. {
  35. return purple_cmd_register(command , format , PURPLE_CMD_P_DEFAULT ,
  36. PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT , PLUGIN_ID , callback , help , NULL) ;
  37. }
  38. void registerCommands()
  39. {
  40. CommandIds[0] = registerCmd(ADD_CMD , UNARY_FMT , ADD_CB , ADDu_HELP) ;
  41. CommandIds[1] = registerCmd(ADD_CMD , BINARY_FMT , ADD_CB , ADDb_HELP) ;
  42. CommandIds[2] = registerCmd(REMOVE_CMD , UNARY_FMT , REMOVE_CB , REMOVE_HELP) ;
  43. CommandIds[3] = registerCmd(DISABLE_CMD , UNARY_FMT , DISABLE_CB , DISABLEu_HELP) ;
  44. CommandIds[4] = registerCmd(DISABLE_CMD , BINARY_FMT , DISABLE_CB , DISABLEb_HELP) ;
  45. CommandIds[5] = registerCmd(ENABLE_CMD , UNARY_FMT , ENABLE_CB , ENABLEu_HELP) ;
  46. CommandIds[6] = registerCmd(ENABLE_CMD , BINARY_FMT , ENABLE_CB , ENABLEb_HELP) ;
  47. CommandIds[7] = registerCmd(ECHO_CMD , BINARY_FMT , ECHO_CB , ECHO_HELP) ;
  48. CommandIds[8] = registerCmd(CHAT_CMD , BINARY_FMT , CHAT_CB , CHAT_HELP) ;
  49. CommandIds[9] = registerCmd(BCAST_CMD , BINARY_FMT , BCAST_CB , BCAST_HELP) ;
  50. CommandIds[10] = registerCmd(STATUS_CMD , UNARY_FMT , STATUS_CB , STATUSu_HELP) ;
  51. CommandIds[11] = registerCmd(STATUS_CMD , BINARY_FMT , STATUS_CB , STATUSb_HELP) ;
  52. CommandIds[12] = registerCmd(HELP_CMD , UNARY_FMT , HELP_CB , HELP_HELP) ;
  53. }
  54. void registerCallbacks()
  55. {
  56. void* convs = purple_conversations_get_handle() ;
  57. purple_signal_connect(convs , RECEIVING_IM_SIGNAL , ThisPlugin ,
  58. PURPLE_CALLBACK(CHAT_RECV_CB) , NULL) ;
  59. purple_signal_connect(convs , RECEIVING_CHAT_SIGNAL , ThisPlugin ,
  60. PURPLE_CALLBACK(CHAT_RECV_CB) , NULL) ;
  61. }
  62. void unregisterCommands()
  63. {
  64. unsigned int i ;
  65. for (i = 0 ; i < N_COMMANDS ; ++i) purple_cmd_unregister(CommandIds[i]) ;
  66. }
  67. void unregisterCallbacks()
  68. {
  69. purple_prefs_disconnect_by_handle(purple_prefs_get_handle()) ;
  70. purple_signal_disconnect(purple_conversations_get_handle() , RECEIVING_IM_SIGNAL ,
  71. ThisPlugin , PURPLE_CALLBACK(CHAT_RECV_CB)) ;
  72. purple_signal_disconnect(purple_conversations_get_handle() , RECEIVING_CHAT_SIGNAL ,
  73. ThisPlugin , PURPLE_CALLBACK(CHAT_RECV_CB)) ;
  74. }
  75. const char* getChannelName(PurpleConversation* aConv)
  76. { return purple_conversation_get_name(aConv) ; }
  77. const char* getProtocol(PurpleAccount* anAccount)
  78. { return purple_account_get_protocol_name(anAccount) ; }
  79. PurpleAccount* getAccount(PurpleConversation* aConv)
  80. { return purple_conversation_get_account(aConv) ; }
  81. const char* getUsername(PurpleAccount* anAccount)
  82. { return purple_account_get_username(anAccount) ; }
  83. const char* getNick(PurpleConversation* aConv)
  84. { return purple_account_get_name_for_display(getAccount(aConv)) ; }
  85. /* model helpers */
  86. void prepareBridgeKeys(const char* bridgeName)
  87. {
  88. snprintf(BridgeKeyBuffer , SM_BUFFER_SIZE , BRIDGE_PREF_FMT ,
  89. BASE_PREF_KEY , bridgeName) ;
  90. snprintf(EnabledKeyBuffer , SM_BUFFER_SIZE , ENABLED_PREF_FMT ,
  91. BridgeKeyBuffer , ENABLED_PREF_KEY) ;
  92. }
  93. void prepareChannelUid(PurpleConversation* aConv)
  94. {
  95. PurpleAccount* anAccount ;
  96. const char* protocol ; const char* username ; const char* channelName ;
  97. anAccount = getAccount(aConv) ; protocol = getProtocol(anAccount) ;
  98. username = getUsername(anAccount) ; channelName = getChannelName(aConv) ;
  99. sprintf(ChannelUidBuffer , CHANNEL_UID_FMT , protocol , username , channelName) ;
  100. }
  101. gint isMatch(gconstpointer a , gconstpointer b) { return strcmp((char*)a , (char*)b) ; }
  102. void prefKey2Name(const char* prefKey , char* nameBuffer)
  103. { snprintf(nameBuffer , SM_BUFFER_SIZE , "%s" , strrchr(prefKey , '/') + 1) ; }
  104. void getBridgeName(PurpleConversation* aConv , char* bridgeNameBuffer)
  105. {
  106. GList* prefsList ; GList* prefsIter ; char* prefKey ; GList* channelsList ;
  107. // search for this channelUid
  108. prepareChannelUid(aConv) ; bridgeNameBuffer[0] = '\0' ;
  109. prefsList = purple_prefs_get_children_names(BASE_PREF_KEY) ;
  110. prefsIter = g_list_first(prefsList) ;
  111. while (prefsIter)
  112. {
  113. #if DEBUG_LOGIC
  114. if (purple_prefs_get_type((char*)prefsIter->data) == PURPLE_PREF_STRING_LIST)
  115. DBGss("getBridgeName() found stored bridgeName='" , strrchr((char*)prefsIter->data , '/') + 1 , "'" , "") ;
  116. #endif
  117. #if DEBUG_VB
  118. else if (purple_prefs_get_type((char*)prefsIter->data) == PURPLE_PREF_BOOLEAN)
  119. DBGsds("getBridgeName() found bool prefKey='" , (char*)prefsIter->data , "' val='" , purple_prefs_get_bool((char*)prefsIter->data) , "'" , "") ;
  120. else if (purple_prefs_get_type((char*)prefsIter->data) == PURPLE_PREF_STRING)
  121. DBGsss("getBridgeName() found string prefKey='" , (char*)prefsIter->data , "' val='" , purple_prefs_get_string((char*)prefsIter->data) , "'" , "") ;
  122. #endif
  123. prefKey = (char*)prefsIter->data ;
  124. if (!isChannelsPref(prefKey)) { prefsIter = g_list_next(prefsIter) ; continue ; }
  125. // set bridgeNameBuffer to channelUid if found
  126. channelsList = purple_prefs_get_string_list(prefKey) ;
  127. if (g_list_find_custom(channelsList , ChannelUidBuffer , (GCompareFunc)isMatch))
  128. prefKey2Name(prefKey , bridgeNameBuffer) ;
  129. #if DEBUG_LOGIC
  130. if (g_list_find_custom(channelsList , ChannelUidBuffer , (GCompareFunc)isMatch))
  131. DBGsss("getBridgeName() found channel=" , ChannelUidBuffer , "' on bridgeName='" , bridgeNameBuffer , "'" , "") ;
  132. #endif
  133. #if DEBUG_VB
  134. else DBGsss("getBridgeName() not found channel='" , ChannelUidBuffer , "' on bridgeName='" , bridgeNameBuffer , "'" , "") ;
  135. #endif
  136. prefsIter = g_list_next(prefsIter) ;
  137. }
  138. g_list_foreach(prefsList , (GFunc)g_free , NULL) ;
  139. g_list_free(prefsIter) ; g_list_free(prefsList) ;
  140. }
  141. unsigned int getNBridges()
  142. {
  143. GList* prefsList ; unsigned int nBridges ;
  144. prefsList = purple_prefs_get_children_names(BASE_PREF_KEY) ;
  145. nBridges = g_list_length(prefsList) / 2 ;
  146. g_list_foreach(prefsList , (GFunc)g_free , NULL) ;
  147. g_list_free(prefsList) ;
  148. return nBridges ;
  149. }
  150. unsigned int getNChannels(const char* bridgePrefKey)
  151. {
  152. GList* channelsList ; unsigned int nChannels ;
  153. channelsList = purple_prefs_get_string_list(bridgePrefKey) ;
  154. nChannels = g_list_length(channelsList) ;
  155. return nChannels ;
  156. }
  157. void getNRelayChannels(char* bridgePrefKey , PurpleConversation* thisConv)
  158. {
  159. // NOTE: thisConv will be excluded from the count - pass in NULL to include it
  160. GList* activeChannelsIter ; PurpleConversation* aConv ;
  161. char thisBridgeName[SM_BUFFER_SIZE] ; char aBridgeName[SM_BUFFER_SIZE] ;
  162. prefKey2Name(bridgePrefKey , thisBridgeName) ;
  163. if (!isChannelsPref(bridgePrefKey)) return ;
  164. activeChannelsIter = g_list_first(purple_get_conversations()) ;
  165. while (activeChannelsIter)
  166. {
  167. aConv = (PurpleConversation*)activeChannelsIter->data ;
  168. getBridgeName(aConv , aBridgeName) ;
  169. if (aConv != thisConv && !strcmp(aBridgeName , thisBridgeName)) ++NRelayChannels ;
  170. activeChannelsIter = g_list_next(activeChannelsIter) ;
  171. }
  172. }
  173. gboolean isChannelsPref(const char* bridgePrefKey)
  174. {
  175. return (purple_prefs_exists(bridgePrefKey) &&
  176. purple_prefs_get_type(bridgePrefKey) == PURPLE_PREF_STRING_LIST) ;
  177. }
  178. gboolean doesBridgeExist(const char* bridgeName)
  179. { prepareBridgeKeys(bridgeName) ; return isChannelsPref(BridgeKeyBuffer) ; }
  180. gboolean isBridgeEnabled(const char* bridgeName)
  181. {
  182. prepareBridgeKeys(bridgeName) ;
  183. return purple_prefs_exists(EnabledKeyBuffer) &&
  184. purple_prefs_get_bool(EnabledKeyBuffer) ;
  185. }
  186. gboolean isChannelBridged(PurpleConversation* aConv)
  187. {
  188. char thisBridgeName[SM_BUFFER_SIZE] ; getBridgeName(aConv , thisBridgeName) ;
  189. return !isBlank(thisBridgeName) ;
  190. }
  191. gboolean areReservedIds(char* bridgeName , char* channelUid , const char* channelName)
  192. {
  193. return (!strcmp(bridgeName , "") || // TODO: better validations?
  194. !strcmp(channelUid , "") ||
  195. !strcmp(channelName , "NickServ") ||
  196. !strcmp(channelName , "MemoServ")) ;
  197. }
  198. void createChannel(char* bridgeName)
  199. {
  200. GList* channelsList = NULL ;
  201. #if DEBUG_VB
  202. DBGss("createChannel() bridgeName='" , bridgeName , "'" , "") ;
  203. #endif
  204. prepareBridgeKeys(bridgeName) ;
  205. // add bridge to store if necessary
  206. if (!doesBridgeExist(bridgeName))
  207. {
  208. purple_prefs_add_bool(EnabledKeyBuffer , TRUE) ;
  209. purple_prefs_add_string_list(BridgeKeyBuffer , channelsList) ;
  210. #if DEBUG_LOGIC
  211. DBGss("createChannel() added new bridgeKey='" , BridgeKeyBuffer , "'" , "") ;
  212. #endif
  213. }
  214. // add channel to store
  215. channelsList = purple_prefs_get_string_list(BridgeKeyBuffer) ;
  216. channelsList = g_list_prepend(channelsList , (gpointer)ChannelUidBuffer) ;
  217. purple_prefs_set_string_list(BridgeKeyBuffer , channelsList) ;
  218. #if DEBUG_LOGIC
  219. DBGss("createChannel() added new channelUid='" , ChannelUidBuffer , "'" , "") ;
  220. #endif
  221. }
  222. void destroyChannel(PurpleConversation* aConv)
  223. {
  224. GList* channelsList = NULL ; GList* channelsIter = NULL ;
  225. char thisBridgeName[SM_BUFFER_SIZE] ; getBridgeName(aConv , thisBridgeName) ;
  226. // remove channel from store
  227. prepareBridgeKeys(thisBridgeName) ; prepareChannelUid(aConv) ;
  228. channelsList = purple_prefs_get_string_list(BridgeKeyBuffer) ;
  229. channelsIter = g_list_first(channelsList) ;
  230. while (channelsIter)
  231. if (!strcmp(channelsIter->data , ChannelUidBuffer))
  232. {
  233. channelsList = g_list_delete_link(channelsList , channelsIter) ;
  234. channelsIter = g_list_first(channelsList) ;
  235. }
  236. else channelsIter = g_list_next(channelsIter) ;
  237. purple_prefs_set_string_list(BridgeKeyBuffer , channelsList) ;
  238. #if DEBUG_LOGIC
  239. DBGssss("destroyChannel() removed channel='" , getChannelName(aConv) , "' from bridge='" , thisBridgeName , ((getNChannels(BridgeKeyBuffer))? "" : "' also removing bridge='") , ((getNChannels(BridgeKeyBuffer))? "" : thisBridgeName) , "'" , "") ;
  240. #endif
  241. if (getNChannels(BridgeKeyBuffer)) return ;
  242. // remove empty bridge from store
  243. purple_prefs_remove(BridgeKeyBuffer) ; purple_prefs_remove(EnabledKeyBuffer) ;
  244. }
  245. void enableBridge(char* bridgeName , gboolean shouldEnable)
  246. {
  247. prepareBridgeKeys(bridgeName) ;
  248. if (purple_prefs_get_bool(EnabledKeyBuffer) == shouldEnable) return ;
  249. purple_prefs_set_bool(EnabledKeyBuffer , shouldEnable) ;
  250. }
  251. void enableBridgeEach(char* bridgePrefKey , gboolean* shouldEnable)
  252. {
  253. char bridgeName[SM_BUFFER_SIZE] ; prefKey2Name(bridgePrefKey , bridgeName) ;
  254. if (isChannelsPref(bridgePrefKey)) enableBridge(bridgeName , *shouldEnable) ;
  255. }
  256. /* event handlers */
  257. void handlePluginInit(PurplePlugin* aPlugin)
  258. { ThisPlugin = aPlugin ; purple_prefs_add_none(BASE_PREF_KEY) ; }
  259. gboolean handlePluginLoaded(PurplePlugin* aPlugin)
  260. {
  261. #if DEBUG_EVS
  262. DBG("handlePluginLoaded()") ;
  263. #endif
  264. registerCommands() ; registerCallbacks() ; chatBufferClear() ; return TRUE ;
  265. }
  266. gboolean handlePluginUnloaded(PurplePlugin* plugin)
  267. {
  268. #if DEBUG_EVS
  269. DBG("handlePluginUnloaded()") ;
  270. #endif
  271. unregisterCommands() ; unregisterCallbacks() ; return TRUE ;
  272. }
  273. gboolean handleChat(PurpleAccount* thisAccount , char** sender , char** msg ,
  274. PurpleConversation* thisConv , PurpleMessageFlags* flags , void* data)
  275. {
  276. char thisBridgeName[SM_BUFFER_SIZE] ;
  277. if (!thisConv) return TRUE ; // supress rogue msgs (autojoined server msgs maybe unbound)
  278. getBridgeName(thisConv , thisBridgeName) ;
  279. #if DEBUG_CHAT // NOTE: DBGchat() should mirror changes to logic here
  280. DBGchat(thisAccount , *sender , thisConv , *msg , *flags , thisBridgeName) ;
  281. #endif
  282. if (!isBridgeEnabled(thisBridgeName)) return FALSE ; // input channel bridge is disabled
  283. if (*flags & PURPLE_MESSAGE_SEND) return FALSE ; // never relay unprefixed local chat
  284. if (!(*flags & PURPLE_MESSAGE_RECV)) return FALSE ; // TODO: handle special message types
  285. prepareRelayChat(NICK_PREFIX , *sender , *msg) ;
  286. relayMessage(thisBridgeName , thisConv) ; chatBufferClear() ;
  287. return FALSE ;
  288. }
  289. /* admin command handlers */
  290. PurpleCmdRet handleAddCmd(PurpleConversation* thisConv , const gchar* command ,
  291. gchar** args , gchar** error , void* data)
  292. {
  293. char thisBridgeName[SM_BUFFER_SIZE] ; char* thatBridgeName ;
  294. #if DEBUG_EVS
  295. DBGcmd(command , *args) ;
  296. #endif
  297. thatBridgeName = (isBlank(*args))? DEFAULT_BRIDGE_NAME : *args ;
  298. if (isChannelBridged(thisConv))
  299. {
  300. getBridgeName(thisConv , thisBridgeName) ;
  301. if (strcmp(thisBridgeName , thatBridgeName)) addConflictResp(thisConv) ;
  302. else addExistsResp(thisConv , thisBridgeName) ;
  303. }
  304. else
  305. {
  306. prepareChannelUid(thisConv) ;
  307. if (!areReservedIds(thatBridgeName , ChannelUidBuffer , getChannelName(thisConv)))
  308. { createChannel(thatBridgeName) ; addResp(thisConv , thatBridgeName) ; }
  309. else addReservedResp(thisConv) ;
  310. }
  311. return PURPLE_CMD_RET_OK ;
  312. }
  313. PurpleCmdRet handleRemoveCmd(PurpleConversation* thisConv , const gchar* command ,
  314. gchar** args , gchar** error , void* data)
  315. {
  316. char thisBridgeName[SM_BUFFER_SIZE] ; getBridgeName(thisConv , thisBridgeName) ;
  317. #if DEBUG_EVS
  318. DBGcmd(command , *args) ;
  319. #endif
  320. if (isBlank(thisBridgeName)) removeUnbridgedResp(thisConv) ;
  321. else { destroyChannel(thisConv) ; removeResp(thisConv , thisBridgeName) ; }
  322. return PURPLE_CMD_RET_OK ;
  323. }
  324. PurpleCmdRet handleEnableCmd(PurpleConversation* thisConv , const gchar* command ,
  325. gchar** args , gchar** error , void* data)
  326. {
  327. gboolean shouldEnable ; char* bridgeName ; GList* prefsList ;
  328. #if DEBUG_EVS
  329. DBGcmd(command , *args) ;
  330. #endif
  331. shouldEnable = !strcmp(command , ENABLE_CMD) ; bridgeName = *args ;
  332. if (!getNBridges()) enableNoneResp(thisConv , "") ;
  333. else if (isBlank(bridgeName))
  334. {
  335. enableAllResp(thisConv , shouldEnable) ;
  336. prefsList = purple_prefs_get_children_names(BASE_PREF_KEY) ;
  337. g_list_foreach(prefsList , (GFunc)enableBridgeEach , &shouldEnable) ;
  338. g_list_foreach(prefsList , (GFunc)enableRespEach , thisConv) ;
  339. g_list_foreach(prefsList , (GFunc)g_free , NULL) ;
  340. g_list_free(prefsList) ;
  341. }
  342. else if (doesBridgeExist(bridgeName))
  343. {
  344. enableBridge(bridgeName , shouldEnable) ;
  345. enableResp(thisConv , bridgeName , shouldEnable) ;
  346. }
  347. else enableNoneResp(thisConv , bridgeName) ;
  348. return PURPLE_CMD_RET_OK ;
  349. }
  350. PurpleCmdRet handleEchoCmd(PurpleConversation* thisConv , const gchar* command ,
  351. gchar** args , gchar** error , void* data)
  352. {
  353. #if DEBUG_EVS
  354. DBGcmd(command , *args) ;
  355. #endif
  356. adminEcho(thisConv , *args) ; return PURPLE_CMD_RET_OK ;
  357. }
  358. PurpleCmdRet handleChatCmd(PurpleConversation* thisConv , const gchar* command ,
  359. gchar** args , gchar** error , void* data)
  360. {
  361. char thisBridgeName[SM_BUFFER_SIZE] ; getBridgeName(thisConv , thisBridgeName) ;
  362. #if DEBUG_EVS
  363. DBGcmd(command , *args) ;
  364. #endif
  365. if (isChannelBridged(thisConv)) adminChat(thisConv , *args , thisBridgeName) ;
  366. else { channelStateMsg(thisConv) ; chatBufferDump(thisConv) ; }
  367. return PURPLE_CMD_RET_OK ;
  368. }
  369. PurpleCmdRet handleBroadcastCmd(PurpleConversation* thisConv , const gchar* command ,
  370. gchar** args , gchar** error , void* data)
  371. {
  372. #if DEBUG_EVS
  373. DBGcmd(command , *args) ;
  374. #endif
  375. broadcastResp(thisConv) ; adminBroadcast(thisConv , *args) ; return PURPLE_CMD_RET_OK ;
  376. }
  377. PurpleCmdRet handleStatusCmd(PurpleConversation* thisConv , const gchar* command ,
  378. gchar** args , gchar** error , void* data)
  379. {
  380. #if DEBUG_EVS
  381. DBGcmd(command , *args) ;
  382. #endif
  383. statusResp(thisConv , *args) ; return PURPLE_CMD_RET_OK ;
  384. }
  385. PurpleCmdRet handleHelpCmd(PurpleConversation* thisConv , const gchar* command ,
  386. gchar** args , gchar** error , void* data)
  387. {
  388. #if DEBUG_EVS
  389. DBGcmd(command , *args) ;
  390. #endif
  391. helpResp(thisConv) ; return PURPLE_CMD_RET_OK ;
  392. }
  393. /* admin command responses */
  394. void addResp(PurpleConversation* thisConv , char* thisBridgeName)
  395. {
  396. chatBufferPutSS("%s '%s'\n" , CH_SET_MSG , thisBridgeName) ;
  397. bridgeStatsMsg(thisBridgeName) ; chatBufferDump(thisConv) ;
  398. }
  399. void addExistsResp(PurpleConversation* thisConv , char* thisBridgeName)
  400. {
  401. chatBufferPutSSS("%s %s '%s'" , THIS_CHANNEL_MSG , CHANNEL_EXISTS_MSG , thisBridgeName) ;
  402. chatBufferDump(thisConv) ;
  403. }
  404. void addConflictResp(PurpleConversation* thisConv)
  405. { chatBufferPut(BRIDGE_CONFLICT_MSG) ; chatBufferDump(thisConv) ; }
  406. void addReservedResp(PurpleConversation* thisConv)
  407. { chatBufferPut(RESERVED_NAME_MSG) ; chatBufferDump(thisConv) ; }
  408. void addFailResp(PurpleConversation* thisConv)
  409. { chatBufferPut(OOM_MSG) ; chatBufferDump(thisConv) ; }
  410. void removeResp(PurpleConversation* thisConv , char* thisBridgeName)
  411. {
  412. chatBufferPutSS("%s '%s'" , CHANNEL_REMOVED_MSG , thisBridgeName) ;
  413. if (doesBridgeExist(thisBridgeName))
  414. { chatBufferCat("\n") ; bridgeStatsMsg(thisBridgeName) ; }
  415. else { chatBufferCatSSSS("\n'" , thisBridgeName , "' " , BRIDGE_REMOVED_MSG) ; }
  416. chatBufferDump(thisConv) ;
  417. }
  418. void removeUnbridgedResp(PurpleConversation* thisConv)
  419. { channelStateMsg(thisConv) ; chatBufferDump(thisConv) ; }
  420. void enableNoneResp(PurpleConversation* thisConv , char* bridgeName)
  421. { bridgeStatsMsg(bridgeName) ; chatBufferDump(thisConv) ; }
  422. void enableAllResp(PurpleConversation* thisConv , gboolean shouldEnable)
  423. {
  424. chatBufferPut(((shouldEnable)? ENABLING_ALL_MSG : DISABLING_ALL_MSG)) ;
  425. chatBufferDump(thisConv) ;
  426. }
  427. void enableResp(PurpleConversation* thisConv , char* bridgeName , gboolean shouldEnable)
  428. {
  429. char* enabledMsg = ((shouldEnable)? ENABLED_MSG : DISABLED_MSG) ;
  430. chatBufferPutSSS("%s '%s' %s" , ENABLE_MSG , bridgeName , enabledMsg) ;
  431. chatBufferDump(thisConv) ;
  432. }
  433. void enableRespEach(char* bridgePrefKey , PurpleConversation* thisConv)
  434. {
  435. char bridgeName[SM_BUFFER_SIZE] ; prefKey2Name(bridgePrefKey , bridgeName) ;
  436. if (isChannelsPref(bridgePrefKey))
  437. enableResp(thisConv , bridgeName , isBridgeEnabled(bridgeName)) ;
  438. }
  439. void adminEcho(PurpleConversation* thisConv , char* msg)
  440. {
  441. prepareRelayChat(NICK_PREFIX , getNick(thisConv) , msg) ; chatBufferDump(thisConv) ;
  442. }
  443. void adminChat(PurpleConversation* thisConv , char* msg , char* thisBridgeName)
  444. {
  445. prepareRelayChat(NICK_PREFIX , getNick(thisConv) , msg) ;
  446. relayMessage(thisBridgeName , NULL) ; chatBufferClear() ;
  447. }
  448. void adminBroadcast(PurpleConversation* thisConv , char* msg)
  449. {
  450. GList* prefsList ;
  451. prepareRelayChat(BCAST_PREFIX , getNick(thisConv) , msg) ;
  452. prefsList = purple_prefs_get_children_names(BASE_PREF_KEY) ;
  453. g_list_foreach(prefsList , (GFunc)relayMessageEach , NULL) ;
  454. g_list_foreach(prefsList , (GFunc)g_free , NULL) ;
  455. g_list_free(prefsList) ; chatBufferClear() ;
  456. }
  457. void broadcastResp(PurpleConversation* thisConv)
  458. {
  459. GList* prefsList ; NRelayChannels = 0 ;
  460. prefsList = purple_prefs_get_children_names(BASE_PREF_KEY) ;
  461. g_list_foreach(prefsList , (GFunc)getNRelayChannels , NULL) ;
  462. g_list_foreach(prefsList , (GFunc)g_free , NULL) ;
  463. g_list_free(prefsList) ;
  464. if (!NRelayChannels) chatBufferPut(NO_BRIDGES_MSG) ;
  465. else chatBufferPutSDS("%s %d %s" , BROADCAST_MSGa , NRelayChannels , BROADCAST_MSGb) ;
  466. chatBufferDump(thisConv) ;
  467. }
  468. void statusResp(PurpleConversation* thisConv , char* bridgeName)
  469. {
  470. unsigned int nBridges ; GList* prefsList ;
  471. #if DEBUG_VB
  472. if (!getNBridges()) DBG("statusResp() no bridges") ; else if (isBlank(bridgeName)) DBG("statusResp() bridge unspecified - listing all") ; else DBGss("statusResp() bridgeName='" , bridgeName , "'" , "") ;
  473. #endif
  474. nBridges = getNBridges() ;
  475. chatBufferPutSDS("%s %d %s" , STATS_MSGa , nBridges , STATS_MSGb) ;
  476. if (nBridges) chatBufferCat("\n") ; else { chatBufferDump(thisConv) ; return ; }
  477. if (!isBlank(bridgeName)) { bridgeStatsMsg(bridgeName) ; chatBufferCat("\n") ; }
  478. else
  479. {
  480. prefsList = purple_prefs_get_children_names(BASE_PREF_KEY) ;
  481. g_list_foreach(prefsList , (GFunc)bridgeStatsMsgEach , NULL) ;
  482. g_list_foreach(prefsList , (GFunc)g_free , NULL) ;
  483. g_list_free(prefsList) ;
  484. }
  485. channelStateMsg(thisConv) ; chatBufferDump(thisConv) ;
  486. }
  487. void helpResp(PurpleConversation* thisConv)
  488. {
  489. unsigned int i ; GList* helpIter ;
  490. for (i = 0 ; i < N_UNIQ_CMDS ; ++i)
  491. {
  492. helpIter = g_list_first(purple_cmd_help(NULL , Commands[i])) ;
  493. while (helpIter)
  494. {
  495. chatBufferPut(helpIter->data) ; chatBufferDump(thisConv) ;
  496. helpIter = g_list_next(helpIter) ;
  497. }
  498. }
  499. }
  500. void channelStateMsg(PurpleConversation* thisConv)
  501. {
  502. // NOTE: callers of channelStateMsg() should eventually call chatBufferDump()
  503. char thisBridgeName[SM_BUFFER_SIZE] ;
  504. chatBufferCatSS(THIS_CHANNEL_MSG , " ") ;
  505. getBridgeName(thisConv , thisBridgeName) ;
  506. if (isBlank(thisBridgeName)) chatBufferCat(UNBRIDGED_MSG) ;
  507. else chatBufferCatSSSS(THIS_BRIDGE_MSG , " '" , thisBridgeName , "'") ;
  508. }
  509. void bridgeStatsMsg(const char* bridgeName)
  510. {
  511. // NOTE: callers of bridgeStatsMsg() should eventually call chatBufferDump()
  512. unsigned int nChannels ;
  513. GList* channelsList ; GList* channelsIter ; char* aChannelUid ;
  514. GList* activeChannelsIter = NULL ; gboolean isActive = FALSE ;
  515. char* activeMsg ; char* protocol ; char* username ; const char* channelName ;
  516. char* network ; char nick[SM_BUFFER_SIZE] ;
  517. #if DEBUG_LOGIC
  518. DBGss("bridgeStatsMsg() bridgeName='" , bridgeName , "' " , ((doesBridgeExist(bridgeName))? "exists" : "not exists - bailing")) ;
  519. #endif
  520. // display non-existent bridge state
  521. if (!doesBridgeExist(bridgeName))
  522. {
  523. if (!getNBridges()) chatBufferCatSS(NO_BRIDGES_MSG , "") ;
  524. else chatBufferCatSSSS(NO_SUCH_BRIDGE_MSG , " '" , bridgeName , "'") ;
  525. return ;
  526. }
  527. else chatBufferCatSSSS(STATS_MSGc , " '" , bridgeName , "' - ") ;
  528. // display bridge state
  529. prepareBridgeKeys(bridgeName) ;
  530. nChannels = getNChannels(BridgeKeyBuffer) ;
  531. if (!nChannels) chatBufferCat(STATS_DELETED_MSG) ;
  532. else
  533. {
  534. if (isBridgeEnabled(bridgeName)) chatBufferCat(STATS_ENABLED_MSG) ;
  535. else chatBufferCat(STATS_DISABLED_MSG) ;
  536. channelUidBufferPutD("%d" , nChannels) ;
  537. chatBufferCatSSSS(" - " , ChannelUidBuffer , " " , STATS_MSGd) ;
  538. }
  539. // display channels
  540. channelsList = purple_prefs_get_string_list(BridgeKeyBuffer) ;
  541. channelsIter = g_list_first(channelsList) ;
  542. while (channelsIter)
  543. {
  544. #if DEBUG_LOGIC
  545. DBGss("bridgeStatsMsg() found stored channelUid='" , (char*)channelsIter->data , "'" , "") ;
  546. #endif
  547. aChannelUid = (char*)channelsIter->data ;
  548. // determine if bridged aChannel is opened or closed
  549. activeChannelsIter = g_list_first(purple_get_conversations()) ;
  550. while (activeChannelsIter)
  551. {
  552. #if DEBUG_VB
  553. if (channelsIter == g_list_first(channelsList))
  554. DBGss("bridgeStatsMsg() got active channelName='" , getChannelName((PurpleConversation*)activeChannelsIter->data) , "'" , "") ;
  555. #endif
  556. prepareChannelUid((PurpleConversation*)activeChannelsIter->data) ;
  557. isActive |= !strcmp(ChannelUidBuffer , aChannelUid) ;
  558. activeChannelsIter = g_list_next(activeChannelsIter) ;
  559. }
  560. activeMsg = (isActive)? CH_ACTIVE_MSG : CH_INACTIVE_MSG ;
  561. #if DEBUG_VB
  562. DBGss("bridgeStatsMsg() aChannel='" , aChannelUid , "' " , activeMsg) ;
  563. #endif
  564. // parse channel data from aChannelUid
  565. channelUidBufferPutS("%s" , aChannelUid) ;
  566. if (!(protocol = strtok(ChannelUidBuffer , UID_DELIMITER)) ||
  567. !(username = strtok(NULL , UID_DELIMITER)) ||
  568. !(channelName = strtok(NULL , UID_DELIMITER)))
  569. continue ;
  570. #if DEBUG_VB
  571. DBGsssss("bridgeStatsMsg() parsed channelUid " , activeMsg , " protocol='" , protocol , "' username='" , username , "' channelName='" , channelName , "'" , "") ;
  572. #endif
  573. // display channel data
  574. chatBufferCatSSSSSS("\n " , activeMsg , " '" , channelName , "' on '" , protocol) ;
  575. if (!strcmp(protocol , IRC_PROTOCOL) &&
  576. (network = strchr(username , '@')) &&
  577. (strncpy(nick , username , network - username)))
  578. {
  579. nick[network - username] = '\0' ;
  580. chatBufferCatSSSS(network , "' as '" , nick , "'") ;
  581. }
  582. else chatBufferCatSSS("' as '" , username , "'") ;
  583. channelsIter = g_list_next(channelsIter) ;
  584. }
  585. }
  586. void bridgeStatsMsgEach(const char* bridgePrefKey , void* unusedGListEachData)
  587. {
  588. char bridgeName[SM_BUFFER_SIZE] ; prefKey2Name(bridgePrefKey , bridgeName) ;
  589. if (isChannelsPref(bridgePrefKey)) bridgeStatsMsg(bridgeName) ; chatBufferCat("\n") ;
  590. }
  591. /* text buffer helpers */
  592. gboolean isBlank(const char* aCstring) { return (!aCstring || !*aCstring) ; }
  593. void channelUidBufferPutS(const char* fmt , const char* s1)
  594. { snprintf(ChannelUidBuffer , SM_BUFFER_SIZE , fmt , s1) ; }
  595. void channelUidBufferPutD(const char* fmt , int d1)
  596. { snprintf(ChannelUidBuffer , SM_BUFFER_SIZE , fmt , d1) ; }
  597. void chatBufferClear() { ChatBuffer[0] = '\0' ; }
  598. void chatBufferPut( const char* s)
  599. { snprintf(ChatBuffer , LG_BUFFER_SIZE , "%s" , s) ; }
  600. void chatBufferPutSS( const char* fmt , const char* s1 , const char* s2)
  601. { snprintf(ChatBuffer , LG_BUFFER_SIZE , fmt , s1 , s2) ; }
  602. void chatBufferPutSSS( const char* fmt , const char* s1 , const char* s2 , const char* s3)
  603. { snprintf(ChatBuffer , LG_BUFFER_SIZE , fmt , s1 , s2 , s3) ; }
  604. void chatBufferPutSDS( const char* fmt , const char* s1 , int d1 , const char* s2)
  605. { snprintf(ChatBuffer , LG_BUFFER_SIZE , fmt , s1 , d1 , s2) ; }
  606. void chatBufferPutSSSS(const char* fmt , const char* s1 , const char* s2 ,
  607. const char* s3 , const char* s4)
  608. { snprintf(ChatBuffer , LG_BUFFER_SIZE , fmt , s1 , s2 , s3 , s4) ; }
  609. void chatBufferCat( const char* s)
  610. { strncat(ChatBuffer , s , LG_BUFFER_SIZE - strlen(ChatBuffer) - 1) ; }
  611. void chatBufferCatSS( const char* s1 , const char* s2)
  612. { chatBufferCat(s1) ; chatBufferCat(s2) ; }
  613. void chatBufferCatSSS( const char* s1 , const char* s2 , const char* s3)
  614. { chatBufferCat(s1) ; chatBufferCat(s2) ; chatBufferCat(s3) ; }
  615. void chatBufferCatSSSS( const char* s1 , const char* s2 , const char* s3 ,
  616. const char* s4)
  617. { chatBufferCat(s1) ; chatBufferCat(s2) ; chatBufferCat(s3) ; chatBufferCat(s4) ; }
  618. void chatBufferCatSSSSS( const char* s1 , const char* s2 , const char* s3 ,
  619. const char* s4 , const char* s5)
  620. { chatBufferCat(s1) ; chatBufferCat(s2) ; chatBufferCat(s3) ; chatBufferCat(s4) ;
  621. chatBufferCat(s5) ; }
  622. void chatBufferCatSSSSSS(const char* s1 , const char* s2 , const char* s3 ,
  623. const char* s4 , const char* s5 , const char* s6)
  624. { chatBufferCat(s1) ; chatBufferCat(s2) ; chatBufferCat(s3) ; chatBufferCat(s4) ;
  625. chatBufferCat(s5) ; chatBufferCat(s6) ; }
  626. void prepareRelayChat(char* prefix , const char* sender , char* msg)
  627. { chatBufferPutSSSS(CHAT_OUT_FMT , prefix , sender , NICK_POSTFIX , msg) ; }
  628. void chatBufferDump(PurpleConversation* thisConv)
  629. {
  630. #if DEBUG_CHAT
  631. DBGs("chatBufferDump() ChatBuffer=\n" , ChatBuffer) ;
  632. #endif
  633. purple_conversation_write(thisConv , BRIDGIN_NICK , ChatBuffer ,
  634. PURPLE_MESSAGE_SYSTEM , time(0)) ;
  635. chatBufferClear() ;
  636. }
  637. void relayMessage(char* thisBridgeName , PurpleConversation* thisConv)
  638. {
  639. // NOTE: thisConv will be excluded from the relay - pass in NULL to include it
  640. GList* activeChannelsIter = NULL ; PurpleConversation* aConv ;
  641. char aBridgeName[SM_BUFFER_SIZE] ; unsigned int convType ;
  642. activeChannelsIter = g_list_first(purple_get_conversations()) ;
  643. while (activeChannelsIter)
  644. {
  645. aConv = (PurpleConversation*)activeChannelsIter->data ;
  646. getBridgeName(aConv , aBridgeName) ;
  647. #if DEBUG_VB
  648. DBGss("relayMessage() got active channelName='" , getChannelName(aConv) ,
  649. ((aConv == thisConv)? " isThisChannel - skipping" :
  650. ((!strcmp(aBridgeName , thisBridgeName))? "' isThisBridge - relaying" : "' notThisBridge - skipping" )) , "") ;
  651. #endif
  652. if (aConv != thisConv && !strcmp(aBridgeName , thisBridgeName))
  653. {
  654. convType = purple_conversation_get_type(aConv) ;
  655. if (convType == PURPLE_CONV_TYPE_IM)
  656. purple_conv_im_send(purple_conversation_get_im_data(aConv) , ChatBuffer) ;
  657. else if (convType == PURPLE_CONV_TYPE_CHAT)
  658. purple_conv_chat_send(purple_conversation_get_chat_data(aConv) , ChatBuffer) ;
  659. }
  660. activeChannelsIter = g_list_next(activeChannelsIter) ;
  661. }
  662. }
  663. void relayMessageEach(const char* bridgePrefKey , PurpleConversation* thisConv)
  664. {
  665. char bridgeName[SM_BUFFER_SIZE] ; prefKey2Name(bridgePrefKey , bridgeName) ;
  666. if (isChannelsPref(bridgePrefKey)) relayMessage(bridgeName , thisConv) ;
  667. }
  668. void alert(char* msg)
  669. {
  670. purple_notify_message(ThisPlugin , PURPLE_NOTIFY_MSG_INFO , msg ,
  671. PLUGIN_VERSION , NULL , NULL , NULL) ;
  672. }
  673. /* main */
  674. PURPLE_INIT_PLUGIN(PLUGIN_NAME , handlePluginInit , PluginInfo)