1 module diggler.context; 2 3 import std.array; 4 5 import diggler.bot; 6 import diggler.tracker; 7 8 import irc.client; 9 import irc.tracker : IrcTracker, TrackedChannel; 10 11 /** 12 * _Context for operations used by command methods. 13 * 14 * $(DPREF command, CommandSet) subtypes an instance 15 * of this type, allowing command methods to access 16 * the properties and methods of this type without 17 * any preceding qualification. 18 * 19 * Some operations are synchronous, non-blocking operations; 20 * they may time a significant duration of time to complete, 21 * but they do not block the thread from handling other events, 22 * such as other command invocations. 23 */ 24 struct Context 25 { 26 private: 27 Bot _bot; 28 Bot.ClientEventHandler _client; 29 BotTracker tracker; 30 string target; 31 IrcUser _user; 32 bool isPm; 33 34 public: 35 this(Bot _bot, Bot.ClientEventHandler client, BotTracker tracker, string target, ref IrcUser _user, bool isPm) 36 { 37 this._bot = _bot; 38 this._client = client; 39 this.tracker = tracker; 40 this.target = target; 41 this._user = _user; 42 this.isPm = isPm; 43 } 44 45 /** 46 * Create a new context from an existing one, 47 * but with a different originating channel/user. 48 * 49 * Params: 50 * ctx = existing context to copy 51 * target = channel name or user nick name 52 */ 53 this(Context ctx, string target) 54 { 55 import irc.protocol : channelPrefixes; 56 this = ctx; 57 58 this.target = target; 59 60 switch(target.front) 61 { 62 foreach(channelPrefix; channelPrefixes) 63 case channelPrefix: 64 this.isPm = false; 65 break; 66 default: 67 this.isPm = true; 68 } 69 } 70 71 /// The current bot. 72 Bot bot() @property pure nothrow 73 { 74 return _bot; 75 } 76 77 /// The current network. 78 IrcClient network() @property pure nothrow 79 { 80 return _client; 81 } 82 83 /// The _user that invoked the command. 84 ref const(IrcUser) user() @property pure nothrow 85 { 86 return _user; 87 } 88 89 /** 90 * The _channel the command was invoked in. 91 * 92 * Throws an exception if the command originated from 93 * a private message. 94 */ 95 TrackedChannel channel() @property 96 { 97 if(isPm) 98 throw new Exception("not in a channel"); 99 100 return *tracker.findChannel(target); 101 } 102 103 /** 104 * Boolean whether or not the command was invoked 105 * from a private message. 106 */ 107 bool isPrivateMessage() @property pure nothrow 108 { 109 return isPm; 110 } 111 112 /** 113 * Reply to the channel in which the command was invoked. 114 * If there is more than one argument, the first argument 115 * is formatted with subsequent ones. 116 * 117 * If the command originated in a private message, 118 * the _reply is sent to the invoking user as a private message. 119 * See_Also: 120 * $(STDREF format, formattedWrite) 121 */ 122 void reply(FmtArgs...)(in char[] fmt, FmtArgs fmtArgs) 123 { 124 _client.sendf(target, fmt, fmtArgs); 125 } 126 127 /** 128 * Wait the given length of time before returning. 129 * 130 * This is a synchronous but non-blocking operation. 131 */ 132 void wait(double time) 133 { 134 auto curFiber = _bot.commandQueue.fiber(); 135 136 _bot.eventLoop.post(() { 137 _bot.eventLoop.wakeFiber(curFiber); 138 }, time); 139 140 curFiber.yield(); 141 } 142 143 /// Result of $(MREF Context.whois). 144 static struct WhoisResult 145 { 146 /// Nickname, username and hostname of the _user. 147 IrcUser user; 148 149 /// Real name of the user. 150 string realName; 151 152 /// Channels the user is currently a member of. 153 string[] channels; 154 155 /// Boolean whether or not the user is an IRC 156 /// (server or network-wide) _operator. 157 bool operator = false; 158 } 159 160 /** 161 * Lookup more information about the user for the given nick name. 162 * 163 * This is a synchronous but non-blocking operation. 164 * Params: 165 * nickName = nick name of user to lookup 166 */ 167 // TODO: handle error response and timeout 168 WhoisResult whois(string nickName) 169 { 170 WhoisResult result; 171 172 auto curFiber = _bot.commandQueue.fiber(); 173 174 void onWhoisReply(IrcUser user, in char[] realName) 175 { 176 if(user.nickName == nickName) 177 { 178 _client.onWhoisReply.unsubscribeHandler(&onWhoisReply); 179 result.user = IrcUser(user.nickName.idup, user.userName.idup, user.hostName.idup); 180 result.realName = realName.idup; 181 _bot.eventLoop.wakeFiber(curFiber); 182 } 183 } 184 185 _client.onWhoisReply ~= &onWhoisReply; 186 _client.queryWhois(nickName); 187 curFiber.yield(); 188 189 return result; 190 } 191 192 // TODO: handle timeout 193 bool isAdmin(string nickName) 194 { 195 auto sortedAdminList = bot.adminList.assumeSorted!((a, b) => a.nickName < b.nickName)(); 196 auto accounts = sortedAdminList.equalRange(Bot.Admin(nickName)); 197 if(accounts.empty) 198 return false; 199 200 if(auto user = tracker.findUser(nickName)) 201 { 202 if(user.payload.isAdmin) 203 return true; 204 205 auto curFiber = _bot.commandQueue.fiber(); 206 bool result = false; 207 208 void onWhoisAccountReply(in char[] nick, in char[] accountName) 209 { 210 if(nick == nickName) 211 { 212 _client.onWhoisAccountReply.unsubscribeHandler(&onWhoisAccountReply); 213 auto sortedAdminList = bot.adminList.assumeSorted!((a, b) => a.nickName < b.nickName)(); 214 result = sortedAdminList.contains(Bot.Admin(nickName, cast(immutable)accountName)); 215 _bot.eventLoop.wakeFiber(curFiber); 216 } 217 } 218 219 void onWhoisEnd(in char[] nick) 220 { 221 if(nick == nickName) 222 { 223 _client.onWhoisAccountReply.unsubscribeHandler(&onWhoisAccountReply); 224 _client.onWhoisEnd.unsubscribeHandler(&onWhoisEnd); 225 _bot.eventLoop.wakeFiber(curFiber); 226 } 227 } 228 229 _client.onWhoisAccountReply ~= &onWhoisAccountReply; 230 _client.onWhoisEnd ~= &onWhoisEnd; 231 _client.queryWhois(nickName); 232 curFiber.yield(); 233 234 return result; 235 } 236 else 237 return false; 238 } 239 240 /** 241 * Disconnect from the current network with the given message. 242 * Params: 243 * msg = comment sent in _quit notification 244 */ 245 void quit(in char[] msg) 246 { 247 _client.quit(msg); 248 } 249 }