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.

atheme2json.py 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. #!/usr/bin/python3
  2. import json
  3. import logging
  4. import sys
  5. from collections import defaultdict
  6. def to_unixnano(timestamp):
  7. return int(timestamp) * (10**9)
  8. # include/atheme/channels.h
  9. CMODE_FLAG_TO_MODE = {
  10. 0x001: 'i', # CMODE_INVITE
  11. 0x010: 'n', # CMODE_NOEXT
  12. 0x080: 's', # CMODE_SEC
  13. 0x100: 't', # CMODE_TOPIC
  14. }
  15. def convert(infile):
  16. out = {
  17. 'version': 1,
  18. 'source': 'atheme',
  19. 'users': defaultdict(dict),
  20. 'channels': defaultdict(dict),
  21. }
  22. channel_to_founder = defaultdict(lambda: (None, None))
  23. for line in infile:
  24. line = line.rstrip('\r\n')
  25. parts = line.split(' ')
  26. category = parts[0]
  27. if category == 'MU':
  28. # user account
  29. # MU AAAAAAAAB shivaram $1$hcspif$nCm4r3S14Me9ifsOPGuJT. user@example.com 1600134392 1600467343 +sC default
  30. name = parts[2]
  31. user = {'name': name, 'hash': parts[3], 'email': parts[4], 'registeredAt': to_unixnano(parts[5])}
  32. out['users'][name].update(user)
  33. pass
  34. elif category == 'MN':
  35. # grouped nick
  36. # MN shivaram slingamn 1600218831 1600467343
  37. username, groupednick = parts[1], parts[2]
  38. if username != groupednick:
  39. user = out['users'][username]
  40. if 'additionalNicks' not in user:
  41. user['additionalNicks'] = []
  42. user['additionalNicks'].append(groupednick)
  43. elif category == 'MDU':
  44. if parts[2] == 'private:usercloak':
  45. username = parts[1]
  46. out['users'][username]['vhost'] = parts[3]
  47. elif category == 'MC':
  48. # channel registration
  49. # MC #mychannel 1600134478 1600467343 +v 272 0 0
  50. # MC #NEWCHANNELTEST 1602270889 1602270974 +vg 1 0 0 jaeger4
  51. chname = parts[1]
  52. chdata = out['channels'][chname]
  53. # XXX just give everyone +nt, regardless of lock status; they can fix it later
  54. chdata.update({'name': chname, 'registeredAt': to_unixnano(parts[2])})
  55. if parts[8] != '':
  56. chdata['key'] = parts[8]
  57. modes = {'n', 't'}
  58. mlock_on, mlock_off = int(parts[5]), int(parts[6])
  59. for flag, mode in CMODE_FLAG_TO_MODE.items():
  60. if flag & mlock_on != 0:
  61. modes.add(mode)
  62. for flag, mode in CMODE_FLAG_TO_MODE.items():
  63. if flag & mlock_off != 0:
  64. modes.remove(mode)
  65. chdata['modes'] = ''.join(modes)
  66. chdata['limit'] = int(parts[7])
  67. elif category == 'MDC':
  68. # auxiliary data for a channel registration
  69. # MDC #mychannel private:topic:setter s
  70. # MDC #mychannel private:topic:text hi again
  71. # MDC #mychannel private:topic:ts 1600135864
  72. chname = parts[1]
  73. category = parts[2]
  74. if category == 'private:topic:text':
  75. out['channels'][chname]['topic'] = parts[3]
  76. elif category == 'private:topic:setter':
  77. out['channels'][chname]['topicSetBy'] = parts[3]
  78. elif category == 'private:topic:ts':
  79. out['channels'][chname]['topicSetAt'] = to_unixnano(parts[3])
  80. elif category == 'CA':
  81. # channel access lists
  82. # CA #mychannel shivaram +AFORafhioqrstv 1600134478 shivaram
  83. chname, username, flags, set_at = parts[1], parts[2], parts[3], int(parts[4])
  84. chname = parts[1]
  85. chdata = out['channels'][chname]
  86. flags = parts[3]
  87. set_at = int(parts[4])
  88. if 'amode' not in chdata:
  89. chdata['amode'] = {}
  90. # see libathemecore/flags.c: +o is op, +O is autoop, etc.
  91. if 'F' in flags:
  92. # there can only be one founder
  93. preexisting_founder, preexisting_set_at = channel_to_founder[chname]
  94. if preexisting_founder is None or set_at < preexisting_set_at:
  95. chdata['founder'] = username
  96. channel_to_founder[chname] = (username, set_at)
  97. # but multiple people can receive the 'q' amode
  98. chdata['amode'][username] = 'q'
  99. elif 'q' in flags:
  100. chdata['amode'][username] = 'q'
  101. elif 'o' in flags or 'O' in flags:
  102. chdata['amode'][username] = 'o'
  103. elif 'h' in flags or 'H' in flags:
  104. chdata['amode'][username] = 'h'
  105. elif 'v' in flags or 'V' in flags:
  106. chdata['amode'][username] = 'v'
  107. else:
  108. pass
  109. # do some basic integrity checks
  110. for chname, chdata in out['channels'].items():
  111. founder = chdata.get('founder')
  112. if founder not in out['users']:
  113. raise ValueError("no user corresponding to channel founder", chname, chdata.get('founder'))
  114. return out
  115. def main():
  116. if len(sys.argv) != 3:
  117. raise Exception("Usage: atheme2json.py atheme_db output.json")
  118. with open(sys.argv[1]) as infile:
  119. output = convert(infile)
  120. with open(sys.argv[2], 'w') as outfile:
  121. json.dump(output, outfile)
  122. if __name__ == '__main__':
  123. logging.basicConfig()
  124. sys.exit(main())