PageRenderTime 22ms CodeModel.GetById 118ms RepoModel.GetById 24ms app.codeStats 0ms

/lib/ergo/core.py

https://github.com/temoon/ergo
Python | 309 lines | 275 code | 22 blank | 12 comment | 2 complexity | c8d502453dec23be872f5a1c6de46fd0 MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. """
  4. Core components.
  5. """
  6. import glob
  7. import logging
  8. import logging.handlers
  9. import os
  10. import threading
  11. import time
  12. import sys
  13. import yaml
  14. from aochat import (
  15. Chat, ChatError,
  16. AOSP_CHANNEL_JOIN,
  17. AOSP_PRIVATE_MESSAGE,
  18. AOSP_PRIVATE_CHANNEL_MESSAGE,
  19. AOSP_CHANNEL_MESSAGE,
  20. )
  21. COMMANDS = {
  22. # Dummy
  23. }
  24. class Error(Exception):
  25. """
  26. Base exception class.
  27. """
  28. pass
  29. class Config(dict):
  30. """
  31. Config.
  32. """
  33. def __init__(self, filename):
  34. dimensions = {
  35. "rk1": {
  36. "host": "chat.d1.funcom.com",
  37. "port": 7101,
  38. "name": "Atlantean",
  39. },
  40. "rk2": {
  41. "host": "chat.d2.funcom.com",
  42. "port": 7102,
  43. "name": "Rimor",
  44. },
  45. }
  46. config = {
  47. "general": {
  48. "include": "ergo.d",
  49. "log_level": "info",
  50. "log_filename": None,
  51. },
  52. "chat": {
  53. "prefix_private": "",
  54. "prefix_group": "#",
  55. },
  56. "ao": {
  57. "dimensions": [
  58. dimensions["rk1"],
  59. dimensions["rk2"],
  60. ],
  61. "accounts": [
  62. {
  63. "username": "ergo",
  64. "password": "",
  65. "dimension": dimensions["rk1"],
  66. "character": "Ergo",
  67. },
  68. ],
  69. },
  70. "commands": {
  71. # Dummy
  72. },
  73. }
  74. # Read settings
  75. if filename:
  76. config = self.merge(self.read(filename), config)
  77. # Merge command settings
  78. for filename in filter(os.path.isfile, glob.glob(os.path.join(config["general"]["include"], "*.conf"))):
  79. config = self.merge(config, self.read(filename))
  80. dict.__init__(self, config)
  81. @staticmethod
  82. def read(filename):
  83. """
  84. Read YAML config.
  85. """
  86. try:
  87. config = yaml.load(file(filename, "r")) or {}
  88. except IOError, error:
  89. raise Error("Config '%s' reading error [%d]: %s" % (filename, error.errno, error.strerror))
  90. except yaml.YAMLError, error:
  91. raise Error("Config '%s' parsing error in position (%s:%s): %s" % (filename, error.problem_mark.line + 1, error.problem_mark.column + 1, error.problem))
  92. return config
  93. @staticmethod
  94. def merge(original, default):
  95. """
  96. Merge two configs.
  97. """
  98. if issubclass(type(original), dict) and issubclass(type(default), dict):
  99. for key in default:
  100. if key in original:
  101. original[key] = Config.merge(original[key], default[key])
  102. else:
  103. original[key] = default[key]
  104. return original
  105. class Logger(logging.Logger):
  106. """
  107. Logger.
  108. """
  109. def __init__(self, level, filename):
  110. logging.Logger.__init__(self, "ergo")
  111. format = logging.Formatter("[%(asctime)s] %(threadName)s: %(message)s", "%Y-%m-%d %H:%M:%S %Z")
  112. handler = logging.FileHandler(filename) if filename else logging.StreamHandler(sys.stdout)
  113. handler.setLevel(logging.DEBUG)
  114. handler.setFormatter(format)
  115. smtp_handler = logging.handlers.SMTPHandler(
  116. mailhost = (config["general"]["smtp_host"], config["general"]["smtp_port"],),
  117. credentials = (config["general"]["smtp_username"], config["general"]["smtp_password"],),
  118. fromaddr = config["general"]["smtp_from"],
  119. toaddrs = config["general"]["smtp_to"],
  120. subject = "Occurrence of an error",
  121. )
  122. smtp_handler.setLevel(logging.ERROR)
  123. smtp_handler.setFormatter(format)
  124. self.setLevel(logging.getLevelName(level.upper()))
  125. self.addHandler(handler)
  126. self.addHandler(smtp_handler)
  127. class Instance(threading.Thread):
  128. """
  129. Bot instance.
  130. """
  131. def __init__(self, username, password, host, port, character, dimension):
  132. threading.Thread.__init__(self)
  133. # Thread settings
  134. self.name = "%s, %s" % (character, dimension,)
  135. # AO chat connection settings
  136. self.username = username
  137. self.password = password
  138. self.host = host
  139. self.port = port
  140. self.character_id = 0
  141. self.character_name = character
  142. self.clan_channel_id = 0
  143. def run(self):
  144. while True:
  145. try:
  146. # Connect to dimension
  147. chat = Chat(self.username, self.password, self.host, self.port)
  148. # Select character by name
  149. for character in chat.characters:
  150. if character.name == self.character_name:
  151. break
  152. else:
  153. log.critical("Unknown character: %s" % self.character_name)
  154. break
  155. # Check if online
  156. if character.online:
  157. log.critical("Character is online: %s" % character.name)
  158. break
  159. self.character_id = character.id
  160. # Login and listen chat
  161. chat.login(character.id)
  162. chat.start(self.callback)
  163. except (SystemExit, KeyboardInterrupt,):
  164. break
  165. except ChatError, error:
  166. log.error(error)
  167. continue
  168. except Exception, error:
  169. log.exception(error)
  170. continue
  171. finally:
  172. time.sleep(1)
  173. def reply_private(self, chat, player, message):
  174. chat.send_private_message(player.id, message)
  175. def reply_group(self, chat, message):
  176. chat.send_private_channel_message(self.character_id, message)
  177. def reply_clan(self, chat, message):
  178. chat.send_channel_message(self.clan_channel_id, message)
  179. def command(self, name, chat, player, args = []):
  180. """
  181. Execute command.
  182. """
  183. try:
  184. COMMANDS[name].callback(chat, self, player, args)
  185. except KeyError:
  186. self.reply_private(chat, player, "Unknown command: %s. Type 'help' for list commands." % name)
  187. except Exception, error:
  188. self.reply_private(chat, player, "Unexpected error occurred. Please, try again later.")
  189. log.exception(error)
  190. def callback(self, chat, packet):
  191. """
  192. Callback for incoming packet.
  193. """
  194. # Log packet
  195. log.debug(repr(packet))
  196. # Private message
  197. if packet.type == AOSP_PRIVATE_MESSAGE.type:
  198. prefix = config["chat"]["prefix_private"]
  199. # Private channel message
  200. elif packet.type == AOSP_PRIVATE_CHANNEL_MESSAGE.type:
  201. prefix = config["chat"]["prefix_group"]
  202. # Channel message
  203. elif packet.type == AOSP_CHANNEL_MESSAGE.type and packet.channel_id == self.clan_channel_id:
  204. prefix = config["chat"]["prefix_clan"]
  205. # Other packet
  206. else:
  207. if packet.type == AOSP_CHANNEL_JOIN.type and packet.channel_name == "Clan (name unknown)":
  208. self.clan_channel_id = packet.channel_id
  209. return
  210. player = Player(packet.character_id)
  211. message = packet.message.strip()
  212. # Parse message
  213. if message.startswith(prefix):
  214. args = filter(len, message.split(" "))
  215. command = args.pop(0).lstrip(prefix)
  216. if not command:
  217. return
  218. else:
  219. return
  220. # Execute command
  221. self.command(command, chat, player, args)
  222. class Player(object):
  223. """
  224. Player.
  225. """
  226. def __init__(self, id):
  227. self.id = id
  228. class Command(object):
  229. """
  230. Command interpreter.
  231. """
  232. def __init__(self, name, desc, callback, help = None):
  233. self.name = name
  234. self.desc = desc
  235. self.callback = callback
  236. self.help = help
  237. # Register command
  238. COMMANDS[name] = self