A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

299 řádky
11 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2009-2012 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. from logging import getLogger, NullHandler
  23. from time import gmtime, strptime
  24. from earwigbot.exceptions import UserNotFoundError
  25. from earwigbot.wiki import constants
  26. from earwigbot.wiki.page import Page
  27. __all__ = ["User"]
  28. class User(object):
  29. """
  30. **EarwigBot: Wiki Toolset: User**
  31. Represents a user on a given :py:class:`~earwigbot.wiki.site.Site`. Has
  32. methods for getting a bunch of information about the user, such as
  33. editcount and user rights, methods for returning the user's userpage and
  34. talkpage, etc.
  35. *Attributes:*
  36. - :py:attr:`site`: the user's corresponding Site object
  37. - :py:attr:`name`: the user's username
  38. - :py:attr:`exists`: ``True`` if the user exists, else ``False``
  39. - :py:attr:`userid`: an integer ID representing the user
  40. - :py:attr:`blockinfo`: information about any current blocks on the user
  41. - :py:attr:`groups`: a list of the user's groups
  42. - :py:attr:`rights`: a list of the user's rights
  43. - :py:attr:`editcount`: the number of edits made by the user
  44. - :py:attr:`registration`: the time the user registered
  45. - :py:attr:`emailable`: ``True`` if you can email the user, or ``False``
  46. - :py:attr:`gender`: the user's gender ("male"/"female"/"unknown")
  47. *Public methods:*
  48. - :py:meth:`reload`: forcibly reloads the user's attributes
  49. - :py:meth:`get_userpage`: returns a Page object representing the user's
  50. userpage
  51. - :py:meth:`get_talkpage`: returns a Page object representing the user's
  52. talkpage
  53. """
  54. def __init__(self, site, name, logger=None):
  55. """Constructor for new User instances.
  56. Takes two arguments, a Site object (necessary for doing API queries),
  57. and the name of the user, preferably without "User:" in front, although
  58. this prefix will be automatically removed by the API if given.
  59. You can also use site.get_user() instead, which returns a User object,
  60. and is preferred.
  61. We won't do any API queries yet for basic information about the user -
  62. save that for when the information is requested.
  63. """
  64. self._site = site
  65. self._name = name
  66. # Set up our internal logger:
  67. if logger:
  68. self._logger = logger
  69. else: # Just set up a null logger to eat up our messages:
  70. self._logger = getLogger("earwigbot.wiki")
  71. self._logger.addHandler(NullHandler())
  72. def __repr__(self):
  73. """Return the canonical string representation of the User."""
  74. return "User(name={0!r}, site={1!r})".format(self._name, self._site)
  75. def __str__(self):
  76. """Return a nice string representation of the User."""
  77. return '<User "{0}" of {1}>'.format(self.name, str(self.site))
  78. def _get_attribute(self, attr):
  79. """Internally used to get an attribute by name.
  80. We'll call _load_attributes() to get this (and all other attributes)
  81. from the API if it is not already defined.
  82. Raises UserNotFoundError if a nonexistant user prevents us from
  83. returning a certain attribute.
  84. """
  85. if not hasattr(self, attr):
  86. self._load_attributes()
  87. if not self._exists:
  88. e = u"User '{0}' does not exist.".format(self._name)
  89. raise UserNotFoundError(e)
  90. return getattr(self, attr)
  91. def _load_attributes(self):
  92. """Internally used to load all attributes from the API.
  93. Normally, this is called by _get_attribute() when a requested attribute
  94. is not defined. This defines it.
  95. """
  96. props = "blockinfo|groups|rights|editcount|registration|emailable|gender"
  97. result = self.site.api_query(action="query", list="users",
  98. ususers=self._name, usprop=props)
  99. res = result["query"]["users"][0]
  100. # normalize our username in case it was entered oddly
  101. self._name = res["name"]
  102. try:
  103. self._userid = res["userid"]
  104. except KeyError: # userid is missing, so user does not exist
  105. self._exists = False
  106. return
  107. self._exists = True
  108. try:
  109. self._blockinfo = {
  110. "by": res["blockedby"],
  111. "reason": res["blockreason"],
  112. "expiry": res["blockexpiry"]
  113. }
  114. except KeyError:
  115. self._blockinfo = False
  116. self._groups = res["groups"]
  117. try:
  118. self._rights = res["rights"].values()
  119. except AttributeError:
  120. self._rights = res["rights"]
  121. self._editcount = res["editcount"]
  122. reg = res["registration"]
  123. try:
  124. self._registration = strptime(reg, "%Y-%m-%dT%H:%M:%SZ")
  125. except TypeError:
  126. # Sometimes the API doesn't give a date; the user's probably really
  127. # old. There's nothing else we can do!
  128. self._registration = gmtime(0)
  129. try:
  130. res["emailable"]
  131. except KeyError:
  132. self._emailable = False
  133. else:
  134. self._emailable = True
  135. self._gender = res["gender"]
  136. @property
  137. def site(self):
  138. """The user's corresponding Site object."""
  139. return self._site
  140. @property
  141. def name(self):
  142. """The user's username.
  143. This will never make an API query on its own, but if one has already
  144. been made by the time this is retrieved, the username may have been
  145. "normalized" from the original input to the constructor, converted into
  146. a Unicode object, with underscores removed, etc.
  147. """
  148. return self._name
  149. @property
  150. def exists(self):
  151. """``True`` if the user exists, or ``False`` if they do not.
  152. Makes an API query only if we haven't made one already.
  153. """
  154. if not hasattr(self, "_exists"):
  155. self._load_attributes()
  156. return self._exists
  157. @property
  158. def userid(self):
  159. """An integer ID used by MediaWiki to represent the user.
  160. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  161. does not exist. Makes an API query only if we haven't made one already.
  162. """
  163. return self._get_attribute("_userid")
  164. @property
  165. def blockinfo(self):
  166. """Information about any current blocks on the user.
  167. If the user is not blocked, returns ``False``. If they are, returns a
  168. dict with three keys: ``"by"`` is the blocker's username, ``"reason"``
  169. is the reason why they were blocked, and ``"expiry"`` is when the block
  170. expires.
  171. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  172. does not exist. Makes an API query only if we haven't made one already.
  173. """
  174. return self._get_attribute("_blockinfo")
  175. @property
  176. def groups(self):
  177. """A list of groups this user is in, including ``"*"``.
  178. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  179. does not exist. Makes an API query only if we haven't made one already.
  180. """
  181. return self._get_attribute("_groups")
  182. @property
  183. def rights(self):
  184. """A list of this user's rights.
  185. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  186. does not exist. Makes an API query only if we haven't made one already.
  187. """
  188. return self._get_attribute("_rights")
  189. @property
  190. def editcount(self):
  191. """Returns the number of edits made by the user.
  192. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  193. does not exist. Makes an API query only if we haven't made one already.
  194. """
  195. return self._get_attribute("_editcount")
  196. @property
  197. def registration(self):
  198. """The time the user registered as a :py:class:`time.struct_time`.
  199. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  200. does not exist. Makes an API query only if we haven't made one already.
  201. """
  202. return self._get_attribute("_registration")
  203. @property
  204. def emailable(self):
  205. """``True`` if the user can be emailed, or ``False`` if they cannot.
  206. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  207. does not exist. Makes an API query only if we haven't made one already.
  208. """
  209. return self._get_attribute("_emailable")
  210. @property
  211. def gender(self):
  212. """The user's gender.
  213. Can return either ``"male"``, ``"female"``, or ``"unknown"``, if they
  214. did not specify it.
  215. Raises :py:exc:`~earwigbot.exceptions.UserNotFoundError` if the user
  216. does not exist. Makes an API query only if we haven't made one already.
  217. """
  218. return self._get_attribute("_gender")
  219. def reload(self):
  220. """Forcibly reload the user's attributes.
  221. Emphasis on *reload*: this is only necessary if there is reason to
  222. believe they have changed.
  223. """
  224. self._load_attributes()
  225. def get_userpage(self):
  226. """Return a Page object representing the user's userpage.
  227. No checks are made to see if it exists or not. Proper site namespace
  228. conventions are followed.
  229. """
  230. prefix = self.site.namespace_id_to_name(constants.NS_USER)
  231. pagename = ':'.join((prefix, self._name))
  232. return Page(self.site, pagename)
  233. def get_talkpage(self):
  234. """Return a Page object representing the user's talkpage.
  235. No checks are made to see if it exists or not. Proper site namespace
  236. conventions are followed.
  237. """
  238. prefix = self.site.namespace_id_to_name(constants.NS_USER_TALK)
  239. pagename = ':'.join((prefix, self._name))
  240. return Page(self.site, pagename)