jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/372790 )
Change subject: [IMPR] Improvements for User class
......................................................................
[IMPR] Improvements for User class
new methods:
- User.logevents(): return user logged activities
- User.last_event: return the last user activity
(btw. the first is the creation of the account)
- User.first_edit: return the first edit of the given user
- User.last_edit: return the last edit of the given user
other changes:
- User.contributions(): enable all site.usercontribs options
(including reverse which might be missing), extend the doc string
- User.uploadedImages(): use User.logevents() here
- User.is_thankable: Simplify result
- site.logevents(): refer mediawiki api but don't list log types here;
some other doc changes
tests added.
Change-Id: I1aa05649b47eff990f63d38663ff647604e7e260
---
M pywikibot/page.py
M pywikibot/site.py
M tests/user_tests.py
3 files changed, 122 insertions(+), 22 deletions(-)
Approvals:
Framawiki: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py
index 6cff93b..373d339 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -3462,6 +3462,40 @@
"""
self.site.unblockuser(self, reason)
+ def logevents(self, **kwargs):
+ """Yield user activities.
+
+ @keyword logtype: only iterate entries of this type
+ (see mediawiki api documentation for available types)
+ @type logtype: basestring
+ @keyword page: only iterate entries affecting this page
+ @type page: Page or basestring
+ @keyword namespace: namespace to retrieve logevents from
+ @type namespace: int or Namespace
+ @keyword start: only iterate entries from and after this Timestamp
+ @type start: Timestamp or ISO date string
+ @keyword end: only iterate entries up to and through this Timestamp
+ @type end: Timestamp or ISO date string
+ @keyword reverse: if True, iterate oldest entries first
+ (default: newest)
+ @type reverse: bool
+ @keyword tag: only iterate entries tagged with this tag
+ @type tag: basestring
+ @keyword total: maximum number of events to iterate
+ @type total: int
+ @rtype: iterable
+ """
+ return self.site.logevents(user=self.username, **kwargs)
+
+ @property
+ def last_event(self):
+ """Return last user activity.
+
+ @return: last user log entry
+ @rtype: LogEntry
+ """
+ return next(iter(self.logevents(total=1)))
+
@deprecated("contributions")
@deprecate_arg("limit", "total") # To be consistent with rest of framework
def editedPages(self, total=500):
@@ -3479,7 +3513,7 @@
yield item[0]
@deprecated_args(limit='total', namespace='namespaces')
- def contributions(self, total=500, namespaces=[]):
+ def contributions(self, total=500, **kwargs):
"""
Yield tuples describing this user edits.
@@ -3490,16 +3524,47 @@
@param total: limit result to this number of pages
@type total: int
- @param namespaces: only iterate links in these namespaces
- @type namespaces: list
+ @keyword start: Iterate contributions starting at this Timestamp
+ @keyword end: Iterate contributions ending at this Timestamp
+ @keyword reverse: Iterate oldest contributions first (default: newest)
+ @keyword namespaces: only iterate pages in these namespaces
+ @type namespaces: iterable of basestring or Namespace key,
+ or a single instance of those types. May be a '|' separated
+ list of namespace identifiers.
+ @keyword showMinor: if True, iterate only minor edits; if False and
+ not None, iterate only non-minor edits (default: iterate both)
+ @keyword top_only: if True, iterate only edits which are the latest
+ revision (default: False)
+ @return tuple of pywikibot.Page, revid, pywikibot.Timestamp, comment
+ @rtype: tuple
"""
for contrib in self.site.usercontribs(
- user=self.username, namespaces=namespaces, total=total):
+ user=self.username, total=total, **kwargs):
ts = pywikibot.Timestamp.fromISOformat(contrib['timestamp'])
yield (Page(self.site, contrib['title'], contrib['ns']),
contrib['revid'],
ts,
contrib.get('comment'))
+
+ @property
+ def first_edit(self):
+ """Return first user contribution.
+
+ @return: first user contribution entry
+ @return tuple of pywikibot.Page, revid, pywikibot.Timestamp, comment
+ @rtype: tuple
+ """
+ return next(self.contributions(reverse=True, total=1))
+
+ @property
+ def last_edit(self):
+ """Return last user contribution.
+
+ @return: last user contribution entry
+ @return tuple of pywikibot.Page, revid, pywikibot.Timestamp, comment
+ @rtype: tuple
+ """
+ return next(self.contributions(total=1))
@deprecate_arg("number", "total")
def uploadedImages(self, total=10):
@@ -3515,8 +3580,8 @@
"""
if not self.isRegistered():
raise StopIteration
- for item in self.site.logevents(
- logtype='upload', user=self.username, total=total):
+ for item in self.logevents(
+ logtype='upload', total=total):
yield (item.page(),
unicode(item.timestamp()),
item.comment(),
@@ -3535,14 +3600,8 @@
@rtype: bool
"""
- if self.isAnonymous():
- return False
- if not self.isRegistered():
- return False
- if 'bot' in self.groups():
- return False
-
- return True
+ return not (self.isAnonymous() or self.isRegistered()
+ or 'bot' in self.groups())
class WikibasePage(BasePage):
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 8accf26..cd7ff26 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -4511,10 +4511,8 @@
@note: logevents with logtype='block' only logs user blocks whereas
site.blocks iterates all blocks including IP ranges.
- @param logtype: only iterate entries of this type (see wiki
- documentation for available types, which will include "block",
- "protect", "rights", "delete", "upload", "move", "import",
- "patrol", "merge")
+ @param logtype: only iterate entries of this type
+ (see mediawiki api documentation for available types)
@type logtype: basestring
@param user: only iterate entries that match this user name
@type user: basestring
@@ -4775,8 +4773,11 @@
list of namespace identifiers.
@param showMinor: if True, iterate only minor edits; if False and
not None, iterate only non-minor edits (default: iterate both)
+ @param total: limit result to this number of pages
+ @type total: int
@param top_only: if True, iterate only edits which are the latest
- revision
+ revision (default: False)
+ @raises Error: either user or userprefix must be non-empty
@raises KeyError: a namespace identifier was not resolved
@raises TypeError: a namespace identifier has an inappropriate
type such as NoneType or bool
diff --git a/tests/user_tests.py b/tests/user_tests.py
index d604a97..f2a8b06 100644
--- a/tests/user_tests.py
+++ b/tests/user_tests.py
@@ -9,11 +9,11 @@
import pywikibot
+from pywikibot import Page, Timestamp, User
from pywikibot.exceptions import AutoblockUser
-from pywikibot.tools import suppress_warnings
-from pywikibot import User
+from pywikibot.tools import StringTypes, suppress_warnings
-from tests.aspects import TestCase, unittest
+from tests.aspects import DefaultSiteTestCase, TestCase, unittest
class TestUserClass(TestCase):
@@ -164,6 +164,46 @@
user.getUserTalkPage)
+class TestUserMethods(DefaultSiteTestCase):
+
+ """Test User methods with bot user."""
+
+ user = True
+
+ def test_contribution(self):
+ """Test the User.usercontribs() method."""
+ mysite = self.get_site()
+ user = User(mysite, mysite.user())
+ uc = list(user.contributions(total=10))
+ if not uc:
+ self.skipTest('User {0} has no contributions on site {1}.'
+ .format(mysite.user(), mysite))
+ self.assertLessEqual(len(uc), 10)
+ last = uc[0]
+ for contrib in uc:
+ self.assertIsInstance(contrib, tuple)
+ self.assertEqual(len(contrib), 4)
+ p, i, t, c = contrib
+ self.assertIsInstance(p, Page)
+ self.assertIsInstance(i, int)
+ self.assertIsInstance(t, Timestamp)
+ self.assertIsInstance(c, StringTypes)
+ self.assertEqual(last, user.last_edit)
+
+ def test_logevents(self):
+ """Test the User.logevents() method."""
+ mysite = self.get_site()
+ user = User(mysite, mysite.user())
+ le = list(user.logevents(total=10))
+ if not le:
+ self.skipTest('User {0} has no logevents on site {1}.'
+ .format(mysite.user(), mysite))
+ self.assertLessEqual(len(le), 10)
+ last = le[0]
+ self.assertTrue(all(event.user() == user.username for event in le))
+ self.assertEqual(last, user.last_event)
+
+
if __name__ == '__main__': # pragma: no cover
try:
unittest.main()
--
To view, visit https://gerrit.wikimedia.org/r/372790
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I1aa05649b47eff990f63d38663ff647604e7e260
Gerrit-Change-Number: 372790
Gerrit-PatchSet: 9
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Magul <tomasz.magulski(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/231754 )
Change subject: [IMPR] NewUsersEntry class retrieves User object for page() method
......................................................................
[IMPR] NewUsersEntry class retrieves User object for page() method
- Derive NewUsersEntry from UserTargetLogEntry which returns a User class
for page() method
- Do not inherit page method in UserTargetLogEntry. Otherwise instantiating
the User object may fail in NewUsersEntry
(and later in BlockEntry with next patch)
- It is no longer necessary to change the class in welcome.py
- Update logentry_tests to check User and FilePage class
- Update TestLogeventsFactoryGenerator to check User class
Bug: T168784
Change-Id: Ib5c0074248102b39f4375e849bec17812afa87c0
---
M pywikibot/logentries.py
M scripts/welcome.py
M tests/logentry_tests.py
M tests/pagegenerators_tests.py
4 files changed, 24 insertions(+), 13 deletions(-)
Approvals:
Framawiki: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py
index fc278f5..c12ff73 100644
--- a/pywikibot/logentries.py
+++ b/pywikibot/logentries.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Objects representing Mediawiki log entries."""
#
-# (C) Pywikibot team, 2007-2017
+# (C) Pywikibot team, 2007-2018
#
# Distributed under the terms of the MIT license.
#
@@ -115,6 +115,7 @@
@note: title may be missing in data dict e.g. by oversight action to
hide the title. In that case a KeyError exception will raise
+ @return: page on action was performed
@rtype: pywikibot.Page
@raise: KeyError: title was missing from log entry
"""
@@ -165,7 +166,7 @@
@rtype: pywikibot.User
"""
if not hasattr(self, '_page'):
- self._page = pywikibot.User(super(UserTargetLogEntry, self).page())
+ self._page = pywikibot.User(self.site, self.data['title'])
return self._page
@@ -394,7 +395,7 @@
return 'auto' in self._params and self._params['auto'] != 0
-class NewUsersEntry(LogEntry):
+class NewUsersEntry(UserTargetLogEntry):
"""New user log entry."""
diff --git a/scripts/welcome.py b/scripts/welcome.py
index 3156ede..1490051 100755
--- a/scripts/welcome.py
+++ b/scripts/welcome.py
@@ -157,7 +157,7 @@
# (C) Filnik, 2007-2011
# (C) Daniel Herding, 2007
# (C) Alex Shih-Han Lin, 2009-2010
-# (C) xqt, 2009-2017
+# (C) xqt, 2009-2018
# (C) Pywikibot team, 2008-2018
#
# Distributed under the terms of the MIT license.
@@ -676,7 +676,7 @@
start=start):
if ue.action() == 'create' or (
ue.action() == 'autocreate' and globalvar.welcomeAuto):
- yield pywikibot.User(ue.page())
+ yield ue.page()
def defineSign(self, force=False):
"""Setup signature."""
diff --git a/tests/logentry_tests.py b/tests/logentry_tests.py
index faf0921..50f900a 100644
--- a/tests/logentry_tests.py
+++ b/tests/logentry_tests.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Test logentries module."""
#
-# (C) Pywikibot team, 2015-2016
+# (C) Pywikibot team, 2015-2018
#
# Distributed under the terms of the MIT license.
#
@@ -11,7 +11,7 @@
import pywikibot
-from pywikibot.logentries import LogEntryFactory
+from pywikibot.logentries import LogEntryFactory, UserTargetLogEntry
from pywikibot.tools import (
MediaWikiVersion,
UnicodeType as unicode,
@@ -86,6 +86,10 @@
if 'title' in logentry.data: # title may be missing
if logtype == 'block' and logentry.isAutoblockRemoval:
self.assertIsInstance(logentry.page(), int)
+ elif isinstance(logentry, UserTargetLogEntry):
+ self.assertIsInstance(logentry.page(), pywikibot.User)
+ elif logtype == 'upload':
+ self.assertIsInstance(logentry.page(), pywikibot.FilePage)
else:
self.assertIsInstance(logentry.page(), pywikibot.Page)
else:
diff --git a/tests/pagegenerators_tests.py b/tests/pagegenerators_tests.py
index 43ca6ce..81386db 100755
--- a/tests/pagegenerators_tests.py
+++ b/tests/pagegenerators_tests.py
@@ -1316,7 +1316,8 @@
self.assertIsNotNone(gen)
pages = set(gen)
self.assertLessEqual(len(pages), 500)
- self.assertTrue(all(isinstance(item, pywikibot.Page) for item in pages))
+ self.assertTrue(all(isinstance(item, pywikibot.User)
+ for item in pages))
def test_logevents_default_multi(self):
"""Test old logevents option handling with limit argument."""
@@ -1326,7 +1327,8 @@
self.assertIsNotNone(gen)
pages = set(gen)
self.assertLessEqual(len(pages), 10)
- self.assertTrue(all(isinstance(item, pywikibot.Page) for item in pages))
+ self.assertTrue(all(isinstance(item, pywikibot.User)
+ for item in pages))
def test_logevents_ns(self):
"""Test old logevents option with limit argument and namespace."""
@@ -1336,7 +1338,8 @@
gen = gf.getCombinedGenerator()
self.assertIsNotNone(gen)
self.assertPagesInNamespaces(gen, 1)
- self.assertTrue(all(isinstance(item, pywikibot.Page) for item in gen))
+ self.assertTrue(all(isinstance(item, pywikibot.User)
+ for item in gen))
def test_logevents_user_multi(self):
"""Test old logevents option for a given user."""
@@ -1354,7 +1357,8 @@
# (no easy way of checking from pages)
self.assertLessEqual(len(pages), 10)
- self.assertTrue(all(isinstance(item, pywikibot.Page) for item in pages))
+ self.assertTrue(all(isinstance(item, pywikibot.User)
+ for item in pages))
def test_logevents_with_start_timestamp(self):
"""Test -logevents which uses timestamp for start."""
@@ -1368,7 +1372,8 @@
self.assertIsNotNone(gen)
pages = set(gen)
self.assertGreater(len(pages), 0)
- self.assertTrue(all(isinstance(item, pywikibot.Page) for item in pages))
+ self.assertTrue(all(isinstance(item, pywikibot.User)
+ for item in pages))
def test_logevents_with_start_and_end_timestamp(self):
"""Test -logevents which uses timestamps for start and end."""
@@ -1387,7 +1392,8 @@
self.assertIsNotNone(gen)
pages = set(gen)
self.assertEqual(len(pages), 1)
- self.assertTrue(all(isinstance(item, pywikibot.Page) for item in pages))
+ self.assertTrue(all(isinstance(item, pywikibot.User)
+ for item in pages))
class PageGeneratorIntersectTestCase(GeneratorIntersectTestCase,
--
To view, visit https://gerrit.wikimedia.org/r/231754
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib5c0074248102b39f4375e849bec17812afa87c0
Gerrit-Change-Number: 231754
Gerrit-PatchSet: 15
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: Happy5214 <happy5214(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <Ladsgroup(a)gmail.com>
Gerrit-Reviewer: Magul <tomasz.magulski(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/423234 )
Change subject: Implement a workaround to generate family file for private wikis
......................................................................
Implement a workaround to generate family file for private wikis
After this change, upon detection of a private wiki during
a generate_family_file.py run, the user will be asked for their
username and password which enables the script to login into the
site, gather siteinfo, and generate the family file successfully.
Bug: T153891
Change-Id: I9993340d76aeed6734f371f40ef65624601c62ea
---
M pywikibot/data/api.py
M pywikibot/site_detect.py
2 files changed, 20 insertions(+), 4 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 1f8ca1f..e1c41ae 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -3079,7 +3079,7 @@
if MediaWikiVersion(self.site.version()) >= MediaWikiVersion('1.27'):
login_request["lgtoken"] = self.get_login_token()
- self.site._loginstatus = -2
+ self.site._loginstatus = -2 # IN_PROGRESS
while True:
login_result = login_request.submit()
if u"login" not in login_result:
diff --git a/pywikibot/site_detect.py b/pywikibot/site_detect.py
index a9dea68..56c9b62 100644
--- a/pywikibot/site_detect.py
+++ b/pywikibot/site_detect.py
@@ -15,6 +15,7 @@
import pywikibot
from pywikibot.comms.http import fetch
+from pywikibot import config
from pywikibot.exceptions import ServerError
from pywikibot.tools import MediaWikiVersion, PY2, PYTHON_VERSION
@@ -172,9 +173,24 @@
self.private_wiki = ('error' in info and
info['error']['code'] == 'readapidenied')
if self.private_wiki:
- return
-
- info = info['query']['general']
+ # user-config.py is not loaded because PYWIKIBOT2_NO_USER_CONFIG
+ # is set to '2' by generate_family_file.py.
+ # Prepare a temporary config for login.
+ username = pywikibot.input(
+ 'Private wiki detected. Login is required.\n'
+ 'Please enter your username?')
+ config.usernames['temporary_family'] = {'temporary_code': username}
+ # Setup a dummy family so that we can create a site object
+ fam = pywikibot.Family()
+ fam.name = 'temporary_family'
+ fam.scriptpath = lambda code: self.api[:-8] # without /api.php
+ fam.langs = {'temporary_code': self.server}
+ site = pywikibot.Site('temporary_code', fam)
+ site.version = lambda: str(self.version)
+ # Now the site object is able to login
+ info = site.siteinfo
+ else:
+ info = info['query']['general']
self.version = MediaWikiVersion.from_generator(info['generator'])
if self.version < MediaWikiVersion('1.17'):
return
--
To view, visit https://gerrit.wikimedia.org/r/423234
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I9993340d76aeed6734f371f40ef65624601c62ea
Gerrit-Change-Number: 423234
Gerrit-PatchSet: 3
Gerrit-Owner: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/350448 )
Change subject: [IMPR] Add BotPasswords in generate_user_files.py
......................................................................
[IMPR] Add BotPasswords in generate_user_files.py
Support it by generating a user-password.py file
For each different username entered, ask if user wants to save a
BotPassword (BotPassword name, BotPassword pass)
and save it in a user-password.py file that contain
(u'myusername', BotPassword(u'mybotpasswordname', u'mysecretbotpasswordpass'))
Doc: https://www.mediawiki.org/wiki/Manual:Pywikibot/BotPasswords
Bug: T143905
Change-Id: I35e30b6b94a360c16d365499c78863ac3aac1663
---
M generate_user_files.py
M pywikibot/config2.py
M pywikibot/tools/__init__.py
3 files changed, 98 insertions(+), 11 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/generate_user_files.py b/generate_user_files.py
index 4de8a47..977dcc1 100755
--- a/generate_user_files.py
+++ b/generate_user_files.py
@@ -204,20 +204,43 @@
# family , you can use '*'
{usernames}
+# The list of BotPasswords is saved in another file. Import it if needed.
+# See https://www.mediawiki.org/wiki/Manual:Pywikibot/BotPasswords to know how
+# use them.
+{botpasswords}
{config_text}"""
SMALL_CONFIG = ('# -*- coding: utf-8 -*-\n'
- u"from __future__ import absolute_import, unicode_literals\n"
- u"family = '{main_family}'\n"
+ 'from __future__ import absolute_import, unicode_literals\n'
+ "family = '{main_family}'\n"
"mylang = '{main_code}'\n"
- u"{usernames}\n")
+ '{usernames}\n'
+ '{botpasswords}\n')
+
+PASSFILE_CONFIG = """# This is an automatically generated file used to store
+# BotPasswords.
+#
+# As a simpler (but less secure) alternative to OAuth, MediaWiki allows bot
+# users to uses BotPasswords to limit the permissions given to a bot.
+# When using BotPasswords, each instance gets keys. This combination can only
+# access the API, not the normal web interface.
+#
+# See https://www.mediawiki.org/wiki/Manual:Pywikibot/BotPasswords for more
+# information.
+{botpasswords}"""
def create_user_config(args=None, force=False):
- """Create a user-config.py in base_dir."""
+ """
+ Create a user-config.py in base_dir.
+
+ Create a user-password.py if necessary.
+ """
_fnc = os.path.join(base_dir, "user-config.py")
- if file_exists(_fnc):
+ _fncpass = os.path.join(base_dir, 'user-password.py')
+ # TODO: T167573: better check for existing user-password file
+ if file_exists(_fnc) or file_exists(_fncpass):
return
if args and force and not config.verbose_output:
@@ -235,13 +258,48 @@
usernames += [get_site_and_lang(main_family, main_code,
main_username)]
+ botpasswords = []
if not main_username:
usernames = "# usernames['{0}']['{1}'] = u'MyUsername'".format(
main_family, main_code)
else:
+ # For each different username entered, ask if user wants to save a
+ # BotPassword (username, BotPassword name, BotPassword pass)
+ seen = set()
+ for username in usernames:
+ if username[2] in seen:
+ continue
+ seen.add(username[2])
+ if pywikibot.input_yn('Do you want to add a BotPassword for {0}?'
+ .format(username[2]),
+ force=force, default=False):
+ if not botpasswords:
+ pywikibot.output(
+ 'See https://www.mediawiki.org/wiki/'
+ 'Manual:Pywikibot/BotPasswords to know '
+ 'how to get codes.')
+ pywikibot.output('Please note that plain text in {0} and '
+ 'anyone with read access to that '
+ 'directory will be able read the file.'
+ .format(_fncpass))
+ message = 'BotPassword\'s "bot name" for {0}'.format(
+ username[2])
+ botpasswordname = pywikibot.input(message, force=force)
+ message = 'BotPassword\'s "password" for BotPassword "{0}" ' \
+ '(no characters will be shown)' \
+ .format(botpasswordname)
+ botpasswordpass = pywikibot.input(message, force=force,
+ password=True)
+ if botpasswordname and botpasswordpass:
+ botpasswords.append((username[2], botpasswordname,
+ botpasswordpass))
+
usernames = '\n'.join(
u"usernames['{0}']['{1}'] = u'{2}'".format(*username)
for username in usernames)
+ botpasswords = '\n'.join(
+ "('{0}', BotPassword('{1}', '{2}'))".format(*botpassword)
+ for botpassword in botpasswords)
config_text = ''
config_content = SMALL_CONFIG
@@ -288,12 +346,16 @@
pywikibot.exception()
try:
+ # Finally save user-config.py
with codecs.open(_fnc, "w", "utf-8") as f:
f.write(config_content.format(main_family=main_family,
main_code=main_code,
usernames=usernames,
- config_text=config_text))
-
+ config_text=config_text,
+ botpasswords='password_file = ' +
+ ('"user-password.py"'
+ if botpasswords
+ else 'None')))
pywikibot.output(u"'%s' written." % _fnc)
except:
try:
@@ -301,6 +363,23 @@
except:
pass
raise
+
+ if botpasswords:
+ # Save if necessary user-password.py
+ try:
+ # First create an empty file with good permissions, before writing
+ # in it
+ with codecs.open(_fncpass, 'w', 'utf-8') as f:
+ f.write('')
+ pywikibot.tools.file_mode_checker(_fncpass, mode=0o600,
+ quiet=True)
+ with codecs.open(_fncpass, 'w', 'utf-8') as f:
+ f.write(PASSFILE_CONFIG.format(botpasswords=botpasswords))
+ pywikibot.tools.file_mode_checker(_fncpass, mode=0o600)
+ pywikibot.output("'{0}' written.".format(_fncpass))
+ except EnvironmentError:
+ os.remove(_fncpass)
+ raise
def main(*args):
@@ -341,6 +420,7 @@
# Only give option for directory change if user-config.py already exists
# in the directory. This will repeat if user-config.py also exists in
# the requested directory.
+ # TODO: T167573: check for user-password.py too
if not force or config.verbose_output:
pywikibot.output(u'\nYour default user directory is "%s"' % base_dir)
while os.path.isfile(os.path.join(base_dir, "user-config.py")):
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index 78898cc..97203a3 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -218,11 +218,18 @@
# By default you are asked for a password on the terminal.
# A password file may be used, e.g. password_file = ".passwd".
# The path to the password file is relative to that of the user_config file.
-# The password file should consist of lines containing
-# Python tuples of any of the following formats:
+# The password file should consist of lines containing Python tuples of any
+# of the following formats:
# (code, family, username, password)
# (family, username, password)
# (username, password)
+# It's also possible (and safer) for bot users to use BotPasswords to limit
+# the permissions given to a bot. When using BotPasswords, each instance gets
+# keys. This combination can only access the API, not the normal web interface.
+# See https://www.mediawiki.org/wiki/Manual:Pywikibot/BotPasswords to know how
+# use them. In this case, the password file should contein a BotPassword object
+# in the following format:
+# (username, BotPassword(botname, botpassword))
password_file = None
# edit summary to use if not supplied by bot script
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index 7e1c9c1..4546304 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -1709,7 +1709,7 @@
return open_archive(filename, use_extension=use_extension)
-def file_mode_checker(filename, mode=0o600):
+def file_mode_checker(filename, mode=0o600, quiet=False):
"""Check file mode and update it, if needed.
@param filename: filename path
@@ -1723,7 +1723,7 @@
if stat.S_ISREG(st_mode) and (st_mode - stat.S_IFREG != mode):
os.chmod(filename, mode)
# re-read and check changes
- if os.stat(filename).st_mode != st_mode:
+ if os.stat(filename).st_mode != st_mode and not quiet:
warn(warn_str.format(filename, st_mode - stat.S_IFREG, mode))
--
To view, visit https://gerrit.wikimedia.org/r/350448
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I35e30b6b94a360c16d365499c78863ac3aac1663
Gerrit-Change-Number: 350448
Gerrit-PatchSet: 23
Gerrit-Owner: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: Magul <tomasz.magulski(a)gmail.com>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: Multichill <maarten(a)mdammers.nl>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zhuyifei1999 <zhuyifei1999(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>