baserules.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2017 New Vector Ltd
  3. # Copyright 2019 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import copy
  17. from synapse.push.rulekinds import PRIORITY_CLASS_INVERSE_MAP, PRIORITY_CLASS_MAP
  18. def list_with_base_rules(rawrules):
  19. """Combine the list of rules set by the user with the default push rules
  20. Args:
  21. rawrules(list): The rules the user has modified or set.
  22. Returns:
  23. A new list with the rules set by the user combined with the defaults.
  24. """
  25. ruleslist = []
  26. # Grab the base rules that the user has modified.
  27. # The modified base rules have a priority_class of -1.
  28. modified_base_rules = {r["rule_id"]: r for r in rawrules if r["priority_class"] < 0}
  29. # Remove the modified base rules from the list, They'll be added back
  30. # in the default postions in the list.
  31. rawrules = [r for r in rawrules if r["priority_class"] >= 0]
  32. # shove the server default rules for each kind onto the end of each
  33. current_prio_class = list(PRIORITY_CLASS_INVERSE_MAP)[-1]
  34. ruleslist.extend(
  35. make_base_prepend_rules(
  36. PRIORITY_CLASS_INVERSE_MAP[current_prio_class], modified_base_rules
  37. )
  38. )
  39. for r in rawrules:
  40. if r["priority_class"] < current_prio_class:
  41. while r["priority_class"] < current_prio_class:
  42. ruleslist.extend(
  43. make_base_append_rules(
  44. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  45. modified_base_rules,
  46. )
  47. )
  48. current_prio_class -= 1
  49. if current_prio_class > 0:
  50. ruleslist.extend(
  51. make_base_prepend_rules(
  52. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  53. modified_base_rules,
  54. )
  55. )
  56. ruleslist.append(r)
  57. while current_prio_class > 0:
  58. ruleslist.extend(
  59. make_base_append_rules(
  60. PRIORITY_CLASS_INVERSE_MAP[current_prio_class], modified_base_rules
  61. )
  62. )
  63. current_prio_class -= 1
  64. if current_prio_class > 0:
  65. ruleslist.extend(
  66. make_base_prepend_rules(
  67. PRIORITY_CLASS_INVERSE_MAP[current_prio_class], modified_base_rules
  68. )
  69. )
  70. return ruleslist
  71. def make_base_append_rules(kind, modified_base_rules):
  72. rules = []
  73. if kind == "override":
  74. rules = BASE_APPEND_OVERRIDE_RULES
  75. elif kind == "underride":
  76. rules = BASE_APPEND_UNDERRIDE_RULES
  77. elif kind == "content":
  78. rules = BASE_APPEND_CONTENT_RULES
  79. # Copy the rules before modifying them
  80. rules = copy.deepcopy(rules)
  81. for r in rules:
  82. # Only modify the actions, keep the conditions the same.
  83. modified = modified_base_rules.get(r["rule_id"])
  84. if modified:
  85. r["actions"] = modified["actions"]
  86. return rules
  87. def make_base_prepend_rules(kind, modified_base_rules):
  88. rules = []
  89. if kind == "override":
  90. rules = BASE_PREPEND_OVERRIDE_RULES
  91. # Copy the rules before modifying them
  92. rules = copy.deepcopy(rules)
  93. for r in rules:
  94. # Only modify the actions, keep the conditions the same.
  95. modified = modified_base_rules.get(r["rule_id"])
  96. if modified:
  97. r["actions"] = modified["actions"]
  98. return rules
  99. BASE_APPEND_CONTENT_RULES = [
  100. {
  101. "rule_id": "global/content/.m.rule.contains_user_name",
  102. "conditions": [
  103. {
  104. "kind": "event_match",
  105. "key": "content.body",
  106. "pattern_type": "user_localpart",
  107. }
  108. ],
  109. "actions": [
  110. "notify",
  111. {"set_tweak": "sound", "value": "default"},
  112. {"set_tweak": "highlight"},
  113. ],
  114. }
  115. ]
  116. BASE_PREPEND_OVERRIDE_RULES = [
  117. {
  118. "rule_id": "global/override/.m.rule.master",
  119. "enabled": False,
  120. "conditions": [],
  121. "actions": ["dont_notify"],
  122. }
  123. ]
  124. BASE_APPEND_OVERRIDE_RULES = [
  125. {
  126. "rule_id": "global/override/.m.rule.suppress_notices",
  127. "conditions": [
  128. {
  129. "kind": "event_match",
  130. "key": "content.msgtype",
  131. "pattern": "m.notice",
  132. "_id": "_suppress_notices",
  133. }
  134. ],
  135. "actions": ["dont_notify"],
  136. },
  137. # NB. .m.rule.invite_for_me must be higher prio than .m.rule.member_event
  138. # otherwise invites will be matched by .m.rule.member_event
  139. {
  140. "rule_id": "global/override/.m.rule.invite_for_me",
  141. "conditions": [
  142. {
  143. "kind": "event_match",
  144. "key": "type",
  145. "pattern": "m.room.member",
  146. "_id": "_member",
  147. },
  148. {
  149. "kind": "event_match",
  150. "key": "content.membership",
  151. "pattern": "invite",
  152. "_id": "_invite_member",
  153. },
  154. {"kind": "event_match", "key": "state_key", "pattern_type": "user_id"},
  155. ],
  156. "actions": [
  157. "notify",
  158. {"set_tweak": "sound", "value": "default"},
  159. {"set_tweak": "highlight", "value": False},
  160. ],
  161. },
  162. # Will we sometimes want to know about people joining and leaving?
  163. # Perhaps: if so, this could be expanded upon. Seems the most usual case
  164. # is that we don't though. We add this override rule so that even if
  165. # the room rule is set to notify, we don't get notifications about
  166. # join/leave/avatar/displayname events.
  167. # See also: https://matrix.org/jira/browse/SYN-607
  168. {
  169. "rule_id": "global/override/.m.rule.member_event",
  170. "conditions": [
  171. {
  172. "kind": "event_match",
  173. "key": "type",
  174. "pattern": "m.room.member",
  175. "_id": "_member",
  176. }
  177. ],
  178. "actions": ["dont_notify"],
  179. },
  180. # This was changed from underride to override so it's closer in priority
  181. # to the content rules where the user name highlight rule lives. This
  182. # way a room rule is lower priority than both but a custom override rule
  183. # is higher priority than both.
  184. {
  185. "rule_id": "global/override/.m.rule.contains_display_name",
  186. "conditions": [{"kind": "contains_display_name"}],
  187. "actions": [
  188. "notify",
  189. {"set_tweak": "sound", "value": "default"},
  190. {"set_tweak": "highlight"},
  191. ],
  192. },
  193. {
  194. "rule_id": "global/override/.m.rule.roomnotif",
  195. "conditions": [
  196. {
  197. "kind": "event_match",
  198. "key": "content.body",
  199. "pattern": "@room",
  200. "_id": "_roomnotif_content",
  201. },
  202. {
  203. "kind": "sender_notification_permission",
  204. "key": "room",
  205. "_id": "_roomnotif_pl",
  206. },
  207. ],
  208. "actions": ["notify", {"set_tweak": "highlight", "value": True}],
  209. },
  210. {
  211. "rule_id": "global/override/.m.rule.tombstone",
  212. "conditions": [
  213. {
  214. "kind": "event_match",
  215. "key": "type",
  216. "pattern": "m.room.tombstone",
  217. "_id": "_tombstone",
  218. },
  219. {
  220. "kind": "event_match",
  221. "key": "state_key",
  222. "pattern": "",
  223. "_id": "_tombstone_statekey",
  224. },
  225. ],
  226. "actions": ["notify", {"set_tweak": "highlight", "value": True}],
  227. },
  228. {
  229. "rule_id": "global/override/.m.rule.reaction",
  230. "conditions": [
  231. {
  232. "kind": "event_match",
  233. "key": "type",
  234. "pattern": "m.reaction",
  235. "_id": "_reaction",
  236. }
  237. ],
  238. "actions": ["dont_notify"],
  239. },
  240. ]
  241. BASE_APPEND_UNDERRIDE_RULES = [
  242. {
  243. "rule_id": "global/underride/.m.rule.call",
  244. "conditions": [
  245. {
  246. "kind": "event_match",
  247. "key": "type",
  248. "pattern": "m.call.invite",
  249. "_id": "_call",
  250. }
  251. ],
  252. "actions": [
  253. "notify",
  254. {"set_tweak": "sound", "value": "ring"},
  255. {"set_tweak": "highlight", "value": False},
  256. ],
  257. },
  258. # XXX: once m.direct is standardised everywhere, we should use it to detect
  259. # a DM from the user's perspective rather than this heuristic.
  260. {
  261. "rule_id": "global/underride/.m.rule.room_one_to_one",
  262. "conditions": [
  263. {"kind": "room_member_count", "is": "2", "_id": "member_count"},
  264. {
  265. "kind": "event_match",
  266. "key": "type",
  267. "pattern": "m.room.message",
  268. "_id": "_message",
  269. },
  270. ],
  271. "actions": [
  272. "notify",
  273. {"set_tweak": "sound", "value": "default"},
  274. {"set_tweak": "highlight", "value": False},
  275. ],
  276. },
  277. # XXX: this is going to fire for events which aren't m.room.messages
  278. # but are encrypted (e.g. m.call.*)...
  279. {
  280. "rule_id": "global/underride/.m.rule.encrypted_room_one_to_one",
  281. "conditions": [
  282. {"kind": "room_member_count", "is": "2", "_id": "member_count"},
  283. {
  284. "kind": "event_match",
  285. "key": "type",
  286. "pattern": "m.room.encrypted",
  287. "_id": "_encrypted",
  288. },
  289. ],
  290. "actions": [
  291. "notify",
  292. {"set_tweak": "sound", "value": "default"},
  293. {"set_tweak": "highlight", "value": False},
  294. ],
  295. },
  296. {
  297. "rule_id": "global/underride/.m.rule.message",
  298. "conditions": [
  299. {
  300. "kind": "event_match",
  301. "key": "type",
  302. "pattern": "m.room.message",
  303. "_id": "_message",
  304. }
  305. ],
  306. "actions": ["notify", {"set_tweak": "highlight", "value": False}],
  307. },
  308. # XXX: this is going to fire for events which aren't m.room.messages
  309. # but are encrypted (e.g. m.call.*)...
  310. {
  311. "rule_id": "global/underride/.m.rule.encrypted",
  312. "conditions": [
  313. {
  314. "kind": "event_match",
  315. "key": "type",
  316. "pattern": "m.room.encrypted",
  317. "_id": "_encrypted",
  318. }
  319. ],
  320. "actions": ["notify", {"set_tweak": "highlight", "value": False}],
  321. },
  322. ]
  323. BASE_RULE_IDS = set()
  324. for r in BASE_APPEND_CONTENT_RULES:
  325. r["priority_class"] = PRIORITY_CLASS_MAP["content"]
  326. r["default"] = True
  327. BASE_RULE_IDS.add(r["rule_id"])
  328. for r in BASE_PREPEND_OVERRIDE_RULES:
  329. r["priority_class"] = PRIORITY_CLASS_MAP["override"]
  330. r["default"] = True
  331. BASE_RULE_IDS.add(r["rule_id"])
  332. for r in BASE_APPEND_OVERRIDE_RULES:
  333. r["priority_class"] = PRIORITY_CLASS_MAP["override"]
  334. r["default"] = True
  335. BASE_RULE_IDS.add(r["rule_id"])
  336. for r in BASE_APPEND_UNDERRIDE_RULES:
  337. r["priority_class"] = PRIORITY_CLASS_MAP["underride"]
  338. r["default"] = True
  339. BASE_RULE_IDS.add(r["rule_id"])