Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684
  1. /*
  2. * Copyright (c) 2006-2013 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.parser_twitter;
  23. import com.dmdirc.addons.parser_twitter.api.TwitterAPI;
  24. import com.dmdirc.addons.parser_twitter.api.TwitterErrorHandler;
  25. import com.dmdirc.addons.parser_twitter.api.TwitterException;
  26. import com.dmdirc.addons.parser_twitter.api.TwitterMessage;
  27. import com.dmdirc.addons.parser_twitter.api.TwitterRawHandler;
  28. import com.dmdirc.addons.parser_twitter.api.TwitterStatus;
  29. import com.dmdirc.addons.parser_twitter.api.TwitterUser;
  30. import com.dmdirc.config.ConfigManager;
  31. import com.dmdirc.config.IdentityManager;
  32. import com.dmdirc.interfaces.ConfigChangeListener;
  33. import com.dmdirc.logger.ErrorManager;
  34. import com.dmdirc.parser.common.CallbackManager;
  35. import com.dmdirc.parser.common.ChannelJoinRequest;
  36. import com.dmdirc.parser.common.CompositionState;
  37. import com.dmdirc.parser.common.DefaultStringConverter;
  38. import com.dmdirc.parser.common.IgnoreList;
  39. import com.dmdirc.parser.common.QueuePriority;
  40. import com.dmdirc.parser.common.ThreadedParser;
  41. import com.dmdirc.parser.interfaces.ChannelClientInfo;
  42. import com.dmdirc.parser.interfaces.ChannelInfo;
  43. import com.dmdirc.parser.interfaces.ClientInfo;
  44. import com.dmdirc.parser.interfaces.LocalClientInfo;
  45. import com.dmdirc.parser.interfaces.StringConverter;
  46. import com.dmdirc.parser.interfaces.callbacks.AuthNoticeListener;
  47. import com.dmdirc.parser.interfaces.callbacks.ChannelJoinListener;
  48. import com.dmdirc.parser.interfaces.callbacks.ChannelMessageListener;
  49. import com.dmdirc.parser.interfaces.callbacks.ChannelModeChangeListener;
  50. import com.dmdirc.parser.interfaces.callbacks.ChannelNamesListener;
  51. import com.dmdirc.parser.interfaces.callbacks.ChannelNickChangeListener;
  52. import com.dmdirc.parser.interfaces.callbacks.ChannelNoticeListener;
  53. import com.dmdirc.parser.interfaces.callbacks.ChannelSelfJoinListener;
  54. import com.dmdirc.parser.interfaces.callbacks.ChannelTopicListener;
  55. import com.dmdirc.parser.interfaces.callbacks.DataInListener;
  56. import com.dmdirc.parser.interfaces.callbacks.DataOutListener;
  57. import com.dmdirc.parser.interfaces.callbacks.DebugInfoListener;
  58. import com.dmdirc.parser.interfaces.callbacks.MotdEndListener;
  59. import com.dmdirc.parser.interfaces.callbacks.MotdLineListener;
  60. import com.dmdirc.parser.interfaces.callbacks.MotdStartListener;
  61. import com.dmdirc.parser.interfaces.callbacks.NetworkDetectedListener;
  62. import com.dmdirc.parser.interfaces.callbacks.NickChangeListener;
  63. import com.dmdirc.parser.interfaces.callbacks.NumericListener;
  64. import com.dmdirc.parser.interfaces.callbacks.PrivateMessageListener;
  65. import com.dmdirc.parser.interfaces.callbacks.PrivateNoticeListener;
  66. import com.dmdirc.parser.interfaces.callbacks.ServerReadyListener;
  67. import com.dmdirc.parser.interfaces.callbacks.SocketCloseListener;
  68. import com.dmdirc.parser.interfaces.callbacks.UnknownMessageListener;
  69. import com.dmdirc.parser.interfaces.callbacks.UserModeDiscoveryListener;
  70. import com.dmdirc.ui.messages.Styliser;
  71. import java.net.URI;
  72. import java.util.ArrayList;
  73. import java.util.Arrays;
  74. import java.util.Calendar;
  75. import java.util.Collection;
  76. import java.util.Collections;
  77. import java.util.Date;
  78. import java.util.HashMap;
  79. import java.util.List;
  80. import java.util.Map;
  81. /**
  82. * Twitter Parser for DMDirc.
  83. */
  84. public class Twitter extends ThreadedParser implements TwitterErrorHandler, TwitterRawHandler,
  85. ConfigChangeListener {
  86. /** Number of loops between clearing of the status cache. */
  87. private static final long PRUNE_COUNT = 20;
  88. /** Maximum age of items to leave in the status cache when pruning. */
  89. private static final long PRUNE_TIME = 3600 * 1000;
  90. /** A map of this parser's implementations of common interfaces. */
  91. protected static final Map<Class<?>, Class<?>> IMPL_MAP = new HashMap<Class<?>, Class<?>>();
  92. static {
  93. IMPL_MAP.put(ChannelClientInfo.class, TwitterChannelClientInfo.class);
  94. IMPL_MAP.put(ChannelInfo.class, TwitterChannelInfo.class);
  95. IMPL_MAP.put(ClientInfo.class, TwitterClientInfo.class);
  96. IMPL_MAP.put(LocalClientInfo.class, TwitterClientInfo.class);
  97. }
  98. /** Are we connected? */
  99. private boolean connected = false;
  100. /** Our owner plugin. */
  101. private final TwitterPlugin myPlugin;
  102. /** Twitter API. */
  103. private TwitterAPI api = new TwitterAPI("", "", "", false, -1, false);
  104. /** Channels we are in. */
  105. private final Map<String, TwitterChannelInfo> channels
  106. = new HashMap<String, TwitterChannelInfo>();
  107. /** Clients we know. */
  108. private final Map<String, TwitterClientInfo> clients
  109. = new HashMap<String, TwitterClientInfo>();
  110. /** When did we last query the API? */
  111. private long lastQueryTime = 0;
  112. /** Username for twitter. */
  113. private final String myUsername;
  114. /** Password for twitter if not able to use oauth. */
  115. private final String myPassword;
  116. /** Callback Manager for Twitter. */
  117. private final CallbackManager myCallbackManager = new CallbackManager(this, IMPL_MAP);
  118. /** String Convertor. */
  119. private final DefaultStringConverter myStringConverter = new DefaultStringConverter();
  120. /** Ignore list (unused). */
  121. private IgnoreList myIgnoreList = new IgnoreList();
  122. /** Myself. */
  123. private TwitterClientInfo myself = null;
  124. /** List of currently active twitter parsers. */
  125. protected static final List<Twitter> PARSERS = new ArrayList<Twitter>();
  126. /** Are we waiting for authentication? */
  127. private boolean wantAuth = false;
  128. /** Server we are connecting to. */
  129. private final String myServerName;
  130. /** API Address to use. */
  131. private final String apiAddress;
  132. /** Are we using API Versioning? */
  133. private boolean apiVersioning = false;
  134. /** What API Version do we want? */
  135. private int apiVersion = -1;
  136. /** Address that created us. */
  137. private final URI myAddress;
  138. /** Main Channel Name. */
  139. private final String mainChannelName;
  140. /** Config Manager for this parser. */
  141. private ConfigManager myConfigManager = null;
  142. /** Map to store misc stuff in. */
  143. private final Map<Object, Object> myMap = new HashMap<Object, Object>();
  144. /** Debug enabled. */
  145. private boolean debugEnabled;
  146. /** Automatically leave & channels? */
  147. private boolean autoLeaveMessageChannel;
  148. /** Save last IDs. */
  149. private boolean saveLastIDs;
  150. /** Last Reply ID. */
  151. private long lastReplyId = -1;
  152. /** Last TimeLine ID. */
  153. private long lastTimelineId = -1;
  154. /** Last DM ID. */
  155. private long lastDirectMessageId = -1;
  156. /** Last IDs in searched hashtag channels. */
  157. private final Map<TwitterChannelInfo, Long> lastSearchIds
  158. = new HashMap<TwitterChannelInfo, Long>();
  159. /** Status count. */
  160. private int statusCount;
  161. /** Get sent messages. */
  162. private boolean getSentMessage;
  163. /** Number of API calls to use. */
  164. private int apicalls;
  165. /** Auto append @ to nicknames. */
  166. private boolean autoAt;
  167. /** Replace opening nickname. */
  168. private boolean replaceOpeningNickname;
  169. /** hide 500 errors. */
  170. private boolean hide500Errors;
  171. /**
  172. * Create a new Twitter Parser!
  173. *
  174. * @param address The address of the server to connect to
  175. * @param myPlugin Plugin that created this parser
  176. */
  177. protected Twitter(final URI address, final TwitterPlugin myPlugin) {
  178. final String[] bits;
  179. if (address.getUserInfo() == null) {
  180. bits = new String[]{};
  181. } else {
  182. bits = address.getUserInfo().split(":");
  183. }
  184. myUsername = bits.length == 0 ? "" : bits[0];
  185. myPassword = bits.length > 1 ? bits[1] : "";
  186. this.myPlugin = myPlugin;
  187. myServerName = address.getHost().toLowerCase();
  188. myAddress = address;
  189. resetState(true);
  190. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "api.address." + myServerName)) {
  191. apiAddress = getConfigManager().getOption(myPlugin.getDomain(), "api.address." + myServerName);
  192. } else {
  193. apiAddress = myServerName + address.getPath();
  194. }
  195. if (getConfigManager().hasOptionBool(myPlugin.getDomain(), "api.versioned." + myServerName)) {
  196. apiVersioning = getConfigManager().getOptionBool(myPlugin.getDomain(), "api.versioned." + myServerName);
  197. if (getConfigManager().hasOptionInt(myPlugin.getDomain(), "api.version." + myServerName)) {
  198. apiVersion = getConfigManager().getOptionInt(myPlugin.getDomain(), "api.version." + myServerName);
  199. }
  200. }
  201. mainChannelName = "&twitter";
  202. }
  203. /** {@inheritDoc} */
  204. @Override
  205. public void disconnect(final String message) {
  206. super.disconnect(message);
  207. connected = false;
  208. PARSERS.remove(this);
  209. api = new TwitterAPI("", "", "", false, -1, false);
  210. getCallbackManager().getCallbackType(SocketCloseListener.class).call();
  211. }
  212. /** {@inheritDoc} */
  213. @Override
  214. public void quit(final String message) {
  215. disconnect(message);
  216. }
  217. /** {@inheritDoc} */
  218. @Override
  219. public void joinChannel(final String channel) {
  220. joinChannels(new ChannelJoinRequest(channel));
  221. }
  222. /** {@inheritDoc} */
  223. @Override
  224. public void joinChannel(final String channel, final String key) {
  225. joinChannels(new ChannelJoinRequest(channel, key));
  226. }
  227. /** {@inheritDoc} */
  228. @Override
  229. public void joinChannels(final ChannelJoinRequest... channels) {
  230. for (final ChannelJoinRequest request : channels) {
  231. final String channel = request.getName().trim();
  232. if (channel.equalsIgnoreCase(mainChannelName)) {
  233. // The client is always in this channel, so ignore joins for it
  234. continue;
  235. }
  236. if (isValidChannelName(channel) && getChannel(channel) == null) {
  237. final TwitterChannelInfo newChannel = new TwitterChannelInfo(channel, this);
  238. newChannel.addChannelClient(new TwitterChannelClientInfo(newChannel, myself));
  239. if (channel.matches("^&[0-9]+$")) {
  240. try {
  241. final long id = Long.parseLong(channel.substring(1));
  242. final TwitterStatus status = api.getStatus(id);
  243. if (status == null) {
  244. newChannel.setLocalTopic("Unknown status, or you do not have access to see it.");
  245. } else {
  246. if (status.getReplyTo() > 0) {
  247. newChannel.setLocalTopic(status.getText() + " [Reply to: &" + status.getReplyTo() + "]");
  248. } else {
  249. newChannel.setLocalTopic(status.getText());
  250. }
  251. newChannel.setTopicSetter(status.getUser().getScreenName());
  252. newChannel.setTopicTime(status.getTime());
  253. final TwitterClientInfo client = (TwitterClientInfo) getClient(status.getUser().getScreenName());
  254. if (client.isFake()) {
  255. client.setFake(false);
  256. clients.put(client.getNickname().toLowerCase(), client);
  257. }
  258. newChannel.addChannelClient(new TwitterChannelClientInfo(newChannel, client));
  259. }
  260. synchronized (this.channels) {
  261. this.channels.put(channel, newChannel);
  262. }
  263. } catch (final NumberFormatException nfe) {
  264. }
  265. } else if (channel.charAt(0) == '#') {
  266. newChannel.setLocalTopic("Search results for " + channel);
  267. synchronized (this.channels) {
  268. this.channels.put(channel, newChannel);
  269. }
  270. }
  271. doJoinChannel(newChannel);
  272. } else {
  273. sendNumericOutput(474, new String[]{":" + myServerName, "474", myself.getNickname(), channel, "Cannot join channel - name is not valid, or you are already there."});
  274. }
  275. }
  276. }
  277. /**
  278. * Remove a channel from the known channels list.
  279. *
  280. * @param channel The channel to part
  281. */
  282. protected void partChannel(final ChannelInfo channel) {
  283. if (channel == null) { return; }
  284. if (channel.getName().equalsIgnoreCase(mainChannelName)) {
  285. doJoinChannel(channel);
  286. } else {
  287. synchronized (channels) {
  288. channels.remove(channel.getName());
  289. }
  290. }
  291. }
  292. /** {@inheritDoc} */
  293. @Override
  294. public ChannelInfo getChannel(final String channel) {
  295. synchronized (channels) {
  296. return channels.containsKey(channel.toLowerCase()) ? channels.get(channel.toLowerCase()) : null;
  297. }
  298. }
  299. /** {@inheritDoc} */
  300. @Override
  301. public Collection<? extends ChannelInfo> getChannels() {
  302. return new ArrayList<TwitterChannelInfo>(channels.values());
  303. }
  304. /** {@inheritDoc} */
  305. @Override
  306. public void setBindIP(final String ip) {
  307. // Ignore
  308. }
  309. /** {@inheritDoc} */
  310. @Override
  311. public int getMaxLength(final String type, final String target) {
  312. return 140;
  313. }
  314. /** {@inheritDoc} */
  315. @Override
  316. public LocalClientInfo getLocalClient() {
  317. return myself;
  318. }
  319. /** {@inheritDoc} */
  320. @Override
  321. public ClientInfo getClient(final String details) {
  322. final String client = TwitterClientInfo.parseHost(details);
  323. return clients.containsKey(client.toLowerCase()) ? clients.get(client.toLowerCase()) : new TwitterClientInfo(details, this).setFake(true);
  324. }
  325. /**
  326. * Tokenise a line.
  327. * splits by " " up to the first " :" everything after this is a single token
  328. *
  329. * @param line Line to tokenise
  330. * @return Array of tokens
  331. */
  332. public static String[] tokeniseLine(final String line) {
  333. if (line == null) {
  334. return new String[]{""};
  335. }
  336. final int lastarg = line.indexOf(" :");
  337. String[] tokens;
  338. if (lastarg > -1) {
  339. final String[] temp = line.substring(0, lastarg).split(" ");
  340. tokens = new String[temp.length + 1];
  341. System.arraycopy(temp, 0, tokens, 0, temp.length);
  342. tokens[temp.length] = line.substring(lastarg + 2);
  343. } else {
  344. tokens = line.split(" ");
  345. }
  346. return tokens;
  347. }
  348. /** {@inheritDoc} */
  349. @Override
  350. public void sendRawMessage(final String message) {
  351. sendRawMessage(message, QueuePriority.NORMAL);
  352. }
  353. /** {@inheritDoc} */
  354. @Override
  355. public void sendInvite(final String channel, final String user) {
  356. // TODO: Handle this properly here instead of faking IRC messages
  357. sendRawMessage("INVITE " + user + " " + channel);
  358. }
  359. /** {@inheritDoc} */
  360. @Override
  361. public void sendRawMessage(final String message, final QueuePriority priority) {
  362. // TODO: Parse some lines in order to fake IRC.
  363. final String[] bits = tokeniseLine(message);
  364. if (bits[0].equalsIgnoreCase("JOIN") && bits.length > 1) {
  365. joinChannel(bits[1]);
  366. } else if (bits[0].equalsIgnoreCase("WHOIS") && bits.length > 1) {
  367. if (bits[1].equalsIgnoreCase(myServerName)) {
  368. sendNumericOutput(311, new String[]{":" + myServerName, "311", myself.getNickname(), bits[1], "user", myServerName, "*", "Psuedo-User for DMDirc " + myServerName + " plugin"});
  369. sendNumericOutput(312, new String[]{":" + myServerName, "312", myself.getNickname(), bits[1], myServerName, "DMDirc " + myServerName + " plugin"});
  370. } else {
  371. final boolean forced = bits.length > 2 && bits[1].equalsIgnoreCase(bits[2]);
  372. final TwitterUser user = forced ? api.getUser(bits[1], true) : api.getCachedUser(bits[1]);
  373. if (user == null) {
  374. final String reason = "No such user found" + (forced ? ", see" : "in cache, try /WHOIS " + bits[1] + " " + bits[1] + " to poll twitter (uses 1 API call) or try") + " http://" + myAddress.getHost() + "/" + bits[1];
  375. getCallbackManager().getCallbackType(NumericListener.class).call(401, new String[]{":" + myServerName, "401", myself.getNickname(), bits[1], reason});
  376. } else {
  377. // Time since last update
  378. final long secondsIdle = (user.getStatus() == null ? user.getRegisteredTime() : System.currentTimeMillis() - user.getStatus().getTime()) / 1000;
  379. final long signonTime = user.getRegisteredTime() / 1000;
  380. getCallbackManager().getCallbackType(NumericListener.class).call(311, new String[]{":" + myServerName, "311", myself.getNickname(), bits[1], "user", myServerName, "*", user.getRealName() + " (http://" + myAddress.getHost() + "/" + user.getScreenName() + ")"});
  381. final TwitterClientInfo client = (TwitterClientInfo) getClient(bits[1]);
  382. if (client != null) {
  383. final StringBuilder channelList = new StringBuilder();
  384. for (final ChannelClientInfo cci : client.getChannelClients()) {
  385. if (channelList.length() > 0) {
  386. channelList.append(' ');
  387. }
  388. channelList.append(cci.getImportantModePrefix());
  389. channelList.append(cci.getChannel().getName());
  390. }
  391. if (channelList.length() > 0) {
  392. getCallbackManager().getCallbackType(NumericListener.class).call(319, new String[]{":" + myServerName, "319", myself.getNickname(), bits[1], channelList.toString()});
  393. }
  394. }
  395. // AWAY Message Abuse!
  396. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "URL: " + user.getURL()});
  397. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "Bio: " + user.getDescription()});
  398. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "Location: " + user.getLocation()});
  399. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "Status: " + user.getStatus().getText()});
  400. if (bits[1].equalsIgnoreCase(myself.getNickname())) {
  401. final long[] apiCalls = api.getRemainingApiCalls();
  402. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "API Allowance: " + apiCalls[1]});
  403. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "API Allowance Remaining: " + apiCalls[0]});
  404. getCallbackManager().getCallbackType(NumericListener.class).call(301, new String[]{":" + myServerName, "301", myself.getNickname(), bits[1], "API Calls Used: " + apiCalls[3]});
  405. }
  406. getCallbackManager().getCallbackType(NumericListener.class).call(312, new String[]{":" + myServerName, "312", myself.getNickname(), bits[1], myServerName, "DMDirc " + myServerName + " plugin"});
  407. getCallbackManager().getCallbackType(NumericListener.class).call(317, new String[]{":" + myServerName, "317", myself.getNickname(), bits[1], Long.toString(secondsIdle), Long.toString(signonTime), "seconds idle, signon time"});
  408. }
  409. }
  410. getCallbackManager().getCallbackType(NumericListener.class).call(318, new String[]{":" + myServerName, "318", myself.getNickname(), bits[1], "End of /WHOIS list."});
  411. } else if (bits[0].equalsIgnoreCase("NAMES") && bits.length > 2) {
  412. if (bits[2].equalsIgnoreCase(mainChannelName)) {
  413. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(bits[2]);
  414. if (channel != null) {
  415. getCallbackManager().getCallbackType(ChannelNamesListener.class).call(channel);
  416. }
  417. }
  418. } else if (bits[0].equalsIgnoreCase("INVITE") && bits.length > 2) {
  419. if (bits[2].equalsIgnoreCase(mainChannelName)) {
  420. final TwitterUser user = api.addFriend(bits[1]);
  421. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(bits[2]);
  422. if (channel != null && user != null) {
  423. final TwitterClientInfo ci = new TwitterClientInfo(user.getScreenName(), this);
  424. clients.put(ci.getNickname().toLowerCase(), ci);
  425. final TwitterChannelClientInfo cci = new TwitterChannelClientInfo(channel, ci);
  426. channel.addChannelClient(cci);
  427. getCallbackManager().getCallbackType(ChannelJoinListener.class).call(channel, cci);
  428. }
  429. } else {
  430. getCallbackManager().getCallbackType(NumericListener.class).call(474, new String[]{":" + myServerName, "482", myself.getNickname(), bits[1], "You can't do that here."});
  431. }
  432. } else if (bits[0].equalsIgnoreCase("KICK") && bits.length > 2) {
  433. if (bits[1].equalsIgnoreCase(mainChannelName)) {
  434. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(bits[1]);
  435. if (channel != null) {
  436. final TwitterChannelClientInfo cci = (TwitterChannelClientInfo) channel.getChannelClient(bits[2]);
  437. if (cci != null) {
  438. cci.kick("Bye");
  439. }
  440. }
  441. } else {
  442. getCallbackManager().getCallbackType(NumericListener.class).call(474, new String[]{":" + myServerName, "482", myself.getNickname(), bits[1], "You can't do that here."});
  443. }
  444. } else {
  445. getCallbackManager().getCallbackType(NumericListener.class).call(474, new String[]{":" + myServerName, "421", myself.getNickname(), bits[0], "Unknown Command - " + message});
  446. }
  447. }
  448. /** {@inheritDoc} */
  449. @Override
  450. public boolean isValidChannelName(final String name) {
  451. return name.matches("^&[0-9]+$") || name.equalsIgnoreCase(mainChannelName) || name.charAt(0) == '#';
  452. }
  453. /** {@inheritDoc} */
  454. @Override
  455. public String getServerName() {
  456. return myServerName + "/" + myself.getNickname();
  457. }
  458. /** {@inheritDoc} */
  459. @Override
  460. public String getNetworkName() {
  461. return myServerName + "/" + myself.getNickname();
  462. }
  463. /** {@inheritDoc} */
  464. @Override
  465. public String getServerSoftware() {
  466. return myServerName;
  467. }
  468. /** {@inheritDoc} */
  469. @Override
  470. public String getServerSoftwareType() {
  471. return "twitter";
  472. }
  473. /** {@inheritDoc} */
  474. @Override
  475. public int getMaxTopicLength() {
  476. return 140;
  477. }
  478. /** {@inheritDoc} */
  479. @Override
  480. public String getBooleanChannelModes() {
  481. return "";
  482. }
  483. /** {@inheritDoc} */
  484. @Override
  485. public String getListChannelModes() {
  486. return "b";
  487. }
  488. /** {@inheritDoc} */
  489. @Override
  490. public int getMaxListModes(final char mode) {
  491. return 0;
  492. }
  493. /** {@inheritDoc} */
  494. @Override
  495. public boolean isUserSettable(final char mode) {
  496. return mode == 'b';
  497. }
  498. /** {@inheritDoc} */
  499. @Override
  500. public String getParameterChannelModes() {
  501. return "";
  502. }
  503. /** {@inheritDoc} */
  504. @Override
  505. public String getDoubleParameterChannelModes() {
  506. return "";
  507. }
  508. /** {@inheritDoc} */
  509. @Override
  510. public String getUserModes() {
  511. return "";
  512. }
  513. /** {@inheritDoc} */
  514. @Override
  515. public String getChannelUserModes() {
  516. return "ov";
  517. }
  518. /** {@inheritDoc} */
  519. @Override
  520. public CallbackManager getCallbackManager() {
  521. return myCallbackManager;
  522. }
  523. /** {@inheritDoc} */
  524. @Override
  525. public long getServerLatency() {
  526. return 0;
  527. }
  528. /** {@inheritDoc} */
  529. @Override
  530. public String getBindIP() {
  531. return "";
  532. }
  533. /** {@inheritDoc} */
  534. @Override
  535. public URI getProxy() {
  536. return null;
  537. }
  538. /** {@inheritDoc} */
  539. @Override
  540. public void setProxy(final URI proxy) {
  541. // TODO: Not supported (yet)
  542. }
  543. /** {@inheritDoc} */
  544. @Override
  545. public Map<Object, Object> getMap() {
  546. return myMap;
  547. }
  548. /** {@inheritDoc} */
  549. @Override
  550. public URI getURI() {
  551. return myAddress;
  552. }
  553. /** {@inheritDoc} */
  554. @Override
  555. public String getChannelPrefixes() {
  556. return "#&";
  557. }
  558. /** {@inheritDoc} */
  559. @Override
  560. public boolean compareURI(final URI uri) {
  561. return myAddress.equals(uri);
  562. }
  563. /** {@inheritDoc} */
  564. @Override
  565. public void sendCTCP(final String target, final String type, final String message) {
  566. if (wantAuth) {
  567. sendPrivateNotice("DMDirc has not been authorised to use this account yet.");
  568. } else if (target.matches("^&[0-9]+$")) {
  569. try {
  570. final long id = Long.parseLong(target.substring(1));
  571. if (type.equalsIgnoreCase("retweet") || type.equalsIgnoreCase("rt")) {
  572. final TwitterStatus status = api.getStatus(id);
  573. if (status == null) {
  574. sendPrivateNotice("Invalid Tweet ID.");
  575. } else {
  576. sendPrivateNotice("Retweeting: <" + status.getUser().getScreenName() + "> " + status.getText());
  577. if (api.retweetStatus(status)) {
  578. sendPrivateNotice("Retweet was successful.");
  579. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(mainChannelName);
  580. checkTopic(channel, myself.getUser().getStatus());
  581. } else {
  582. sendPrivateNotice("Retweeting Failed.");
  583. }
  584. }
  585. } else if (type.equalsIgnoreCase("delete") || type.equalsIgnoreCase("del")) {
  586. final TwitterStatus status = api.getStatus(id);
  587. if (status == null) {
  588. sendPrivateNotice("Invalid Tweet ID.");
  589. } else {
  590. sendPrivateNotice("Deleting: <" + status.getUser().getScreenName() + "> " + status.getText());
  591. if (api.deleteStatus(status)) {
  592. sendPrivateNotice("Deleting was successful, deleted tweets will still be accessible for some time.");
  593. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(mainChannelName);
  594. checkTopic(channel, myself.getUser().getStatus());
  595. } else {
  596. sendPrivateNotice("Deleting Failed.");
  597. }
  598. }
  599. }
  600. } catch (final NumberFormatException nfe) {
  601. sendPrivateNotice("Invalid Tweet ID.");
  602. }
  603. } else if (target.equalsIgnoreCase(mainChannelName) || target.charAt(0) == '#') {
  604. if (type.equalsIgnoreCase("update") || type.equalsIgnoreCase("refresh")) {
  605. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(target);
  606. sendChannelNotice(channel, "Refreshing...");
  607. if (channel != null && !getUpdates(channel)) {
  608. sendChannelNotice(channel, "No new items found.");
  609. }
  610. }
  611. } else {
  612. sendPrivateNotice("This parser does not support CTCPs.");
  613. }
  614. }
  615. /** {@inheritDoc} */
  616. @Override
  617. public void sendCTCPReply(final String target, final String type, final String message) {
  618. sendPrivateNotice("This parser does not support CTCP replies.");
  619. }
  620. /** {@inheritDoc} */
  621. @Override
  622. public void sendMessage(final String target, final String message) {
  623. if (target.equalsIgnoreCase(mainChannelName)) {
  624. if (wantAuth) {
  625. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(target);
  626. final String[] bits = message.split(" ");
  627. if (bits[0].equalsIgnoreCase("usepw")) {
  628. sendChannelMessage(channel, "Switching to once-off password authentication, please enter your password.");
  629. api.setUseOAuth(false);
  630. return;
  631. }
  632. try {
  633. if (api.useOAuth()) {
  634. api.setAccessPin(bits[0]);
  635. if (api.isAllowed(true)) {
  636. IdentityManager.getIdentityManager().getGlobalConfigIdentity().setOption(myPlugin.getDomain(), "token-" + myServerName + "-" + myUsername, api.getToken());
  637. IdentityManager.getIdentityManager().getGlobalConfigIdentity().setOption(myPlugin.getDomain(), "tokenSecret-" + myServerName + "-" + myUsername, api.getTokenSecret());
  638. sendChannelMessage(channel, "Thank you for authorising DMDirc.");
  639. updateTwitterChannel();
  640. wantAuth = false;
  641. } else {
  642. sendChannelMessage(channel, "Authorising DMDirc failed, please try again: " + api.getOAuthURL());
  643. }
  644. } else {
  645. api.setPassword(message);
  646. if (api.isAllowed(true)) {
  647. sendChannelMessage(channel, "Password accepted. Please note you will need to do this every time unless your password is given in the URL.");
  648. updateTwitterChannel();
  649. wantAuth = false;
  650. } else {
  651. sendChannelMessage(channel, "Password seems incorrect, please try again.");
  652. }
  653. }
  654. } catch (final TwitterException te) {
  655. sendChannelMessage(channel, "There was a problem authorising DMDirc (" + te.getCause().getMessage() + ").");
  656. sendChannelMessage(channel, "Please try again: " + api.getOAuthURL());
  657. }
  658. } else {
  659. if (setStatus(message)) {
  660. sendPrivateNotice("Setting status ok.");
  661. } else {
  662. sendPrivateNotice("Setting status failed.");
  663. }
  664. }
  665. } else if (wantAuth) {
  666. sendPrivateNotice("DMDirc has not been authorised to use this account yet.");
  667. } else if (target.matches("^&[0-9]+$")) {
  668. try {
  669. if (setStatus(message, Long.parseLong(target.substring(1)))) {
  670. sendPrivateNotice("Setting status ok.");
  671. if (autoLeaveMessageChannel) {
  672. partChannel(getChannel(target));
  673. }
  674. } else {
  675. sendPrivateNotice("Setting status failed.");
  676. }
  677. } catch (final NumberFormatException nfe) {
  678. }
  679. } else if (target.matches("^#.+$")) {
  680. sendPrivateNotice("Messages to '" + target + "' are not currently supported.");
  681. } else {
  682. if (api.newDirectMessage(target, message)) {
  683. sendPrivateNotice("Sending Direct Message to '" + target + "' was successful.");
  684. } else {
  685. sendPrivateNotice("Sending Direct Message to '" + target + "' failed.");
  686. }
  687. }
  688. }
  689. /** {@inheritDoc} */
  690. @Override
  691. public void sendNotice(final String target, final String message) {
  692. sendPrivateNotice("This parser does not support notices.");
  693. }
  694. /** {@inheritDoc} */
  695. @Override
  696. public void sendAction(final String target, final String message) {
  697. sendPrivateNotice("This parser does not support CTCPs.");
  698. }
  699. /** {@inheritDoc} */
  700. @Override
  701. public String getLastLine() {
  702. return "";
  703. }
  704. /** {@inheritDoc} */
  705. @Override
  706. public String[] parseHostmask(final String hostmask) {
  707. return TwitterClientInfo.parseHostFull(hostmask, myPlugin, this);
  708. }
  709. /** {@inheritDoc} */
  710. @Override
  711. public int getLocalPort() {
  712. return api.getPort();
  713. }
  714. /** {@inheritDoc} */
  715. @Override
  716. public long getPingTime() {
  717. return System.currentTimeMillis() - lastQueryTime;
  718. }
  719. /** {@inheritDoc} */
  720. @Override
  721. public void setPingTimerInterval(final long newValue) {
  722. /* Do Nothing. */
  723. }
  724. /** {@inheritDoc} */
  725. @Override
  726. public long getPingTimerInterval() {
  727. return -1;
  728. }
  729. /** {@inheritDoc} */
  730. @Override
  731. public void setPingTimerFraction(final int newValue) {
  732. /* Do Nothing. */
  733. }
  734. /** {@inheritDoc} */
  735. @Override
  736. public int getPingTimerFraction() {
  737. return -1;
  738. }
  739. /**
  740. * Send a notice to the client.
  741. *
  742. * @param message Message to send.
  743. */
  744. protected void sendPrivateNotice(final String message) {
  745. getCallbackManager().getCallbackType(PrivateNoticeListener.class).call(message, myServerName);
  746. }
  747. /**
  748. * Send a PM to the client.
  749. *
  750. * @param message Message to send.
  751. * @param hostname Who is the message from?
  752. * @param target Who is the message to?
  753. */
  754. private void sendPrivateMessage(final String message, final String hostname, final String target) {
  755. if (hostname.equalsIgnoreCase(myUsername)) {
  756. getCallbackManager().getCallbackType(UnknownMessageListener.class).call(message, target, hostname);
  757. } else {
  758. getCallbackManager().getCallbackType(PrivateMessageListener.class).call(message, hostname);
  759. }
  760. }
  761. /**
  762. * Send a message to the given channel.
  763. *
  764. * @param channel Channel to send message to
  765. * @param message Message to send.
  766. */
  767. private void sendChannelMessage(final ChannelInfo channel, final String message) {
  768. sendChannelMessage(channel, message, myServerName);
  769. }
  770. /**
  771. * Send a message to the given channel.
  772. *
  773. * @param channel Channel to send message to
  774. * @param message Message to send.
  775. * @param hostname Hostname that the message is from.
  776. */
  777. private void sendChannelMessage(final ChannelInfo channel, final String message, final String hostname) {
  778. sendChannelMessage(channel, new Date(), message, null, hostname);
  779. }
  780. /**
  781. * Send a message to the given channel.
  782. *
  783. * @param channel Channel to send message to
  784. * @param date The timestamp to be used for the message
  785. * @param message Message to send.
  786. * @param cci Channel Client to send from
  787. * @param hostname Hostname that the message is from.
  788. */
  789. private void sendChannelMessage(final ChannelInfo channel, final Date date, final String message, final ChannelClientInfo cci, final String hostname) {
  790. getCallbackManager().getCallbackType(ChannelMessageListener.class).call(date, channel, cci, message, hostname);
  791. }
  792. /**
  793. * Send a notice to the given channel.
  794. *
  795. * @param channel Channel to send notice to
  796. * @param notice Notice to send.
  797. */
  798. private void sendChannelNotice(final ChannelInfo channel, final String notice) {
  799. sendChannelNotice(channel, notice, myServerName);
  800. }
  801. /**
  802. * Send a notice to the given channel.
  803. *
  804. * @param channel Channel to send notice to
  805. * @param notice Notice to send.
  806. * @param hostname Hostname that the notice is from.
  807. */
  808. private void sendChannelNotice(final ChannelInfo channel, final String notice, final String hostname) {
  809. sendChannelNotice(channel, new Date(), notice, null, hostname);
  810. }
  811. /**
  812. * Send a notice to the given channel.
  813. *
  814. * @param channel Channel to send notice to
  815. * @param date The timestamp to be used for the notice
  816. * @param notice Notice to send.
  817. * @param cci Channel Client to send from
  818. * @param hostname Hostname that the notice is from.
  819. */
  820. private void sendChannelNotice(final ChannelInfo channel, final Date date, final String notice, final ChannelClientInfo cci, final String hostname) {
  821. getCallbackManager().getCallbackType(ChannelNoticeListener.class).call(date, channel, cci, notice, hostname);
  822. }
  823. /**
  824. * Show the user an ascii failwhale!
  825. */
  826. public void showFailWhale() {
  827. final String prefix = Character.toString(Styliser.CODE_FIXED)
  828. + Character.toString(Styliser.CODE_HEXCOLOUR);
  829. sendPrivateNotice(prefix + "EB5405,71C5C5 ");
  830. sendPrivateNotice(prefix + "EB5405,71C5C5 W W W ");
  831. sendPrivateNotice(prefix + "EB5405,71C5C5 W W W W ");
  832. sendPrivateNotice(prefix + "FFFFFF,71C5C5 '." + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5 W ");
  833. sendPrivateNotice(prefix + "FFFFFF,71C5C5 .-\"\"-._ \\ \\.--| ");
  834. sendPrivateNotice(prefix + "FFFFFF,71C5C5 / \"-..__) .-' ");
  835. sendPrivateNotice(prefix + "FFFFFF,71C5C5 | _ / ");
  836. sendPrivateNotice(prefix + "FFFFFF,71C5C5 \\'-.__, .__.,' ");
  837. sendPrivateNotice(prefix + "FFFFFF,71C5C5 `'----'._\\--' ");
  838. sendPrivateNotice(prefix + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V" + Styliser.CODE_HEXCOLOUR + "FFFFFF,71C5C5V" + Styliser.CODE_HEXCOLOUR + "EB5405,71C5C5V");
  839. }
  840. /**
  841. * Check if the given user is known on the channel, and add them if they
  842. * are not.
  843. *
  844. * @param user User to check
  845. * @return true if user was already on the channel, false if they were added.
  846. */
  847. private boolean checkUserOnChannel(final TwitterUser user) {
  848. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(mainChannelName);
  849. if (channel == null) {
  850. doDebug(Debug.stateError, "Tried to check user (" + user.getScreenName() + "), but channel is null.");
  851. return false;
  852. }
  853. if (channel.getChannelClient(user.getScreenName()) == null) {
  854. // User not found, perhaps a rename?
  855. for (final ChannelClientInfo cci : channel.getChannelClients()) {
  856. final TwitterClientInfo ci = (TwitterClientInfo) cci.getClient();
  857. if (ci.getUserID() == user.getID()) {
  858. final String oldName = ci.getNickname();
  859. ci.setUser(user);
  860. renameClient(ci, oldName);
  861. }
  862. }
  863. final TwitterClientInfo ci = new TwitterClientInfo(user.getScreenName(), this);
  864. clients.put(ci.getNickname().toLowerCase(), ci);
  865. final TwitterChannelClientInfo cci = new TwitterChannelClientInfo(channel, ci);
  866. channel.addChannelClient(cci);
  867. getCallbackManager().getCallbackType(ChannelJoinListener.class).call(channel, cci);
  868. return false;
  869. } else {
  870. return true;
  871. }
  872. }
  873. /**
  874. * Send a Debug Message using the parser debug api.
  875. *
  876. * @param code Debug Code for the message.
  877. * @param message Content of the message.
  878. */
  879. private void doDebug(final Debug code, final String message) {
  880. if (debugEnabled) {
  881. getCallbackManager().getCallbackType(DebugInfoListener.class).call(code.ordinal(), message);
  882. }
  883. }
  884. /**
  885. * Run the twitter parser.
  886. */
  887. @Override
  888. public void run() {
  889. resetState();
  890. if (myUsername.isEmpty()) {
  891. sendPrivateNotice("Unable to connect to " + myServerName + " without a username. Disconnecting.");
  892. getCallbackManager().getCallbackType(SocketCloseListener.class).call();
  893. return;
  894. }
  895. // Get the consumerKey and consumerSecret for this server if known
  896. // else default to our twitter key and secret
  897. final String consumerKey;
  898. final String consumerSecret;
  899. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "consumerKey-" + myServerName)) {
  900. consumerKey = getConfigManager().getOption(myPlugin.getDomain(), "consumerKey-" + myServerName);
  901. } else {
  902. consumerKey = "qftK3mAbLfbWWHf8shiyjw";
  903. }
  904. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "consumerSecret-" + myServerName)) {
  905. consumerSecret = getConfigManager().getOption(myPlugin.getDomain(), "consumerSecret-" + myServerName);
  906. } else {
  907. consumerSecret = "flPr2TJGp4795DeTu4VkUlNLX8g25SpXWXZ7SKW0Bg";
  908. }
  909. final String token;
  910. final String tokenSecret;
  911. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "token-" + myServerName + "-" + myUsername)) {
  912. token = getConfigManager().getOption(myPlugin.getDomain(), "token-" + myServerName + "-" + myUsername);
  913. } else {
  914. token = "";
  915. }
  916. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "tokenSecret-" + myServerName + "-" + myUsername)) {
  917. tokenSecret = getConfigManager().getOption(myPlugin.getDomain(), "tokenSecret-" + myServerName + "-" + myUsername);
  918. } else {
  919. tokenSecret = "";
  920. }
  921. api = new TwitterAPI(myUsername, myPassword, apiAddress, "", consumerKey, consumerSecret, token, tokenSecret, apiVersioning, apiVersion, getConfigManager().getOptionBool(myPlugin.getDomain(), "autoAt"));
  922. api.setSource("DMDirc");
  923. PARSERS.add(this);
  924. api.addErrorHandler(this);
  925. api.addRawHandler(this);
  926. api.setDebug(debugEnabled);
  927. getConfigManager().addChangeListener(myPlugin.getDomain(), this);
  928. connected = api.checkConnection();
  929. if (!connected) {
  930. sendPrivateNotice("Unable to connect to " + myServerName + ". Disconnecting.");
  931. getCallbackManager().getCallbackType(SocketCloseListener.class).call();
  932. return;
  933. }
  934. final TwitterChannelInfo channel = new TwitterChannelInfo(mainChannelName, this);
  935. synchronized (channels) {
  936. channels.put(mainChannelName, channel);
  937. }
  938. channel.addChannelClient(new TwitterChannelClientInfo(channel, myself));
  939. // Fake 001
  940. getCallbackManager().getCallbackType(ServerReadyListener.class).call();
  941. // Fake 005
  942. getCallbackManager().getCallbackType(NetworkDetectedListener.class).call(getNetworkName(), getServerSoftware(), getServerSoftwareType());
  943. // Fake MOTD
  944. getCallbackManager().getCallbackType(AuthNoticeListener.class).call("Welcome to " + myServerName + ".");
  945. getCallbackManager().getCallbackType(MotdStartListener.class).call("- " + myServerName + " Message of the Day -");
  946. getCallbackManager().getCallbackType(MotdLineListener.class).call("- This is an experimental parser, to allow DMDirc to use " + myServerName);
  947. getCallbackManager().getCallbackType(MotdLineListener.class).call("- ");
  948. getCallbackManager().getCallbackType(MotdLineListener.class).call("- Your timeline appears in " + mainChannelName + " (topic is your last status)");
  949. getCallbackManager().getCallbackType(MotdLineListener.class).call("- All messages sent to this channel (or topics set) will cause the status to be set.");
  950. getCallbackManager().getCallbackType(MotdLineListener.class).call("- ");
  951. getCallbackManager().getCallbackType(MotdLineListener.class).call("- Messages can be replied to using /msg &<messageid> <reply>");
  952. getCallbackManager().getCallbackType(MotdLineListener.class).call("- Messages can be retweeted by using /ctcp &<messageid> RT or /ctcp &<messageid> RETWEET");
  953. getCallbackManager().getCallbackType(MotdLineListener.class).call("- ");
  954. getCallbackManager().getCallbackType(MotdEndListener.class).call(false, "End of /MOTD command");
  955. // Fake some more on-connect crap
  956. getCallbackManager().getCallbackType(UserModeDiscoveryListener.class).call(myself, "");
  957. channel.setLocalTopic("No status known.");
  958. doJoinChannel(channel);
  959. sendChannelMessage(channel, "Checking to see if we have been authorised to use the account \"" + api.getLoginUsername() + "\"...");
  960. if (api.isAllowed(false)) {
  961. sendChannelMessage(channel, "DMDirc has been authorised to use the account \"" + api.getLoginUsername() + "\"");
  962. updateTwitterChannel();
  963. } else {
  964. wantAuth = true;
  965. if (api.useOAuth()) {
  966. sendChannelMessage(channel, "Sorry, DMDirc has not been authorised to use the account \"" + api.getLoginUsername() + "\"");
  967. sendChannelMessage(channel, "");
  968. sendChannelMessage(channel, "Before you can use DMDirc with " + myServerName + " you need to authorise it.");
  969. sendChannelMessage(channel, "");
  970. sendChannelMessage(channel, "To do this, please visit: " + api.getOAuthURL());
  971. sendChannelMessage(channel, "and then type the PIN here.");
  972. } else {
  973. sendChannelMessage(channel, "Sorry, You did not provide DMDirc with a password for the account \"" + api.getLoginUsername() + "\" and the server \"" + myServerName + "\" does not support OAuth or is not accepting our key.");
  974. sendChannelMessage(channel, "");
  975. sendChannelMessage(channel, "Before you can use DMDirc with " + myServerName + " you need to provide a password.");
  976. sendChannelMessage(channel, "");
  977. sendChannelMessage(channel, "To do this, please type the password here, or set it correctly in the URL (twitter://" + myUsername + ":your_password@" + myServerName + myAddress.getPath() + ").");
  978. }
  979. }
  980. if (saveLastIDs) {
  981. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "lastReplyId-" + myServerName + "-" + myUsername)) {
  982. lastReplyId = TwitterAPI.parseLong(getConfigManager().getOption(myPlugin.getDomain(), "lastReplyId-" + myServerName + "-" + myUsername), -1);
  983. }
  984. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "lastTimelineId-" + myServerName + "-" + myUsername)) {
  985. lastTimelineId = TwitterAPI.parseLong(getConfigManager().getOption(myPlugin.getDomain(), "lastTimelineId-" + myServerName + "-" + myUsername), -1);
  986. }
  987. if (getConfigManager().hasOptionString(myPlugin.getDomain(), "lastDirectMessageId-" + myServerName + "-" + myUsername)) {
  988. lastDirectMessageId = TwitterAPI.parseLong(getConfigManager().getOption(myPlugin.getDomain(), "lastDirectMessageId-" + myServerName + "-" + myUsername), -1);
  989. }
  990. }
  991. boolean first = true; // Used to let used know if there was no new items.
  992. int count = 0;
  993. while (connected) {
  994. final int startCalls = wantAuth ? 0 : api.getUsedCalls();
  995. // Get Updates
  996. if (!wantAuth) {
  997. final boolean foundUpdates = getUpdates(channel);
  998. if (first && !foundUpdates) {
  999. sendChannelMessage(channel, "No new items found.");
  1000. }
  1001. first = false;
  1002. // Store last IDs
  1003. IdentityManager.getIdentityManager().getGlobalConfigIdentity().setOption(myPlugin.getDomain(), "lastReplyId-" + myServerName + "-" + myUsername, Long.toString(lastReplyId));
  1004. IdentityManager.getIdentityManager().getGlobalConfigIdentity().setOption(myPlugin.getDomain(), "lastTimelineId-" + myServerName + "-" + myUsername, Long.toString(lastTimelineId));
  1005. IdentityManager.getIdentityManager().getGlobalConfigIdentity().setOption(myPlugin.getDomain(), "lastDirectMessageId-" + myServerName + "-" + myUsername, Long.toString(lastDirectMessageId));
  1006. // Get Updates for search channels
  1007. for (final TwitterChannelInfo searchChannel : channels.values()) {
  1008. if (searchChannel.getName().startsWith("#")) {
  1009. getUpdates(searchChannel);
  1010. }
  1011. }
  1012. }
  1013. // Calculate number of calls remaining.
  1014. final int endCalls = wantAuth ? 0 : api.getUsedCalls();
  1015. final long[] apiCalls = wantAuth ? new long[]{0L, 0L, System.currentTimeMillis(), (long) api.getUsedCalls()} : api.getRemainingApiCalls();
  1016. doDebug(Debug.apiCalls, "Twitter calls Remaining: " + apiCalls[0]);
  1017. // laconica doesn't rate limit, so time to reset is always 0, in this case
  1018. // we will assume the time of the next hour.
  1019. final Calendar cal = Calendar.getInstance();
  1020. cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY) + 1, 0, 0);
  1021. final long timeLeft = (apiCalls[2] > 0 ? apiCalls[2] : cal.getTimeInMillis()) - System.currentTimeMillis();
  1022. final long sleepTime;
  1023. if (wantAuth) {
  1024. // When waiting for auth, sleep for less time so that when the
  1025. // auth happens, we can quickly start twittering!
  1026. sleepTime = 5 * 1000;
  1027. } else if (!api.isAllowed()) {
  1028. // If we aren't allowed, but aren't waiting for auth, then
  1029. // sleep for 1 minute.
  1030. sleepTime = 60 * 1000;
  1031. } else if (apiCalls[1] == 0L) {
  1032. // Twitter has said we have no API Calls in total, so sleep for
  1033. // 10 minutes and try again.
  1034. // (This will also happen if twitter didn't respond for some reason)
  1035. sleepTime = 10 * 60 * 1000;
  1036. // Also alert the user.
  1037. twitterFail("Unable to communicate with twitter or no API calls allowed, retrying in 10 minutes.");
  1038. } else if (api.getUsedCalls() > apicalls) {
  1039. // Sleep for the rest of the hour, we have done too much!
  1040. sleepTime = timeLeft;
  1041. } else {
  1042. // Else work out how many calls we have left.
  1043. // Whichever is less between the number of calls we want to make
  1044. // and the number of calls twitter is going to allow us to make.
  1045. final long callsLeft = Math.min(apicalls - api.getUsedCalls(), apiCalls[0]);
  1046. // How many calls do we make each time?
  1047. // If this is less than 0 (If there was a time reset between
  1048. // calculating the start and end calls used) then assume 3.
  1049. final long callsPerTime = (endCalls - startCalls) > 0 ? endCalls - startCalls : 3;
  1050. doDebug(Debug.apiCalls, "\tCalls Remaining: " + callsLeft);
  1051. doDebug(Debug.apiCalls, "\tCalls per time: " + callsPerTime);
  1052. // And divide this by the number of calls we make each time to
  1053. // see how many times we have to sleep this hour.
  1054. final long sleepsRequired = callsLeft / callsPerTime;
  1055. doDebug(Debug.sleepTime, "\tSleeps Required: " + sleepsRequired);
  1056. doDebug(Debug.sleepTime, "\tTime Left: " + timeLeft);
  1057. // Then finally discover how long we need to sleep for.
  1058. sleepTime = (sleepsRequired > 0) ? timeLeft / sleepsRequired : timeLeft;
  1059. }
  1060. doDebug(Debug.sleepTime, "Sleeping for: " + sleepTime);
  1061. // Sleep for sleep time,
  1062. // If we have a negative sleep time, use 5 minutes.
  1063. try {
  1064. Thread.sleep(sleepTime > 0 ? sleepTime : 5 * 60 * 1000);
  1065. } catch (final InterruptedException ex) {
  1066. }
  1067. if (++count > PRUNE_COUNT) {
  1068. api.pruneStatusCache(System.currentTimeMillis() - PRUNE_TIME);
  1069. }
  1070. }
  1071. }
  1072. /**
  1073. * Get updates from twitter.
  1074. *
  1075. * @param channel Channel that this is being used for.
  1076. * @return True if any items are found, else false.
  1077. */
  1078. private boolean getUpdates(final TwitterChannelInfo channel) {
  1079. boolean foundItems = false;
  1080. if (!wantAuth && api.isAllowed()) {
  1081. lastQueryTime = System.currentTimeMillis();
  1082. if (channel.getName().startsWith("#")) {
  1083. long lastId = lastSearchIds.containsKey(channel) ? lastSearchIds.get(channel) : -1;
  1084. final List<TwitterStatus> statuses = api.getSearchResults(channel.getName(), lastId);
  1085. foundItems = !statuses.isEmpty();
  1086. for (final TwitterStatus status : statuses) {
  1087. final ChannelClientInfo cci = channel.getChannelClient(status.getUserName(), true);
  1088. sendChannelMessage(channel, new Date(status.getTime()), status.getText(), cci, status.getUserName());
  1089. lastId = Math.max(lastId, status.getID());
  1090. }
  1091. lastSearchIds.put(channel, lastId);
  1092. } else {
  1093. final int statusesPerAttempt = Math.min(200, statusCount);
  1094. final List<TwitterStatus> statuses = new ArrayList<TwitterStatus>();
  1095. for (final TwitterStatus status : api.getReplies(lastReplyId, statusesPerAttempt)) {
  1096. statuses.add(status);
  1097. if (status.getRetweetId() > lastReplyId) {
  1098. lastReplyId = status.getRetweetId();
  1099. }
  1100. }
  1101. for (final TwitterStatus status : api.getFriendsTimeline(lastTimelineId, statusesPerAttempt)) {
  1102. if (!statuses.contains(status)) {
  1103. statuses.add(status);
  1104. }
  1105. // Add new friends that may have been added elsewhere.
  1106. if (status.isRetweet()) {
  1107. checkUserOnChannel(status.getRetweetUser());
  1108. } else {
  1109. checkUserOnChannel(status.getUser());
  1110. }
  1111. if (status.getRetweetId() > lastTimelineId) {
  1112. lastTimelineId = status.getRetweetId();
  1113. }
  1114. }
  1115. Collections.sort(statuses);
  1116. for (final TwitterStatus status : statuses) {
  1117. foundItems = true;
  1118. final ChannelClientInfo cci = channel.getChannelClient(status.getUser().getScreenName());
  1119. String message = String.format("%s %c15 &%d", status.getText(), Styliser.CODE_COLOUR, status.getID());
  1120. if (status.getReplyTo() > 0) {
  1121. message += String.format(" %cin reply to &%d %1$c", Styliser.CODE_ITALIC, status.getReplyTo());
  1122. }
  1123. if (status.isRetweet()) {
  1124. message += String.format(" %c%c15[Retweet by %s]%1$c", Styliser.CODE_BOLD, Styliser.CODE_COLOUR, status.getRetweetUser().getScreenName());
  1125. }
  1126. final String hostname = status.getUser().getScreenName();
  1127. sendChannelMessage(channel, new Date(status.getTime()), message, cci, hostname);
  1128. }
  1129. final List<TwitterMessage> directMessages = new ArrayList<TwitterMessage>();
  1130. for (final TwitterMessage directMessage : api.getDirectMessages(lastDirectMessageId)) {
  1131. directMessages.add(directMessage);
  1132. if (directMessage.getID() > lastDirectMessageId) {
  1133. lastDirectMessageId = directMessage.getID();
  1134. }
  1135. }
  1136. if (getSentMessage) {
  1137. for (final TwitterMessage directMessage : api.getSentDirectMessages(lastDirectMessageId)) {
  1138. directMessages.add(directMessage);
  1139. if (directMessage.getID() > lastDirectMessageId) {
  1140. lastDirectMessageId = directMessage.getID();
  1141. }
  1142. }
  1143. }
  1144. Collections.sort(directMessages);
  1145. for (final TwitterMessage dm : directMessages) {
  1146. sendPrivateMessage(dm.getText(), dm.getSenderScreenName(), dm.getTargetScreenName());
  1147. }
  1148. if (myself != null && myself.getUser() != null) {
  1149. checkTopic(channel, myself.getUser().getStatus());
  1150. }
  1151. }
  1152. }
  1153. return foundItems;
  1154. }
  1155. /**
  1156. * Reset the state of the parser.
  1157. */
  1158. private void resetState() {
  1159. resetState(false);
  1160. }
  1161. /**
  1162. * Reset the state of the parser.
  1163. *
  1164. * @param simpleMyself Don't check the config when setting myself if true.
  1165. */
  1166. private void resetState(final boolean simpleMyself) {
  1167. connected = false;
  1168. synchronized (channels) {
  1169. channels.clear();
  1170. }
  1171. clients.clear();
  1172. if (!simpleMyself && autoAt) {
  1173. myself = new TwitterClientInfo("@" + myUsername, this);
  1174. } else {
  1175. myself = new TwitterClientInfo(myUsername, this);
  1176. }
  1177. setCachedSettings();
  1178. }
  1179. /**
  1180. * Get the Twitter API Object.
  1181. *
  1182. * @return The Twitter API Object
  1183. */
  1184. public TwitterAPI getApi() {
  1185. return api;
  1186. }
  1187. /** {@inheritDoc} */
  1188. @Override
  1189. public StringConverter getStringConverter() {
  1190. return myStringConverter;
  1191. }
  1192. /** {@inheritDoc} */
  1193. @Override
  1194. public void setIgnoreList(final IgnoreList ignoreList) {
  1195. myIgnoreList = ignoreList;
  1196. }
  1197. /** {@inheritDoc} */
  1198. @Override
  1199. public IgnoreList getIgnoreList() {
  1200. return myIgnoreList;
  1201. }
  1202. /**
  1203. * Set the twitter status.
  1204. *
  1205. * @param message Status to use.
  1206. * @return True if status was updated, else false.
  1207. */
  1208. public boolean setStatus(final String message) {
  1209. return setStatus(message, -1);
  1210. }
  1211. /**
  1212. * Set the twitter status.
  1213. *
  1214. * @param message Status to use.
  1215. * @param replyToId The ID this status is in reply to, or -1
  1216. * @return True if status was updated, else false.
  1217. */
  1218. private boolean setStatus(final String message, final long replyToId) {
  1219. final StringBuffer newStatus = new StringBuffer(message);
  1220. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(mainChannelName);
  1221. if (channel != null && replaceOpeningNickname) {
  1222. final String[] bits = message.split(" ");
  1223. if (bits[0].charAt(bits[0].length() - 1) == ':') {
  1224. final String name = bits[0].substring(0, bits[0].length() - 1);
  1225. final ChannelClientInfo cci = channel.getChannelClient(name);
  1226. if (cci != null) {
  1227. if (cci.getClient().getNickname().charAt(0) == '@') {
  1228. bits[0] = cci.getClient().getNickname();
  1229. } else {
  1230. bits[0] = "@" + cci.getClient().getNickname();
  1231. }
  1232. newStatus.setLength(0);
  1233. for (final String bit : bits) {
  1234. if (newStatus.length() > 0) {
  1235. newStatus.append(' ');
  1236. }
  1237. newStatus.append(bit);
  1238. }
  1239. }
  1240. }
  1241. }
  1242. if (api.setStatus(newStatus.toString(), replyToId)) {
  1243. checkTopic(channel, myself.getUser().getStatus());
  1244. return true;
  1245. }
  1246. return false;
  1247. }
  1248. /**
  1249. * Rename the given client from the given name.
  1250. *
  1251. * @param client Client to rename
  1252. * @param old Old nickname
  1253. */
  1254. public void renameClient(final TwitterClientInfo client, final String old) {
  1255. clients.remove(old.toLowerCase());
  1256. clients.put(client.getNickname().toLowerCase(), client);
  1257. getCallbackManager().getCallbackType(NickChangeListener.class).call(client, old);
  1258. for (final ChannelClientInfo cci : client.getChannelClients()) {
  1259. getCallbackManager().getCallbackType(ChannelNickChangeListener.class).call(client, cci.getChannel(), old);
  1260. }
  1261. }
  1262. @Override
  1263. public int getMaxLength() {
  1264. return 140;
  1265. }
  1266. /**
  1267. * Make the core think a channel was joined.
  1268. *
  1269. * @param channel Channel to join.
  1270. */
  1271. private void doJoinChannel(final ChannelInfo channel) {
  1272. // Fake Join Channel
  1273. getCallbackManager().getCallbackType(ChannelSelfJoinListener.class).call(channel);
  1274. getCallbackManager().getCallbackType(ChannelTopicListener.class).call(channel, true);
  1275. getCallbackManager().getCallbackType(ChannelNamesListener.class).call(channel);
  1276. getCallbackManager().getCallbackType(ChannelModeChangeListener.class).call(channel, null, "", "");
  1277. }
  1278. /**
  1279. * Update the users and topic of the main channel.
  1280. */
  1281. private void updateTwitterChannel() {
  1282. final TwitterChannelInfo channel = (TwitterChannelInfo) getChannel(mainChannelName);
  1283. checkTopic(channel, myself.getUser().getStatus());
  1284. channel.clearChannelClients();
  1285. channel.addChannelClient(new TwitterChannelClientInfo(channel, myself));
  1286. for (final TwitterUser user : api.getFriends()) {
  1287. final TwitterClientInfo ci = new TwitterClientInfo(user.getScreenName(), this);
  1288. clients.put(ci.getNickname().toLowerCase(), ci);
  1289. final TwitterChannelClientInfo cci = new TwitterChannelClientInfo(channel, ci);
  1290. channel.addChannelClient(cci);
  1291. }
  1292. api.getFollowers();
  1293. getCallbackManager().getCallbackType(ChannelNamesListener.class).call(channel);
  1294. }
  1295. /**
  1296. * Check if the topic in the given channel has been changed, and if it has
  1297. * fire the callback.
  1298. *
  1299. * @param channel channel to check.
  1300. * @param status Status to use to update the topic with.
  1301. */
  1302. private void checkTopic(final TwitterChannelInfo channel, final TwitterStatus status) {
  1303. if (channel == null || status == null) {
  1304. return;
  1305. }
  1306. final String oldStatus = channel.getTopic();
  1307. final String newStatus = (status.isRetweet()) ? status.getRetweetText() : status.getText();
  1308. if (!newStatus.equalsIgnoreCase(oldStatus)) {
  1309. channel.setTopicSetter(status.getUser().getScreenName());
  1310. channel.setTopicTime(status.getTime() / 1000);
  1311. channel.setLocalTopic(newStatus);
  1312. getCallbackManager().getCallbackType(ChannelTopicListener.class).call(channel, false);
  1313. }
  1314. }
  1315. /** {@inheritDoc} */
  1316. @Override
  1317. public void handleTwitterError(final TwitterAPI api, final Throwable t, final String source, final String twitterInput, final String twitterOutput, final String message) {
  1318. final boolean showError = !debugEnabled && hide500Errors;
  1319. if (showError && message.matches("^\\(50[0-9]\\).*")) {
  1320. return;
  1321. }
  1322. try {
  1323. if (message.isEmpty()) {
  1324. twitterFail("Recieved an error: " + source);
  1325. } else if (debugEnabled) {
  1326. twitterFail("Recieved an error from twitter: " + message + (debugEnabled ? " [" + source + "]" : ""));
  1327. }
  1328. if (t != null) {
  1329. doDebug(Debug.twitterError, t.getClass().getSimpleName() + ": " + t + " -> " + t.getMessage());
  1330. }
  1331. // And give more information:
  1332. doDebug(Debug.twitterErrorMore, "Source: " + source);
  1333. doDebug(Debug.twitterErrorMore, "Input: " + twitterInput);
  1334. doDebug(Debug.twitterErrorMore, "Output: ");
  1335. for (final String out : twitterOutput.split("\n")) {
  1336. doDebug(Debug.twitterErrorMore, " " + out);
  1337. }
  1338. doDebug(Debug.twitterErrorMore, "");
  1339. doDebug(Debug.twitterErrorMore, "Exception:");
  1340. final String[] trace = ErrorManager.getTrace(t);
  1341. for (final String out : trace) {
  1342. doDebug(Debug.twitterErrorMore, " " + out);
  1343. }
  1344. doDebug(Debug.twitterErrorMore, "==================================");
  1345. } catch (final Exception t2) {
  1346. doDebug(Debug.twitterError, "wtf? (See Console for stack trace) " + t2);
  1347. t2.printStackTrace();
  1348. }
  1349. }
  1350. /**
  1351. * This method will send data to the NumericListener and the DataInListener
  1352. *
  1353. * @param numeric Numeric
  1354. * @param token Tokenised Representation.
  1355. */
  1356. private void sendNumericOutput(final int numeric, final String[] token) {
  1357. getCallbackManager().getCallbackType(NumericListener.class).call(numeric, token);
  1358. final StringBuffer output = new StringBuffer();
  1359. for (final String bit : token) {
  1360. output.append(' ');
  1361. output.append(bit);
  1362. }
  1363. getCallbackManager().getCallbackType(DataInListener.class).call(output.toString().trim());
  1364. }
  1365. /** {@inheritDoc} */
  1366. @Override
  1367. public void handleRawTwitterInput(final TwitterAPI api, final String data) {
  1368. doDebug(Debug.dataIn, "-------------------------");
  1369. getCallbackManager().getCallbackType(DataInListener.class).call("-------------------------");
  1370. for (final String line : data.split("\n")) {
  1371. doDebug(Debug.dataIn, line);
  1372. getCallbackManager().getCallbackType(DataInListener.class).call(line);
  1373. }
  1374. }
  1375. /** {@inheritDoc} */
  1376. @Override
  1377. public void handleRawTwitterOutput(final TwitterAPI api, final String data) {
  1378. doDebug(Debug.dataOut, "-------------------------");
  1379. getCallbackManager().getCallbackType(DataOutListener.class).call("-------------------------", true);
  1380. for (final String line : data.split("\n")) {
  1381. doDebug(Debug.dataOut, line);
  1382. getCallbackManager().getCallbackType(DataOutListener.class).call(line, true);
  1383. }
  1384. }
  1385. /**
  1386. * Let the user know twitter failed in some way.
  1387. *
  1388. * @param message Message to send to the user
  1389. */
  1390. private void twitterFail(final String message) {
  1391. if (Math.random() <= 0.10) {
  1392. showFailWhale();
  1393. }
  1394. doDebug(Debug.twitterError, message);
  1395. sendPrivateNotice(message);
  1396. }
  1397. /**
  1398. * Returns the TwitterPlugin that owns us.
  1399. *
  1400. * @return The TwitterPlugin that owns us.
  1401. */
  1402. public TwitterPlugin getMyPlugin() {
  1403. return myPlugin;
  1404. }
  1405. /** {@inheritDoc} */
  1406. @Override
  1407. public void configChanged(final String domain, final String key) {
  1408. setCachedSettings();
  1409. if (domain.equalsIgnoreCase(myPlugin.getDomain())) {
  1410. if (key.equalsIgnoreCase("debugEnabled")) {
  1411. api.setDebug(debugEnabled);
  1412. } else if (key.equalsIgnoreCase("autoAt")) {
  1413. sendPrivateNotice("'autoAt' setting was changed, reconnect needed.");
  1414. disconnect("'autoAt' setting was changed, reconnect needed.");
  1415. }
  1416. }
  1417. }
  1418. /**
  1419. * Get the config manager for this parser instance.
  1420. *
  1421. * @return the ConfigManager for this parser.
  1422. */
  1423. protected ConfigManager getConfigManager() {
  1424. if (myConfigManager == null) {
  1425. myConfigManager = new ConfigManager(myAddress.getScheme(), getServerSoftwareType(), getNetworkName(), getServerName());
  1426. }
  1427. return myConfigManager;
  1428. }
  1429. /**
  1430. * Get settings from config for cache variables.
  1431. */
  1432. private void setCachedSettings() {
  1433. saveLastIDs = getConfigManager().getOptionBool(myPlugin.getDomain(), "saveLastIDs");
  1434. statusCount = getConfigManager().getOptionInt(myPlugin.getDomain(), "statuscount");
  1435. getSentMessage = getConfigManager().getOptionBool(myPlugin.getDomain(), "getSentMessages");
  1436. apicalls = getConfigManager().getOptionInt(myPlugin.getDomain(), "apicalls");
  1437. autoAt = getConfigManager().getOptionBool(myPlugin.getDomain(), "autoAt");
  1438. replaceOpeningNickname = getConfigManager().getOptionBool(myPlugin.getDomain(), "replaceOpeningNickname");
  1439. hide500Errors = getConfigManager().getOptionBool(myPlugin.getDomain(), "hide500Errors");
  1440. debugEnabled = getConfigManager().getOptionBool(myPlugin.getDomain(), "debugEnabled");
  1441. autoLeaveMessageChannel = getConfigManager().getOptionBool(myPlugin.getDomain(), "autoLeaveMessageChannel");
  1442. }
  1443. /** {@inheritDoc} */
  1444. @Override
  1445. public Collection<? extends ChannelJoinRequest> extractChannels(final URI uri) {
  1446. return new ArrayList<ChannelJoinRequest>();
  1447. }
  1448. /** {@inheritDoc} */
  1449. @Override
  1450. public List<String> getServerInformationLines() {
  1451. return Arrays.asList(new String[]{"Twitter IRC parser: " + getServerName()});
  1452. }
  1453. /** {@inheritDoc} */
  1454. @Override
  1455. public void setCompositionState(final String host, final CompositionState state) {
  1456. // Do nothing
  1457. }
  1458. /** {@inheritDoc} */
  1459. @Override
  1460. public void requestGroupList(final String searchTerms) {
  1461. // Do nothing
  1462. }
  1463. }