review_recent_signups.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import argparse
  15. import sys
  16. import time
  17. from datetime import datetime
  18. from typing import List
  19. import attr
  20. from synapse.config._base import (
  21. Config,
  22. RootConfig,
  23. find_config_files,
  24. read_config_files,
  25. )
  26. from synapse.config.database import DatabaseConfig
  27. from synapse.storage.database import DatabasePool, LoggingTransaction, make_conn
  28. from synapse.storage.engines import create_engine
  29. class ReviewConfig(RootConfig):
  30. "A config class that just pulls out the database config"
  31. config_classes = [DatabaseConfig]
  32. @attr.s(auto_attribs=True)
  33. class UserInfo:
  34. user_id: str
  35. creation_ts: int
  36. emails: List[str] = attr.Factory(list)
  37. private_rooms: List[str] = attr.Factory(list)
  38. public_rooms: List[str] = attr.Factory(list)
  39. ips: List[str] = attr.Factory(list)
  40. def get_recent_users(txn: LoggingTransaction, since_ms: int) -> List[UserInfo]:
  41. """Fetches recently registered users and some info on them."""
  42. sql = """
  43. SELECT name, creation_ts FROM users
  44. WHERE
  45. ? <= creation_ts
  46. AND deactivated = 0
  47. """
  48. txn.execute(sql, (since_ms / 1000,))
  49. user_infos = [UserInfo(user_id, creation_ts) for user_id, creation_ts in txn]
  50. for user_info in user_infos:
  51. user_info.emails = DatabasePool.simple_select_onecol_txn(
  52. txn,
  53. table="user_threepids",
  54. keyvalues={"user_id": user_info.user_id, "medium": "email"},
  55. retcol="address",
  56. )
  57. sql = """
  58. SELECT room_id, canonical_alias, name, join_rules
  59. FROM local_current_membership
  60. INNER JOIN room_stats_state USING (room_id)
  61. WHERE user_id = ? AND membership = 'join'
  62. """
  63. txn.execute(sql, (user_info.user_id,))
  64. for room_id, canonical_alias, name, join_rules in txn:
  65. if join_rules == "public":
  66. user_info.public_rooms.append(canonical_alias or name or room_id)
  67. else:
  68. user_info.private_rooms.append(canonical_alias or name or room_id)
  69. user_info.ips = DatabasePool.simple_select_onecol_txn(
  70. txn,
  71. table="user_ips",
  72. keyvalues={"user_id": user_info.user_id},
  73. retcol="ip",
  74. )
  75. return user_infos
  76. def main() -> None:
  77. parser = argparse.ArgumentParser()
  78. parser.add_argument(
  79. "-c",
  80. "--config-path",
  81. action="append",
  82. metavar="CONFIG_FILE",
  83. help="The config files for Synapse.",
  84. required=True,
  85. )
  86. parser.add_argument(
  87. "-s",
  88. "--since",
  89. metavar="duration",
  90. help="Specify how far back to review user registrations for, defaults to 7d (i.e. 7 days).",
  91. default="7d",
  92. )
  93. parser.add_argument(
  94. "-e",
  95. "--exclude-emails",
  96. action="store_true",
  97. help="Exclude users that have validated email addresses",
  98. )
  99. parser.add_argument(
  100. "-u",
  101. "--only-users",
  102. action="store_true",
  103. help="Only print user IDs that match.",
  104. )
  105. config = ReviewConfig()
  106. config_args = parser.parse_args(sys.argv[1:])
  107. config_files = find_config_files(search_paths=config_args.config_path)
  108. config_dict = read_config_files(config_files)
  109. config.parse_config_dict(
  110. config_dict,
  111. )
  112. since_ms = time.time() * 1000 - Config.parse_duration(config_args.since)
  113. exclude_users_with_email = config_args.exclude_emails
  114. include_context = not config_args.only_users
  115. for database_config in config.database.databases:
  116. if "main" in database_config.databases:
  117. break
  118. engine = create_engine(database_config.config)
  119. with make_conn(database_config, engine, "review_recent_signups") as db_conn:
  120. # This generates a type of Cursor, not LoggingTransaction.
  121. user_infos = get_recent_users(db_conn.cursor(), since_ms) # type: ignore[arg-type]
  122. for user_info in user_infos:
  123. if exclude_users_with_email and user_info.emails:
  124. continue
  125. if include_context:
  126. print_public_rooms = ""
  127. if user_info.public_rooms:
  128. print_public_rooms = "(" + ", ".join(user_info.public_rooms[:3])
  129. if len(user_info.public_rooms) > 3:
  130. print_public_rooms += ", ..."
  131. print_public_rooms += ")"
  132. print("# Created:", datetime.fromtimestamp(user_info.creation_ts))
  133. print("# Email:", ", ".join(user_info.emails) or "None")
  134. print("# IPs:", ", ".join(user_info.ips))
  135. print(
  136. "# Number joined public rooms:",
  137. len(user_info.public_rooms),
  138. print_public_rooms,
  139. )
  140. print("# Number joined private rooms:", len(user_info.private_rooms))
  141. print("#")
  142. print(user_info.user_id)
  143. if include_context:
  144. print()
  145. if __name__ == "__main__":
  146. main()