1 /++ 2 + Module for IRCv3 core features. 3 +/ 4 module virc.ircv3.base; 5 6 /++ 7 + 8 +/ 9 enum IRCV3Commands { 10 cap = "CAP", 11 metadata = "METADATA", 12 authenticate = "AUTHENTICATE", 13 account = "ACCOUNT", 14 starttls = "STARTTLS", 15 monitor = "MONITOR", 16 batch = "BATCH", 17 chghost = "CHGHOST", 18 fail = "FAIL", 19 warn = "WARN", 20 note = "NOTE", 21 tagmsg = "TAGMSG", 22 } 23 24 /++ 25 + 26 +/ 27 enum CapabilityServerSubcommands { 28 ls = "LS", 29 acknowledge = "ACK", 30 notAcknowledge = "NAK", 31 list = "LIST", 32 new_ = "NEW", 33 delete_ = "DEL" 34 } 35 36 /++ 37 + 38 +/ 39 enum CapabilityClientSubcommands { 40 ls = "LS", 41 list = "LIST", 42 request = "REQ", 43 end = "END" 44 } 45 46 /++ 47 + 48 +/ 49 struct Capability { 50 /// 51 string name; 52 ///DEPRECATED - indicates that the capability cannot be disabled 53 bool isSticky; 54 ///Indicates that the capability is being disabled 55 bool isDisabled; 56 ///DEPRECATED - indicates that the client must acknowledge this cap via CAP ACK 57 bool mustAck; 58 /// 59 string value; 60 61 alias name this; 62 63 @disable this(); 64 65 /// 66 this(string str) @safe pure @nogc in { 67 import std.range : empty; 68 assert(!str.empty); 69 } do { 70 import std.algorithm.comparison : among; 71 import std.algorithm.searching : findSplit; 72 import std.range : front, popFront; 73 import std.utf : byCodeUnit; 74 auto prefix = str.byCodeUnit.front; 75 setFlagsByPrefix(prefix); 76 if (prefix.among('~', '=', '-')) { 77 str.popFront(); 78 } 79 auto split = str.findSplit("="); 80 this(split[0], split[2]); 81 } 82 /// 83 this(string key, string val) @safe pure @nogc nothrow { 84 name = key; 85 value = val; 86 } 87 /++ 88 + Indicates whether or not this is a vendor-specific capability. 89 + 90 + Often these are draft implementations of not-yet-accepted capabilities. 91 +/ 92 bool isVendorSpecific() @safe pure @nogc nothrow { 93 import std.algorithm.searching : canFind; 94 import std.utf : byCodeUnit; 95 return name.byCodeUnit.canFind('/'); 96 } 97 /// 98 string toString() const pure @safe { 99 import std.range : empty; 100 return name~(value.empty ? "" : "=")~value; 101 } 102 private void setFlagsByPrefix(char chr) @safe pure @nogc nothrow { 103 switch (chr) { 104 case '~': 105 mustAck = true; 106 break; 107 case '=': 108 isSticky = true; 109 break; 110 case '-': 111 isDisabled = true; 112 break; 113 default: 114 break; 115 } 116 } 117 } 118 /// 119 @safe pure @nogc unittest { 120 { 121 auto cap = Capability("~account-notify"); 122 assert(cap.name == "account-notify"); 123 assert(cap.mustAck); 124 } 125 { 126 auto cap = Capability("=account-notify"); 127 assert(cap.name == "account-notify"); 128 assert(cap.isSticky); 129 } 130 { 131 auto cap = Capability("-account-notify"); 132 assert(cap.name == "account-notify"); 133 assert(cap.isDisabled); 134 } 135 { 136 auto cap = Capability("example.com/cap"); 137 assert(cap.name == "example.com/cap"); 138 assert(cap.isVendorSpecific); 139 } 140 { 141 auto cap = Capability("cap=value"); 142 assert(cap.name == "cap"); 143 assert(cap.value == "value"); 144 } 145 { 146 auto cap = Capability("cap=value/notvendor"); 147 assert(cap.name == "cap"); 148 assert(cap.value == "value/notvendor"); 149 assert(!cap.isVendorSpecific); 150 } 151 }