Mirror of github.com/gissleh/irc
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

290 lines
6.4 KiB

6 years ago
  1. package isupport
  2. import (
  3. "strconv"
  4. "strings"
  5. "sync"
  6. )
  7. // ISupport is a data structure containing server instructions about
  8. // supported modes, encodings, lengths, prefixes, and so on. It is built
  9. // from the 005 numeric's data, and has helper methods that makes sense
  10. // of it. It's thread-safe through a reader/writer lock, so the locks will
  11. // only block in the short duration post-registration when the 005s come in
  12. type ISupport struct {
  13. lock sync.RWMutex
  14. raw map[string]string
  15. prefixes map[rune]rune
  16. modeOrder string
  17. prefixOrder string
  18. chanModes []string
  19. }
  20. // Get gets an isupport key. This is unprocessed data, and a helper should
  21. // be used if available.
  22. func (isupport *ISupport) Get(key string) (value string, ok bool) {
  23. isupport.lock.RLock()
  24. value, ok = isupport.raw[key]
  25. isupport.lock.RUnlock()
  26. return
  27. }
  28. // Number gets a key and converts it to a number.
  29. func (isupport *ISupport) Number(key string) (value int, ok bool) {
  30. isupport.lock.RLock()
  31. strValue, ok := isupport.raw[key]
  32. isupport.lock.RUnlock()
  33. if !ok {
  34. return 0, ok
  35. }
  36. value, err := strconv.Atoi(strValue)
  37. if err != nil {
  38. return value, false
  39. }
  40. return value, ok
  41. }
  42. // ParsePrefixedNick parses a full nick into its components.
  43. // Example: "@+HammerTime62" -> `"HammerTime62", "ov", "@+"`
  44. func (isupport *ISupport) ParsePrefixedNick(fullnick string) (nick, modes, prefixes string) {
  45. isupport.lock.RLock()
  46. defer isupport.lock.RUnlock()
  47. if fullnick == "" || isupport.prefixes == nil {
  48. return fullnick, "", ""
  49. }
  50. for i, ch := range fullnick {
  51. if mode, ok := isupport.prefixes[ch]; ok {
  52. modes += string(mode)
  53. prefixes += string(ch)
  54. } else {
  55. nick = fullnick[i:]
  56. break
  57. }
  58. }
  59. return nick, modes, prefixes
  60. }
  61. // HighestPrefix gets the highest-level prefix declared by PREFIX
  62. func (isupport *ISupport) HighestPrefix(prefixes string) rune {
  63. isupport.lock.RLock()
  64. defer isupport.lock.RUnlock()
  65. if len(prefixes) == 1 {
  66. return rune(prefixes[0])
  67. }
  68. for _, prefix := range isupport.prefixOrder {
  69. if strings.ContainsRune(prefixes, prefix) {
  70. return prefix
  71. }
  72. }
  73. return rune(0)
  74. }
  75. // HighestMode gets the highest-level mode declared by PREFIX
  76. func (isupport *ISupport) HighestMode(modes string) rune {
  77. isupport.lock.RLock()
  78. defer isupport.lock.RUnlock()
  79. if len(modes) == 1 {
  80. return rune(modes[0])
  81. }
  82. for _, mode := range isupport.modeOrder {
  83. if strings.ContainsRune(modes, mode) {
  84. return mode
  85. }
  86. }
  87. return rune(0)
  88. }
  89. // IsModeHigher returns true if `current` is a higher mode than `other`.
  90. func (isupport *ISupport) IsModeHigher(current rune, other rune) bool {
  91. isupport.lock.RLock()
  92. defer isupport.lock.RUnlock()
  93. if current == other {
  94. return false
  95. }
  96. if current == 0 {
  97. return false
  98. }
  99. if other == 0 {
  100. return true
  101. }
  102. for _, mode := range isupport.modeOrder {
  103. if mode == current {
  104. return true
  105. } else if mode == other {
  106. return false
  107. }
  108. }
  109. return false
  110. }
  111. // SortModes returns the modes in order. Any unknown modes will be omitted.
  112. func (isupport *ISupport) SortModes(modes string) string {
  113. result := ""
  114. for _, ch := range isupport.modeOrder {
  115. for _, ch2 := range modes {
  116. if ch2 == ch {
  117. result += string(ch)
  118. }
  119. }
  120. }
  121. return result
  122. }
  123. // SortPrefixes returns the prefixes in order. Any unknown prefixes will be omitted.
  124. func (isupport *ISupport) SortPrefixes(prefixes string) string {
  125. result := ""
  126. for _, ch := range isupport.prefixOrder {
  127. for _, ch2 := range prefixes {
  128. if ch2 == ch {
  129. result += string(ch)
  130. }
  131. }
  132. }
  133. return result
  134. }
  135. // Mode gets the mode for the prefix.
  136. func (isupport *ISupport) Mode(prefix rune) rune {
  137. isupport.lock.RLock()
  138. defer isupport.lock.RUnlock()
  139. return isupport.prefixes[prefix]
  140. }
  141. // Prefix gets the prefix for the mode. It's a bit slower
  142. // than the other way around, but is a far less frequently
  143. // used.
  144. func (isupport *ISupport) Prefix(mode rune) rune {
  145. isupport.lock.RLock()
  146. defer isupport.lock.RUnlock()
  147. for prefix, mappedMode := range isupport.prefixes {
  148. if mappedMode == mode {
  149. return prefix
  150. }
  151. }
  152. return rune(0)
  153. }
  154. // Prefixes gets the prefixes in the order of the modes, skipping any
  155. // invalid modes.
  156. func (isupport *ISupport) Prefixes(modes string) string {
  157. result := ""
  158. for _, mode := range modes {
  159. prefix := isupport.Prefix(mode)
  160. if prefix != mode {
  161. result += string(prefix)
  162. }
  163. }
  164. return result
  165. }
  166. // IsChannel returns whether the target name is a channel.
  167. func (isupport *ISupport) IsChannel(targetName string) bool {
  168. isupport.lock.RLock()
  169. defer isupport.lock.RUnlock()
  170. return strings.Contains(isupport.raw["CHANTYPES"], string(targetName[0]))
  171. }
  172. // IsPermissionMode returns whether the flag is a permission mode
  173. func (isupport *ISupport) IsPermissionMode(flag rune) bool {
  174. isupport.lock.RLock()
  175. defer isupport.lock.RUnlock()
  176. return strings.ContainsRune(isupport.modeOrder, flag)
  177. }
  178. // ChannelModeType returns a number from 0 to 3 based on what block of mode
  179. // in the CHANMODES variable it fits into. If it's not found at all, it will
  180. // return -1
  181. func (isupport *ISupport) ChannelModeType(mode rune) int {
  182. isupport.lock.RLock()
  183. defer isupport.lock.RUnlock()
  184. // User permission modes function exactly like the first block
  185. // when it comes to add/remove
  186. if strings.ContainsRune(isupport.modeOrder, mode) {
  187. return 0
  188. }
  189. for i, block := range isupport.chanModes {
  190. if strings.ContainsRune(block, mode) {
  191. return i
  192. }
  193. }
  194. return -1
  195. }
  196. // Set sets an isupport key, and related structs. This should only be used
  197. // if a 005 packet contains the Key-Value pair or if it can be "polyfilled"
  198. // in some other way.
  199. func (isupport *ISupport) Set(key, value string) {
  200. key = strings.ToUpper(key)
  201. isupport.lock.Lock()
  202. if isupport.raw == nil {
  203. isupport.raw = make(map[string]string, 32)
  204. }
  205. isupport.raw[key] = value
  206. switch key {
  207. case "PREFIX": // PREFIX=(ov)@+
  208. {
  209. split := strings.SplitN(value[1:], ")", 2)
  210. isupport.prefixOrder = split[1]
  211. isupport.modeOrder = split[0]
  212. isupport.prefixes = make(map[rune]rune, len(split[0]))
  213. for i, ch := range split[0] {
  214. isupport.prefixes[rune(split[1][i])] = ch
  215. }
  216. }
  217. case "CHANMODES": // CHANMODES=eIbq,k,flj,CFLNPQcgimnprstz
  218. {
  219. isupport.chanModes = strings.Split(value, ",")
  220. }
  221. }
  222. isupport.lock.Unlock()
  223. }
  224. // Reset clears everything.
  225. func (isupport *ISupport) Reset() {
  226. isupport.lock.Lock()
  227. isupport.prefixOrder = ""
  228. isupport.modeOrder = ""
  229. isupport.prefixes = nil
  230. isupport.chanModes = nil
  231. for key := range isupport.raw {
  232. delete(isupport.raw, key)
  233. }
  234. isupport.lock.Unlock()
  235. }