baserules.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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, use_new_defaults=False):
  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. use_new_defaults(bool): Whether to use the new experimental default rules when
  23. appending or prepending default rules.
  24. Returns:
  25. A new list with the rules set by the user combined with the defaults.
  26. """
  27. ruleslist = []
  28. # Grab the base rules that the user has modified.
  29. # The modified base rules have a priority_class of -1.
  30. modified_base_rules = {r["rule_id"]: r for r in rawrules if r["priority_class"] < 0}
  31. # Remove the modified base rules from the list, They'll be added back
  32. # in the default postions in the list.
  33. rawrules = [r for r in rawrules if r["priority_class"] >= 0]
  34. # shove the server default rules for each kind onto the end of each
  35. current_prio_class = list(PRIORITY_CLASS_INVERSE_MAP)[-1]
  36. ruleslist.extend(
  37. make_base_prepend_rules(
  38. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  39. modified_base_rules,
  40. use_new_defaults,
  41. )
  42. )
  43. for r in rawrules:
  44. if r["priority_class"] < current_prio_class:
  45. while r["priority_class"] < current_prio_class:
  46. ruleslist.extend(
  47. make_base_append_rules(
  48. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  49. modified_base_rules,
  50. use_new_defaults,
  51. )
  52. )
  53. current_prio_class -= 1
  54. if current_prio_class > 0:
  55. ruleslist.extend(
  56. make_base_prepend_rules(
  57. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  58. modified_base_rules,
  59. use_new_defaults,
  60. )
  61. )
  62. ruleslist.append(r)
  63. while current_prio_class > 0:
  64. ruleslist.extend(
  65. make_base_append_rules(
  66. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  67. modified_base_rules,
  68. use_new_defaults,
  69. )
  70. )
  71. current_prio_class -= 1
  72. if current_prio_class > 0:
  73. ruleslist.extend(
  74. make_base_prepend_rules(
  75. PRIORITY_CLASS_INVERSE_MAP[current_prio_class],
  76. modified_base_rules,
  77. use_new_defaults,
  78. )
  79. )
  80. return ruleslist
  81. def make_base_append_rules(kind, modified_base_rules, use_new_defaults=False):
  82. rules = []
  83. if kind == "override":
  84. rules = (
  85. NEW_APPEND_OVERRIDE_RULES
  86. if use_new_defaults
  87. else BASE_APPEND_OVERRIDE_RULES
  88. )
  89. elif kind == "underride":
  90. rules = (
  91. NEW_APPEND_UNDERRIDE_RULES
  92. if use_new_defaults
  93. else BASE_APPEND_UNDERRIDE_RULES
  94. )
  95. elif kind == "content":
  96. rules = BASE_APPEND_CONTENT_RULES
  97. # Copy the rules before modifying them
  98. rules = copy.deepcopy(rules)
  99. for r in rules:
  100. # Only modify the actions, keep the conditions the same.
  101. modified = modified_base_rules.get(r["rule_id"])
  102. if modified:
  103. r["actions"] = modified["actions"]
  104. return rules
  105. def make_base_prepend_rules(kind, modified_base_rules, use_new_defaults=False):
  106. rules = []
  107. if kind == "override":
  108. rules = BASE_PREPEND_OVERRIDE_RULES
  109. # Copy the rules before modifying them
  110. rules = copy.deepcopy(rules)
  111. for r in rules:
  112. # Only modify the actions, keep the conditions the same.
  113. modified = modified_base_rules.get(r["rule_id"])
  114. if modified:
  115. r["actions"] = modified["actions"]
  116. return rules
  117. BASE_APPEND_CONTENT_RULES = [
  118. {
  119. "rule_id": "global/content/.m.rule.contains_user_name",
  120. "conditions": [
  121. {
  122. "kind": "event_match",
  123. "key": "content.body",
  124. "pattern_type": "user_localpart",
  125. }
  126. ],
  127. "actions": [
  128. "notify",
  129. {"set_tweak": "sound", "value": "default"},
  130. {"set_tweak": "highlight"},
  131. ],
  132. }
  133. ]
  134. BASE_PREPEND_OVERRIDE_RULES = [
  135. {
  136. "rule_id": "global/override/.m.rule.master",
  137. "enabled": False,
  138. "conditions": [],
  139. "actions": ["dont_notify"],
  140. }
  141. ]
  142. BASE_APPEND_OVERRIDE_RULES = [
  143. {
  144. "rule_id": "global/override/.m.rule.suppress_notices",
  145. "conditions": [
  146. {
  147. "kind": "event_match",
  148. "key": "content.msgtype",
  149. "pattern": "m.notice",
  150. "_id": "_suppress_notices",
  151. }
  152. ],
  153. "actions": ["dont_notify"],
  154. },
  155. # NB. .m.rule.invite_for_me must be higher prio than .m.rule.member_event
  156. # otherwise invites will be matched by .m.rule.member_event
  157. {
  158. "rule_id": "global/override/.m.rule.invite_for_me",
  159. "conditions": [
  160. {
  161. "kind": "event_match",
  162. "key": "type",
  163. "pattern": "m.room.member",
  164. "_id": "_member",
  165. },
  166. {
  167. "kind": "event_match",
  168. "key": "content.membership",
  169. "pattern": "invite",
  170. "_id": "_invite_member",
  171. },
  172. {"kind": "event_match", "key": "state_key", "pattern_type": "user_id"},
  173. ],
  174. "actions": [
  175. "notify",
  176. {"set_tweak": "sound", "value": "default"},
  177. {"set_tweak": "highlight", "value": False},
  178. ],
  179. },
  180. # Will we sometimes want to know about people joining and leaving?
  181. # Perhaps: if so, this could be expanded upon. Seems the most usual case
  182. # is that we don't though. We add this override rule so that even if
  183. # the room rule is set to notify, we don't get notifications about
  184. # join/leave/avatar/displayname events.
  185. # See also: https://matrix.org/jira/browse/SYN-607
  186. {
  187. "rule_id": "global/override/.m.rule.member_event",
  188. "conditions": [
  189. {
  190. "kind": "event_match",
  191. "key": "type",
  192. "pattern": "m.room.member",
  193. "_id": "_member",
  194. }
  195. ],
  196. "actions": ["dont_notify"],
  197. },
  198. # This was changed from underride to override so it's closer in priority
  199. # to the content rules where the user name highlight rule lives. This
  200. # way a room rule is lower priority than both but a custom override rule
  201. # is higher priority than both.
  202. {
  203. "rule_id": "global/override/.m.rule.contains_display_name",
  204. "conditions": [{"kind": "contains_display_name"}],
  205. "actions": [
  206. "notify",
  207. {"set_tweak": "sound", "value": "default"},
  208. {"set_tweak": "highlight"},
  209. ],
  210. },
  211. {
  212. "rule_id": "global/override/.m.rule.roomnotif",
  213. "conditions": [
  214. {
  215. "kind": "event_match",
  216. "key": "content.body",
  217. "pattern": "@room",
  218. "_id": "_roomnotif_content",
  219. },
  220. {
  221. "kind": "sender_notification_permission",
  222. "key": "room",
  223. "_id": "_roomnotif_pl",
  224. },
  225. ],
  226. "actions": ["notify", {"set_tweak": "highlight", "value": True}],
  227. },
  228. {
  229. "rule_id": "global/override/.m.rule.tombstone",
  230. "conditions": [
  231. {
  232. "kind": "event_match",
  233. "key": "type",
  234. "pattern": "m.room.tombstone",
  235. "_id": "_tombstone",
  236. },
  237. {
  238. "kind": "event_match",
  239. "key": "state_key",
  240. "pattern": "",
  241. "_id": "_tombstone_statekey",
  242. },
  243. ],
  244. "actions": ["notify", {"set_tweak": "highlight", "value": True}],
  245. },
  246. {
  247. "rule_id": "global/override/.m.rule.reaction",
  248. "conditions": [
  249. {
  250. "kind": "event_match",
  251. "key": "type",
  252. "pattern": "m.reaction",
  253. "_id": "_reaction",
  254. }
  255. ],
  256. "actions": ["dont_notify"],
  257. },
  258. ]
  259. NEW_APPEND_OVERRIDE_RULES = [
  260. {
  261. "rule_id": "global/override/.m.rule.encrypted",
  262. "conditions": [
  263. {
  264. "kind": "event_match",
  265. "key": "type",
  266. "pattern": "m.room.encrypted",
  267. "_id": "_encrypted",
  268. }
  269. ],
  270. "actions": ["notify"],
  271. },
  272. {
  273. "rule_id": "global/override/.m.rule.suppress_notices",
  274. "conditions": [
  275. {
  276. "kind": "event_match",
  277. "key": "type",
  278. "pattern": "m.room.message",
  279. "_id": "_suppress_notices_type",
  280. },
  281. {
  282. "kind": "event_match",
  283. "key": "content.msgtype",
  284. "pattern": "m.notice",
  285. "_id": "_suppress_notices",
  286. },
  287. ],
  288. "actions": [],
  289. },
  290. {
  291. "rule_id": "global/underride/.m.rule.suppress_edits",
  292. "conditions": [
  293. {
  294. "kind": "event_match",
  295. "key": "m.relates_to.m.rel_type",
  296. "pattern": "m.replace",
  297. "_id": "_suppress_edits",
  298. }
  299. ],
  300. "actions": [],
  301. },
  302. {
  303. "rule_id": "global/override/.m.rule.invite_for_me",
  304. "conditions": [
  305. {
  306. "kind": "event_match",
  307. "key": "type",
  308. "pattern": "m.room.member",
  309. "_id": "_member",
  310. },
  311. {
  312. "kind": "event_match",
  313. "key": "content.membership",
  314. "pattern": "invite",
  315. "_id": "_invite_member",
  316. },
  317. {"kind": "event_match", "key": "state_key", "pattern_type": "user_id"},
  318. ],
  319. "actions": ["notify", {"set_tweak": "sound", "value": "default"}],
  320. },
  321. {
  322. "rule_id": "global/override/.m.rule.contains_display_name",
  323. "conditions": [{"kind": "contains_display_name"}],
  324. "actions": [
  325. "notify",
  326. {"set_tweak": "sound", "value": "default"},
  327. {"set_tweak": "highlight"},
  328. ],
  329. },
  330. {
  331. "rule_id": "global/override/.m.rule.tombstone",
  332. "conditions": [
  333. {
  334. "kind": "event_match",
  335. "key": "type",
  336. "pattern": "m.room.tombstone",
  337. "_id": "_tombstone",
  338. },
  339. {
  340. "kind": "event_match",
  341. "key": "state_key",
  342. "pattern": "",
  343. "_id": "_tombstone_statekey",
  344. },
  345. ],
  346. "actions": [
  347. "notify",
  348. {"set_tweak": "sound", "value": "default"},
  349. {"set_tweak": "highlight"},
  350. ],
  351. },
  352. {
  353. "rule_id": "global/override/.m.rule.roomnotif",
  354. "conditions": [
  355. {
  356. "kind": "event_match",
  357. "key": "content.body",
  358. "pattern": "@room",
  359. "_id": "_roomnotif_content",
  360. },
  361. {
  362. "kind": "sender_notification_permission",
  363. "key": "room",
  364. "_id": "_roomnotif_pl",
  365. },
  366. ],
  367. "actions": [
  368. "notify",
  369. {"set_tweak": "highlight"},
  370. {"set_tweak": "sound", "value": "default"},
  371. ],
  372. },
  373. {
  374. "rule_id": "global/override/.m.rule.call",
  375. "conditions": [
  376. {
  377. "kind": "event_match",
  378. "key": "type",
  379. "pattern": "m.call.invite",
  380. "_id": "_call",
  381. }
  382. ],
  383. "actions": ["notify", {"set_tweak": "sound", "value": "ring"}],
  384. },
  385. ]
  386. BASE_APPEND_UNDERRIDE_RULES = [
  387. {
  388. "rule_id": "global/underride/.m.rule.call",
  389. "conditions": [
  390. {
  391. "kind": "event_match",
  392. "key": "type",
  393. "pattern": "m.call.invite",
  394. "_id": "_call",
  395. }
  396. ],
  397. "actions": [
  398. "notify",
  399. {"set_tweak": "sound", "value": "ring"},
  400. {"set_tweak": "highlight", "value": False},
  401. ],
  402. },
  403. # XXX: once m.direct is standardised everywhere, we should use it to detect
  404. # a DM from the user's perspective rather than this heuristic.
  405. {
  406. "rule_id": "global/underride/.m.rule.room_one_to_one",
  407. "conditions": [
  408. {"kind": "room_member_count", "is": "2", "_id": "member_count"},
  409. {
  410. "kind": "event_match",
  411. "key": "type",
  412. "pattern": "m.room.message",
  413. "_id": "_message",
  414. },
  415. ],
  416. "actions": [
  417. "notify",
  418. {"set_tweak": "sound", "value": "default"},
  419. {"set_tweak": "highlight", "value": False},
  420. ],
  421. },
  422. # XXX: this is going to fire for events which aren't m.room.messages
  423. # but are encrypted (e.g. m.call.*)...
  424. {
  425. "rule_id": "global/underride/.m.rule.encrypted_room_one_to_one",
  426. "conditions": [
  427. {"kind": "room_member_count", "is": "2", "_id": "member_count"},
  428. {
  429. "kind": "event_match",
  430. "key": "type",
  431. "pattern": "m.room.encrypted",
  432. "_id": "_encrypted",
  433. },
  434. ],
  435. "actions": [
  436. "notify",
  437. {"set_tweak": "sound", "value": "default"},
  438. {"set_tweak": "highlight", "value": False},
  439. ],
  440. },
  441. {
  442. "rule_id": "global/underride/.m.rule.message",
  443. "conditions": [
  444. {
  445. "kind": "event_match",
  446. "key": "type",
  447. "pattern": "m.room.message",
  448. "_id": "_message",
  449. }
  450. ],
  451. "actions": ["notify", {"set_tweak": "highlight", "value": False}],
  452. },
  453. # XXX: this is going to fire for events which aren't m.room.messages
  454. # but are encrypted (e.g. m.call.*)...
  455. {
  456. "rule_id": "global/underride/.m.rule.encrypted",
  457. "conditions": [
  458. {
  459. "kind": "event_match",
  460. "key": "type",
  461. "pattern": "m.room.encrypted",
  462. "_id": "_encrypted",
  463. }
  464. ],
  465. "actions": ["notify", {"set_tweak": "highlight", "value": False}],
  466. },
  467. ]
  468. NEW_APPEND_UNDERRIDE_RULES = [
  469. {
  470. "rule_id": "global/underride/.m.rule.room_one_to_one",
  471. "conditions": [
  472. {"kind": "room_member_count", "is": "2", "_id": "member_count"},
  473. {
  474. "kind": "event_match",
  475. "key": "content.body",
  476. "pattern": "*",
  477. "_id": "body",
  478. },
  479. ],
  480. "actions": ["notify", {"set_tweak": "sound", "value": "default"}],
  481. },
  482. {
  483. "rule_id": "global/underride/.m.rule.message",
  484. "conditions": [
  485. {
  486. "kind": "event_match",
  487. "key": "content.body",
  488. "pattern": "*",
  489. "_id": "body",
  490. },
  491. ],
  492. "actions": ["notify"],
  493. "enabled": False,
  494. },
  495. ]
  496. BASE_RULE_IDS = set()
  497. for r in BASE_APPEND_CONTENT_RULES:
  498. r["priority_class"] = PRIORITY_CLASS_MAP["content"]
  499. r["default"] = True
  500. BASE_RULE_IDS.add(r["rule_id"])
  501. for r in BASE_PREPEND_OVERRIDE_RULES:
  502. r["priority_class"] = PRIORITY_CLASS_MAP["override"]
  503. r["default"] = True
  504. BASE_RULE_IDS.add(r["rule_id"])
  505. for r in BASE_APPEND_OVERRIDE_RULES:
  506. r["priority_class"] = PRIORITY_CLASS_MAP["override"]
  507. r["default"] = True
  508. BASE_RULE_IDS.add(r["rule_id"])
  509. for r in BASE_APPEND_UNDERRIDE_RULES:
  510. r["priority_class"] = PRIORITY_CLASS_MAP["underride"]
  511. r["default"] = True
  512. BASE_RULE_IDS.add(r["rule_id"])
  513. NEW_RULE_IDS = set()
  514. for r in BASE_APPEND_CONTENT_RULES:
  515. r["priority_class"] = PRIORITY_CLASS_MAP["content"]
  516. r["default"] = True
  517. NEW_RULE_IDS.add(r["rule_id"])
  518. for r in BASE_PREPEND_OVERRIDE_RULES:
  519. r["priority_class"] = PRIORITY_CLASS_MAP["override"]
  520. r["default"] = True
  521. NEW_RULE_IDS.add(r["rule_id"])
  522. for r in NEW_APPEND_OVERRIDE_RULES:
  523. r["priority_class"] = PRIORITY_CLASS_MAP["override"]
  524. r["default"] = True
  525. NEW_RULE_IDS.add(r["rule_id"])
  526. for r in NEW_APPEND_UNDERRIDE_RULES:
  527. r["priority_class"] = PRIORITY_CLASS_MAP["underride"]
  528. r["default"] = True
  529. NEW_RULE_IDS.add(r["rule_id"])