A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

functions.py 7.1 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. # -*- coding: utf-8 -*-
  2. """
  3. EarwigBot's Wiki Toolset: Misc Functions
  4. This module, a component of the wiki package, contains miscellaneous functions
  5. that are not methods of any class, like get_site().
  6. There's no need to import this module explicitly. All functions here are
  7. automatically available from wiki.
  8. """
  9. from cookielib import LWPCookieJar, LoadError
  10. import errno
  11. from getpass import getpass
  12. from os import chmod, path
  13. import stat
  14. import config
  15. from wiki.exceptions import SiteNotFoundError
  16. from wiki.site import Site
  17. __all__ = ["get_site"]
  18. _cookiejar = None
  19. def _load_config():
  20. """Called by a config-requiring function, such as get_site(), when config
  21. has not been loaded. This will usually happen only if we're running code
  22. directly from Python's interpreter and not the bot itself, because
  23. earwigbot.py or core/main.py will already call these functions.
  24. """
  25. is_encrypted = config.verify_config()
  26. if is_encrypted: # passwords in the config file are encrypted
  27. key = getpass("Enter key to unencrypt bot passwords: ")
  28. config.parse_config(key)
  29. else:
  30. config.parse_config(None)
  31. def _get_cookiejar():
  32. """Returns a LWPCookieJar object loaded from our .cookies file. The same
  33. one is returned every time.
  34. The .cookies file is located in the project root, same directory as
  35. config.json and earwigbot.py. If it doesn't exist, we will create the file
  36. and set it to be readable and writeable only by us. If it exists but the
  37. information inside is bogus, we will ignore it.
  38. This is normally called by _get_site_object_from_dict() (in turn called by
  39. get_site()), and the cookiejar is passed to our Site's constructor, used
  40. when it makes API queries. This way, we can easily preserve cookies between
  41. sites (e.g., for CentralAuth), making logins easier.
  42. """
  43. global _cookiejar
  44. if _cookiejar is not None:
  45. return _cookiejar
  46. cookie_file = path.join(config.root_dir, ".cookies")
  47. _cookiejar = LWPCookieJar(cookie_file)
  48. try:
  49. _cookiejar.load()
  50. except LoadError:
  51. # file contains bad data, so ignore it completely
  52. pass
  53. except IOError as e:
  54. if e.errno == errno.ENOENT: # "No such file or directory"
  55. # create the file and restrict reading/writing only to the owner,
  56. # so others can't peak at our cookies
  57. open(cookie_file, "w").close()
  58. chmod(cookie_file, stat.S_IRUSR|stat.S_IWUSR)
  59. else:
  60. raise
  61. return _cookiejar
  62. def _get_site_object_from_dict(name, d):
  63. """Return a Site object based on the contents of a dict, probably acquired
  64. through our config file, and a separate name.
  65. """
  66. project = d.get("project")
  67. lang = d.get("lang")
  68. base_url = d.get("baseURL")
  69. article_path = d.get("articlePath")
  70. script_path = d.get("scriptPath")
  71. sql = (d.get("sqlServer"), d.get("sqlDB"))
  72. namespaces = d.get("namespaces", {})
  73. login = (config.wiki.get("username"), config.wiki.get("password"))
  74. cookiejar = _get_cookiejar()
  75. for key, value in namespaces.items(): # Convert string keys to integers
  76. del namespaces[key]
  77. try:
  78. namespaces[int(key)] = value
  79. except ValueError: # Data is broken, ignore it
  80. namespaces = None
  81. break
  82. return Site(name=name, project=project, lang=lang, base_url=base_url,
  83. article_path=article_path, script_path=script_path, sql=sql,
  84. namespaces=namespaces, login=login, cookiejar=cookiejar)
  85. def get_site(name=None, project=None, lang=None):
  86. """Returns a Site instance based on information from our config file.
  87. With no arguments, returns the default site as specified by our config
  88. file. This is default = config.wiki["defaultSite"];
  89. config.wiki["sites"][default].
  90. With `name` specified, returns the site specified by
  91. config.wiki["sites"][name].
  92. With `project` and `lang` specified, returns the site specified by the
  93. member of config.wiki["sites"], `s`, for which s["project"] == project and
  94. s["lang"] == lang.
  95. We will attempt to login to the site automatically
  96. using config.wiki["username"] and config.wiki["password"] if both are
  97. defined.
  98. Specifying a project without a lang or a lang without a project will raise
  99. TypeError. If all three args are specified, `name` will be first tried,
  100. then `project` and `lang`. If, with any number of args, a site cannot be
  101. found in the config, SiteNotFoundError is raised.
  102. """
  103. # check if config has been loaded, and load it if it hasn't
  104. if not config.is_config_loaded():
  105. _load_config()
  106. # someone specified a project without a lang (or a lang without a project)!
  107. if (project is None and lang is not None) or (project is not None and
  108. lang is None):
  109. e = "Keyword arguments 'lang' and 'project' must be specified together."
  110. raise TypeError(e)
  111. # no args given, so return our default site (project is None implies lang
  112. # is None, so we don't need to add that in)
  113. if name is None and project is None:
  114. try:
  115. default = config.wiki["defaultSite"]
  116. except KeyError:
  117. e = "Default site is not specified in config."
  118. raise SiteNotFoundError(e)
  119. try:
  120. site = config.wiki["sites"][default]
  121. except KeyError:
  122. e = "Default site specified by config is not in the config's sites list."
  123. raise SiteNotFoundError(e)
  124. return _get_site_object_from_dict(default, site)
  125. # name arg given, but don't look at others unless `name` isn't found
  126. if name is not None:
  127. try:
  128. site = config.wiki["sites"][name]
  129. except KeyError:
  130. if project is None: # implies lang is None, so only name was given
  131. e = "Site '{0}' not found in config.".format(name)
  132. raise SiteNotFoundError(e)
  133. for sitename, site in config.wiki["sites"].items():
  134. if site["project"] == project and site["lang"] == lang:
  135. return _get_site_object_from_dict(sitename, site)
  136. e = "Neither site '{0}' nor site '{1}:{2}' found in config."
  137. e.format(name, project, lang)
  138. raise SiteNotFoundError(e)
  139. else:
  140. return _get_site_object_from_dict(name, site)
  141. # if we end up here, then project and lang are both not None
  142. for sitename, site in config.wiki["sites"].items():
  143. if site["project"] == project and site["lang"] == lang:
  144. return _get_site_object_from_dict(sitename, site)
  145. e = "Site '{0}:{1}' not found in config.".format(project, lang)
  146. raise SiteNotFoundError(e)
  147. def add_site():
  148. """STUB: config editing is required first.
  149. Returns True if the site was added successfully or False if the site was
  150. already in our config. Raises ConfigError if saving the updated file failed
  151. for some reason."""
  152. pass
  153. def del_site(name):
  154. """STUB: config editing is required first.
  155. Returns True if the site was removed successfully or False if the site was
  156. not in our config originally. Raises ConfigError if saving the updated file
  157. failed for some reason."""
  158. pass