A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

220 linhas
8.8 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. """
  23. EarwigBot's Wiki Toolset: Misc Functions
  24. This module, a component of the wiki package, contains miscellaneous functions
  25. that are not methods of any class, like get_site().
  26. There's no need to import this module explicitly. All functions here are
  27. automatically available from earwigbot.wiki.
  28. """
  29. from cookielib import LWPCookieJar, LoadError
  30. import errno
  31. from getpass import getpass
  32. from os import chmod, path
  33. import platform
  34. import stat
  35. import earwigbot
  36. from earwigbot.config import config
  37. from earwigbot.wiki.exceptions import SiteNotFoundError
  38. from earwigbot.wiki.site import Site
  39. __all__ = ["get_site", "add_site", "del_site"]
  40. _cookiejar = None
  41. def _load_config():
  42. """Called by a config-requiring function, such as get_site(), when config
  43. has not been loaded. This will usually happen only if we're running code
  44. directly from Python's interpreter and not the bot itself, because
  45. earwigbot.py or core/main.py will already call these functions.
  46. """
  47. is_encrypted = config.load()
  48. if is_encrypted: # Passwords in the config file are encrypted
  49. key = getpass("Enter key to unencrypt bot passwords: ")
  50. config._decryption_key = key
  51. config.decrypt(config.wiki, "password")
  52. def _get_cookiejar():
  53. """Returns a LWPCookieJar object loaded from our .cookies file. The same
  54. one is returned every time.
  55. The .cookies file is located in the project root, same directory as
  56. config.json and earwigbot.py. If it doesn't exist, we will create the file
  57. and set it to be readable and writeable only by us. If it exists but the
  58. information inside is bogus, we will ignore it.
  59. This is normally called by _get_site_object_from_dict() (in turn called by
  60. get_site()), and the cookiejar is passed to our Site's constructor, used
  61. when it makes API queries. This way, we can easily preserve cookies between
  62. sites (e.g., for CentralAuth), making logins easier.
  63. """
  64. global _cookiejar
  65. if _cookiejar is not None:
  66. return _cookiejar
  67. cookie_file = path.join(config.root_dir, ".cookies")
  68. _cookiejar = LWPCookieJar(cookie_file)
  69. try:
  70. _cookiejar.load()
  71. except LoadError:
  72. pass # File contains bad data, so ignore it completely
  73. except IOError as e:
  74. if e.errno == errno.ENOENT: # "No such file or directory"
  75. # Create the file and restrict reading/writing only to the owner,
  76. # so others can't peak at our cookies:
  77. open(cookie_file, "w").close()
  78. chmod(cookie_file, stat.S_IRUSR|stat.S_IWUSR)
  79. else:
  80. raise
  81. return _cookiejar
  82. def _get_site_object_from_dict(name, d):
  83. """Return a Site object based on the contents of a dict, probably acquired
  84. through our config file, and a separate name.
  85. """
  86. project = d.get("project")
  87. lang = d.get("lang")
  88. base_url = d.get("baseURL")
  89. article_path = d.get("articlePath")
  90. script_path = d.get("scriptPath")
  91. sql = d.get("sql", {})
  92. namespaces = d.get("namespaces", {})
  93. login = (config.wiki.get("username"), config.wiki.get("password"))
  94. cookiejar = _get_cookiejar()
  95. user_agent = config.wiki.get("userAgent")
  96. assert_edit = config.wiki.get("assert")
  97. maxlag = config.wiki.get("maxlag")
  98. search_config = config.wiki.get("search")
  99. if user_agent:
  100. user_agent = user_agent.replace("$1", earwigbot.__version__)
  101. user_agent = user_agent.replace("$2", platform.python_version())
  102. for key, value in namespaces.items(): # Convert string keys to integers
  103. del namespaces[key]
  104. try:
  105. namespaces[int(key)] = value
  106. except ValueError: # Data is broken, ignore it
  107. namespaces = None
  108. break
  109. return Site(name=name, project=project, lang=lang, base_url=base_url,
  110. article_path=article_path, script_path=script_path, sql=sql,
  111. namespaces=namespaces, login=login, cookiejar=cookiejar,
  112. user_agent=user_agent, assert_edit=assert_edit, maxlag=maxlag,
  113. search_config=search_config)
  114. def get_site(name=None, project=None, lang=None):
  115. """Returns a Site instance based on information from our config file.
  116. With no arguments, returns the default site as specified by our config
  117. file. This is default = config.wiki["defaultSite"];
  118. config.wiki["sites"][default].
  119. With `name` specified, returns the site specified by
  120. config.wiki["sites"][name].
  121. With `project` and `lang` specified, returns the site specified by the
  122. member of config.wiki["sites"], `s`, for which s["project"] == project and
  123. s["lang"] == lang.
  124. We will attempt to login to the site automatically
  125. using config.wiki["username"] and config.wiki["password"] if both are
  126. defined.
  127. Specifying a project without a lang or a lang without a project will raise
  128. TypeError. If all three args are specified, `name` will be first tried,
  129. then `project` and `lang`. If, with any number of args, a site cannot be
  130. found in the config, SiteNotFoundError is raised.
  131. """
  132. # Check if config has been loaded, and load it if it hasn't:
  133. if not config.is_loaded():
  134. _load_config()
  135. # Someone specified a project without a lang (or a lang without a project)!
  136. if (project is None and lang is not None) or (project is not None and
  137. lang is None):
  138. e = "Keyword arguments 'lang' and 'project' must be specified together."
  139. raise TypeError(e)
  140. # No args given, so return our default site (project is None implies lang
  141. # is None, so we don't need to add that in):
  142. if name is None and project is None:
  143. try:
  144. default = config.wiki["defaultSite"]
  145. except KeyError:
  146. e = "Default site is not specified in config."
  147. raise SiteNotFoundError(e)
  148. try:
  149. site = config.wiki["sites"][default]
  150. except KeyError:
  151. e = "Default site specified by config is not in the config's sites list."
  152. raise SiteNotFoundError(e)
  153. return _get_site_object_from_dict(default, site)
  154. # Name arg given, but don't look at others unless `name` isn't found:
  155. if name is not None:
  156. try:
  157. site = config.wiki["sites"][name]
  158. except KeyError:
  159. if project is None: # Implies lang is None, so only name was given
  160. e = "Site '{0}' not found in config.".format(name)
  161. raise SiteNotFoundError(e)
  162. for sitename, site in config.wiki["sites"].items():
  163. if site["project"] == project and site["lang"] == lang:
  164. return _get_site_object_from_dict(sitename, site)
  165. e = "Neither site '{0}' nor site '{1}:{2}' found in config."
  166. e.format(name, project, lang)
  167. raise SiteNotFoundError(e)
  168. else:
  169. return _get_site_object_from_dict(name, site)
  170. # If we end up here, then project and lang are both not None:
  171. for sitename, site in config.wiki["sites"].items():
  172. if site["project"] == project and site["lang"] == lang:
  173. return _get_site_object_from_dict(sitename, site)
  174. e = "Site '{0}:{1}' not found in config.".format(project, lang)
  175. raise SiteNotFoundError(e)
  176. def add_site():
  177. """STUB: config editing is required first.
  178. Returns True if the site was added successfully or False if the site was
  179. already in our config. Raises ConfigError if saving the updated file failed
  180. for some reason."""
  181. pass
  182. def del_site(name):
  183. """STUB: config editing is required first.
  184. Returns True if the site was removed successfully or False if the site was
  185. not in our config originally. Raises ConfigError if saving the updated file
  186. failed for some reason."""
  187. pass