main.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. ## ---- imports ----
  2. ## importing libraries required for the WS server;
  3. ## the only dependency you have to install manually
  4. ## is 'websocket' module (you can probably do that
  5. ## by running 'pip install websockets' in terminal)
  6. ## Note: if you are using hyperbola like me,
  7. ## you won't find python-pip in repositories
  8. ## as it is not considered free software.
  9. ## But you can use pacman to install python packages
  10. ## instead: for this project you can simply run
  11. ## $ doas pacman -Sy python-websockets
  12. ## to install the websockets package
  13. import websockets
  14. import asyncio
  15. import codecs
  16. import time
  17. import json
  18. import sys
  19. ## ---- reading settings from every possible place ----
  20. ## trying to read the config file (otherwise setting
  21. ## everything to default)
  22. try:
  23. ## trying to read config file to get settings
  24. settings = codecs.open("config.json", "r", encoding = "UTF-8").read()
  25. except:
  26. ## using defaults if there is no file or it is
  27. ## unaccessible for whatever reason
  28. settings = {
  29. "port": 6392,
  30. "visible_message_history_length": 100,
  31. }
  32. ## also reading arguments given from the cli (argv)
  33. ## and overwriting settings from the file (or defauts)
  34. possible_arguments_list_shorts = {
  35. "p": "port",
  36. "h": "visible_message_history_length",
  37. }
  38. possible_arguments_list_longs = {
  39. "port": "port",
  40. "history-length": "visible_message_history_length",
  41. }
  42. current_arg = ""
  43. for arg in sys.argv[1:]:
  44. if (arg == "--help"):
  45. print( codecs.open("src/help.txt", "r", encoding = "UTF-8").read() )
  46. sys.exit(0)
  47. elif (arg == "--version"):
  48. print("Version: 0.1 (developer_preview)")
  49. sys.exit(0)
  50. elif arg[:2] == "--":
  51. if arg[2:] in possible_arguments_list_longs:
  52. current_arg = possible_arguments_list_longs[arg[2:]]
  53. else:
  54. print("[ERROR] Unrecognized argument: {}\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
  55. sys.exit(1)
  56. elif arg[:1] == "-":
  57. if arg[1:] in possible_arguments_list_shorts:
  58. current_arg = possible_arguments_list_shorts[arg[1:]]
  59. else:
  60. print("[ERROR] Unrecognized argument: {}\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
  61. sys.exit(1)
  62. else:
  63. if current_arg != "":
  64. settings[current_arg] = arg
  65. current_arg = ""
  66. else:
  67. print("[ERROR] Positional argument '{}' detected, but none expected\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
  68. sys.exit(1)
  69. ## certain settings values have to be converted
  70. ## into proper data types; doing it there
  71. settings["visible_message_history_length"] = int(settings["visible_message_history_length"])
  72. ## ---- defining useful server functions ----
  73. ## place for future code
  74. ## ---- actual server code ----
  75. ## sent messages array
  76. messages = []
  77. ## defining client handler for websockets
  78. async def client_handler(websocket, requested_path):
  79. ## client tells us if it is
  80. ## a listener or a sender
  81. responce = await websocket.recv()
  82. client_role = json.loads(responce)
  83. ## listener code
  84. if client_role == "listener_basic":
  85. ## remembering last message ID to send only new messages
  86. last_message_id = len(messages) - settings["visible_message_history_length"]
  87. ## anti infinite loop patch
  88. if last_message_id < 0:
  89. last_message_id = 0
  90. ## using "try" for safety
  91. try:
  92. ## always try to send new messages
  93. while True:
  94. new_messages_amount = ((len(messages)) - last_message_id)
  95. if new_messages_amount > 0:
  96. last_message_id += new_messages_amount
  97. print("new_messages_amount = {}".format(new_messages_amount))
  98. if new_messages_amount == 1:
  99. print("pass 1")
  100. msg = messages[-1]
  101. await websocket.send( json.dumps(msg) )
  102. else:
  103. print("pass 2")
  104. msg = messages[-new_messages_amount:]
  105. for i in msg:
  106. print("pass 3")
  107. await websocket.send( json.dumps(i) )
  108. await asyncio.sleep(0.2)
  109. else:
  110. await asyncio.sleep(1)
  111. ## closing connection if anything goes wrong
  112. except Exception as e:
  113. print("[client_handler] Warning: got exception \"{}\", closing connection...".format(e))
  114. return 1
  115. ## sender code
  116. elif client_role == "sender_basic":
  117. try:
  118. ## always listening for messages
  119. while True:
  120. new_message = await websocket.recv()
  121. ## "sanity check"
  122. try:
  123. msg = json.loads(new_message)
  124. ## checking if all required objects are present
  125. if not ( ("data" in msg) and ("nickname" in msg["data"]) and ("content" in msg["data"]) ):
  126. print("[client_handler:sender_basic:sanity_check] Warning: message is not formatted properly, closing connection...")
  127. return 1
  128. except Exception as e:
  129. print("[client_handler:sender_basic:sanity_check] Warning: unexpected exception \"{}\", closing connection...".format(e))
  130. return 1
  131. ## add server timestamp to the message
  132. msg["timestamp_server"] = time.time()
  133. ## add message to global list
  134. messages.append(msg)
  135. ## closing connection on exceptions
  136. except Exception as e:
  137. print("[client_handler:sender_basic] Warning: unexpected exception \"{}\", closing connection...".format(e))
  138. ## getting rid of unknown client types
  139. else:
  140. print("[client_handler] Warning: client tried to connect as \"{}\", closing connection...".format(client_role))
  141. return 0
  142. ## starting websocket server
  143. websocket_server = websockets.serve(client_handler, "", settings["port"])
  144. asyncio.get_event_loop().run_until_complete(websocket_server)
  145. asyncio.get_event_loop().run_forever()