├── .github
└── no-response.yml
├── .gitignore
├── LICENSE-zhcn.txt
├── LICENSE.md
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── README.rst
├── bin
└── BingWallpaper
├── build.spec
├── makeinstaller.nsi
├── pybingwallpaper
├── __init__.py
├── bingwallpaper.py
├── config.py
├── log.py
├── main.py
├── ntlmauth
│ ├── HTTPNtlmAuthHandler.py
│ ├── U32.py
│ ├── __init__.py
│ ├── des.py
│ ├── des_c.py
│ ├── des_data.py
│ └── ntlm.py
├── py23.py
├── record.py
├── rev.py
├── setter.py
├── webutil.py
└── winsetter.py
├── requirements-pack.txt
├── requirements-py2.txt
├── requirements-win.txt
├── res
└── bingwallpaper.ico
├── setup.cfg
├── setup.py
├── test
├── testapppath.py
└── testconfigmodule.py
└── wiki
└── pics
├── database
├── open-database.png
├── tree-view-and-data-tab.png
├── view-image.png
└── view-record.png
├── install-startup.png
└── startup-property.png
/.github/no-response.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/.github/no-response.yml
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build
11 | eggs
12 | parts
13 | bin
14 | !/bin/
15 | var
16 | sdist
17 | develop-eggs
18 | .installed.cfg
19 | lib
20 | lib64
21 |
22 | # Installer logs
23 | pip-log.txt
24 |
25 | # Unit test / coverage reports
26 | .coverage
27 | .tox
28 | nosetests.xml
29 |
30 | # Translations
31 | *.mo
32 |
33 | # Mr Developer
34 | .mr.developer.cfg
35 | .project
36 | .pydevproject
37 |
38 | # Data Files
39 | data/*
40 | !data/.gitkeep
41 |
42 | # Vim files
43 | .*.s[a-w][a-z]
44 | *.un~
45 | Session.vim
46 | .netrwhist
47 | *~
48 | tags
49 |
50 | # installer
51 | pybingwp-*.exe
52 |
53 | # config file
54 | settings.conf
55 |
56 | # Spyder projects
57 | .spyderworkspace
58 | .spyderproject
59 |
60 | # database record
61 | *.db
62 | .ropeproject/
63 |
64 | venv/
65 |
66 | .idea/
67 | .vscode/
68 | .mypy_cache/
69 |
--------------------------------------------------------------------------------
/LICENSE-zhcn.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/LICENSE-zhcn.txt
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 genzj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 genzj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.md
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyBingWallpaper
2 |
3 | ## What pybingwallpaper does
4 |
5 | Download the wallpaper offered by [*Bing.com*](https://www.bing.com) and set it
6 | current wallpaper background.
7 |
8 | Another project for the same purpose but download from National Geographic can
9 | be found [here](https://github.com/genzj/pyngwallpaper)
10 |
11 | ----------
12 |
13 | ## Auto startup
14 |
15 | ### Windows
16 |
17 | Download installer from [releases page](https://github.com/genzj/pybingwallpaper/releases) and install it.
18 |
19 |
**Note: Please backup the `settings.conf` under the installation path before upgrading!**
20 | **注意:建议升级前备份安装目录下的`settings.conf`文件!**
21 |
22 | A shortcut will be created in your startup folder. You can edit the configuration to adjust features.
23 |
24 | ### Linux with Gnome
25 |
26 | You just need to add a startup application:
27 |
28 | gnome-session-properties
29 |
30 | then *add* a startup program with:
31 |
32 | Name: pybingwallpaper
33 | Command: python3 /path/to/pybingwallpaper/src/main.py -b
34 | Comment: download and update wallpaper!
35 |
36 | You can also append arguments in *Command* box.
37 |
38 | ----------
39 |
40 | ## Usage
41 |
42 | usage: pybingwallpaper [-h] [-v] [--config-file CONFIG_FILE]
43 | [--generate-config] [-b] [--foreground]
44 | [-c {au,br,ca,cn,de,fr,jp,nz,us,uk,auto}]
45 | [--market MARKET] [-d] [-i INTERVAL] [-k]
46 | [-m {prefer,collect,highest,insist,manual,never}]
47 | [--collect COLLECT] [--image-size IMAGE_SIZE]
48 | [-o OFFSET] [--proxy-server PROXY_SERVER]
49 | [--proxy-port PROXY_PORT]
50 | [--proxy-username PROXY_USERNAME]
51 | [--proxy-password PROXY_PASSWORD] [--redownload]
52 | [-s {no,win,gnome2,gnome3}] [--setter-args SETTER_ARGS]
53 | [-t OUTPUT_FOLDER] [--database-file DATABASE_FILE]
54 | [--database-no-image] [--server {global,china,custom}]
55 | [--custom-server CUSTOMSERVER]
56 |
57 | Download the wallpaper offered by Bing.com and set it current wallpaper
58 | background.
59 |
60 | optional arguments:
61 | -h, --help show this help message and exit
62 | -v, --version show version information
63 | --config-file CONFIG_FILE
64 | 'specify configuration file, use `settings.conf` in
65 | installation directory by default.
66 | --generate-config generate a configuration file containing arguments
67 | specified in command line and exit. to generate
68 | default config file, issue without other command
69 | arguments. path and name of configuration file can be
70 | specified with --config-file
71 | -b, --background work in background (daemon mode) and check wallpaper
72 | periodically (interval can be set by --interval).
73 | --foreground force working in foreground mode to cancel the effect
74 | of `background` in config file.
75 | -c {au,br,ca,cn,de,fr,jp,nz,us,uk,auto}, --country {au,br,ca,cn,de,fr,jp,nz,us
76 | ,uk,auto}
77 | select country code sent to bing.com. bing.com in
78 | different countries may show different backgrounds.
79 | au: Australia br: Brazil ca: Canada cn: China
80 | de:Germany fr: France jp: Japan nz: New Zealand us:
81 | USA uk: United Kingdom auto: select country according
82 | to your IP address (by Bing.com) Note: only China(cn),
83 | New Zealand(nz) and USA(us) have high resolution
84 | (1920x1200) wallpapers; the rest offer 1366x768 only.
85 | --market MARKET specify market from which the wallpaper should be
86 | downloaded. Market is a more generic way to specify
87 | language-country of bing.com. The list of markets may
88 | grow sometimes, and different language of the same
89 | country may have different image, so consider using it
90 | instead of country. Market code should be specified in
91 | format 'xy-ab' such as en-us. Note: specify this
92 | parameter will override any settings to --country.
93 | -d, --debug enable debug outputs. The more --debug the more
94 | detailed the log will be
95 | -i INTERVAL, --interval INTERVAL
96 | interval between each two wallpaper checkings in unit
97 | of hours. applicable only in `background` mode. at
98 | lease 1 hour; 2 hours by default.
99 | -k, --keep-file-name keep the original filename. By default downloaded file
100 | will be renamed as 'wallpaper.jpg'. Keep file name
101 | will retain all downloaded photos
102 | -m {prefer,collect,highest,insist,manual,never,uhd}, --size-mode {prefer,collect,highest,insist,manual,never,uhd}
103 | set selecting strategy when wallpapers in different
104 | size are available (4K, 1920x1200, 1920x1080 and
105 | 1366x768). `prefer` (default) detect and download the
106 | highest available resolution; `insist` always use
107 | 1920x1200 resolution and ignore other pictures (Note:
108 | some countries have only normal size wallpapers, if
109 | `insist` is adopted with those sites, no wallpaper can
110 | be downloaded, see `--country` for more); `highest` is
111 | an alias of `prefer` `never` always use normal
112 | resolution (1366x768); `manual` use resolution
113 | specified in `--image-size` `collect` is obsolete and
114 | only kept for backward compatibility, equals `prefer`
115 | mode together with --collect=accompany option. `uhd`
116 | insists using 4K resolution, or abort wallpaper
117 | downloading if it's not available.
118 | --collect COLLECT items to be collected besides main wallpaper, can be
119 | assigned more than once in CLI or as comma separated
120 | list from config file. currently `accompany`, `video`
121 | and `hdvideo` are supported. `accompany` - some
122 | markets (i.e. en-ww) offers two wallpapers every day
123 | with same image but different Bing logo (English and
124 | Chinese respectively). enables this will download both
125 | original wallpaper and its accompanyings. `video` -
126 | bing sometimes (not everyday) release interesting
127 | animated mp4 background, use this to collect them.
128 | `hdvideo` - HD version of video.
129 | --image-size IMAGE_SIZE
130 | specify resolution of image to download. check
131 | `--size-mode` for more
132 | -o OFFSET, --offset OFFSET
133 | start downloading from the photo of 'N' days ago.
134 | specify 0 to download photo of today.
135 | --proxy-server PROXY_SERVER
136 | proxy server url, ex: http://10.1.1.1
137 | --proxy-port PROXY_PORT
138 | port of proxy server, default: 80
139 | --proxy-username PROXY_USERNAME
140 | optional username for proxy server authentication
141 | --proxy-password PROXY_PASSWORD
142 | optional password for proxy server authentication
143 | --redownload do not check history records. Download must be done.
144 | downloaded picture will still be recorded in history
145 | file.
146 | -s {no,win,gnome2,gnome3}, --setter {no,win,gnome2,gnome3}
147 | specify interface to be called for setting wallpaper.
148 | 'no' indicates downloading-only; 'gnome2/3' are only
149 | for Linux with gnome; 'win' is for Windows only.
150 | Customized setter can be added as dev doc described.
151 | Default: win for win32, gnome3 for Linux
152 | --setter-args SETTER_ARGS
153 | arguments for external setters
154 | -t OUTPUT_FOLDER, --output-folder OUTPUT_FOLDER
155 | specify the folder to store photos. Use
156 | '~/MyBingWallpapers' folder in Linux, 'C:/Documents
157 | and Settings//MyBingWallpapers in
158 | Windows XP or 'C:/Users//MyBingWallpapers' in Windows 7 by default
160 | --database-file DATABASE_FILE
161 | specify the sqlite3 database used to store meta info
162 | of photos. leave it blank to disable database storage.
163 | --database-no-image images will be embedded into database by default.
164 | Exclude images from database can reduce the size of
165 | database file.
166 | --server {global,china,custom}
167 | select bing server used for meta data and wallpaper
168 | pictures. it seems bing.com uses different servers and
169 | domain names in china. global: use bing.com of course.
170 | china: use s.cn.bing.net. (note: use this may freeze
171 | market or country to China zh-CN) custom: use the
172 | server specified in option "customserver"
173 | --custom-server CUSTOMSERVER
174 | specify server used for meta data and wallpaper photo.
175 | you need to set --server to 'custom' to enable the
176 | custom server address.
177 | ----------
178 |
179 | ## Release Note
180 |
181 | * **2021-03-19 1.6.0**
182 | * Support UHD resolution (#63)
183 | * `highest` mode is an alias of `prefer` now.
184 |
185 | * **2019-04-30 1.5.5**
186 | * Compatible with Bing's new URL format (accompany pictures this time, #55 #56)
187 |
188 | * **2019-03-15 1.5.4**
189 | * Compatible with Bing's new URL format
190 |
191 | * **2016-04-06 1.5.1**
192 | * Minor bug fix for Python 3.5 (#43)
193 | * Obsolete 1.5.0 due to MS Windows Defender raise (false) alarm on the
194 | installer (#44)
195 |
196 | * **2015-12-08 1.5.0**
197 | * Can collect video now! (#34)
198 | * Decouple collect mode from market setting (#35)
199 | * Better compatibility with win 8 and higher (#31 and #40)
200 | * Avoid deleting collected wallpaper at uninstallation (#33)
201 |
202 | * **2014-09-08 1.4.4**
203 | * Support configurable bing server address (#27)
204 |
205 | * **2014-04-18 1.4.4b01**
206 | * Support download records database (#18), you can build you own bing album now.
207 |
208 | * **2013-12-24 1.4.3**
209 | * Fix #13 enhance robustness of network connection status: retry in
210 | 60 seconds after network failure
211 | * Fix #14 download image with Chinese logo whenever a 1920x1200 picture is
212 | downloaded: add a collect mode, read
213 | [use collect mode](https://github.com/genzj/pybingwallpaper/wiki/Use-collect-mode)
214 | [使用收集模式](https://github.com/genzj/pybingwallpaper/wiki/%E4%BD%BF%E7%94%A8%E6%94%B6%E9%9B%86%E6%A8%A1%E5%BC%8F)
215 | for more
216 | * Fix #15 use n=1 instead of n=10 for more backtracking room: change
217 | default n to 1
218 | * Fix #16 can't read settings.conf when run out of installation dir: read
219 | settings.conf from the same path of main.py by default
220 |
221 | * **2013-12-24 1.4.2**
222 | * Support http/https proxy.
223 | Read
224 | [配置指南](https://github.com/genzj/pybingwallpaper/wiki/%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F)
225 | [Proxy guidance](https://github.com/genzj/pybingwallpaper/wiki/How-to-use-pybingwallpaper-with-proxy)
226 | for details.
227 |
228 | * **2013-12-21 1.4.1**
229 | * Background mode bugfix
230 |
231 | * **2013-12-21 1.4.0**
232 | * Use configuration file;
233 | * Support specify market
234 | * Support manually set picture resolution
235 | * Support downloading 1920x1080 images for pages without wallpaper link
236 |
237 | * **2013-09-24 1.3.0**
238 | * supports high resolution wallpapers
239 | * change default checking interval to 2 hours
240 | * obsolete option `-f` and `--persistence`
241 | * fix none type error when offset exceeds boundary issue #2
242 | * fix none type error when download picture fails issue #3
243 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | What pybingwallpaper does
2 | ==========================
3 |
4 | Download the wallpaper offered by `Bing.com `_ and set it current wallpaper background.
5 |
6 |
7 | Installation
8 | ============
9 |
10 | With installer (Windows Only)
11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12 |
13 | Installing with the installer is recommended for Windows users unless. Installers can be downloaded from `project website`_.
14 |
15 | With PIP
16 | ~~~~~~~~
17 |
18 | **NOTE** To install pybingwallpaper in Windows with PIP, it's highly recommended to install the `precompiled Pillow `_ in advance since install it from PIP could be cumbersome.
19 |
20 | ::
21 |
22 | pip install -U PyBingWallpaper
23 |
24 | To install the development version, use::
25 |
26 | pip install -U git+https://github.com/genzj/pybingwallpaper.git
27 |
28 | **NOTE** For Windows users, the setter must be copied into the folder containing ``BingWallpaper.exe``, normally named ``Scripts`` in Python installation path or virtual-env path. E.g. assuming the PyBingWallpaper is installed under virtualenv ``.\venv``, the `winsetter.py`_ must be downloaded to the ``.\venv\Scripts`` folder.
29 |
30 |
31 | To report bug
32 | =================
33 |
34 | Open a issue at https://github.com/genzj/pybingwallpaper/issues
35 |
36 |
37 | How to Contribute
38 | =================
39 |
40 | Refer to `project website`_ for details.
41 |
42 |
43 | .. _`project website`: https://github.com/genzj/pybingwallpaper/
44 | .. _`winsetter.py`: https://raw.githubusercontent.com/genzj/pybingwallpaper/c25d8191988dbd98c77297a7b1e3c0e98f2cccc6/pybingwallpaper/winsetter.py
45 |
46 |
--------------------------------------------------------------------------------
/bin/BingWallpaper:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from sys import exit as sys_exit
3 |
4 | from pybingwallpaper.main import main
5 |
6 | if __name__ == '__main__':
7 | sys_exit(main())
8 |
--------------------------------------------------------------------------------
/build.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 | import inspect
3 | import sys
4 | from os.path import join as pathjoin, dirname, abspath
5 |
6 | def current_path():
7 | f = inspect.getabsfile(current_path)
8 | return dirname(abspath(f))
9 |
10 | hiddenimports = []
11 | datas = []
12 |
13 | if sys.platform.startswith('win'):
14 | hiddenimports.extend([
15 | 'PIL','PIL.Image',
16 | 'win32', 'win32.win32gui',
17 | ])
18 | datas.insert(0, (pathjoin('pybingwallpaper', 'winsetter.py'), '.'))
19 |
20 | if sys.version_info[:2] < (3, 0):
21 | hiddenimports.extend([
22 | 'urllib', 'urllib2', 'urlparse',
23 | 'subprocess32', 'configparser',
24 | ])
25 | else:
26 | hiddenimports.extend([
27 | 'urllib',
28 | ])
29 |
30 | block_cipher = None
31 |
32 | a = Analysis([pathjoin('bin', 'BingWallpaper')],
33 | pathex=[current_path(), ],
34 | binaries=None,
35 | datas=datas,
36 | hiddenimports=hiddenimports,
37 | hookspath=[],
38 | runtime_hooks=[],
39 | excludes=[
40 | 'tkinter', 'tk', 'tcl',
41 | ],
42 | win_no_prefer_redirects=False,
43 | win_private_assemblies=False,
44 | cipher=block_cipher)
45 | pyz = PYZ(a.pure, a.zipped_data,
46 | cipher=block_cipher)
47 | exe = EXE(pyz,
48 | a.scripts,
49 | exclude_binaries=True,
50 | name='BingWallpaper',
51 | debug=False,
52 | strip=True,
53 | upx=True,
54 | console=False )
55 | cliexe = EXE(pyz,
56 | a.scripts,
57 | exclude_binaries=True,
58 | name='BingWallpaper-cli',
59 | debug=False,
60 | strip=True,
61 | upx=True,
62 | console=True )
63 | coll = COLLECT(exe, cliexe,
64 | a.binaries,
65 | a.zipfiles,
66 | a.datas,
67 | strip=False,
68 | upx=True,
69 | name='BingWallpaper')
70 |
--------------------------------------------------------------------------------
/makeinstaller.nsi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/makeinstaller.nsi
--------------------------------------------------------------------------------
/pybingwallpaper/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/pybingwallpaper/__init__.py
--------------------------------------------------------------------------------
/pybingwallpaper/bingwallpaper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import json
3 | import re
4 |
5 | from datetime import datetime
6 |
7 | from . import log
8 | from . import webutil
9 |
10 | _logger = log.getChild('bingwallpaper')
11 |
12 |
13 | def _property_need_loading(f):
14 | def wrapper(*args, **kwargs):
15 | args[0]._assert_load(f.__name__)
16 | return f(*args, **kwargs)
17 |
18 | return wrapper
19 |
20 |
21 | class HighResolutionSetting:
22 | settings = dict()
23 |
24 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
25 | raise NotImplementedError()
26 |
27 | @staticmethod
28 | def get_by_name(name):
29 | if name not in HighResolutionSetting.settings:
30 | raise ValueError('{} is not a legal resolution setting'.format(name))
31 | return HighResolutionSetting.settings[name]
32 |
33 |
34 | class UHDResolution(HighResolutionSetting):
35 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
36 | wplink = webutil.urljoin(rooturl, '_'.join([imgurlbase, 'UHD.jpg']))
37 | _logger.debug('in UHD mode, get url %s', wplink)
38 | return wplink,
39 |
40 |
41 | class PreferHighResolution(HighResolutionSetting):
42 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
43 | resolutions = ['UHD', '1920x1200', '1920x1080', ]
44 | candidates = [
45 | webutil.urljoin(rooturl, '_'.join([imgurlbase, suffix + '.jpg']))
46 | for suffix in resolutions
47 | ]
48 | for url in candidates:
49 | _logger.debug('in prefer mode, detect existence of pic %s', url)
50 | if webutil.test_header(url):
51 | _logger.debug('in prefer mode, decided url: %s', url)
52 | return url,
53 | return webutil.urljoin(rooturl, fallbackurl),
54 |
55 |
56 | class InsistHighResolution(HighResolutionSetting):
57 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
58 | if has_wp:
59 | wplink = webutil.urljoin(rooturl, '_'.join([imgurlbase, '1920x1200.jpg']))
60 | _logger.debug('in insist mode, get high resolution url %s', wplink)
61 | else:
62 | wplink = None
63 | _logger.debug('in insist mode, drop normal resolution pic')
64 | return wplink,
65 |
66 |
67 | class NeverHighResolution(HighResolutionSetting):
68 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
69 | wplink = webutil.urljoin(rooturl, fallbackurl)
70 | _logger.debug('never use high resolution, use %s', wplink)
71 | return wplink,
72 |
73 |
74 | class HighestResolution(HighResolutionSetting):
75 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
76 | if has_wp:
77 | wplink = webutil.urljoin(rooturl, '_'.join([imgurlbase, '1920x1200.jpg']))
78 | _logger.debug('support wallpaper, get high resolution url %s', wplink)
79 | else:
80 | wplink = webutil.urljoin(rooturl, '_'.join([imgurlbase, '1920x1080.jpg']))
81 | _logger.debug('not support wallpaper, use second highest resolution %s', wplink)
82 | return wplink,
83 |
84 |
85 | class ManualHighResolution(HighResolutionSetting):
86 | def get_pic_url(self, rooturl, imgurlbase, fallbackurl, has_wp, resolution):
87 | if not re.match(r'\d+x\d+', resolution):
88 | _logger.error('invalid resolution "%s" for manual mode', resolution)
89 | raise ValueError('invalid resolution "%s"' % (resolution,))
90 | wplink = webutil.urljoin(rooturl, ''.join([imgurlbase, '_', resolution, '.jpg']))
91 | _logger.debug('manually specify resolution, use %s', wplink)
92 | return wplink,
93 |
94 |
95 | HighResolutionSetting.settings['uhd'] = UHDResolution
96 | HighResolutionSetting.settings['prefer'] = PreferHighResolution
97 | HighResolutionSetting.settings['insist'] = InsistHighResolution
98 | HighResolutionSetting.settings['never'] = NeverHighResolution
99 | HighResolutionSetting.settings['highest'] = PreferHighResolution
100 | HighResolutionSetting.settings['manual'] = ManualHighResolution
101 |
102 |
103 | class AssetCollector:
104 | __registered_collectors = dict()
105 |
106 | @classmethod
107 | def register(cls, name, collector):
108 | if name in cls.__registered_collectors:
109 | raise Exception(
110 | 'collector {!s} has been registered by {!r}'.format(
111 | name, cls.__registered_collectors[name]
112 | )
113 | )
114 | cls.__registered_collectors[name] = collector
115 |
116 | @classmethod
117 | def get(cls, name, *init_args, **init_kwargs):
118 | if name not in cls.__registered_collectors:
119 | _logger.warning('collector %s is not supported', name)
120 | # return a blank collect
121 | return AssetCollector()
122 | return cls.__registered_collectors[name](*init_args, **init_kwargs)
123 |
124 | def collect(self, rooturl, curimage):
125 | return None
126 |
127 |
128 | class AccompanyImageCollector(AssetCollector):
129 | def collect(self, rooturl, curimage):
130 | img_url_base = curimage['urlbase']
131 | has_wp = curimage.get('wp', False)
132 | if has_wp and '_ZH_' not in img_url_base:
133 | _logger.debug('%s may have a Chinese brother', img_url_base)
134 | zh_link = [webutil.urljoin(rooturl, '_'.join([img_url_base, 'ZH_1920x1200.jpg'])), ]
135 | else:
136 | _logger.debug('no chinese logo for %s', img_url_base)
137 | zh_link = None
138 | return zh_link
139 |
140 |
141 | class VideoCollector(AssetCollector):
142 | def collect(self, rooturl, curimage):
143 | vlink = list()
144 | if 'vid' not in curimage:
145 | return None
146 | for video_format, _, video_url in curimage['vid']['sources']:
147 | if video_format.endswith('hd'):
148 | continue
149 | elif video_url.startswith('//'):
150 | video_url = 'http:' + video_url
151 | vlink.append(video_url)
152 | return vlink
153 |
154 |
155 | class HdVideoCollector(AssetCollector):
156 | def collect(self, rooturl, curimage):
157 | vlink = list()
158 | if 'vid' not in curimage:
159 | return None
160 | for video_format, _, video_url in curimage['vid']['sources']:
161 | if not video_format.endswith('hd'):
162 | continue
163 | elif video_url.startswith('//'):
164 | video_url = 'http:' + video_url
165 | vlink.append(video_url)
166 | return vlink
167 |
168 |
169 | AssetCollector.register('accompany', AccompanyImageCollector)
170 | AssetCollector.register('video', VideoCollector)
171 | AssetCollector.register('hdvideo', HdVideoCollector)
172 |
173 |
174 | class BingWallpaperPage:
175 | BASE_URL = 'http://www.bing.com'
176 | IMAGE_API = '/HPImageArchive.aspx?format=js&mbl=1&idx={idx}&n={n}&video=1'
177 |
178 | def __init__(self, idx, n=1, base=BASE_URL, api=IMAGE_API, country_code=None,
179 | market_code=None, high_resolution=PreferHighResolution, resolution='1920x1200',
180 | collect=None):
181 | self.idx = idx
182 | self.n = n
183 | self.base = base
184 | self.api = api
185 | self.reset()
186 | self.url = webutil.urljoin(self.base, self.api.format(idx=idx, n=n))
187 | self.country_code = country_code
188 | self.market_code = market_code
189 | self.high_resolution = high_resolution
190 | self.resolution = resolution
191 | self.collect = collect or []
192 | if market_code:
193 | BingWallpaperPage.validate_market(market_code)
194 | self.url = '&'.join([self.url, 'mkt={}'.format(market_code)])
195 | elif country_code:
196 | self.url = '&'.join([self.url, 'cc={}'.format(country_code)])
197 |
198 | def reset(self):
199 | self.__loaded = False
200 | self.content = ''
201 | self.__img_link = None
202 | self.discovered = 0
203 | self.filtered = 0
204 | self.wplinks = []
205 |
206 | def _parse(self, raw_file):
207 | try:
208 | self.content = json.loads(raw_file)
209 | except Exception as ex:
210 | _logger.exception(ex)
211 | return False
212 |
213 | # including blank response or 'null' in json
214 | if not self.content:
215 | return False
216 |
217 | _logger.debug(self.content)
218 |
219 | self.__images = self.content['images']
220 | if 'market' in self.content and 'mkt' in self.content['market']:
221 | self.act_market = self.content['market']['mkt']
222 | self._update_img_link()
223 |
224 | _logger.warning('links to be downloaded: %s', self.wplinks)
225 |
226 | return True
227 |
228 | def _get_metadata(self, i):
229 | metadata = dict()
230 | meta_field = [
231 | 'copyright', 'copyrightlink', 'hsh'
232 | ]
233 | for f in meta_field:
234 | metadata[f] = i.get(f, None)
235 | metadata['market'] = self.act_market
236 | metadata['startdate'] = datetime.strptime(i['startdate'], '%Y%m%d').date()
237 | metadata['enddate'] = datetime.strptime(i['enddate'], '%Y%m%d').date()
238 | metadata['fullstartdate'] = datetime.strptime(i['fullstartdate'], '%Y%m%d%H%M')
239 |
240 | return metadata
241 |
242 | def _update_img_link(self):
243 | del self.wplinks[:]
244 | for i in self.__images:
245 | metadata = self._get_metadata(i)
246 | has_wp = i.get('wp', False)
247 | _logger.debug(
248 | 'handling %s, rooturl=%s, img_url_base=%s, has_wp=%s, resolution=%s, act_market=%s',
249 | i['url'], self.base, i['urlbase'], has_wp, self.resolution, self.act_market
250 | )
251 | wplink = self.high_resolution().get_pic_url(self.base, i['urlbase'], i['url'], has_wp, self.resolution)
252 | collections = list()
253 | for collector_name in self.collect:
254 | asset = AssetCollector.get(collector_name).collect(self.base, i)
255 | if asset:
256 | collections += asset
257 | if wplink:
258 | self.wplinks.append((wplink + tuple(collections), metadata))
259 |
260 | def load(self):
261 | self.reset()
262 | _logger.info('loading from %s', self.url)
263 | raw_file = webutil.loadpage(self.url)
264 |
265 | if raw_file:
266 | _logger.info('%d bytes loaded', len(raw_file))
267 | self.__loaded = self._parse(raw_file)
268 | else:
269 | _logger.error('can\'t download photo page')
270 |
271 | def loaded(self):
272 | return self.__loaded
273 |
274 | @_property_need_loading
275 | def images(self):
276 | return self.__images
277 |
278 | @_property_need_loading
279 | def image_links(self):
280 | return self.wplinks
281 |
282 | def _assert_load(self, attr):
283 | if not self.loaded():
284 | raise Exception('use property "{}" before loading'.format(attr))
285 |
286 | def __str__(self):
287 | s_basic = ''
290 | s_all = s_basic + ', images="{}">'.format(self.images())
291 | return s_all
292 |
293 | def __repr__(self):
294 | return '{}({})'.format(self.__class__.__name__, repr(self.url))
295 |
296 | @staticmethod
297 | def validate_market(market_code):
298 | #
299 | if not re.match(r'\w\w-\w\w', market_code):
300 | raise ValueError('%s is not a valid market code.' % (market_code,))
301 | return True
302 |
--------------------------------------------------------------------------------
/pybingwallpaper/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import argparse
3 | import codecs
4 | import io
5 | import sys
6 | from argparse import Namespace
7 | from configparser import ConfigParser
8 | from copy import copy
9 |
10 | from . import log
11 | from .log import PAGEDUMP
12 |
13 | _logger = log.getChild('config')
14 |
15 |
16 | def _dumpconfig(parser, level=PAGEDUMP):
17 | _logger.log(level, 'a config file parsed as:')
18 | for section_name, section in parser.items():
19 | _logger.log(level, ' + section %s', section_name)
20 | _logger.log(level, ' |')
21 | for key, value in section.items():
22 | _logger.log(level, ' +-- %s = %s', key, value)
23 | _logger.log(level, '')
24 |
25 |
26 | def str_to_bool(x):
27 | return True if bool(x) and x.lower() != 'false' else False
28 |
29 |
30 | class ConfigParameter:
31 | """
32 | ConfigParameter is an abstract of configuration data which integrate config file
33 | and commandline-like argument data models.
34 | """
35 |
36 | def __init__(self, name, defaults=None, type=None, choices=None, help='', loader_srcs=None, loader_opts=None):
37 | """
38 | name - name of option.
39 | defaults - default values which can be specified as a dict with keys
40 | specify different default values of corresponding platform,
41 | e.g. {'win32': True, 'linux': False, 'darwin': False, '*': True}
42 | name of platforms are defined as in sys.platform and '*' stands for
43 | unspecified platforms which is equivalent to assigning value directly
44 | to defaults. That is, 'default=1' works same as 'default={'*':1}'
45 | type - the type to which the parameter should be converted, e.g.
46 | "type=int" employes int() on loaded values by loader.
47 | choices - a container of the allowable values for the parameter. a
48 | ValueError will be raised if type converted value not a member of
49 | not-none choices container
50 | help - human readable message describe meaning of this parameter
51 | loader_srcs - list of supported sources from which this parameter could be loaded
52 | 'all' is a reserved name for all known loader sources;
53 | other source names are decided by certain loaders
54 | loader_opts - options specified as dict will be used by certain loaders
55 | read document of loaders for available options
56 | """
57 | self.logger = log.getChild(self.__class__.__name__)
58 | self.name = str(name)
59 | self.validate_name()
60 | self.defaults = defaults if isinstance(defaults, dict) else {'*': defaults}
61 | if type is not None:
62 | self.type = type
63 | if choices is not None:
64 | self.choices = choices
65 | self.help = help
66 | self.loader_opts = loader_opts if loader_opts is not None else dict()
67 | self.loader_srcs = loader_srcs if loader_srcs is not None else ['all']
68 | self.logger.debug('parameter %s created', self)
69 |
70 | def validate_name(self):
71 | if any(map(lambda x: x.isspace(), self.name)):
72 | self.logger.error('invalid name %s detected', self.name)
73 | raise ValueError("parameter name can't contain space")
74 |
75 | def get_default(self, platform=None):
76 | if not platform:
77 | platform = sys.platform
78 | if platform not in self.defaults:
79 | platform = '*'
80 | return self.defaults[platform]
81 |
82 | def get_option(self, loader_key, option_name, default=None):
83 | opts = self.loader_opts.get(loader_key, dict())
84 | if option_name in opts:
85 | return opts[option_name]
86 | else:
87 | return default
88 |
89 | def is_loader_supported(self, loader_name):
90 | return 'all' in self.loader_srcs or loader_name in self.loader_srcs
91 |
92 | def type_cast(self, value):
93 | if hasattr(self, 'type'):
94 | return self.type(value)
95 | return value
96 |
97 | def __repr__(self):
98 | return '''{}(name={}, defaults={}, type={}, choices={}, help={}, loader_opts={})'''.format(
99 | self.__class__.__name__,
100 | repr(self.name),
101 | repr(self.defaults),
102 | repr(self.type if hasattr(self, 'type') else None),
103 | repr(self.choices if hasattr(self, 'choices') else None),
104 | repr(self.help),
105 | repr(self.loader_opts)
106 | )
107 |
108 | def __eq__(self, other):
109 | return self.name == other.name
110 |
111 |
112 | class ConfigDatabase:
113 | def __init__(self, prog, description=None, parameters=None):
114 | self.logger = log.getChild(self.__class__.__name__)
115 | self.prog = prog
116 | self.description = description
117 | self.parameters = list(parameters) if parameters is not None else list()
118 |
119 | def add_param(self, param):
120 | if param not in self.parameters:
121 | self.parameters.append(param)
122 | self.logger.debug('parameter %s added into config db "%s"', param, self.prog)
123 | else:
124 | raise NameError('duplicated parameter name "%s" found' % (param.name,))
125 |
126 | def __repr__(self):
127 | return '{}(prog={}, description={}, parameters={})'.format(
128 | self.__class__.__name__,
129 | repr(self.prog),
130 | repr(self.description),
131 | repr(self.parameters)
132 | )
133 |
134 |
135 | class ConfigLoader:
136 | def load(self, db, generate_default=False, *args, **kwargs):
137 | raise NotImplemented()
138 |
139 |
140 | class ConfigDumper:
141 | def dump(self, db, conf, buf, *args, **kwargs):
142 | raise NotImplemented()
143 |
144 |
145 | class ConfigFileLoader(ConfigLoader):
146 | """
147 | Options keyword: 'conffile'
148 | Supported options can be set in ConfigParameter:
149 | section - under which section this value is saved
150 | use the DEFAULT section if not specified
151 | key - with what name this value is saved
152 | converter
153 | - a callable which converts string to desired object
154 | use type_cast of parameter if not specified
155 | Ref:
156 | http://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
157 | """
158 | OPT_KEY = 'conffile'
159 |
160 | class ConfigValueError(ValueError):
161 | pass
162 |
163 | def load_value(self, param, parser, _, generate_default):
164 | section = param.get_option(self.OPT_KEY, 'section', None)
165 | if section is None:
166 | section = parser.default_section
167 | key = param.get_option(self.OPT_KEY, 'key', param.name)
168 | converter = param.get_option(self.OPT_KEY, 'converter', param.type_cast)
169 |
170 | if parser.has_option(section, key):
171 | value = parser.get(section, key)
172 | loaded = True
173 | elif generate_default:
174 | value = param.get_default()
175 | loaded = True
176 | else:
177 | value = None
178 | loaded = False
179 | if loaded:
180 | value = converter(value)
181 | if hasattr(param, 'choices') and value not in param.choices:
182 | raise ConfigFileLoader.ConfigValueError(
183 | "config setting %s/%s: invalid choice: %s (choose from %s)" % (
184 | section, key, value, ", ".join(param.choices))
185 | )
186 | return loaded, key, value
187 |
188 | def load(self, db, data, generate_default=False):
189 | ans = Namespace()
190 | parser = ConfigParser()
191 | parser.read_file(data)
192 | _dumpconfig(parser)
193 | for param in db.parameters:
194 | if not param.is_loader_supported(self.OPT_KEY):
195 | continue
196 | loaded, key, value = \
197 | self.load_value(param, parser, ans, generate_default)
198 | if loaded:
199 | setattr(ans, key, value)
200 | return ans
201 |
202 |
203 | class ConfigFileDumper(ConfigDumper):
204 | """
205 | Options keyword: 'conffile'
206 | Supported options can be set in ConfigParameter:
207 | section - under which section this value is saved
208 | use the DEFAULT section if not specified
209 | key - with what name this value is saved.
210 | use the name of parameter if not given
211 | formatter
212 | - a callable which converts value to string
213 | use built-in str() if not specified
214 | Ref:
215 | http://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
216 | """
217 | OPT_KEY = 'conffile'
218 |
219 | def get_param_by_name(self, db, name):
220 | by_key = list(filter(
221 | lambda param: name == param.get_option(self.OPT_KEY, 'key', None),
222 | db.parameters
223 | ))
224 | if len(by_key) > 1:
225 | raise ValueError('More than one parameters are set with name {}'.format(name))
226 | elif len(by_key) == 1:
227 | return by_key[0]
228 |
229 | by_name = list(filter(lambda param: name == param.name, db.parameters))
230 | if len(by_name) > 1:
231 | raise ValueError('More than one parameters are set with key {}'.format(name))
232 | elif len(by_name) == 1:
233 | return by_name[0]
234 | else:
235 | return None
236 |
237 | def dump(self, db, conf, buf, *args, **kwargs):
238 | parser = ConfigParser()
239 | for k, v in vars(conf).items():
240 | param = self.get_param_by_name(db, k)
241 | if param is None:
242 | _logger.warn('ignore an unknown config %s=%s', k, v)
243 | if not param.is_loader_supported(self.OPT_KEY):
244 | continue
245 | section = param.get_option(self.OPT_KEY, 'section', parser.default_section)
246 | if section != parser.default_section and \
247 | not parser.has_section(section):
248 | parser.add_section(section)
249 | formatter = param.get_option(self.OPT_KEY, 'formatter', str)
250 | parser.set(section, k, formatter(v))
251 | _dumpconfig(parser)
252 | parser.write(buf, kwargs.get('space_around_delimiters', True))
253 | if _logger.isEnabledFor(PAGEDUMP):
254 | dbg_buf = io.StringIO()
255 | parser.write(dbg_buf, kwargs.get('space_around_delimiters', True))
256 | _logger.log(PAGEDUMP, 'output file:\n%s', dbg_buf.getvalue())
257 |
258 |
259 | class CommandLineArgumentsLoader(ConfigLoader):
260 | """
261 | Options keyword: 'cli'
262 | Supported options can be set in ConfigParameter:
263 | flags - container which will be converted to CLI option flags
264 | other options can be found here
265 | http://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
266 | """
267 | OPT_KEY = 'cli'
268 |
269 | @staticmethod
270 | def param_to_arg_opts(param, generate_default):
271 | # load common options
272 | opts = {
273 | 'help': param.help,
274 | 'dest': param.name,
275 | }
276 | if hasattr(param, 'type'):
277 | opts['type'] = param.type
278 | if hasattr(param, 'choices'):
279 | opts['choices'] = param.choices
280 |
281 | if generate_default:
282 | opts['default'] = param.get_default()
283 | else:
284 | opts['default'] = argparse.SUPPRESS
285 |
286 | # load specific options at last so that
287 | # specific ones take higher priority in case a same
288 | # key occurs in both common part and loader_opts part
289 | specific_opts = param.loader_opts.get(CommandLineArgumentsLoader.OPT_KEY, dict())
290 | opts.update(specific_opts)
291 | if 'flags' in opts:
292 | del (opts['flags'])
293 | _logger.debug('options to argparser: %s', opts)
294 | return opts
295 |
296 | @staticmethod
297 | def param_to_arg_flags(param):
298 | flags = param.get_option(CommandLineArgumentsLoader.OPT_KEY, 'flags', None)
299 | if flags:
300 | ans = flags
301 | elif len(param.name) == 1:
302 | # simple name like 'a' will be converted to '-a'
303 | ans = ['-' + param.name, ]
304 | else:
305 | # long name like 'debug' will be converted to '--debug'
306 | ans = ['--' + param.name, ]
307 | _logger.debug('flags to argparser: %s', ans)
308 | return ans
309 |
310 | @staticmethod
311 | def assemble_parser(db, generate_default):
312 | parser = argparse.ArgumentParser(prog=db.prog, description=db.description)
313 | for param in db.parameters:
314 | if not param.is_loader_supported(CommandLineArgumentsLoader.OPT_KEY):
315 | continue
316 | _logger.debug('loading %s', param)
317 | parser.add_argument(
318 | *CommandLineArgumentsLoader.param_to_arg_flags(param),
319 | **CommandLineArgumentsLoader.param_to_arg_opts(param, generate_default)
320 | )
321 | return parser
322 |
323 | def load(self, db, data, generate_default=False):
324 | _logger.debug('parsing options %s', data)
325 | parser = CommandLineArgumentsLoader.assemble_parser(db, generate_default)
326 | return parser.parse_args(data)
327 |
328 |
329 | class DefaultValueLoader(ConfigLoader):
330 | OPT_KEY = 'defload'
331 |
332 | def __init__(self, platform=None):
333 | self.platform = platform
334 |
335 | def load(self, db, data=None, generate_default=True):
336 | ans = Namespace()
337 | if not generate_default:
338 | return ans
339 | for param in db.parameters:
340 | if not param.is_loader_supported(self.OPT_KEY):
341 | continue
342 | val = param.get_default(self.platform)
343 | val = param.type(val) if hasattr(param, 'type') else val
344 | setattr(ans, param.name, val)
345 | return ans
346 |
347 |
348 | def merge_config(config, increment):
349 | _logger.debug('merge %s into original %s', increment, config)
350 | ans = copy(config)
351 | ans.__dict__.update(increment.__dict__)
352 | _logger.debug('generate %s', ans)
353 | return ans
354 |
355 |
356 | def pretty(config, sep='\n'):
357 | lines = ['{} = {}'.format(str(k), repr(v)) for k, v in config.__dict__.items()]
358 | lines.sort()
359 | return sep.join(lines)
360 |
361 |
362 | def to_file(db, config, filename, dumper=None):
363 | dumper = ConfigFileDumper() if not dumper else dumper
364 | with codecs.open(filename, 'w', encoding='utf-8') as outf:
365 | dumper.dump(db, config, outf)
366 | outf.flush()
367 |
368 |
369 | def from_file(db, filename, loader=None):
370 | loader = ConfigFileLoader() if not loader else loader
371 | with codecs.open(filename, 'r', encoding='utf-8') as inf:
372 | return loader.load(db, inf)
373 |
--------------------------------------------------------------------------------
/pybingwallpaper/log.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import logging
3 | from logging import INFO, DEBUG
4 |
5 | # bypass import optimization, levels are meant to be imported for other modules although not used in this file
6 | assert [INFO, DEBUG]
7 |
8 | PAGEDUMP = 5
9 | logging.addLevelName(PAGEDUMP, 'PAGEDUMP')
10 |
11 | _logger = None
12 | _children = []
13 |
14 |
15 | def __init(app_name):
16 | global _logger
17 | _logger = logging.getLogger(app_name)
18 | if not _logger.handlers:
19 | _loggerHandler = logging.StreamHandler()
20 | _loggerHandler.setLevel(PAGEDUMP)
21 | _loggerHandler.setFormatter(logging.Formatter('[%(asctime)s - %(levelname)s - %(name)s] %(message)s'))
22 | _logger.addHandler(_loggerHandler)
23 | _logger.setLevel(INFO)
24 |
25 |
26 | def getChild(*args, **kwargs):
27 | child_logger = _logger.getChild(*args, **kwargs)
28 | if child_logger not in _children:
29 | _children.append(child_logger)
30 | return child_logger
31 |
32 |
33 | def setDebugLevel(level):
34 | _logger.setLevel(level)
35 | list(map(lambda l: l.setLevel(level), _children))
36 |
37 |
38 | __init('bingwallpaper')
39 |
40 | if __name__ == '__main__':
41 | log = _logger.getChild('logtest')
42 | log.setLevel(logging.DEBUG)
43 | log.info('Info test')
44 | log.warn('Warn test')
45 | log.error('Error test')
46 | log.critical('Critical test')
47 | log.debug('debug test')
48 | try:
49 | def __ex_test():
50 | raise Exception('exception test')
51 |
52 |
53 | __ex_test()
54 | except Exception as ex:
55 | log.exception(ex)
56 |
--------------------------------------------------------------------------------
/pybingwallpaper/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import errno
3 | import os
4 | import sched
5 | from datetime import datetime
6 | from os.path import basename, dirname, abspath
7 | from os.path import expanduser, join as path_join, isfile, isdir, splitext
8 | from sys import argv, exit as sys_exit, platform
9 |
10 | from . import bingwallpaper
11 | from . import config
12 | from . import log
13 | from . import record
14 | from . import setter
15 | from . import webutil
16 | from .rev import REV
17 | from .webutil import urlparse, parse_qs
18 |
19 | NAME = 'pybingwallpaper'
20 | LINK = 'https://github.com/genzj/pybingwallpaper'
21 |
22 | if platform == 'win32':
23 | HISTORY_FILE = path_join(
24 | os.getenv('APPDATA', expanduser('~')),
25 | 'Genzj',
26 | 'PyBingWallpaper',
27 | 'bing-wallpaper-history.json'
28 | )
29 | else:
30 | HISTORY_FILE = path_join(expanduser('~'), '.bing-wallpaper-history.json')
31 |
32 | _logger = log.getChild('main')
33 |
34 |
35 | class CannotLoadImagePage(Exception):
36 | pass
37 |
38 |
39 | def load_setters():
40 | if platform == 'win32':
41 | return ['no', 'win']
42 | else:
43 | return ['no', 'gnome3', 'gnome2']
44 |
45 |
46 | def prepare_config_db():
47 | params = []
48 |
49 | setters = load_setters()
50 |
51 | configdb = config.ConfigDatabase(
52 | prog=NAME,
53 | description='Download the wallpaper offered by Bing.com '
54 | + 'and set it current wallpaper background.'
55 | )
56 |
57 | params.append(config.ConfigParameter(
58 | 'version',
59 | help='show version information',
60 | loader_srcs=['cli'],
61 | loader_opts={'cli': {
62 | 'flags': ('-v', '--version'),
63 | 'action': 'version',
64 | 'version': '%(prog)s-{} ({})'.format(REV, LINK),
65 | }}
66 | ))
67 |
68 | params.append(config.ConfigParameter(
69 | 'config_file',
70 | defaults=path_join(get_app_path(), 'settings.conf'),
71 | help=''''specify configuration file, use `settings.conf`
72 | in installation directory by default.''',
73 | loader_srcs=['cli', 'defload'],
74 | loader_opts={'cli': {
75 | 'flags': ('--config-file',),
76 | }}
77 | ))
78 |
79 | params.append(config.ConfigParameter(
80 | 'generate_config',
81 | defaults=False,
82 | help='''generate a configuration file containing arguments
83 | specified in command line and exit. to generate default config
84 | file, issue without other command arguments.
85 | path and name of configuration file can be specified with
86 | --config-file''',
87 | loader_srcs=['cli', 'defload'],
88 | loader_opts={'cli': {
89 | 'flags': ('--generate-config',),
90 | 'action': 'store_true'
91 | }}
92 | ))
93 |
94 | params.append(config.ConfigParameter(
95 | 'background',
96 | defaults=False,
97 | help='''work in background (daemon mode) and check
98 | wallpaper periodically (interval can be set by --interval).''',
99 | loader_opts={'cli': {
100 | 'flags': ('-b', '--background'),
101 | 'action': 'store_true',
102 | }, 'conffile': {
103 | 'section': 'Daemon',
104 | 'converter': config.str_to_bool,
105 | }}
106 | ))
107 |
108 | params.append(config.ConfigParameter(
109 | 'foreground',
110 | defaults=False,
111 | help='''force working in foreground mode to cancel
112 | the effect of `background` in config file.''',
113 | loader_srcs=['cli', 'defload'],
114 | loader_opts={'cli': {
115 | 'flags': ('--foreground',),
116 | 'action': 'store_true'
117 | }}
118 | ))
119 |
120 | params.append(config.ConfigParameter(
121 | 'country', defaults='auto',
122 | choices=('au', 'br', 'ca', 'cn', 'de', 'fr', 'jp', 'nz', 'us', 'uk', 'auto'),
123 | help='''
124 | (Obsolete, use --market instead)
125 | select country code used to access bing.com API.
126 | au: Australia br: Brazil ca: Canada cn: China de:Germany
127 | fr: France jp: Japan nz: New Zealand us: USA uk: United Kingdom
128 | auto: select country according to your IP address (by Bing.com)
129 | Note: only China(cn), New Zealand(nz) and USA(us) have
130 | high resolution (1920x1200) wallpapers; the rest offer 1366x768 only.''',
131 | loader_opts={'cli': {
132 | 'flags': ('-c', '--country'),
133 | }, 'conffile': {
134 | 'section': 'Download',
135 | }}
136 | ))
137 | params.append(config.ConfigParameter(
138 | 'market', defaults='',
139 | help='''specify region from which the wallpaper should be downloaded.
140 | Market code should be specified in format 'xy-ab' such as en-us.
141 | Use '--list-markets' to view all available markets.
142 | Note: specify this parameter will override any settings to --country.
143 | ''',
144 | loader_opts={'cli': {
145 | 'flags': ('--market',),
146 | }, 'conffile': {
147 | 'section': 'Download',
148 | }}
149 | ))
150 | params.append(config.ConfigParameter(
151 | 'list_markets',
152 | defaults=False,
153 | help='''list all available values for the --market option and exit''',
154 | loader_srcs=['cli', 'defload'],
155 | loader_opts={'cli': {
156 | 'flags': ('--list-markets',),
157 | 'action': 'store_true'
158 | }}
159 | ))
160 | params.append(config.ConfigParameter(
161 | 'debug', defaults=0,
162 | help='''enable debug outputs.
163 | The more --debug the more detailed the log will be''',
164 | loader_opts={'cli': {
165 | 'flags': ('-d', '--debug'),
166 | 'action': 'count',
167 | }, 'conffile': {
168 | 'converter': int,
169 | 'section': 'Debug',
170 | }}
171 | ))
172 |
173 | def convert_interval(interval):
174 | i = int(interval)
175 | return i if i >= 1 else 1
176 |
177 | params.append(config.ConfigParameter(
178 | 'interval',
179 | type=convert_interval, defaults=2,
180 | help='''interval between each two wallpaper checkings
181 | in unit of hours. applicable only in `background` mode.
182 | at lease 1 hour; 2 hours by default.''',
183 | loader_opts={'cli': {
184 | 'flags': ('-i', '--interval'),
185 | }, 'conffile': {
186 | 'section': 'Daemon',
187 | }}
188 | ))
189 | params.append(config.ConfigParameter(
190 | 'keep_file_name', defaults=False,
191 | help='''keep the original filename. By default
192 | downloaded file will be renamed as 'wallpaper.jpg'.
193 | Keep file name will retain all downloaded photos
194 | ''',
195 | loader_opts={'cli': {
196 | 'flags': ('-k', '--keep-file-name'),
197 | 'action': 'store_true',
198 | }, 'conffile': {
199 | 'section': 'Download',
200 | 'converter': config.str_to_bool,
201 | }}
202 | ))
203 |
204 | params.append(config.ConfigParameter(
205 | 'size_mode', defaults='prefer',
206 | choices=('prefer', 'collect', 'highest', 'insist', 'manual', 'never', 'uhd'),
207 | help='''set selecting strategy when wallpapers in different
208 | size are available (4K, 1920x1200, 1920x1080 and 1366x768).
209 | `prefer` (default) detect and download the highest
210 | available resolution;
211 | `insist` always use 1920x1200 resolution and ignore
212 | other pictures (Note: some countries have only
213 | normal size wallpapers, if `insist` is adopted
214 | with those sites, no wallpaper can be downloaded,
215 | see `--country` for more);
216 | `highest` is an alias of `prefer`
217 | `never` always use normal resolution (1366x768);
218 | `manual` use resolution specified in `--image-size`
219 | `collect` is obsolete and only kept for backward
220 | compatibility, equals `prefer` mode together with
221 | --collect=accompany option.
222 | `uhd` insists using 4K resolution, or abort wallpaper
223 | downloading if it's not available.
224 | ''',
225 | loader_opts={'cli': {
226 | 'flags': ('-m', '--size-mode'),
227 | }, 'conffile': {
228 | 'section': 'Download',
229 | }}
230 | ))
231 |
232 | params.append(config.ConfigParameter(
233 | 'collect', defaults=[],
234 | help='''items to be collected besides main wallpaper, can be assigned
235 | more than once in CLI or as comma separated list from config file.
236 | currently `accompany`, `video` and `hdvideo` are supported.
237 | `accompany` - some markets (i.e. en-ww) offers two wallpapers
238 | every day with same image but different Bing logo (English and
239 | Chinese respectively). enables this will download both original
240 | wallpaper and its accompanyings.
241 | `video` - bing sometimes (not everyday) release interesting
242 | animated mp4 background, use this to collect them.
243 | `hdvideo` - HD version of video.''',
244 | loader_opts={'cli': {
245 | 'flags': ('--collect',),
246 | 'action': 'append',
247 | }, 'conffile': {
248 | 'formatter': lambda args: ','.join(args),
249 | 'converter': lambda arg: [_p.strip() for _p in arg.split(',') if
250 | _p.strip()],
251 | 'section': 'Download',
252 | }}
253 | ))
254 |
255 | params.append(config.ConfigParameter(
256 | 'image_size', defaults='',
257 | help='''specify resolution of image to download. check
258 | `--size-mode` for more''',
259 | loader_opts={'cli': {
260 | 'flags': ('--image-size',),
261 | }, 'conffile': {
262 | 'section': 'Download',
263 | }}
264 | ))
265 | params.append(config.ConfigParameter(
266 | 'offset', type=int, defaults='0',
267 | help='''start downloading from the photo of 'N' days ago.
268 | specify 0 to download photo of today.''',
269 | loader_opts={'cli': {
270 | 'flags': ('-o', '--offset'),
271 | }, 'conffile': {
272 | 'section': 'Download',
273 | }}
274 | ))
275 | params.append(config.ConfigParameter(
276 | 'proxy_server', defaults='',
277 | help='''proxy server value, ex: http://10.1.1.1''',
278 | loader_opts={'cli': {
279 | 'flags': ('--proxy-server',),
280 | }, 'conffile': {
281 | 'section': 'Proxy',
282 | }}
283 | ))
284 | params.append(config.ConfigParameter(
285 | 'proxy_port', defaults='80',
286 | help='''port of proxy server, default: 80''',
287 | loader_opts={'cli': {
288 | 'flags': ('--proxy-port',),
289 | }, 'conffile': {
290 | 'section': 'Proxy',
291 | }}
292 | ))
293 | params.append(config.ConfigParameter(
294 | 'proxy_username', defaults='',
295 | help='''optional username for proxy server authentication''',
296 | loader_opts={'cli': {
297 | 'flags': ('--proxy-username',),
298 | }, 'conffile': {
299 | 'section': 'Proxy',
300 | }}
301 | ))
302 | params.append(config.ConfigParameter(
303 | 'proxy_password', defaults='',
304 | help='''optional password for proxy server authentication''',
305 | loader_opts={'cli': {
306 | 'flags': ('--proxy-password',),
307 | }, 'conffile': {
308 | 'section': 'Proxy',
309 | }}
310 | ))
311 | params.append(config.ConfigParameter(
312 | 'redownload', defaults=False,
313 | help='''do not check history records. Download
314 | must be done. downloaded picture will still
315 | be recorded in history file.
316 | ''',
317 | loader_opts={'cli': {
318 | 'action': 'store_true',
319 | }, 'conffile': {
320 | 'section': 'Download',
321 | 'converter': config.str_to_bool
322 | }}
323 | ))
324 | params.append(config.ConfigParameter(
325 | 'setter', choices=setters,
326 | defaults=setters[1],
327 | help='''specify interface to be called for
328 | setting wallpaper. 'no'
329 | indicates downloading-only; 'gnome2/3'
330 | are only for Linux with gnome; 'win' is
331 | for Windows only. Customized setter can
332 | be added as dev doc described. Default: {}
333 | '''.format(setters[1]),
334 | loader_opts={'cli': {
335 | 'flags': ('-s', '--setter'),
336 | }, 'conffile': {
337 | 'section': 'Setter',
338 | }}
339 | ))
340 |
341 | params.append(config.ConfigParameter(
342 | 'setter_args', defaults=[],
343 | help='''arguments for external setters''',
344 | loader_opts={'cli': {
345 | 'flags': ('--setter-args',),
346 | 'action': 'append',
347 | }, 'conffile': {
348 | 'formatter': lambda args: ','.join(args),
349 | 'section': 'Setter',
350 | }}
351 | ))
352 |
353 | params.append(config.ConfigParameter(
354 | 'output_folder',
355 | defaults=path_join(expanduser('~'), 'MyBingWallpapers'),
356 | help='''specify the folder to store photos.
357 | Use '~/MyBingWallpapers' folder in Linux,
358 | 'C:/Documents and Settings//MyBingWallpapers
359 | in Windows XP or 'C:/Users//MyBingWallpapers'
360 | in Windows 7 by default
361 | ''',
362 | loader_opts={'cli': {
363 | 'flags': ('-t', '--output-folder'),
364 | }, 'conffile': {
365 | 'section': 'Download',
366 | }}
367 | ))
368 |
369 | params.append(config.ConfigParameter(
370 | 'database_file',
371 | defaults='',
372 | help='''specify the sqlite3 database used to store meta info of photos.
373 | leave it blank to disable database storage.
374 | ''',
375 | loader_opts={'cli': {
376 | 'flags': ('--database-file',),
377 | }, 'conffile': {
378 | 'section': 'Database',
379 | }}
380 | ))
381 |
382 | params.append(config.ConfigParameter(
383 | 'database_no_image',
384 | defaults=False,
385 | help='''images will be embedded into database by default. Exclude
386 | images from database can reduce the size of database file.
387 | ''',
388 | loader_opts={'cli': {
389 | 'flags': ('--database-no-image',),
390 | 'action': 'store_false',
391 | }, 'conffile': {
392 | 'section': 'Database',
393 | 'converter': config.str_to_bool
394 | }}
395 | ))
396 |
397 | params.append(config.ConfigParameter(
398 | 'server', defaults='global',
399 | choices=('global', 'china', 'custom'),
400 | help='''select bing server used for meta data
401 | and wallpaper pictures. it seems bing.com uses different
402 | servers and domain names in china.
403 | global: use bing.com of course.
404 | china: use s.cn.bing.net. (note: use this may freeze market
405 | or country to China zh-CN)
406 | custom: use the server specified in option "customserver"
407 | ''',
408 | loader_opts={'cli': {
409 | 'flags': ('--server',),
410 | }, 'conffile': {
411 | 'section': 'Download',
412 | }}
413 | ))
414 |
415 | def url(s):
416 | from .webutil import urlparse
417 | value = ('http://' + s) \
418 | if s and not urlparse(s).scheme \
419 | else s
420 | return value + '/' if value and not value.endswith('/') else value
421 |
422 | params.append(config.ConfigParameter('customserver', defaults='',
423 | type=url,
424 | help='''specify server used for meta data and wallpaper photo.
425 | you need to set --server to 'custom' to enable the custom server
426 | address.''',
427 | loader_opts={'cli': {
428 | 'flags': ('--custom-server',),
429 | }, 'conffile': {
430 | 'section': 'Download',
431 | }}
432 | ))
433 |
434 | for p in params:
435 | configdb.add_param(p)
436 | return configdb
437 |
438 |
439 | def makedirs(d):
440 | try:
441 | os.makedirs(d)
442 | except OSError as exc: # Python >2.5
443 | if exc.errno == errno.EEXIST:
444 | pass
445 | else:
446 | raise
447 |
448 |
449 | def prepare_output_dir(d):
450 | makedirs(d)
451 | if isdir(d):
452 | return True
453 | else:
454 | _logger.critical('can not create output folder %s', d)
455 | if os.access(d, os.W_OK | os.R_OK):
456 | return True
457 | else:
458 | _logger.critical('can not access output folder %s', d)
459 |
460 |
461 | def download_wallpaper(run_config):
462 | records = list()
463 | idx = run_config.offset
464 | country_code = None if run_config.country == 'auto' else run_config.country
465 | market_code = None if not run_config.market else run_config.market
466 | if run_config.server == 'global':
467 | base_url = 'http://www.bing.com'
468 | elif run_config.server == 'china':
469 | base_url = 'http://s.cn.bing.net'
470 | else:
471 | base_url = run_config.customserver
472 |
473 | try:
474 | s = bingwallpaper.BingWallpaperPage(
475 | idx,
476 | base=base_url,
477 | country_code=country_code,
478 | market_code=market_code,
479 | high_resolution=bingwallpaper.HighResolutionSetting.get_by_name(
480 | run_config.size_mode
481 | ),
482 | resolution=run_config.image_size,
483 | collect=set(run_config.collect)
484 | )
485 | _logger.debug(repr(s))
486 | s.load()
487 | _logger.log(log.PAGEDUMP, str(s))
488 | except Exception:
489 | _logger.fatal('error happened during loading from bing.com.', exc_info=1)
490 | return None
491 |
492 | if not s.loaded():
493 | _logger.error('can not load url %s. aborting...', s.url)
494 | raise CannotLoadImagePage(s)
495 | for wplinks, metadata in s.image_links():
496 | _logger.debug('%s photo list: %s', metadata, wplinks)
497 | mainlink = wplinks[0]
498 | copyright_ = metadata['copyright']
499 | outfile = get_output_filename(run_config, mainlink)
500 | rec = record.default_manager.get_by_url(mainlink)
501 | _logger.debug('related download records: %s', rec)
502 |
503 | if outfile == rec['local_file']:
504 | if not run_config.redownload:
505 | _logger.info('file has been downloaded before, exit')
506 | return None
507 | else:
508 | _logger.info('file has been downloaded before, redownload it')
509 |
510 | _logger.info('download photo of "%s"', copyright_)
511 | raw = save_a_picture(mainlink, copyright_, outfile)
512 | if not raw:
513 | continue
514 | r = record.DownloadRecord(
515 | mainlink, outfile, copyright_,
516 | raw=None if run_config.database_no_image else raw,
517 | market=metadata['market'],
518 | start_time=metadata['fullstartdate'],
519 | end_time=metadata['enddate'],
520 | )
521 | records.append(r)
522 | collect_assets(wplinks[1:], metadata, run_config, records)
523 | return records
524 |
525 | _logger.info('bad luck, no wallpaper today:(')
526 | return None
527 |
528 |
529 | def collect_assets(wplinks, metadata, run_config, records):
530 | output_folder = run_config.output_folder
531 | copyright_ = metadata['copyright']
532 | market = metadata['market']
533 | for link in wplinks:
534 | _logger.debug(
535 | 'downloading assets of "%s" from %s to %s',
536 | copyright_, link, output_folder
537 | )
538 | filename = get_output_filename(run_config, link)
539 | raw = save_a_picture(link, copyright_, filename, optional=True)
540 | if not raw:
541 | continue
542 | elif filename.endswith('jpg'):
543 | r = record.DownloadRecord(
544 | link, filename, copyright_,
545 | raw=None if run_config.database_no_image else raw,
546 | is_accompany=True, market=market
547 | )
548 | records.append(r)
549 | _logger.info('assets "%s" of "%s" has been downloaded to %s',
550 | link, copyright_, output_folder)
551 |
552 |
553 | def save_a_picture(pic_url, _, outfile, optional=False):
554 | picture_content = webutil.loadurl(pic_url, optional=optional)
555 | if picture_content:
556 | with open(outfile, 'wb') as of:
557 | of.write(picture_content)
558 | _logger.info('file saved %s', outfile)
559 | return picture_content
560 |
561 |
562 | def get_output_filename(run_config, link):
563 | link = urlparse(link)
564 | filename = basename(link.path)
565 | if filename == 'th':
566 | # 2019-03 new url style encoding filename in url parameters
567 | filename = parse_qs(link.query).get(
568 | 'id',
569 | [datetime.now().strftime('bingwallpaper_%Y-%m-%d_%H%M%S.jpg'), ]
570 | )[0]
571 | if not run_config.keep_file_name:
572 | filename = 'wallpaper{}'.format(splitext(filename)[1])
573 | return path_join(run_config.output_folder, filename)
574 |
575 |
576 | def load_history():
577 | try:
578 | f = open(HISTORY_FILE, 'r')
579 | except IOError as ex:
580 | if ex.errno == errno.ENOENT:
581 | _logger.info('{} not found, ignore download history'.format(HISTORY_FILE))
582 | else:
583 | _logger.warning('error occurs when recover downloading history', exc_info=1)
584 | except Exception:
585 | _logger.warning('error occurs when recover downloading history', exc_info=1)
586 | else:
587 | record.default_manager.load(f)
588 | f.close()
589 |
590 |
591 | def save_history(records, run_config, keepold=False):
592 | last_record = records[0]
593 | if not keepold:
594 | record.default_manager.clear()
595 | record.default_manager.add(last_record)
596 | try:
597 | f = open(HISTORY_FILE, 'w')
598 | f.truncate(0)
599 | except Exception:
600 | _logger.warning(
601 | 'error occurs when store downloading history',
602 | exc_info=1
603 | )
604 | else:
605 | record.default_manager.save(f)
606 | f.close()
607 |
608 | if not run_config.database_file:
609 | return
610 |
611 | rm = record.SqlDatabaseRecordManager('database %s' % (run_config.database_file,))
612 | for r in records:
613 | rm.add(r)
614 |
615 | try:
616 | rm.save(run_config.database_file)
617 | except Exception:
618 | _logger.error(
619 | 'error occurs when store records into database %s',
620 | run_config.database_file,
621 | exc_info=1
622 | )
623 |
624 |
625 | def set_debug_details(level_number):
626 | if not level_number:
627 | level = log.INFO
628 | elif level_number == 1:
629 | level = log.DEBUG
630 | elif level_number >= 2:
631 | level = log.PAGEDUMP
632 | else:
633 | level = log.INFO
634 | log.setDebugLevel(level)
635 |
636 |
637 | def start(daemon=None):
638 | if daemon:
639 | _logger.info('daemon %s triggers an update', str(daemon))
640 |
641 | # reload config again in case the config file has been modified after
642 | # last shooting
643 | config_db = prepare_config_db()
644 | run_config = load_config(config_db)
645 | setter.load_ext_setters(dirname(abspath(argv[0])))
646 |
647 | prepare_output_dir(run_config.output_folder)
648 | prepare_output_dir(dirname(HISTORY_FILE))
649 |
650 | load_history()
651 | install_proxy(run_config)
652 | try:
653 | file_records = download_wallpaper(run_config)
654 | except CannotLoadImagePage:
655 | if not run_config.foreground and run_config.background and daemon:
656 | _logger.info("network error happened, daemon will retry in 60 seconds")
657 | timeout = 60
658 | else:
659 | _logger.info("network error happened. please retry after Internet connection restore.")
660 | timeout = None
661 | file_records = None
662 | else:
663 | timeout = run_config.interval * 3600
664 |
665 | if file_records:
666 | save_history(file_records, run_config)
667 | if not file_records or run_config.setter == 'no':
668 | _logger.info('nothing to set')
669 | else:
670 | s = setter.get(run_config.setter)()
671 | # use the first image as wallpaper, accompanying images are just
672 | # to fulfill your collection
673 | wallpaper_record = file_records[0]
674 | _logger.info('setting wallpaper %s', wallpaper_record['local_file'])
675 | s.set(wallpaper_record['local_file'], run_config.setter_args)
676 | _logger.info('all done. enjoy your new wallpaper')
677 |
678 | if not run_config.foreground and run_config.background and daemon:
679 | schedule_next_poll(timeout, daemon, run_config.interval)
680 | elif run_config.foreground:
681 | _logger.info('force foreground mode from command line')
682 |
683 |
684 | def schedule_next_poll(timeout, daemon, interval):
685 | if not daemon:
686 | _logger.error('no scheduler')
687 | else:
688 | _logger.debug('schedule next running in %d seconds', interval * 3600)
689 | daemon.enter(timeout, 1, start, (daemon,))
690 |
691 |
692 | def start_daemon():
693 | daemon = sched.scheduler()
694 |
695 | start(daemon)
696 | _logger.info('daemon %s is running', str(daemon))
697 | daemon.run()
698 | _logger.info('daemon %s exited', str(daemon))
699 |
700 |
701 | def load_config(config_db, args=None):
702 | args = argv[1:] if args is None else args
703 | set_debug_details(args.count('--debug') + args.count('-d'))
704 |
705 | default_config = config.DefaultValueLoader().load(config_db)
706 | _logger.debug('default config:\n\t%s', config.pretty(default_config, '\n\t'))
707 |
708 | # parse cli options at first because we need the config file path in it
709 | cli_config = config.CommandLineArgumentsLoader().load(config_db, argv[1:])
710 | _logger.debug('cli arg parsed:\n\t%s', config.pretty(cli_config, '\n\t'))
711 | run_config = config.merge_config(default_config, cli_config)
712 |
713 | if run_config.list_markets:
714 | list_markets()
715 |
716 | if run_config.generate_config:
717 | generate_config_file(config_db, run_config)
718 |
719 | config_file = run_config.config_file
720 | if not isfile(config_file):
721 | _logger.warning("can't find config file %s, use default settings and cli settings",
722 | config_file)
723 | else:
724 | try:
725 | conf_config = config.from_file(config_db, run_config.config_file)
726 | except config.ConfigFileLoader.ConfigValueError as err:
727 | _logger.error(err)
728 | sys_exit(1)
729 |
730 | # noinspection PyUnboundLocalVariable
731 | _logger.debug('config file parsed:\n\t%s', config.pretty(conf_config, '\n\t'))
732 | run_config = config.merge_config(run_config, conf_config)
733 |
734 | # override saved settings again with cli options again, because we want
735 | # command line options to take higher priority
736 | run_config = config.merge_config(run_config, cli_config)
737 | if run_config.setter_args:
738 | run_config.setter_args = ','.join(run_config.setter_args).split(',')
739 | else:
740 | run_config.setter_args = list()
741 |
742 | # backward compatibility modifications
743 | if run_config.size_mode == 'collect':
744 | _logger.warning(
745 | 'size_mode=collect is obsolete, considering use collect=accompany instead'
746 | )
747 | run_config.size_mode = 'highest'
748 | if 'accompany' not in run_config.collect:
749 | run_config.collect.append('accompany')
750 |
751 | _logger.info('running config is:\n\t%s', config.pretty(run_config, '\n\t'))
752 | return run_config
753 |
754 |
755 | def save_config(configdb, run_config, filename=None):
756 | filename = run_config.config_file if not filename else filename
757 | config.to_file(configdb, run_config, filename)
758 |
759 |
760 | def generate_config_file(configdb, config_content):
761 | filename = config_content.config_file
762 | _logger.info(
763 | 'save following config to file %s:\n\t%s',
764 | filename,
765 | config.pretty(config_content, '\n\t')
766 | )
767 | save_config(configdb, config_content, filename)
768 | sys_exit(0)
769 |
770 |
771 | def install_proxy(config):
772 | from itertools import product
773 | if not config.proxy_server:
774 | _logger.debug('no proxy server specified')
775 | return
776 | else:
777 | if len(config.proxy_password) <= 4:
778 | hidden_password = '*' * len(config.proxy_password)
779 | else:
780 | hidden_password = '%s%s%s' % (
781 | config.proxy_password[0],
782 | '*' * (len(config.proxy_password) - 2),
783 | config.proxy_password[-1]
784 | )
785 | _logger.info('user specified proxy: "%s:%s"', config.proxy_server, config.proxy_port)
786 | _logger.debug('proxy username: "%s" password: "%s"', config.proxy_username, hidden_password)
787 | proxy_sites_protocol = ('http', 'https')
788 | proxy_sites = ('bing.com', 'www.bing.com', 'cn.bing.com', 'nz.bing.com', 's.cn.bing.net')
789 |
790 | proxy_sites = [p + '://' + s for p, s in product(('http', 'https'), proxy_sites)]
791 | if config.customserver and config.customserver not in proxy_sites:
792 | proxy_sites += (config.customserver,)
793 | webutil.setup_proxy(
794 | proxy_sites_protocol, config.proxy_server, config.proxy_port,
795 | proxy_sites, config.proxy_username, config.proxy_password
796 | )
797 |
798 |
799 | def get_app_path(app_file=None):
800 | app_file = app_file if app_file else argv[0]
801 | app_path = dirname(app_file)
802 | app_path = '.' if not app_path else app_path
803 | old_path = abspath(os.curdir)
804 | os.chdir(app_path)
805 | app_path = abspath(os.curdir)
806 | os.chdir(old_path)
807 | return os.path.normcase(app_path)
808 |
809 |
810 | def list_markets():
811 | # extracted from Bing Account settings page
812 | markets = (
813 | ("es-AR", "Argentina",),
814 | ("en-AU", "Australia",),
815 | ("de-AT", "Austria",),
816 | ("nl-BE", "Belgium - Dutch",),
817 | ("fr-BE", "Belgium - French",),
818 | ("pt-BR", "Brazil",),
819 | ("en-CA", "Canada - English",),
820 | ("fr-CA", "Canada - French",),
821 | ("es-CL", "Chile",),
822 | ("zh-CN", "China",),
823 | ("da-DK", "Denmark",),
824 | ("ar-EG", "Egypt",),
825 | ("fi-FI", "Finland",),
826 | ("fr-FR", "France",),
827 | ("de-DE", "Germany",),
828 | ("zh-HK", "Hong Kong SAR",),
829 | ("en-IN", "India",),
830 | ("en-ID", "Indonesia",),
831 | ("en-IE", "Ireland",),
832 | ("it-IT", "Italy",),
833 | ("ja-JP", "Japan",),
834 | ("ko-KR", "Korea",),
835 | ("en-MY", "Malaysia",),
836 | ("es-MX", "Mexico",),
837 | ("nl-NL", "Netherlands",),
838 | ("en-NZ", "New Zealand",),
839 | ("nb-NO", "Norway",),
840 | ("en-PH", "Philippines",),
841 | ("pl-PL", "Poland",),
842 | ("pt-PT", "Portugal",),
843 | ("ru-RU", "Russia",),
844 | ("ar-SA", "Saudi Arabia",),
845 | ("en-SG", "Singapore",),
846 | ("en-ZA", "South Africa",),
847 | ("es-ES", "Spain",),
848 | ("sv-SE", "Sweden",),
849 | ("fr-CH", "Switzerland - French",),
850 | ("de-CH", "Switzerland - German",),
851 | ("zh-TW", "Taiwan",),
852 | ("tr-TR", "Turkey",),
853 | ("ar-AE", "United Arab Emirates",),
854 | ("en-GB", "United Kingdom",),
855 | ("en-US", "United States - English",),
856 | ("es-US", "United States - Spanish",),
857 | )
858 | print('Available markets:')
859 | for k, v in markets:
860 | print(k, ' ', v)
861 | sys_exit(0)
862 |
863 |
864 | def main():
865 | config_db = prepare_config_db()
866 | run_config = load_config(config_db)
867 | set_debug_details(run_config.debug)
868 | if not run_config.foreground and run_config.background:
869 | start_daemon()
870 | else:
871 | start(None)
872 | return 0
873 |
874 |
875 | if __name__ == '__main__':
876 | sys_exit(main())
877 |
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/HTTPNtlmAuthHandler.py:
--------------------------------------------------------------------------------
1 | # This library is free software: you can redistribute it and/or
2 | # modify it under the terms of the GNU Lesser General Public
3 | # License as published by the Free Software Foundation, either
4 | # version 3 of the License, or (at your option) any later version.
5 |
6 | # This library is distributed in the hope that it will be useful,
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 | # Lesser General Public License for more details.
10 | #
11 | # You should have received a copy of the GNU Lesser General Public
12 | # License along with this library. If not, see or .
13 |
14 | import socket
15 | from . import ntlm
16 | from ..py23 import get_moved_attr, import_moved
17 | import inspect
18 |
19 | urllib_request = import_moved('urllib2', 'urllib.request')
20 | HTTPConnection = get_moved_attr('httplib', 'http.client', 'HTTPConnection')
21 | HTTPSConnection = get_moved_attr('httplib', 'http.client', 'HTTPSConnection')
22 | addinfourl = get_moved_attr('urllib', 'urllib.response', 'addinfourl')
23 | URLError = get_moved_attr('urllib2', 'urllib.error', 'URLError')
24 |
25 | def debug_output(*args, **kwargs):
26 | lineno = inspect.currentframe().f_back.f_lineno
27 | #print("debug at line ", lineno, ": ", *args, **kwargs)
28 |
29 | class AbstractNtlmAuthHandler:
30 |
31 | def __init__(self, password_mgr=None):
32 | if password_mgr is None:
33 | password_mgr = urllib_request.HTTPPasswordMgr()
34 | self.passwd = password_mgr
35 | self.add_password = self.passwd.add_password
36 |
37 | def http_error_authentication_required(self, auth_header_field, req, fp, headers):
38 | auth_header_value_list = headers.get_all(auth_header_field)
39 | if auth_header_value_list:
40 | if any([hv.lower() == 'ntlm' for hv in auth_header_value_list]):
41 | fp.close()
42 | return self.retry_using_http_NTLM_auth(req, auth_header_field, None, headers)
43 |
44 | def retry_using_http_NTLM_auth(self, req, auth_header_field, realm, headers):
45 | user, pw = self.passwd.find_user_password(realm, req.get_full_url())
46 | debug_output(user, pw)
47 | if pw is not None:
48 | user_parts = user.split('\\', 1)
49 | if len(user_parts) == 1:
50 | UserName = user_parts[0]
51 | DomainName = ''
52 | type1_flags = ntlm.NTLM_ttype1_FLAGS & ~ntlm.NTLM_NegotiateOemDomainSupplied
53 | else:
54 | DomainName = user_parts[0].upper()
55 | UserName = user_parts[1]
56 | type1_flags = ntlm.NTLM_ttype1_FLAGS
57 | # ntlm secures a socket, so we must use the same socket for the complete handshake
58 | debug_output(hex(type1_flags))
59 | headers = dict(req.headers)
60 | headers.update(req.unredirected_hdrs)
61 | auth = 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(user, type1_flags)
62 | if req.headers.get(self.auth_header, None) == auth:
63 | debug_output("no auth_header")
64 | return None
65 | headers[self.auth_header] = auth
66 |
67 | host = req.get_host()
68 | if not host:
69 | raise URLError('no host given')
70 | h = None
71 | if req.get_full_url().startswith('https://'):
72 | h = HTTPSConnection(host) # will parse host:port
73 | else:
74 | h = HTTPConnection(host) # will parse host:port
75 | # we must keep the connection because NTLM authenticates the connection, not single requests
76 | headers["Connection"] = "Keep-Alive"
77 | headers = dict((name.title(), val) for name, val in list(headers.items()))
78 | h.request(req.get_method(), req.get_selector(), req.data, headers)
79 | r = h.getresponse()
80 | r.begin()
81 | r._safe_read(int(r.getheader('content-length')))
82 | debug_output('data read')
83 | try:
84 | if r.getheader('set-cookie'):
85 | # this is important for some web applications that store authentication-related info in cookies (it took a long time to figure out)
86 | headers['Cookie'] = r.getheader('set-cookie')
87 | debug_output('cookie: ', headers['Cookie'])
88 | except TypeError:
89 | debug_output('no cookie')
90 | pass
91 | r.fp = None # remove the reference to the socket, so that it can not be closed by the response object (we want to keep the socket open)
92 | auth_header_value = r.getheader(auth_header_field, None)
93 | debug_output(r.headers)
94 | debug_output(auth_header_field, ': ', auth_header_value)
95 | (ServerChallenge, NegotiateFlags) = ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value[5:])
96 | debug_output('server c ', ServerChallenge, ' server flags ', hex(NegotiateFlags))
97 | auth = 'NTLM %s' % ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, UserName, DomainName, pw, NegotiateFlags)
98 | headers[self.auth_header] = auth
99 | debug_output('auth ', auth)
100 | headers["Connection"] = "Close"
101 | headers = dict((name.title(), val) for name, val in list(headers.items()))
102 | try:
103 | h.request(req.get_method(), req.get_selector(), req.data, headers)
104 | # none of the configured handlers are triggered, for example redirect-responses are not handled!
105 | response = h.getresponse()
106 | debug_output('data 3 read')
107 | def notimplemented():
108 | raise NotImplementedError
109 | response.readline = notimplemented
110 | return addinfourl(response, response.msg, req.get_full_url())
111 | except socket.error as err:
112 | raise URLError(err)
113 | else:
114 | return None
115 |
116 |
117 | class HTTPNtlmAuthHandler(AbstractNtlmAuthHandler, urllib_request.BaseHandler):
118 |
119 | auth_header = 'Authorization'
120 | handler_order = 480 # before Digest & Basic auth
121 |
122 | def http_error_401(self, req, fp, code, msg, headers):
123 | return self.http_error_authentication_required('www-authenticate', req, fp, headers)
124 |
125 |
126 | class ProxyNtlmAuthHandler(AbstractNtlmAuthHandler, urllib_request.BaseHandler):
127 | """
128 | CAUTION: this class has NOT been tested at all!!!
129 | use at your own risk
130 | """
131 | auth_header = 'Proxy-authorization'
132 | handler_order = 480 # before Digest & Basic auth
133 |
134 | def http_error_407(self, req, fp, code, msg, headers):
135 | return self.http_error_authentication_required('proxy-authenticate', req, fp, headers)
136 |
137 |
138 | if __name__ == "__main__":
139 | url = "http://ntlmprotectedserver/securedfile.html"
140 | user = "DOMAIN\\User"
141 | password = "Password"
142 | passman = urllib_request.HTTPPasswordMgrWithDefaultRealm()
143 | passman.add_password(None, url, user , password)
144 | auth_basic = urllib_request.HTTPBasicAuthHandler(passman)
145 | auth_digest = urllib_request.HTTPDigestAuthHandler(passman)
146 | auth_NTLM = HTTPNtlmAuthHandler(passman)
147 |
148 | # disable proxies (just for testing)
149 | proxy_handler = urllib_request.ProxyHandler({})
150 |
151 | opener = urllib_request.build_opener(proxy_handler, auth_NTLM) #, auth_digest, auth_basic)
152 |
153 | urllib_request.install_opener(opener)
154 |
155 | response = urllib_request.urlopen(url)
156 | print((response.read()))
157 |
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/U32.py:
--------------------------------------------------------------------------------
1 | # This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
2 | # Copyright 2001 Dmitry A. Rozmanov
3 | #
4 | # This library is free software: you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation, either
7 | # version 3 of the License, or (at your option) any later version.
8 |
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library. If not, see or .
16 |
17 |
18 | C = 0x1000000000
19 |
20 | def norm(n):
21 | return n & 0xFFFFFFFF
22 |
23 |
24 | class U32:
25 | v = 0
26 |
27 | def __init__(self, value = 0):
28 | self.v = C + norm(abs(int(value)))
29 |
30 | def set(self, value = 0):
31 | self.v = C + norm(abs(int(value)))
32 |
33 | def __repr__(self):
34 | return hex(norm(self.v))
35 |
36 | def __long__(self): return int(norm(self.v))
37 | def __int__(self): return int(norm(self.v))
38 | def __chr__(self): return chr(norm(self.v))
39 |
40 | def __add__(self, b):
41 | r = U32()
42 | r.v = C + norm(self.v + b.v)
43 | return r
44 |
45 | def __sub__(self, b):
46 | r = U32()
47 | if self.v < b.v:
48 | r.v = C + norm(0x100000000 - (b.v - self.v))
49 | else: r.v = C + norm(self.v - b.v)
50 | return r
51 |
52 | def __mul__(self, b):
53 | r = U32()
54 | r.v = C + norm(self.v * b.v)
55 | return r
56 |
57 | def __div__(self, b):
58 | r = U32()
59 | r.v = C + (norm(self.v) / norm(b.v))
60 | return r
61 |
62 | def __mod__(self, b):
63 | r = U32()
64 | r.v = C + (norm(self.v) % norm(b.v))
65 | return r
66 |
67 | def __neg__(self): return U32(self.v)
68 | def __pos__(self): return U32(self.v)
69 | def __abs__(self): return U32(self.v)
70 |
71 | def __invert__(self):
72 | r = U32()
73 | r.v = C + norm(~self.v)
74 | return r
75 |
76 | def __lshift__(self, b):
77 | r = U32()
78 | r.v = C + norm(self.v << b)
79 | return r
80 |
81 | def __rshift__(self, b):
82 | r = U32()
83 | r.v = C + (norm(self.v) >> b)
84 | return r
85 |
86 | def __and__(self, b):
87 | r = U32()
88 | r.v = C + norm(self.v & b.v)
89 | return r
90 |
91 | def __or__(self, b):
92 | r = U32()
93 | r.v = C + norm(self.v | b.v)
94 | return r
95 |
96 | def __xor__(self, b):
97 | r = U32()
98 | r.v = C + norm(self.v ^ b.v)
99 | return r
100 |
101 | def __not__(self):
102 | return U32(not norm(self.v))
103 |
104 | def truth(self):
105 | return norm(self.v)
106 |
107 | def __cmp__(self, b):
108 | if norm(self.v) > norm(b.v): return 1
109 | elif norm(self.v) < norm(b.v): return -1
110 | else: return 0
111 |
112 | def __bool__(self):
113 | return norm(self.v)
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/pybingwallpaper/ntlmauth/__init__.py
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/des.py:
--------------------------------------------------------------------------------
1 | # This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
2 | # Copyright 2001 Dmitry A. Rozmanov
3 | #
4 | # This library is free software: you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation, either
7 | # version 3 of the License, or (at your option) any later version.
8 |
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library. If not, see or .
16 | try:
17 | from . import des_c
18 | except ValueError:
19 | import des_c
20 |
21 | #---------------------------------------------------------------------
22 | class DES:
23 |
24 | des_c_obj = None
25 |
26 | #-----------------------------------------------------------------
27 | def __init__(self, key_str):
28 | ""
29 | k = str_to_key56(key_str)
30 | k = key56_to_key64(k)
31 | key_str = b''
32 | for i in k:
33 | key_str += bytes((i & 0xFF, ))
34 | self.des_c_obj = des_c.DES(key_str)
35 |
36 | #-----------------------------------------------------------------
37 | def encrypt(self, plain_text):
38 | ""
39 | return self.des_c_obj.encrypt(plain_text)
40 |
41 | #-----------------------------------------------------------------
42 | def decrypt(self, crypted_text):
43 | ""
44 | return self.des_c_obj.decrypt(crypted_text)
45 |
46 | #---------------------------------------------------------------------
47 | #Some Helpers
48 | #---------------------------------------------------------------------
49 |
50 | DESException = 'DESException'
51 |
52 | #---------------------------------------------------------------------
53 | def str_to_key56(key_str):
54 | ""
55 | if type(key_str) != type(''):
56 | #rise DESException, 'ERROR. Wrong key type.'
57 | pass
58 | if len(key_str) < 7:
59 | key_str = key_str + b'\000\000\000\000\000\000\000'[:(7 - len(key_str))]
60 | key_56 = []
61 | for i in key_str[:7]: key_56.append(i)
62 |
63 | return key_56
64 |
65 | #---------------------------------------------------------------------
66 | def key56_to_key64(key_56):
67 | ""
68 | key = []
69 | for i in range(8): key.append(0)
70 |
71 | key[0] = key_56[0];
72 | key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1);
73 | key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2);
74 | key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3);
75 | key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4);
76 | key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5);
77 | key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6);
78 | key[7] = (key_56[6] << 1) & 0xFF;
79 |
80 | key = set_key_odd_parity(key)
81 |
82 | return key
83 |
84 | #---------------------------------------------------------------------
85 | def set_key_odd_parity(key):
86 | ""
87 | for i in range(len(key)):
88 | for k in range(7):
89 | bit = 0
90 | t = key[i] >> k
91 | bit = (t ^ bit) & 0x1
92 | key[i] = (key[i] & 0xFE) | bit
93 |
94 | return key
95 |
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/des_c.py:
--------------------------------------------------------------------------------
1 | # This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
2 | # Copyright 2001 Dmitry A. Rozmanov
3 | #
4 | # This library is free software: you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation, either
7 | # version 3 of the License, or (at your option) any later version.
8 |
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library. If not, see or .
16 |
17 | try:
18 | from .U32 import U32
19 | except ValueError:
20 | from U32 import U32
21 | # --NON ASCII COMMENT ELIDED--
22 | #typedef unsigned char des_cblock[8];
23 | #define HDRSIZE 4
24 |
25 | def c2l(c):
26 | "char[4] to unsigned long"
27 | l = U32(c[0])
28 | l = l | (U32(c[1]) << 8)
29 | l = l | (U32(c[2]) << 16)
30 | l = l | (U32(c[3]) << 24)
31 | return l
32 |
33 | def c2ln(c,l1,l2,n):
34 | "char[n] to two unsigned long???"
35 | c = c + n
36 | l1, l2 = U32(0), U32(0)
37 |
38 | f = 0
39 | if n == 8:
40 | l2 = l2 | (U32(c[7]) << 24)
41 | f = 1
42 | if f or (n == 7):
43 | l2 = l2 | (U32(c[6]) << 16)
44 | f = 1
45 | if f or (n == 6):
46 | l2 = l2 | (U32(c[5]) << 8)
47 | f = 1
48 | if f or (n == 5):
49 | l2 = l2 | U32(c[4])
50 | f = 1
51 | if f or (n == 4):
52 | l1 = l1 | (U32(c[3]) << 24)
53 | f = 1
54 | if f or (n == 3):
55 | l1 = l1 | (U32(c[2]) << 16)
56 | f = 1
57 | if f or (n == 2):
58 | l1 = l1 | (U32(c[1]) << 8)
59 | f = 1
60 | if f or (n == 1):
61 | l1 = l1 | U32(c[0])
62 | return (l1, l2)
63 |
64 | def l2c(l):
65 | "unsigned long to char[4]"
66 | c = []
67 | c.append(int(l & U32(0xFF)))
68 | c.append(int((l >> 8) & U32(0xFF)))
69 | c.append(int((l >> 16) & U32(0xFF)))
70 | c.append(int((l >> 24) & U32(0xFF)))
71 | return c
72 |
73 | def n2l(c, l):
74 | "network to host long"
75 | l = U32(c[0] << 24)
76 | l = l | (U32(c[1]) << 16)
77 | l = l | (U32(c[2]) << 8)
78 | l = l | (U32(c[3]))
79 | return l
80 |
81 | def l2n(l, c):
82 | "host to network long"
83 | c = []
84 | c.append(int((l >> 24) & U32(0xFF)))
85 | c.append(int((l >> 16) & U32(0xFF)))
86 | c.append(int((l >> 8) & U32(0xFF)))
87 | c.append(int((l ) & U32(0xFF)))
88 | return c
89 |
90 | def l2cn(l1, l2, c, n):
91 | ""
92 | for i in range(n): c.append(0x00)
93 | f = 0
94 | if f or (n == 8):
95 | c[7] = int((l2 >> 24) & U32(0xFF))
96 | f = 1
97 | if f or (n == 7):
98 | c[6] = int((l2 >> 16) & U32(0xFF))
99 | f = 1
100 | if f or (n == 6):
101 | c[5] = int((l2 >> 8) & U32(0xFF))
102 | f = 1
103 | if f or (n == 5):
104 | c[4] = int((l2 ) & U32(0xFF))
105 | f = 1
106 | if f or (n == 4):
107 | c[3] = int((l1 >> 24) & U32(0xFF))
108 | f = 1
109 | if f or (n == 3):
110 | c[2] = int((l1 >> 16) & U32(0xFF))
111 | f = 1
112 | if f or (n == 2):
113 | c[1] = int((l1 >> 8) & U32(0xFF))
114 | f = 1
115 | if f or (n == 1):
116 | c[0] = int((l1 ) & U32(0xFF))
117 | f = 1
118 | return c[:n]
119 |
120 | # array of data
121 | # static unsigned long des_SPtrans[8][64]={
122 | # static unsigned long des_skb[8][64]={
123 | try:
124 | from .des_data import des_SPtrans, des_skb
125 | except ValueError:
126 | from des_data import des_SPtrans, des_skb
127 |
128 | def D_ENCRYPT(tup, u, t, s):
129 | L, R, S = tup
130 | #print 'LRS1', L, R, S, u, t, '-->',
131 | u = (R ^ s[S])
132 | t = R ^ s[S + 1]
133 | t = ((t >> 4) + (t << 28))
134 | L = L ^ (des_SPtrans[1][int((t ) & U32(0x3f))] | \
135 | des_SPtrans[3][int((t >> 8) & U32(0x3f))] | \
136 | des_SPtrans[5][int((t >> 16) & U32(0x3f))] | \
137 | des_SPtrans[7][int((t >> 24) & U32(0x3f))] | \
138 | des_SPtrans[0][int((u ) & U32(0x3f))] | \
139 | des_SPtrans[2][int((u >> 8) & U32(0x3f))] | \
140 | des_SPtrans[4][int((u >> 16) & U32(0x3f))] | \
141 | des_SPtrans[6][int((u >> 24) & U32(0x3f))])
142 | #print 'LRS:', L, R, S, u, t
143 | return ((L, R, S), u, t, s)
144 |
145 |
146 | def PERM_OP (tup, n, m):
147 | "tup - (a, b, t)"
148 | a, b, t = tup
149 | t = ((a >> n) ^ b) & m
150 | b = b ^ t
151 | a = a ^ (t << n)
152 | return (a, b, t)
153 |
154 | def HPERM_OP (tup, n, m):
155 | "tup - (a, t)"
156 | a, t = tup
157 | t = ((a << (16 - n)) ^ a) & m
158 | a = a ^ t ^ (t >> (16 - n))
159 | return (a, t)
160 |
161 | shifts2 = [0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0]
162 |
163 | class DES:
164 | KeySched = None # des_key_schedule
165 |
166 | def __init__(self, key_str):
167 | # key - UChar[8]
168 | key = []
169 | #~ for i in key_str: key.append(ord(i))
170 | #print 'key:', key
171 | self.KeySched = des_set_key(key_str)
172 | #print 'schedule:', self.KeySched, len(self.KeySched)
173 |
174 | def decrypt(self, str):
175 | # block - UChar[]
176 | block = []
177 | for i in str: block.append(ord(i))
178 | #print block
179 | block = des_ecb_encrypt(block, self.KeySched, 0)
180 | res = b''
181 | for i in block: res = res + (chr(i))
182 | return res
183 |
184 | def encrypt(self, str):
185 | # block - UChar[]
186 | block = []
187 | for i in str: block.append(i)
188 | block = des_ecb_encrypt(block, self.KeySched, 1)
189 | res = b''
190 | for i in block: res = res + bytes((i,))
191 | return res
192 |
193 |
194 |
195 |
196 |
197 |
198 | #------------------------
199 | def des_encript(input, ks, encrypt):
200 | # input - U32[]
201 | # output - U32[]
202 | # ks - des_key_shedule - U32[2][16]
203 | # encrypt - int
204 | # l, r, t, u - U32
205 | # i - int
206 | # s - U32[]
207 |
208 | l = input[0]
209 | r = input[1]
210 | t = U32(0)
211 | u = U32(0)
212 |
213 | r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
214 | l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
215 | r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
216 | l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
217 | r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
218 |
219 | t = (r << 1)|(r >> 31)
220 | r = (l << 1)|(l >> 31)
221 | l = t
222 |
223 | s = ks # ???????????????
224 | #print l, r
225 | if(encrypt):
226 | for i in range(0, 32, 4):
227 | rtup, u, t, s = D_ENCRYPT((l, r, i + 0), u, t, s)
228 | l = rtup[0]
229 | r = rtup[1]
230 | rtup, u, t, s = D_ENCRYPT((r, l, i + 2), u, t, s)
231 | r = rtup[0]
232 | l = rtup[1]
233 | else:
234 | for i in range(30, 0, -4):
235 | rtup, u, t, s = D_ENCRYPT((l, r, i - 0), u, t, s)
236 | l = rtup[0]
237 | r = rtup[1]
238 | rtup, u, t, s = D_ENCRYPT((r, l, i - 2), u, t, s)
239 | r = rtup[0]
240 | l = rtup[1]
241 | #print l, r
242 | l = (l >> 1)|(l << 31)
243 | r = (r >> 1)|(r << 31)
244 |
245 | r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
246 | l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
247 | r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
248 | l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
249 | r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
250 |
251 | output = [l]
252 | output.append(r)
253 | l, r, t, u = U32(0), U32(0), U32(0), U32(0)
254 | return output
255 |
256 | def des_ecb_encrypt(input, ks, encrypt):
257 | # input - des_cblock - UChar[8]
258 | # output - des_cblock - UChar[8]
259 | # ks - des_key_shedule - U32[2][16]
260 | # encrypt - int
261 |
262 | #print input
263 | l0 = c2l(input[0:4])
264 | l1 = c2l(input[4:8])
265 | ll = [l0]
266 | ll.append(l1)
267 | #print ll
268 | ll = des_encript(ll, ks, encrypt)
269 | #print ll
270 | l0 = ll[0]
271 | l1 = ll[1]
272 | output = l2c(l0)
273 | output = output + l2c(l1)
274 | #print output
275 | l0, l1, ll[0], ll[1] = U32(0), U32(0), U32(0), U32(0)
276 | return output
277 |
278 | def des_set_key(key):
279 | # key - des_cblock - UChar[8]
280 | # schedule - des_key_schedule
281 |
282 | # register unsigned long c,d,t,s;
283 | # register unsigned char *in;
284 | # register unsigned long *k;
285 | # register int i;
286 |
287 | #k = schedule
288 | # in = key
289 | k = []
290 | c = c2l(key[0:4])
291 | d = c2l(key[4:8])
292 | t = U32(0)
293 |
294 | d, c, t = PERM_OP((d, c, t), 4, U32(0x0f0f0f0f))
295 | c, t = HPERM_OP((c, t), -2, U32(0xcccc0000))
296 | d, t = HPERM_OP((d, t), -2, U32(0xcccc0000))
297 | d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
298 | c, d, t = PERM_OP((c, d, t), 8, U32(0x00ff00ff))
299 | d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
300 |
301 | d = (((d & U32(0x000000ff)) << 16)|(d & U32(0x0000ff00))|((d & U32(0x00ff0000)) >> 16)|((c & U32(0xf0000000)) >> 4))
302 | c = c & U32(0x0fffffff)
303 |
304 | for i in range(16):
305 | if (shifts2[i]):
306 | c = ((c >> 2)|(c << 26))
307 | d = ((d >> 2)|(d << 26))
308 | else:
309 | c = ((c >> 1)|(c << 27))
310 | d = ((d >> 1)|(d << 27))
311 | c = c & U32(0x0fffffff)
312 | d = d & U32(0x0fffffff)
313 |
314 | s= des_skb[0][int((c ) & U32(0x3f))]|\
315 | des_skb[1][int(((c>> 6) & U32(0x03))|((c>> 7) & U32(0x3c)))]|\
316 | des_skb[2][int(((c>>13) & U32(0x0f))|((c>>14) & U32(0x30)))]|\
317 | des_skb[3][int(((c>>20) & U32(0x01))|((c>>21) & U32(0x06)) | ((c>>22) & U32(0x38)))]
318 |
319 | t= des_skb[4][int((d ) & U32(0x3f) )]|\
320 | des_skb[5][int(((d>> 7) & U32(0x03))|((d>> 8) & U32(0x3c)))]|\
321 | des_skb[6][int((d>>15) & U32(0x3f) )]|\
322 | des_skb[7][int(((d>>21) & U32(0x0f))|((d>>22) & U32(0x30)))]
323 | #print s, t
324 |
325 | k.append(((t << 16)|(s & U32(0x0000ffff))) & U32(0xffffffff))
326 | s = ((s >> 16)|(t & U32(0xffff0000)))
327 | s = (s << 4)|(s >> 28)
328 | k.append(s & U32(0xffffffff))
329 |
330 | schedule = k
331 |
332 | return schedule
333 |
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/des_data.py:
--------------------------------------------------------------------------------
1 | # This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
2 | # Copyright 2001 Dmitry A. Rozmanov
3 | #
4 | # This library is free software: you can redistribute it and/or
5 | # modify it under the terms of the GNU Lesser General Public
6 | # License as published by the Free Software Foundation, either
7 | # version 3 of the License, or (at your option) any later version.
8 |
9 | # This library is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 | # Lesser General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Lesser General Public
15 | # License along with this library. If not, see or .
16 | try:
17 | from .U32 import U32
18 | except ValueError:
19 | from U32 import U32
20 | # static unsigned long des_SPtrans[8][64]={
21 |
22 | des_SPtrans =\
23 | [
24 | #nibble 0
25 | [
26 | U32(0x00820200), U32(0x00020000), U32(0x80800000), U32(0x80820200),
27 | U32(0x00800000), U32(0x80020200), U32(0x80020000), U32(0x80800000),
28 | U32(0x80020200), U32(0x00820200), U32(0x00820000), U32(0x80000200),
29 | U32(0x80800200), U32(0x00800000), U32(0x00000000), U32(0x80020000),
30 | U32(0x00020000), U32(0x80000000), U32(0x00800200), U32(0x00020200),
31 | U32(0x80820200), U32(0x00820000), U32(0x80000200), U32(0x00800200),
32 | U32(0x80000000), U32(0x00000200), U32(0x00020200), U32(0x80820000),
33 | U32(0x00000200), U32(0x80800200), U32(0x80820000), U32(0x00000000),
34 | U32(0x00000000), U32(0x80820200), U32(0x00800200), U32(0x80020000),
35 | U32(0x00820200), U32(0x00020000), U32(0x80000200), U32(0x00800200),
36 | U32(0x80820000), U32(0x00000200), U32(0x00020200), U32(0x80800000),
37 | U32(0x80020200), U32(0x80000000), U32(0x80800000), U32(0x00820000),
38 | U32(0x80820200), U32(0x00020200), U32(0x00820000), U32(0x80800200),
39 | U32(0x00800000), U32(0x80000200), U32(0x80020000), U32(0x00000000),
40 | U32(0x00020000), U32(0x00800000), U32(0x80800200), U32(0x00820200),
41 | U32(0x80000000), U32(0x80820000), U32(0x00000200), U32(0x80020200),
42 | ],
43 |
44 | #nibble 1
45 | [
46 | U32(0x10042004), U32(0x00000000), U32(0x00042000), U32(0x10040000),
47 | U32(0x10000004), U32(0x00002004), U32(0x10002000), U32(0x00042000),
48 | U32(0x00002000), U32(0x10040004), U32(0x00000004), U32(0x10002000),
49 | U32(0x00040004), U32(0x10042000), U32(0x10040000), U32(0x00000004),
50 | U32(0x00040000), U32(0x10002004), U32(0x10040004), U32(0x00002000),
51 | U32(0x00042004), U32(0x10000000), U32(0x00000000), U32(0x00040004),
52 | U32(0x10002004), U32(0x00042004), U32(0x10042000), U32(0x10000004),
53 | U32(0x10000000), U32(0x00040000), U32(0x00002004), U32(0x10042004),
54 | U32(0x00040004), U32(0x10042000), U32(0x10002000), U32(0x00042004),
55 | U32(0x10042004), U32(0x00040004), U32(0x10000004), U32(0x00000000),
56 | U32(0x10000000), U32(0x00002004), U32(0x00040000), U32(0x10040004),
57 | U32(0x00002000), U32(0x10000000), U32(0x00042004), U32(0x10002004),
58 | U32(0x10042000), U32(0x00002000), U32(0x00000000), U32(0x10000004),
59 | U32(0x00000004), U32(0x10042004), U32(0x00042000), U32(0x10040000),
60 | U32(0x10040004), U32(0x00040000), U32(0x00002004), U32(0x10002000),
61 | U32(0x10002004), U32(0x00000004), U32(0x10040000), U32(0x00042000),
62 | ],
63 |
64 | #nibble 2
65 | [
66 | U32(0x41000000), U32(0x01010040), U32(0x00000040), U32(0x41000040),
67 | U32(0x40010000), U32(0x01000000), U32(0x41000040), U32(0x00010040),
68 | U32(0x01000040), U32(0x00010000), U32(0x01010000), U32(0x40000000),
69 | U32(0x41010040), U32(0x40000040), U32(0x40000000), U32(0x41010000),
70 | U32(0x00000000), U32(0x40010000), U32(0x01010040), U32(0x00000040),
71 | U32(0x40000040), U32(0x41010040), U32(0x00010000), U32(0x41000000),
72 | U32(0x41010000), U32(0x01000040), U32(0x40010040), U32(0x01010000),
73 | U32(0x00010040), U32(0x00000000), U32(0x01000000), U32(0x40010040),
74 | U32(0x01010040), U32(0x00000040), U32(0x40000000), U32(0x00010000),
75 | U32(0x40000040), U32(0x40010000), U32(0x01010000), U32(0x41000040),
76 | U32(0x00000000), U32(0x01010040), U32(0x00010040), U32(0x41010000),
77 | U32(0x40010000), U32(0x01000000), U32(0x41010040), U32(0x40000000),
78 | U32(0x40010040), U32(0x41000000), U32(0x01000000), U32(0x41010040),
79 | U32(0x00010000), U32(0x01000040), U32(0x41000040), U32(0x00010040),
80 | U32(0x01000040), U32(0x00000000), U32(0x41010000), U32(0x40000040),
81 | U32(0x41000000), U32(0x40010040), U32(0x00000040), U32(0x01010000),
82 | ],
83 |
84 | #nibble 3
85 | [
86 | U32(0x00100402), U32(0x04000400), U32(0x00000002), U32(0x04100402),
87 | U32(0x00000000), U32(0x04100000), U32(0x04000402), U32(0x00100002),
88 | U32(0x04100400), U32(0x04000002), U32(0x04000000), U32(0x00000402),
89 | U32(0x04000002), U32(0x00100402), U32(0x00100000), U32(0x04000000),
90 | U32(0x04100002), U32(0x00100400), U32(0x00000400), U32(0x00000002),
91 | U32(0x00100400), U32(0x04000402), U32(0x04100000), U32(0x00000400),
92 | U32(0x00000402), U32(0x00000000), U32(0x00100002), U32(0x04100400),
93 | U32(0x04000400), U32(0x04100002), U32(0x04100402), U32(0x00100000),
94 | U32(0x04100002), U32(0x00000402), U32(0x00100000), U32(0x04000002),
95 | U32(0x00100400), U32(0x04000400), U32(0x00000002), U32(0x04100000),
96 | U32(0x04000402), U32(0x00000000), U32(0x00000400), U32(0x00100002),
97 | U32(0x00000000), U32(0x04100002), U32(0x04100400), U32(0x00000400),
98 | U32(0x04000000), U32(0x04100402), U32(0x00100402), U32(0x00100000),
99 | U32(0x04100402), U32(0x00000002), U32(0x04000400), U32(0x00100402),
100 | U32(0x00100002), U32(0x00100400), U32(0x04100000), U32(0x04000402),
101 | U32(0x00000402), U32(0x04000000), U32(0x04000002), U32(0x04100400),
102 | ],
103 |
104 | #nibble 4
105 | [
106 | U32(0x02000000), U32(0x00004000), U32(0x00000100), U32(0x02004108),
107 | U32(0x02004008), U32(0x02000100), U32(0x00004108), U32(0x02004000),
108 | U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x00004100),
109 | U32(0x02000108), U32(0x02004008), U32(0x02004100), U32(0x00000000),
110 | U32(0x00004100), U32(0x02000000), U32(0x00004008), U32(0x00000108),
111 | U32(0x02000100), U32(0x00004108), U32(0x00000000), U32(0x02000008),
112 | U32(0x00000008), U32(0x02000108), U32(0x02004108), U32(0x00004008),
113 | U32(0x02004000), U32(0x00000100), U32(0x00000108), U32(0x02004100),
114 | U32(0x02004100), U32(0x02000108), U32(0x00004008), U32(0x02004000),
115 | U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x02000100),
116 | U32(0x02000000), U32(0x00004100), U32(0x02004108), U32(0x00000000),
117 | U32(0x00004108), U32(0x02000000), U32(0x00000100), U32(0x00004008),
118 | U32(0x02000108), U32(0x00000100), U32(0x00000000), U32(0x02004108),
119 | U32(0x02004008), U32(0x02004100), U32(0x00000108), U32(0x00004000),
120 | U32(0x00004100), U32(0x02004008), U32(0x02000100), U32(0x00000108),
121 | U32(0x00000008), U32(0x00004108), U32(0x02004000), U32(0x02000008),
122 | ],
123 |
124 | #nibble 5
125 | [
126 | U32(0x20000010), U32(0x00080010), U32(0x00000000), U32(0x20080800),
127 | U32(0x00080010), U32(0x00000800), U32(0x20000810), U32(0x00080000),
128 | U32(0x00000810), U32(0x20080810), U32(0x00080800), U32(0x20000000),
129 | U32(0x20000800), U32(0x20000010), U32(0x20080000), U32(0x00080810),
130 | U32(0x00080000), U32(0x20000810), U32(0x20080010), U32(0x00000000),
131 | U32(0x00000800), U32(0x00000010), U32(0x20080800), U32(0x20080010),
132 | U32(0x20080810), U32(0x20080000), U32(0x20000000), U32(0x00000810),
133 | U32(0x00000010), U32(0x00080800), U32(0x00080810), U32(0x20000800),
134 | U32(0x00000810), U32(0x20000000), U32(0x20000800), U32(0x00080810),
135 | U32(0x20080800), U32(0x00080010), U32(0x00000000), U32(0x20000800),
136 | U32(0x20000000), U32(0x00000800), U32(0x20080010), U32(0x00080000),
137 | U32(0x00080010), U32(0x20080810), U32(0x00080800), U32(0x00000010),
138 | U32(0x20080810), U32(0x00080800), U32(0x00080000), U32(0x20000810),
139 | U32(0x20000010), U32(0x20080000), U32(0x00080810), U32(0x00000000),
140 | U32(0x00000800), U32(0x20000010), U32(0x20000810), U32(0x20080800),
141 | U32(0x20080000), U32(0x00000810), U32(0x00000010), U32(0x20080010),
142 | ],
143 |
144 | #nibble 6
145 | [
146 | U32(0x00001000), U32(0x00000080), U32(0x00400080), U32(0x00400001),
147 | U32(0x00401081), U32(0x00001001), U32(0x00001080), U32(0x00000000),
148 | U32(0x00400000), U32(0x00400081), U32(0x00000081), U32(0x00401000),
149 | U32(0x00000001), U32(0x00401080), U32(0x00401000), U32(0x00000081),
150 | U32(0x00400081), U32(0x00001000), U32(0x00001001), U32(0x00401081),
151 | U32(0x00000000), U32(0x00400080), U32(0x00400001), U32(0x00001080),
152 | U32(0x00401001), U32(0x00001081), U32(0x00401080), U32(0x00000001),
153 | U32(0x00001081), U32(0x00401001), U32(0x00000080), U32(0x00400000),
154 | U32(0x00001081), U32(0x00401000), U32(0x00401001), U32(0x00000081),
155 | U32(0x00001000), U32(0x00000080), U32(0x00400000), U32(0x00401001),
156 | U32(0x00400081), U32(0x00001081), U32(0x00001080), U32(0x00000000),
157 | U32(0x00000080), U32(0x00400001), U32(0x00000001), U32(0x00400080),
158 | U32(0x00000000), U32(0x00400081), U32(0x00400080), U32(0x00001080),
159 | U32(0x00000081), U32(0x00001000), U32(0x00401081), U32(0x00400000),
160 | U32(0x00401080), U32(0x00000001), U32(0x00001001), U32(0x00401081),
161 | U32(0x00400001), U32(0x00401080), U32(0x00401000), U32(0x00001001),
162 | ],
163 |
164 | #nibble 7
165 | [
166 | U32(0x08200020), U32(0x08208000), U32(0x00008020), U32(0x00000000),
167 | U32(0x08008000), U32(0x00200020), U32(0x08200000), U32(0x08208020),
168 | U32(0x00000020), U32(0x08000000), U32(0x00208000), U32(0x00008020),
169 | U32(0x00208020), U32(0x08008020), U32(0x08000020), U32(0x08200000),
170 | U32(0x00008000), U32(0x00208020), U32(0x00200020), U32(0x08008000),
171 | U32(0x08208020), U32(0x08000020), U32(0x00000000), U32(0x00208000),
172 | U32(0x08000000), U32(0x00200000), U32(0x08008020), U32(0x08200020),
173 | U32(0x00200000), U32(0x00008000), U32(0x08208000), U32(0x00000020),
174 | U32(0x00200000), U32(0x00008000), U32(0x08000020), U32(0x08208020),
175 | U32(0x00008020), U32(0x08000000), U32(0x00000000), U32(0x00208000),
176 | U32(0x08200020), U32(0x08008020), U32(0x08008000), U32(0x00200020),
177 | U32(0x08208000), U32(0x00000020), U32(0x00200020), U32(0x08008000),
178 | U32(0x08208020), U32(0x00200000), U32(0x08200000), U32(0x08000020),
179 | U32(0x00208000), U32(0x00008020), U32(0x08008020), U32(0x08200000),
180 | U32(0x00000020), U32(0x08208000), U32(0x00208020), U32(0x00000000),
181 | U32(0x08000000), U32(0x08200020), U32(0x00008000), U32(0x00208020),
182 | ],
183 | ]
184 |
185 | #static unsigned long des_skb[8][64]={
186 |
187 | des_skb = \
188 | [
189 | #for C bits (numbered as per FIPS 46) 1 2 3 4 5 6
190 | [
191 | U32(0x00000000),U32(0x00000010),U32(0x20000000),U32(0x20000010),
192 | U32(0x00010000),U32(0x00010010),U32(0x20010000),U32(0x20010010),
193 | U32(0x00000800),U32(0x00000810),U32(0x20000800),U32(0x20000810),
194 | U32(0x00010800),U32(0x00010810),U32(0x20010800),U32(0x20010810),
195 | U32(0x00000020),U32(0x00000030),U32(0x20000020),U32(0x20000030),
196 | U32(0x00010020),U32(0x00010030),U32(0x20010020),U32(0x20010030),
197 | U32(0x00000820),U32(0x00000830),U32(0x20000820),U32(0x20000830),
198 | U32(0x00010820),U32(0x00010830),U32(0x20010820),U32(0x20010830),
199 | U32(0x00080000),U32(0x00080010),U32(0x20080000),U32(0x20080010),
200 | U32(0x00090000),U32(0x00090010),U32(0x20090000),U32(0x20090010),
201 | U32(0x00080800),U32(0x00080810),U32(0x20080800),U32(0x20080810),
202 | U32(0x00090800),U32(0x00090810),U32(0x20090800),U32(0x20090810),
203 | U32(0x00080020),U32(0x00080030),U32(0x20080020),U32(0x20080030),
204 | U32(0x00090020),U32(0x00090030),U32(0x20090020),U32(0x20090030),
205 | U32(0x00080820),U32(0x00080830),U32(0x20080820),U32(0x20080830),
206 | U32(0x00090820),U32(0x00090830),U32(0x20090820),U32(0x20090830),
207 | ],
208 |
209 | #for C bits (numbered as per FIPS 46) 7 8 10 11 12 13
210 | [
211 | U32(0x00000000),U32(0x02000000),U32(0x00002000),U32(0x02002000),
212 | U32(0x00200000),U32(0x02200000),U32(0x00202000),U32(0x02202000),
213 | U32(0x00000004),U32(0x02000004),U32(0x00002004),U32(0x02002004),
214 | U32(0x00200004),U32(0x02200004),U32(0x00202004),U32(0x02202004),
215 | U32(0x00000400),U32(0x02000400),U32(0x00002400),U32(0x02002400),
216 | U32(0x00200400),U32(0x02200400),U32(0x00202400),U32(0x02202400),
217 | U32(0x00000404),U32(0x02000404),U32(0x00002404),U32(0x02002404),
218 | U32(0x00200404),U32(0x02200404),U32(0x00202404),U32(0x02202404),
219 | U32(0x10000000),U32(0x12000000),U32(0x10002000),U32(0x12002000),
220 | U32(0x10200000),U32(0x12200000),U32(0x10202000),U32(0x12202000),
221 | U32(0x10000004),U32(0x12000004),U32(0x10002004),U32(0x12002004),
222 | U32(0x10200004),U32(0x12200004),U32(0x10202004),U32(0x12202004),
223 | U32(0x10000400),U32(0x12000400),U32(0x10002400),U32(0x12002400),
224 | U32(0x10200400),U32(0x12200400),U32(0x10202400),U32(0x12202400),
225 | U32(0x10000404),U32(0x12000404),U32(0x10002404),U32(0x12002404),
226 | U32(0x10200404),U32(0x12200404),U32(0x10202404),U32(0x12202404),
227 | ],
228 |
229 | #for C bits (numbered as per FIPS 46) 14 15 16 17 19 20
230 | [
231 | U32(0x00000000),U32(0x00000001),U32(0x00040000),U32(0x00040001),
232 | U32(0x01000000),U32(0x01000001),U32(0x01040000),U32(0x01040001),
233 | U32(0x00000002),U32(0x00000003),U32(0x00040002),U32(0x00040003),
234 | U32(0x01000002),U32(0x01000003),U32(0x01040002),U32(0x01040003),
235 | U32(0x00000200),U32(0x00000201),U32(0x00040200),U32(0x00040201),
236 | U32(0x01000200),U32(0x01000201),U32(0x01040200),U32(0x01040201),
237 | U32(0x00000202),U32(0x00000203),U32(0x00040202),U32(0x00040203),
238 | U32(0x01000202),U32(0x01000203),U32(0x01040202),U32(0x01040203),
239 | U32(0x08000000),U32(0x08000001),U32(0x08040000),U32(0x08040001),
240 | U32(0x09000000),U32(0x09000001),U32(0x09040000),U32(0x09040001),
241 | U32(0x08000002),U32(0x08000003),U32(0x08040002),U32(0x08040003),
242 | U32(0x09000002),U32(0x09000003),U32(0x09040002),U32(0x09040003),
243 | U32(0x08000200),U32(0x08000201),U32(0x08040200),U32(0x08040201),
244 | U32(0x09000200),U32(0x09000201),U32(0x09040200),U32(0x09040201),
245 | U32(0x08000202),U32(0x08000203),U32(0x08040202),U32(0x08040203),
246 | U32(0x09000202),U32(0x09000203),U32(0x09040202),U32(0x09040203),
247 | ],
248 |
249 | #for C bits (numbered as per FIPS 46) 21 23 24 26 27 28
250 | [
251 | U32(0x00000000),U32(0x00100000),U32(0x00000100),U32(0x00100100),
252 | U32(0x00000008),U32(0x00100008),U32(0x00000108),U32(0x00100108),
253 | U32(0x00001000),U32(0x00101000),U32(0x00001100),U32(0x00101100),
254 | U32(0x00001008),U32(0x00101008),U32(0x00001108),U32(0x00101108),
255 | U32(0x04000000),U32(0x04100000),U32(0x04000100),U32(0x04100100),
256 | U32(0x04000008),U32(0x04100008),U32(0x04000108),U32(0x04100108),
257 | U32(0x04001000),U32(0x04101000),U32(0x04001100),U32(0x04101100),
258 | U32(0x04001008),U32(0x04101008),U32(0x04001108),U32(0x04101108),
259 | U32(0x00020000),U32(0x00120000),U32(0x00020100),U32(0x00120100),
260 | U32(0x00020008),U32(0x00120008),U32(0x00020108),U32(0x00120108),
261 | U32(0x00021000),U32(0x00121000),U32(0x00021100),U32(0x00121100),
262 | U32(0x00021008),U32(0x00121008),U32(0x00021108),U32(0x00121108),
263 | U32(0x04020000),U32(0x04120000),U32(0x04020100),U32(0x04120100),
264 | U32(0x04020008),U32(0x04120008),U32(0x04020108),U32(0x04120108),
265 | U32(0x04021000),U32(0x04121000),U32(0x04021100),U32(0x04121100),
266 | U32(0x04021008),U32(0x04121008),U32(0x04021108),U32(0x04121108),
267 | ],
268 |
269 | #for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
270 | [
271 | U32(0x00000000),U32(0x10000000),U32(0x00010000),U32(0x10010000),
272 | U32(0x00000004),U32(0x10000004),U32(0x00010004),U32(0x10010004),
273 | U32(0x20000000),U32(0x30000000),U32(0x20010000),U32(0x30010000),
274 | U32(0x20000004),U32(0x30000004),U32(0x20010004),U32(0x30010004),
275 | U32(0x00100000),U32(0x10100000),U32(0x00110000),U32(0x10110000),
276 | U32(0x00100004),U32(0x10100004),U32(0x00110004),U32(0x10110004),
277 | U32(0x20100000),U32(0x30100000),U32(0x20110000),U32(0x30110000),
278 | U32(0x20100004),U32(0x30100004),U32(0x20110004),U32(0x30110004),
279 | U32(0x00001000),U32(0x10001000),U32(0x00011000),U32(0x10011000),
280 | U32(0x00001004),U32(0x10001004),U32(0x00011004),U32(0x10011004),
281 | U32(0x20001000),U32(0x30001000),U32(0x20011000),U32(0x30011000),
282 | U32(0x20001004),U32(0x30001004),U32(0x20011004),U32(0x30011004),
283 | U32(0x00101000),U32(0x10101000),U32(0x00111000),U32(0x10111000),
284 | U32(0x00101004),U32(0x10101004),U32(0x00111004),U32(0x10111004),
285 | U32(0x20101000),U32(0x30101000),U32(0x20111000),U32(0x30111000),
286 | U32(0x20101004),U32(0x30101004),U32(0x20111004),U32(0x30111004),
287 | ],
288 |
289 | #for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
290 | [
291 | U32(0x00000000),U32(0x08000000),U32(0x00000008),U32(0x08000008),
292 | U32(0x00000400),U32(0x08000400),U32(0x00000408),U32(0x08000408),
293 | U32(0x00020000),U32(0x08020000),U32(0x00020008),U32(0x08020008),
294 | U32(0x00020400),U32(0x08020400),U32(0x00020408),U32(0x08020408),
295 | U32(0x00000001),U32(0x08000001),U32(0x00000009),U32(0x08000009),
296 | U32(0x00000401),U32(0x08000401),U32(0x00000409),U32(0x08000409),
297 | U32(0x00020001),U32(0x08020001),U32(0x00020009),U32(0x08020009),
298 | U32(0x00020401),U32(0x08020401),U32(0x00020409),U32(0x08020409),
299 | U32(0x02000000),U32(0x0A000000),U32(0x02000008),U32(0x0A000008),
300 | U32(0x02000400),U32(0x0A000400),U32(0x02000408),U32(0x0A000408),
301 | U32(0x02020000),U32(0x0A020000),U32(0x02020008),U32(0x0A020008),
302 | U32(0x02020400),U32(0x0A020400),U32(0x02020408),U32(0x0A020408),
303 | U32(0x02000001),U32(0x0A000001),U32(0x02000009),U32(0x0A000009),
304 | U32(0x02000401),U32(0x0A000401),U32(0x02000409),U32(0x0A000409),
305 | U32(0x02020001),U32(0x0A020001),U32(0x02020009),U32(0x0A020009),
306 | U32(0x02020401),U32(0x0A020401),U32(0x02020409),U32(0x0A020409),
307 | ],
308 |
309 | #for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
310 | [
311 | U32(0x00000000),U32(0x00000100),U32(0x00080000),U32(0x00080100),
312 | U32(0x01000000),U32(0x01000100),U32(0x01080000),U32(0x01080100),
313 | U32(0x00000010),U32(0x00000110),U32(0x00080010),U32(0x00080110),
314 | U32(0x01000010),U32(0x01000110),U32(0x01080010),U32(0x01080110),
315 | U32(0x00200000),U32(0x00200100),U32(0x00280000),U32(0x00280100),
316 | U32(0x01200000),U32(0x01200100),U32(0x01280000),U32(0x01280100),
317 | U32(0x00200010),U32(0x00200110),U32(0x00280010),U32(0x00280110),
318 | U32(0x01200010),U32(0x01200110),U32(0x01280010),U32(0x01280110),
319 | U32(0x00000200),U32(0x00000300),U32(0x00080200),U32(0x00080300),
320 | U32(0x01000200),U32(0x01000300),U32(0x01080200),U32(0x01080300),
321 | U32(0x00000210),U32(0x00000310),U32(0x00080210),U32(0x00080310),
322 | U32(0x01000210),U32(0x01000310),U32(0x01080210),U32(0x01080310),
323 | U32(0x00200200),U32(0x00200300),U32(0x00280200),U32(0x00280300),
324 | U32(0x01200200),U32(0x01200300),U32(0x01280200),U32(0x01280300),
325 | U32(0x00200210),U32(0x00200310),U32(0x00280210),U32(0x00280310),
326 | U32(0x01200210),U32(0x01200310),U32(0x01280210),U32(0x01280310),
327 | ],
328 |
329 | #for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
330 | [
331 | U32(0x00000000),U32(0x04000000),U32(0x00040000),U32(0x04040000),
332 | U32(0x00000002),U32(0x04000002),U32(0x00040002),U32(0x04040002),
333 | U32(0x00002000),U32(0x04002000),U32(0x00042000),U32(0x04042000),
334 | U32(0x00002002),U32(0x04002002),U32(0x00042002),U32(0x04042002),
335 | U32(0x00000020),U32(0x04000020),U32(0x00040020),U32(0x04040020),
336 | U32(0x00000022),U32(0x04000022),U32(0x00040022),U32(0x04040022),
337 | U32(0x00002020),U32(0x04002020),U32(0x00042020),U32(0x04042020),
338 | U32(0x00002022),U32(0x04002022),U32(0x00042022),U32(0x04042022),
339 | U32(0x00000800),U32(0x04000800),U32(0x00040800),U32(0x04040800),
340 | U32(0x00000802),U32(0x04000802),U32(0x00040802),U32(0x04040802),
341 | U32(0x00002800),U32(0x04002800),U32(0x00042800),U32(0x04042800),
342 | U32(0x00002802),U32(0x04002802),U32(0x00042802),U32(0x04042802),
343 | U32(0x00000820),U32(0x04000820),U32(0x00040820),U32(0x04040820),
344 | U32(0x00000822),U32(0x04000822),U32(0x00040822),U32(0x04040822),
345 | U32(0x00002820),U32(0x04002820),U32(0x00042820),U32(0x04042820),
346 | U32(0x00002822),U32(0x04002822),U32(0x00042822),U32(0x04042822),
347 | ]
348 |
349 | ]
--------------------------------------------------------------------------------
/pybingwallpaper/ntlmauth/ntlm.py:
--------------------------------------------------------------------------------
1 | # This library is free software: you can redistribute it and/or
2 | # modify it under the terms of the GNU Lesser General Public
3 | # License as published by the Free Software Foundation, either
4 | # version 3 of the License, or (at your option) any later version.
5 |
6 | # This library is distributed in the hope that it will be useful,
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 | # Lesser General Public License for more details.
10 | #
11 | # You should have received a copy of the GNU Lesser General Public
12 | # License along with this library. If not, see or .
13 |
14 | import struct
15 | import base64
16 | import string
17 | try:
18 | from . import des
19 | except ValueError:
20 | import des
21 | import hashlib
22 | import hmac
23 | import random
24 | import re
25 | import binascii
26 | from socket import gethostname
27 |
28 | NTLM_NegotiateUnicode = 0x00000001
29 | NTLM_NegotiateOEM = 0x00000002
30 | NTLM_RequestTarget = 0x00000004
31 | NTLM_Unknown9 = 0x00000008
32 | NTLM_NegotiateSign = 0x00000010
33 | NTLM_NegotiateSeal = 0x00000020
34 | NTLM_NegotiateDatagram = 0x00000040
35 | NTLM_NegotiateLanManagerKey = 0x00000080
36 | NTLM_Unknown8 = 0x00000100
37 | NTLM_NegotiateNTLM = 0x00000200
38 | NTLM_NegotiateNTOnly = 0x00000400
39 | NTLM_Anonymous = 0x00000800
40 | NTLM_NegotiateOemDomainSupplied = 0x00001000
41 | NTLM_NegotiateOemWorkstationSupplied = 0x00002000
42 | NTLM_Unknown6 = 0x00004000
43 | NTLM_NegotiateAlwaysSign = 0x00008000
44 | NTLM_TargetttypeDomain = 0x00010000
45 | NTLM_TargetttypeServer = 0x00020000
46 | NTLM_TargetttypeShare = 0x00040000
47 | NTLM_NegotiateExtendedSecurity = 0x00080000
48 | NTLM_NegotiateIdentify = 0x00100000
49 | NTLM_Unknown5 = 0x00200000
50 | NTLM_RequestNonNTSessionKey = 0x00400000
51 | NTLM_NegotiateTargetInfo = 0x00800000
52 | NTLM_Unknown4 = 0x01000000
53 | NTLM_NegotiateVersion = 0x02000000
54 | NTLM_Unknown3 = 0x04000000
55 | NTLM_Unknown2 = 0x08000000
56 | NTLM_Unknown1 = 0x10000000
57 | NTLM_Negotiate128 = 0x20000000
58 | NTLM_NegotiateKeyExchange = 0x40000000
59 | NTLM_Negotiate56 = 0x80000000
60 |
61 | # we send these flags with our ttype 1 message
62 | NTLM_ttype1_FLAGS = (NTLM_NegotiateUnicode | \
63 | NTLM_NegotiateOEM | \
64 | NTLM_RequestTarget | \
65 | NTLM_NegotiateNTLM | \
66 | NTLM_NegotiateOemDomainSupplied | \
67 | NTLM_NegotiateOemWorkstationSupplied | \
68 | NTLM_NegotiateAlwaysSign | \
69 | NTLM_NegotiateExtendedSecurity | \
70 | NTLM_NegotiateVersion | \
71 | NTLM_Negotiate128 | \
72 | NTLM_Negotiate56 )
73 | NTLM_ttype2_FLAGS = (NTLM_NegotiateUnicode | \
74 | NTLM_RequestTarget | \
75 | NTLM_NegotiateNTLM | \
76 | NTLM_NegotiateAlwaysSign | \
77 | NTLM_NegotiateExtendedSecurity | \
78 | NTLM_NegotiateTargetInfo | \
79 | NTLM_NegotiateVersion | \
80 | NTLM_Negotiate128 | \
81 | NTLM_Negotiate56)
82 |
83 | NTLM_MsvAvEOL = 0 # Indicates that this is the last AV_PAIR in the list. AvLen MUST be 0. This ttype of information MUST be present in the AV pair list.
84 | NTLM_MsvAvNbComputerName = 1 # The server's NetBIOS computer name. The name MUST be in Unicode, and is not null-terminated. This ttype of information MUST be present in the AV_pair list.
85 | NTLM_MsvAvNbDomainName = 2 # The server's NetBIOS domain name. The name MUST be in Unicode, and is not null-terminated. This ttype of information MUST be present in the AV_pair list.
86 | NTLM_MsvAvDnsComputerName = 3 # The server's Active Directory DNS computer name. The name MUST be in Unicode, and is not null-terminated.
87 | NTLM_MsvAvDnsDomainName = 4 # The server's Active Directory DNS domain name. The name MUST be in Unicode, and is not null-terminated.
88 | NTLM_MsvAvDnsTreeName = 5 # The server's Active Directory (AD) DNS forest tree name. The name MUST be in Unicode, and is not null-terminated.
89 | NTLM_MsvAvFlags = 6 # A field containing a 32-bit value indicating server or client configuration. 0x00000001: indicates to the client that the account authentication is constrained. 0x00000002: indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.
90 | NTLM_MsvAvTimestamp = 7 # A FILETIME structure ([MS-DTYP] section 2.3.1) in little-endian byte order that contains the server local time.<12>
91 | NTLM_MsAvRestrictions = 8 #A Restriction_Encoding structure (section 2.2.2.2). The Value field contains a structure representing the integrity level of the security principal, as well as a MachineID created at computer startup to identify the calling machine. <13>
92 |
93 |
94 | """
95 | utility functions for Microsoft NTLM authentication
96 |
97 | References:
98 | [MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol Specification
99 | http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
100 |
101 | [MS-NTHT]: NTLM Over HTTP Protocol Specification
102 | http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf
103 |
104 | Cntlm Authentication Proxy
105 | http://cntlm.awk.cz/
106 |
107 | NTLM Authorization Proxy Server
108 | http://sourceforge.net/projects/ntlmaps/
109 |
110 | Optimized Attack for NTLM2 Session Response
111 | http://www.blackhat.com/presentations/bh-asia-04/bh-jp-04-pdfs/bh-jp-04-seki.pdf
112 | """
113 | def dump_NegotiateFlags(NegotiateFlags):
114 | if NegotiateFlags & NTLM_NegotiateUnicode:
115 | print("NTLM_NegotiateUnicode set")
116 | if NegotiateFlags & NTLM_NegotiateOEM:
117 | print("NTLM_NegotiateOEM set")
118 | if NegotiateFlags & NTLM_RequestTarget:
119 | print("NTLM_RequestTarget set")
120 | if NegotiateFlags & NTLM_Unknown9:
121 | print("NTLM_Unknown9 set")
122 | if NegotiateFlags & NTLM_NegotiateSign:
123 | print("NTLM_NegotiateSign set")
124 | if NegotiateFlags & NTLM_NegotiateSeal:
125 | print("NTLM_NegotiateSeal set")
126 | if NegotiateFlags & NTLM_NegotiateDatagram:
127 | print("NTLM_NegotiateDatagram set")
128 | if NegotiateFlags & NTLM_NegotiateLanManagerKey:
129 | print("NTLM_NegotiateLanManagerKey set")
130 | if NegotiateFlags & NTLM_Unknown8:
131 | print("NTLM_Unknown8 set")
132 | if NegotiateFlags & NTLM_NegotiateNTLM:
133 | print("NTLM_NegotiateNTLM set")
134 | if NegotiateFlags & NTLM_NegotiateNTOnly:
135 | print("NTLM_NegotiateNTOnly set")
136 | if NegotiateFlags & NTLM_Anonymous:
137 | print("NTLM_Anonymous set")
138 | if NegotiateFlags & NTLM_NegotiateOemDomainSupplied:
139 | print("NTLM_NegotiateOemDomainSupplied set")
140 | if NegotiateFlags & NTLM_NegotiateOemWorkstationSupplied:
141 | print("NTLM_NegotiateOemWorkstationSupplied set")
142 | if NegotiateFlags & NTLM_Unknown6:
143 | print("NTLM_Unknown6 set")
144 | if NegotiateFlags & NTLM_NegotiateAlwaysSign:
145 | print("NTLM_NegotiateAlwaysSign set")
146 | if NegotiateFlags & NTLM_TargetttypeDomain:
147 | print("NTLM_TargetttypeDomain set")
148 | if NegotiateFlags & NTLM_TargetttypeServer:
149 | print("NTLM_TargetttypeServer set")
150 | if NegotiateFlags & NTLM_TargetttypeShare:
151 | print("NTLM_TargetttypeShare set")
152 | if NegotiateFlags & NTLM_NegotiateExtendedSecurity:
153 | print("NTLM_NegotiateExtendedSecurity set")
154 | if NegotiateFlags & NTLM_NegotiateIdentify:
155 | print("NTLM_NegotiateIdentify set")
156 | if NegotiateFlags & NTLM_Unknown5:
157 | print("NTLM_Unknown5 set")
158 | if NegotiateFlags & NTLM_RequestNonNTSessionKey:
159 | print("NTLM_RequestNonNTSessionKey set")
160 | if NegotiateFlags & NTLM_NegotiateTargetInfo:
161 | print("NTLM_NegotiateTargetInfo set")
162 | if NegotiateFlags & NTLM_Unknown4:
163 | print("NTLM_Unknown4 set")
164 | if NegotiateFlags & NTLM_NegotiateVersion:
165 | print("NTLM_NegotiateVersion set")
166 | if NegotiateFlags & NTLM_Unknown3:
167 | print("NTLM_Unknown3 set")
168 | if NegotiateFlags & NTLM_Unknown2:
169 | print("NTLM_Unknown2 set")
170 | if NegotiateFlags & NTLM_Unknown1:
171 | print("NTLM_Unknown1 set")
172 | if NegotiateFlags & NTLM_Negotiate128:
173 | print("NTLM_Negotiate128 set")
174 | if NegotiateFlags & NTLM_NegotiateKeyExchange:
175 | print("NTLM_NegotiateKeyExchange set")
176 | if NegotiateFlags & NTLM_Negotiate56:
177 | print("NTLM_Negotiate56 set")
178 |
179 | def create_NTLM_NEGOTIATE_MESSAGE(user, type1_flags = NTLM_ttype1_FLAGS):
180 | BODY_LENGTH = 40
181 | Payload_start = BODY_LENGTH # in bytes
182 | protocol = b'NTLMSSP\0' #name
183 |
184 | ttype = struct.pack(' 0:
143 | raise Exception('''Can't deal with database created by higher program version %s''' % (ver,))
144 |
145 | _logger.info('current db version %s needs upgrade to %s', ver, self.LATEST_DB_VERSION)
146 | while self.vercmp(ver, self.LATEST_DB_VERSION) < 0:
147 | next_ver, script = self.DB_UPGRADE_SCRIPTS[ver]
148 | _logger.debug('upgrading database from %s to %s, execute %s', ver, next_ver, script)
149 | cur = conn.cursor()
150 | try:
151 | cur.executescript(script)
152 | except Exception as err:
153 | _logger.fatal('error happened during database upgrade "%s"',
154 | script, exc_info=1)
155 | conn.rollback()
156 | raise err
157 | ver = next_ver
158 | _logger.info('upgrading script executed successfully, now db is %s', self.judge_version(conn))
159 |
160 | def create_scheme(self, conn):
161 | cur = conn.cursor()
162 | cur.execute('''
163 | CREATE TABLE [BingWallpaperRecords] (
164 | [Url] CHAR(1024) NOT NULL ON CONFLICT FAIL,
165 | [DownloadTime] DATETIME NOT NULL ON CONFLICT FAIL,
166 | [StartTime] DATETIME,
167 | [EndTime] DATETIME,
168 | [LocalFilePath] CHAR(1024),
169 | [Description] TEXT(1024),
170 | [Market] TEXT(64) DEFAULT "",
171 | [Image] BLOB,
172 | [IsAccompany] BOOLEAN DEFAULT False,
173 | CONSTRAINT [sqlite_autoindex_BingWallpaperRecords_1] PRIMARY KEY ([Url]));
174 | ''')
175 | cur.execute('''
176 | CREATE TABLE [BingWallpaperCore] (
177 | [MajorVer] INTEGER,
178 | [MinorVer] INTEGER,
179 | [Build] INTEGER);
180 | ''')
181 | cur.execute('''
182 | INSERT INTO [BingWallpaperCore]
183 | (MajorVer, MinorVer, Build)
184 | VALUES (?, ?, ?)
185 | ''', self.LATEST_DB_VERSION)
186 | conn.commit()
187 | _logger.debug('created db from prog version %s', self.judge_version(conn))
188 |
189 | def vercmp(self, ver1, ver2):
190 | if ver1 == ver2:
191 | return 0
192 | elif ver1[0] > ver2[0] or \
193 | (ver1[0] == ver2[0] and ver1[1] > ver2[1]) or \
194 | (ver1[0] == ver2[0] and ver1[1] == ver2[1] and ver1[2] > ver2[2]):
195 | return 1
196 | return -1
197 |
198 | def judge_version(self, conn):
199 | ver = None
200 | cur = conn.cursor()
201 | tables = cur.execute('''
202 | SELECT lower(name) FROM [sqlite_master] WHERE type=='table';
203 | ''').fetchall()
204 | _logger.debug('find tables %s in database', tables)
205 | if ('bingwallpaperrecords',) not in tables and ('bingwallpapercore',) not in tables:
206 | ver = (0, 0, 0)
207 | elif ('bingwallpaperrecords',) in tables and ('bingwallpapercore',) not in tables:
208 | ver = (4, 4, 1)
209 | elif ('bingwallpaperrecords',) in tables and ('bingwallpapercore',) in tables:
210 | ver = cur.execute('''
211 | SELECT MajorVer, MinorVer, Build FROM [BingWallpaperCore];
212 | ''').fetchone()
213 | if not ver:
214 | raise Exception('Corrupted database with tables %s', tables)
215 | return ver
216 |
--------------------------------------------------------------------------------
/pybingwallpaper/rev.py:
--------------------------------------------------------------------------------
1 | REV = '1.6.0'
2 |
--------------------------------------------------------------------------------
/pybingwallpaper/setter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import glob
3 | import os.path
4 | import sys
5 | from importlib import import_module
6 |
7 | from . import log
8 | from .py23 import import_moved
9 |
10 | subprocess = import_moved('subprocess32', 'subprocess')
11 |
12 |
13 | class WallpaperSetter:
14 | def __init__(self):
15 | self._logger = log.getChild(self.__class__.__name__)
16 |
17 | def set(self, path, args):
18 | raise NotImplementedError()
19 |
20 |
21 | class ShellWallpaperSetter(WallpaperSetter):
22 | TIMEOUT_SEC = 5
23 |
24 | def _cmd(self, path, args):
25 | raise NotImplementedError()
26 |
27 | def _cb(self, status, out, err, ex):
28 | self._logger.debug('cmd exit code: %s, OUT:\n%s\nERR:\n%s',
29 | str(status), repr(out), repr(err))
30 | if ex:
31 | self._logger.exception(ex)
32 | return status is not None and status == 0 and not ex
33 |
34 | def set(self, path, args):
35 | p = None
36 | cmd = self._cmd(path, args)
37 | try:
38 | self._logger.debug('about to execute %s', cmd)
39 | p = subprocess.Popen(cmd,
40 | stdout=subprocess.PIPE, stderr=subprocess.PIPE)
41 | out, err = p.communicate(timeout=self.TIMEOUT_SEC)
42 | except Exception as ex:
43 | if p:
44 | p.kill()
45 | return self._cb(p.poll(), p.stdout.read(), p.stderr.read(), ex)
46 | else:
47 | return self._cb(None, None, None, ex)
48 | else:
49 | return self._cb(p.poll(), out, err, None)
50 |
51 |
52 | class Gnome2Setter(ShellWallpaperSetter):
53 | def _cmd(self, path, args):
54 | return ["gconftool-2", "--type=string",
55 | "--set", "/desktop/gnome/background/picture_filename",
56 | '"{}"'.format(path)]
57 |
58 |
59 | class Gnome3Setter(ShellWallpaperSetter):
60 | def _cmd(self, path, args):
61 | return ["gsettings", "set",
62 | "org.gnome.desktop.background", "picture-uri",
63 | '"file://{}"'.format(path)]
64 |
65 |
66 | class WallpaperSetterFactory:
67 | def __init__(self, name):
68 | self.registered = dict()
69 | self.name = name
70 |
71 | def register(self, name, c):
72 | if name in self.registered \
73 | and c is not self.registered[name]:
74 | raise NameError(
75 | '{} has been registered by {}'.format(name, self.registered[name]))
76 | self.registered[name] = c
77 |
78 | def get(self, name):
79 | if name not in self.registered:
80 | raise NameError(
81 | 'unregistered setter {}'.format(name))
82 | return self.registered[name]
83 |
84 |
85 | def load_ext_setters(path):
86 | logger = log.getChild('load_ext_setters')
87 | for i in glob.iglob(os.path.join(path, '*setter.py')):
88 | if os.path.basename(i) == 'setter.py':
89 | continue
90 | logger.debug('"%s" seems like a setter', i)
91 | name, ext = os.path.splitext(os.path.basename(i))
92 | try:
93 | logger.debug('loading %s', name)
94 | import_module(name)
95 | except Exception:
96 | logger.warning('cannot loading setter %s', name, exc_info=1)
97 |
98 |
99 | _default_wallpaper_factory = WallpaperSetterFactory('default')
100 |
101 | register = _default_wallpaper_factory.register
102 | get = _default_wallpaper_factory.get
103 |
104 | if sys.platform.startswith('linux'):
105 | register('gnome2', Gnome2Setter)
106 | register('gnome3', Gnome3Setter)
107 |
--------------------------------------------------------------------------------
/pybingwallpaper/webutil.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import gzip
3 | import ssl
4 | from io import BytesIO
5 |
6 | from . import log
7 | from .ntlmauth import HTTPNtlmAuthHandler
8 | from .py23 import get_moved_attr, import_moved
9 |
10 | _logger = log.getChild('webutil')
11 |
12 | Request = get_moved_attr('urllib2', 'urllib.request', 'Request')
13 | urlopen = get_moved_attr('urllib2', 'urllib.request', 'urlopen')
14 | URLError = get_moved_attr('urllib2', 'urllib.error', 'URLError')
15 | urlparse = get_moved_attr('urlparse', 'urllib.parse', 'urlparse')
16 | parse_qs = get_moved_attr('urlparse', 'urllib.parse', 'parse_qs')
17 | urlencode = get_moved_attr('urllib', 'urllib.parse', 'urlencode')
18 | urljoin = get_moved_attr('urlparse', 'urllib.parse', 'urljoin')
19 | url_request = import_moved('urllib2', 'urllib.request')
20 |
21 |
22 | def setup_proxy(proxy_protocols, proxy_url, proxy_port, sites, username="", password=""):
23 | proxy_dict = {p: '%s:%s' % (proxy_url, proxy_port) for p in proxy_protocols}
24 | ph = url_request.ProxyHandler(proxy_dict)
25 | passman = url_request.HTTPPasswordMgrWithDefaultRealm()
26 |
27 | _logger.info('add proxy site %s', sites)
28 | passman.add_password(None, sites, username, password)
29 | pnah = HTTPNtlmAuthHandler.ProxyNtlmAuthHandler(passman)
30 | pbah = url_request.ProxyBasicAuthHandler(passman)
31 | pdah = url_request.ProxyDigestAuthHandler(passman)
32 |
33 | cp = url_request.HTTPCookieProcessor()
34 | context = ssl.create_default_context()
35 | opener = url_request.build_opener(cp,
36 | url_request.HTTPSHandler(debuglevel=1, context=context),
37 | url_request.HTTPHandler(debuglevel=99),
38 | ph, pnah, pbah, pdah,
39 | url_request.HTTPErrorProcessor())
40 | url_request.install_opener(opener)
41 |
42 |
43 | def _ungzip(html):
44 | if html[:6] == b'\x1f\x8b\x08\x00\x00\x00':
45 | html = gzip.GzipFile(fileobj=BytesIO(html)).read()
46 | return html
47 |
48 |
49 | def loadurl(url, headers=None, optional=False):
50 | headers = headers or dict()
51 | if not url:
52 | return None
53 | _logger.debug('getting url %s, headers %s', url, headers)
54 | if 'User-Agent' not in headers:
55 | headers[
56 | 'User-Agent'
57 | ] = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36'
58 | try:
59 | req = Request(url=url, headers=headers)
60 | con = urlopen(req)
61 | except Exception as err:
62 | if not optional:
63 | _logger.error('error %s occurs during load %s with header %s', err, url, headers)
64 | _logger.debug('', exc_info=1)
65 | return None
66 | if con:
67 | _logger.debug("Hit %s code: %s", str(con), con.getcode())
68 | data = con.read()
69 | data = _ungzip(data)
70 | _logger.log(log.PAGEDUMP, repr(data))
71 | return data
72 | else:
73 | _logger.error("No data returned.")
74 | return None
75 |
76 |
77 | def loadpage(url, codec=('utf8', 'strict'), headers=None, optional=False):
78 | headers = headers or dict()
79 | data = loadurl(url, headers=headers, optional=optional)
80 | return data.decode(*codec) if data else None
81 |
82 |
83 | def postto(url, datadict, headers=None, decodec='gbk'):
84 | headers = headers or dict()
85 | params = urlencode(datadict)
86 | _logger.info('Post %s to %s, headers %s', params, url, headers)
87 | try:
88 | req = Request(url=url, data=params)
89 | for k, v in list(headers.items()):
90 | req.add_header(k, v)
91 | con = urlopen(req)
92 | if con:
93 | _logger.debug("Hit %s %d", str(con), con.getcode())
94 | data = con.read(-1)
95 | return data.decode(decodec)
96 | else:
97 | _logger.error("No data returned.")
98 | return None
99 |
100 | except Exception as err:
101 | _logger.error('error %s occurs during post %s to %s', err, params, url)
102 | _logger.debug('', exc_info=1)
103 |
104 |
105 | def test_header(url, extra_headers=None):
106 | headers = {
107 | 'method': 'HEAD',
108 | }
109 | if extra_headers:
110 | headers.update(extra_headers)
111 | resp = loadurl(url, headers, True)
112 | return resp is not None
113 |
--------------------------------------------------------------------------------
/pybingwallpaper/winsetter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | from importlib import import_module
4 | from os.path import dirname, splitext
5 |
6 | from pybingwallpaper import log, setter
7 | from pybingwallpaper.py23 import import_moved
8 |
9 | if sys.platform.startswith('win32'):
10 | winreg = import_moved('_winreg', 'winreg')
11 | Image = import_module('PIL.Image')
12 | win32gui = import_module('win32.win32gui')
13 |
14 |
15 | def convert_photo_to_bmp(inpath, outpath):
16 | if splitext(inpath)[1] == '.bmp':
17 | return
18 | Image.open(inpath).save(outpath)
19 |
20 |
21 | SPI_SETDESKWALLPAPER = 0x0014
22 |
23 |
24 | class Win32WallpaperSetter(setter.WallpaperSetter):
25 | KEY = winreg.HKEY_CURRENT_USER
26 | SUB_KEY = 'Control Panel\\Desktop'
27 | VALUE_NAME = 'Wallpaper'
28 | BACKUP = True
29 |
30 | def _read_value(self, k, valuename=None):
31 | if not valuename:
32 | valuename = self.VALUE_NAME
33 | try:
34 | value = winreg.QueryValueEx(k, valuename)
35 | if value[1] != winreg.REG_SZ:
36 | self._logger.fatal('cannot handle non-REG_SZ value %s', value)
37 | return None
38 | except Exception:
39 | self._logger.warn('error encountered during reading value %s', valuename, exc_info=1)
40 | return None
41 | self._logger.debug('read {} from {} get {}'.format(valuename, k, value))
42 | return value
43 |
44 | def _set_value(self, k, v, valuename=None):
45 | if not valuename:
46 | valuename = self.VALUE_NAME
47 | self._logger.debug('set %s\\%s\\%s to %s', self.KEY, self.SUB_KEY, valuename, v)
48 | try:
49 | winreg.SetValueEx(k, valuename, 0, winreg.REG_SZ, v)
50 | except Exception:
51 | self._logger.error('error encountered during setting value %s', valuename, exc_info=1)
52 | return False
53 | self._logger.debug('set {} of {} to {} succeeds'.format(valuename, k, v))
54 | return True
55 |
56 | def set(self, path, args):
57 | k = None
58 | ret = False
59 | in_path = path.replace('/', '\\')
60 | path = "{}\\wallpaper.bmp".format(dirname(in_path))
61 | # windows only supports BMP, convert before setting
62 | try:
63 | convert_photo_to_bmp(in_path, path)
64 | except Exception as ex:
65 | self._logger.exception(ex)
66 | return False
67 |
68 | try:
69 | k = winreg.OpenKey(self.KEY, self.SUB_KEY, 0, winreg.KEY_READ | winreg.KEY_SET_VALUE)
70 | last_value = self._read_value(k)
71 | if last_value and self.BACKUP:
72 | ret = self._set_value(k, last_value[0], self.VALUE_NAME + 'Backup')
73 | self._set_value(k, '0', 'TileWallpaper')
74 | self._set_value(k, '10', 'WallpaperStyle')
75 | win32gui.SystemParametersInfo(SPI_SETDESKWALLPAPER, path, 1 + 2)
76 | except Exception as ex:
77 | ret = False
78 | self._logger.exception(ex)
79 | finally:
80 | if k:
81 | k.Close()
82 | return ret
83 |
84 |
85 | setter.register('win', Win32WallpaperSetter)
86 |
87 | if __name__ == '__main__':
88 | log.setDebugLevel(log.DEBUG)
89 | setter = Win32WallpaperSetter()
90 | setter.set(r'w.jpg', None)
91 |
--------------------------------------------------------------------------------
/requirements-pack.txt:
--------------------------------------------------------------------------------
1 | pyinstaller~=3.2
2 |
--------------------------------------------------------------------------------
/requirements-py2.txt:
--------------------------------------------------------------------------------
1 | appdirs==1.4.3
2 | configparser==3.5.0
3 | packaging==16.8
4 | pyparsing==2.2.0
5 | six==1.10.0
6 | subprocess32==3.2.7
7 |
--------------------------------------------------------------------------------
/requirements-win.txt:
--------------------------------------------------------------------------------
1 | Pillow>=4
2 | pypiwin32
3 |
--------------------------------------------------------------------------------
/res/bingwallpaper.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/res/bingwallpaper.ico
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from pybingwallpaper.rev import REV
4 | from setuptools import setup, find_packages
5 |
6 | if sys.version_info[0] == 2:
7 | import codecs
8 | fopen = codecs.open
9 | else:
10 | fopen = open
11 |
12 | # Utility function to read the README file.
13 | # Used for the long_description. It's nice, because now 1) we have a top level
14 | # README file and 2) it's easier to type in the README file than to put a raw
15 | # string in below ...
16 | def read(fname):
17 | return fopen(os.path.join(os.path.dirname(__file__), fname), encoding='utf8').read()
18 |
19 | def os_requires():
20 | return ['Pillow', 'pypiwin32'] if sys.platform == 'win32' else []
21 |
22 | def backport_requires():
23 | return ['configparser', 'subprocess32'] if sys.version_info[0] == 2 else []
24 |
25 | def requires():
26 | return os_requires() + backport_requires()
27 |
28 | setup(
29 | name = 'PyBingWallpaper',
30 | version = REV,
31 | author = 'genzj',
32 | author_email = 'zj0512@gmail.com',
33 | description = (
34 | 'A simple Bing.com wallpaper downloader'
35 | ),
36 | license = 'MIT',
37 | keywords = 'bing wallpaper',
38 | url = 'https://github.com/genzj/pybingwallpaper',
39 |
40 |
41 | packages=find_packages(exclude=['res', 'test', 'wiki']),
42 | install_requires=requires(),
43 |
44 | entry_points={
45 | 'console_scripts': [
46 | 'BingWallpaper=pybingwallpaper.main:main',
47 | ],
48 | },
49 |
50 | long_description=read('README.rst'),
51 | classifiers=[
52 | 'Development Status :: 6 - Mature',
53 | 'Environment :: Console',
54 | 'Topic :: Desktop Environment',
55 | 'Intended Audience :: End Users/Desktop',
56 | 'License :: OSI Approved :: MIT License',
57 |
58 | 'Operating System :: Microsoft :: Windows :: Windows XP',
59 | 'Operating System :: Microsoft :: Windows :: Windows Vista',
60 | 'Operating System :: Microsoft :: Windows :: Windows 7',
61 | 'Operating System :: Microsoft :: Windows :: Windows 8',
62 | 'Operating System :: Microsoft :: Windows :: Windows 8.1',
63 | 'Operating System :: Microsoft :: Windows :: Windows 10',
64 | 'Operating System :: POSIX :: Linux',
65 | 'Operating System :: POSIX :: Other',
66 |
67 | 'Programming Language :: Python :: 2',
68 | 'Programming Language :: Python :: 2.3',
69 | 'Programming Language :: Python :: 2.4',
70 | 'Programming Language :: Python :: 2.5',
71 | 'Programming Language :: Python :: 2.6',
72 | 'Programming Language :: Python :: 2.7',
73 | 'Programming Language :: Python :: 3',
74 | 'Programming Language :: Python :: 3.3',
75 | 'Programming Language :: Python :: 3.4',
76 | 'Programming Language :: Python :: 3.5',
77 | ],
78 | )
79 |
--------------------------------------------------------------------------------
/test/testapppath.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import unittest
4 | import os
5 | import sys
6 | sys.path.append('../pybingwallpaper')
7 |
8 | from main import get_app_path
9 |
10 | SOURCE_DIR=''
11 |
12 | class TestConfigureParameter(unittest.TestCase):
13 | @classmethod
14 | def setUpClass(cls):
15 | cls.oridir = os.path.abspath(os.curdir)
16 |
17 | def give_work_dir(self, _dir):
18 | os.chdir(self.oridir)
19 | os.chdir(_dir)
20 | self.curdir = os.path.abspath(os.curdir)
21 |
22 | def and_app_src(self, srcfile):
23 | self.srcfile = srcfile
24 |
25 | def expect_dir(self, _expect):
26 | _expect = os.path.abspath(_expect)
27 | _expect = os.path.normcase(_expect)
28 | self.assertEqual(get_app_path(self.srcfile), _expect, 'apppath incorrect')
29 |
30 | def curdir_still_same(self):
31 | curdir = os.path.abspath(os.curdir)
32 | self.assertEqual(curdir, self.curdir, 'curdir changed')
33 |
34 | def test_run_in_src_dir(self):
35 | self.give_work_dir('../pybingwallpaper')
36 | self.and_app_src('main.py')
37 | self.expect_dir(r'E://Work/Python/pybingwallpaper/pybingwallpaper')
38 | self.curdir_still_same()
39 |
40 | def test_run_in_cur_dir(self):
41 | self.give_work_dir('.')
42 | self.and_app_src('../pybingwallpaper/main.py')
43 | self.expect_dir(r'E://Work/Python/pybingwallpaper/pybingwallpaper')
44 | self.curdir_still_same()
45 |
46 | def test_run_from_root(self):
47 | self.give_work_dir('/')
48 | self.and_app_src(r'work/python/pybingwallpaper/pybingwallpaper/main.py')
49 | self.expect_dir(r'E:\Work\Python\pybingwallpaper\src')
50 | self.curdir_still_same()
51 |
52 | def test_run_in_same_disk(self):
53 | self.give_work_dir('e:\\')
54 | self.and_app_src(r'work/python/pybingwallpaper/pybingwallpaper/main.py')
55 | self.expect_dir(r'E:\Work\Python\pybingwallpaper\src')
56 | self.curdir_still_same()
57 |
58 | def test_run_in_other_disk(self):
59 | self.give_work_dir('d:')
60 | self.and_app_src(r'e:/work/python/pybingwallpaper/pybingwallpaper/main.py')
61 | self.expect_dir(r'E:\Work\Python\pybingwallpaper\src')
62 | self.curdir_still_same()
63 |
64 | def test_run_in_other_disk_dir(self):
65 | self.give_work_dir('c:/windows')
66 | self.and_app_src(r'e:/work/python/pybingwallpaper/pybingwallpaper/main.py')
67 | self.expect_dir(r'E:\Work\Python\pybingwallpaper\src')
68 | self.curdir_still_same()
69 |
--------------------------------------------------------------------------------
/test/testconfigmodule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import unittest
4 | import random
5 |
6 | import sys
7 | sys.path.append('../pybingwallpaper')
8 |
9 | import config
10 | from config import ConfigParameter
11 | from config import ConfigDatabase
12 | from config import CommandLineArgumentsLoader
13 | from config import DefaultValueLoader
14 | from config import ConfigFileLoader
15 | from config import ConfigFileDumper
16 | from config import Namespace
17 |
18 | def getdb():
19 | return ConfigDatabase('test1', description='test desc')
20 |
21 | # TODO: Add cases to test loader_srcs option
22 | class TestConfigureParameter(unittest.TestCase):
23 | def setUp(self):
24 | pass
25 |
26 | def test_import_config_module(self):
27 | self.assertIsNotNone(ConfigParameter)
28 | self.assertIsNotNone(ConfigDatabase)
29 |
30 | def test_init_param(self):
31 | p = ConfigParameter('test1')
32 | self.assertIsNotNone(p)
33 |
34 | def test_name(self):
35 | names = ['klb', '1ab', 's#a']
36 | for n in names:
37 | p = ConfigParameter(name = n)
38 | self.assertEqual(p.name, n)
39 |
40 | def test_invalid_name(self):
41 | names = ['k b', '\tab', 's\na']
42 | for n in names:
43 | with self.assertRaises(ValueError, msg="parameter name can't contain space"):
44 | ConfigParameter(name = n)
45 |
46 | class TestConfigureDatabase(unittest.TestCase):
47 | def setUp(self):
48 | pass
49 |
50 | def test_prog(self):
51 | db = getdb()
52 | self.assertEqual(db.prog, 'test1')
53 |
54 | def test_desc(self):
55 | db = ConfigDatabase('test1', 'a test database')
56 | self.assertEqual(db.prog, 'test1')
57 | self.assertEqual(db.description, 'a test database')
58 |
59 | def test_parameter_init(self):
60 | params = [
61 | ConfigParameter('123'),
62 | ConfigParameter('456')
63 | ]
64 | db = ConfigDatabase('test1', parameters=params)
65 | self.assertListEqual(db.parameters, params)
66 |
67 | def test_repr(self):
68 | params = [
69 | ConfigParameter('123', type=''),
70 | ConfigParameter('456', type='')
71 | ]
72 | db = ConfigDatabase('test1', description='test desc', parameters=params)
73 | dbcopy = eval(repr(db))
74 | self.assertEqual(db.prog, dbcopy.prog)
75 | self.assertEqual(db.description, dbcopy.description)
76 | self.assertListEqual(db.parameters, dbcopy.parameters)
77 |
78 | def test_add_parameters(self):
79 | params = [
80 | ConfigParameter('123'),
81 | ConfigParameter('456')
82 | ]
83 | new_param = ConfigParameter('789')
84 | db = ConfigDatabase('test1', description='test desc', parameters=params)
85 | self.assertListEqual(db.parameters, params)
86 | db.add_param(new_param)
87 | self.assertListEqual(db.parameters, params+[new_param,])
88 |
89 | def test_no_dup_param(self):
90 | params = [
91 | ConfigParameter('123', type=int),
92 | ConfigParameter('456', defaults=9)
93 | ]
94 | new_param = ConfigParameter('123')
95 | db = ConfigDatabase('test1', description='test desc', parameters=params)
96 | self.assertListEqual(db.parameters, params)
97 | with self.assertRaises(NameError, msg='duplicated parameter name "%s" found'%(new_param.name,)):
98 | db.add_param(new_param)
99 | self.assertListEqual(db.parameters, params)
100 |
101 |
102 | class TestCliLoader(unittest.TestCase):
103 | def getdb(self):
104 | return ConfigDatabase('test1', description='test desc')
105 |
106 | def getloader(self):
107 | return CommandLineArgumentsLoader()
108 |
109 | def test_invalid_arg(self):
110 | loader = self.getloader()
111 | db = getdb()
112 | p = ConfigParameter(name='param1', type=int)
113 | db.add_param(p)
114 | with self.assertRaises(SystemExit) as se:
115 | loader.load(db, ['--not-exist'])
116 | self.assertEqual(se.exception.code, 2)
117 |
118 | def test_version(self):
119 | loader = self.getloader()
120 | db = getdb()
121 | p = ConfigParameter(name='notused', loader_opts={'cli':{
122 | 'action': 'version',
123 | 'flags':('-v','--version'),
124 | 'version': 'test-version-1234'
125 | }})
126 | db.add_param(p)
127 | with self.assertRaises(SystemExit) as se:
128 | loader.load(db, ['-v'])
129 | self.assertEqual(se.exception.code, 0)
130 | with self.assertRaises(SystemExit) as se:
131 | loader.load(db, ['--version'])
132 | self.assertEqual(se.exception.code, 0)
133 |
134 | def test_name(self):
135 | db = getdb()
136 | cli_opts = {'flags':['-p']}
137 | p = ConfigParameter(name='param1', type=lambda s:int(s,0), loader_opts={'cli':cli_opts})
138 | db.add_param(p)
139 | loader = self.getloader()
140 |
141 | with self.assertRaises(SystemExit) as se:
142 | loader.load(db, ['--param1', '1'])
143 | self.assertEqual(se.exception.code, 2)
144 |
145 | ans = loader.load(db, ['-p', '1'])
146 | self.assertEqual(getattr(ans, p.name), 1)
147 |
148 | def test_load_int(self):
149 | ds = [
150 | ('0', 0),
151 | ('0x1aedead0b', 0x1aedead0b),
152 | ('0b0011', 3),
153 | ('-9571293', -9571293),
154 | ]
155 |
156 | db = getdb()
157 | p = ConfigParameter(name='param1', type=lambda s:int(s,0))
158 | db.add_param(p)
159 | loader = self.getloader()
160 | for s, d in ds:
161 | ans = loader.load(db, ['--param1', s])
162 | self.assertEqual(getattr(ans, p.name), d)
163 |
164 | def test_load_str(self):
165 | ds = [
166 | ' ',
167 | '#123',
168 | 'as_',
169 | '9 9'
170 | ]
171 |
172 | db = getdb()
173 | p = ConfigParameter(name='param1')
174 | db.add_param(p)
175 | loader = self.getloader()
176 | for s in ds:
177 | ans = loader.load(db, ['--param1', s])
178 | self.assertEqual(getattr(ans, p.name), s)
179 |
180 | def test_load_choice(self):
181 | good = ['c1', 'c3', 'c2']
182 | choices = ('c0', 'c1', 'c2', 'c3')
183 | db = getdb()
184 | p = ConfigParameter(name='param1', defaults='c1', choices=choices)
185 | db.add_param(p)
186 | loader = self.getloader()
187 | # try legal ones
188 | for s in good:
189 | ans = loader.load(db, ['--param1', s], generate_default=True)
190 | self.assertEqual(getattr(ans, p.name), s)
191 | # test use default
192 | ans = loader.load(db, [], generate_default=True)
193 | self.assertEqual(getattr(ans, p.name), good[0])
194 |
195 | # test illegal value
196 | with self.assertRaises(SystemExit) as se:
197 | loader.load(db, ['--param1', 'no-good'], generate_default=True)
198 | self.assertEqual(se.exception.code, 2)
199 |
200 | def test_load_true(self):
201 | cli_opts = {'action':'store_true'}
202 | db = getdb()
203 | p = ConfigParameter(name='param1', defaults=False, loader_opts={'cli':cli_opts})
204 | db.add_param(p)
205 | loader = self.getloader()
206 | ans = loader.load(db, ['--param1'])
207 | self.assertTrue(getattr(ans, p.name))
208 | ans = loader.load(db, [])
209 | self.assertFalse(hasattr(ans, p.name))
210 | ans = loader.load(db, [], generate_default=True)
211 | self.assertFalse(getattr(ans, p.name))
212 |
213 | def test_load_false(self):
214 | cli_opts = {'action':'store_false'}
215 | db = getdb()
216 | p = ConfigParameter(name='param1', defaults=True, loader_opts={'cli':cli_opts})
217 | db.add_param(p)
218 | loader = self.getloader()
219 | ans = loader.load(db, ['--param1'])
220 | self.assertFalse(getattr(ans, p.name))
221 | ans = loader.load(db, [], generate_default=True)
222 | self.assertTrue(getattr(ans, p.name))
223 |
224 | def test_load_count(self):
225 | cli_opts = {'action':'count'}
226 | db = getdb()
227 | p = ConfigParameter(name='d', defaults=0, loader_opts={'cli':cli_opts})
228 | db.add_param(p)
229 | loader = self.getloader()
230 | ans = loader.load(db, ['-d'], generate_default=True)
231 | self.assertEqual(getattr(ans, p.name), 1)
232 | ans = loader.load(db, [], generate_default=True)
233 | self.assertEqual(getattr(ans, p.name), 0)
234 | ans = loader.load(db, ['-d', '-d', '-d'], generate_default=True)
235 | self.assertEqual(getattr(ans, p.name), 3)
236 | c = random.randint(0, 256)
237 | ans = loader.load(db, ['-'+'d'*c], generate_default=True)
238 | self.assertEqual(getattr(ans, p.name), c)
239 |
240 | class TestDefaultValueLoader(unittest.TestCase):
241 | def getloader(self, platform=None):
242 | return DefaultValueLoader(platform)
243 |
244 | def test_load_plain_def(self):
245 | loader = self.getloader()
246 | db = getdb()
247 | p = ConfigParameter(name='intparam', defaults=0)
248 | db.add_param(p)
249 | p = ConfigParameter(name='strparam', defaults='blah blah blah')
250 | db.add_param(p)
251 | p = ConfigParameter(name='noneparam')
252 | db.add_param(p)
253 | ans = loader.load(db)
254 | self.assertEqual(ans.intparam, 0)
255 | self.assertEqual(ans.strparam, 'blah blah blah')
256 | self.assertIsNone(ans.noneparam)
257 |
258 | def test_load_cur_platform(self):
259 | loader = self.getloader()
260 | db = getdb()
261 | p = ConfigParameter(name='param', defaults={sys.platform:'myval', '*':'otherval'})
262 | db.add_param(p)
263 | ans = loader.load(db)
264 | self.assertEqual(ans.param, 'myval')
265 |
266 | def test_load_other_platform(self):
267 | defs = {
268 | 'linux': 'linuxval',
269 | 'win': 'win32val',
270 | '*': 'otherval'
271 | }
272 | db = getdb()
273 | p = ConfigParameter(name='param', defaults=defs)
274 | db.add_param(p)
275 | loader = self.getloader('linux')
276 | ans = loader.load(db)
277 | self.assertEqual(ans.param, 'linuxval')
278 | loader = self.getloader('darwin')
279 | ans = loader.load(db)
280 | self.assertEqual(ans.param, 'otherval')
281 | loader = self.getloader('win')
282 | ans = loader.load(db)
283 | self.assertEqual(ans.param, 'win32val')
284 |
285 | def test_load_with_type(self):
286 | loader = self.getloader()
287 | db = getdb()
288 | p = ConfigParameter(name='param', type=lambda x:int(x,0), defaults='0xffff')
289 | db.add_param(p)
290 | ans = loader.load(db)
291 | self.assertEqual(type(ans.param), int)
292 | self.assertEqual(ans.param, 0xffff)
293 |
294 | def test_load_overwrite(self):
295 | loader = self.getloader()
296 | db = getdb()
297 | p = ConfigParameter(name='param', defaults='defval')
298 | db.add_param(p)
299 | ans = loader.load(db)
300 | self.assertEqual(ans.param, 'defval')
301 | ans.param = 'modified'
302 | self.assertEqual(ans.param, 'modified')
303 |
304 | from io import StringIO
305 | class TestConfigFileLoader(unittest.TestCase):
306 | def setUp(self):
307 | self.config_file = StringIO('''
308 | [DEFAULT]
309 | # default section values
310 | topParam1 = 1
311 | topParam2 = "s-value"
312 | topParam3 =
313 |
314 | [section1]
315 | secParam1 = 1 2 3
316 | secParam2 =
317 |
318 | [section3]
319 | secParam2 = somevalue
320 | ''')
321 | def getloader(self):
322 | return ConfigFileLoader()
323 |
324 | def test_load_plain_value(self):
325 | loader = self.getloader()
326 | db = getdb()
327 | p = ConfigParameter(name='topParam1')
328 | db.add_param(p)
329 | p = ConfigParameter(name='topParam2')
330 | db.add_param(p)
331 | p = ConfigParameter(name='topParam3')
332 | db.add_param(p)
333 | p = ConfigParameter(name='topParamx')
334 | db.add_param(p)
335 | ans = loader.load(db, self.config_file)
336 | self.assertEqual(ans.topParam1, '1')
337 | self.assertEqual(ans.topParam2, '"s-value"')
338 | self.assertEqual(ans.topParam3, '')
339 | self.assertFalse(hasattr(ans, 'topParamx'))
340 |
341 | def test_load_type_cast(self):
342 | loader = self.getloader()
343 | db = getdb()
344 | p = ConfigParameter(name='topParam1', type=int)
345 | db.add_param(p)
346 | p = ConfigParameter(name='topParam2', type=None)
347 | db.add_param(p)
348 | p = ConfigParameter(name='topParamx', type=float)
349 | db.add_param(p)
350 | ans = loader.load(db, self.config_file)
351 | self.assertEqual(type(ans.topParam1), int)
352 | self.assertEqual(ans.topParam1, 1)
353 | self.assertEqual(type(ans.topParam2), str)
354 | self.assertEqual(ans.topParam2, '"s-value"')
355 | self.assertFalse(hasattr(ans, 'topParamx'))
356 |
357 | def test_config_section(self):
358 | loader = self.getloader()
359 | db = getdb()
360 | getSection = lambda secname: {'section': secname}
361 | p = ConfigParameter(name='topParam2', loader_opts={'conffile':getSection(None)})
362 | db.add_param(p)
363 |
364 | p = ConfigParameter(name='secParam1', loader_opts={'conffile':getSection('section1')})
365 | db.add_param(p)
366 |
367 | p = ConfigParameter(name='secParam2', loader_opts={'conffile':getSection('section3')})
368 | db.add_param(p)
369 |
370 | p = ConfigParameter(name='secParamx', loader_opts={'conffile':getSection('sectionx')})
371 | db.add_param(p)
372 |
373 | ans = loader.load(db, self.config_file)
374 | self.assertEqual(ans.topParam2, '"s-value"')
375 | self.assertEqual(ans.secParam1, '1 2 3')
376 | self.assertEqual(ans.secParam2, 'somevalue')
377 | self.assertFalse(hasattr(ans, 'topParamx'))
378 |
379 | def test_load_default(self):
380 | loader = self.getloader()
381 | db = getdb()
382 | p = ConfigParameter(name='topParam3', defaults='def-1')
383 | db.add_param(p)
384 | p = ConfigParameter(
385 | name='secParamx',
386 | type=float, defaults='0',
387 | loader_opts={'conffile':{'section':'section3'}}
388 | )
389 | db.add_param(p)
390 | ans = loader.load(db, self.config_file, generate_default=True)
391 | self.assertEqual(ans.topParam3, '')
392 | self.assertEqual(type(ans.secParamx), float)
393 | self.assertEqual(ans.secParamx, float(0))
394 |
395 | class TestConfigFileDumper(unittest.TestCase):
396 | def setUp(self):
397 | self.conf = Namespace()
398 | choices = ['cal1', 'cal2', 'cal3']
399 | setattr(self.conf, 'intparam', 0x77992213)
400 | setattr(self.conf, 'strparam', 'a complicat3d string#!')
401 | setattr(self.conf, 'trueparam', True)
402 | setattr(self.conf, 'falseparam', False)
403 | setattr(self.conf, 'choiceparam', choices[1])
404 | self.db = getdb()
405 | p = ConfigParameter(name='intparam', type=int)
406 | self.db.add_param(p)
407 | p = ConfigParameter(name='strparam', type=str)
408 | self.db.add_param(p)
409 | p = ConfigParameter(name='trueparam', type=bool,
410 | loader_opts={'conffile':{'section':'section_1'}})
411 | self.db.add_param(p)
412 | p = ConfigParameter(name='falseparam', type=bool,
413 | loader_opts={'conffile':{
414 | 'converter':lambda x: True if bool(x) and x.lower() != 'false' else False
415 | }})
416 | self.db.add_param(p)
417 | p = ConfigParameter(name='choiceparam', choices=choices)
418 | self.db.add_param(p)
419 |
420 | def getloader(self):
421 | return ConfigFileLoader()
422 |
423 | def getdumper(self):
424 | return ConfigFileDumper()
425 |
426 | def test_dump_config(self):
427 | buf = StringIO()
428 | loader = self.getloader()
429 | dumper = self.getdumper()
430 | ret = dumper.dump(self.db, self.conf, buf)
431 | self.assertNotEqual(ret, 0)
432 | buf.seek(0)
433 | ans = loader.load(self.db, buf)
434 | for k, v in vars(self.conf).items():
435 | self.assertTrue(hasattr(ans, k))
436 | self.assertEqual(type(getattr(ans, k)), type(v))
437 | self.assertEqual(getattr(ans, k), v)
438 | self.assertEqual(ans, self.conf)
439 |
440 | class TestOtherUtil(unittest.TestCase):
441 | def test_merge(self):
442 | ns1 = Namespace()
443 | ns2 = Namespace()
444 | ns1.param1 = 123
445 | ns2.param1 = 456
446 | ns1.parama = 'a'
447 | ns2.paramb = ('1', 2, 's')
448 | ans = config.merge_config(ns1, ns2)
449 | self.assertEqual(ns1.param1, 123)
450 | self.assertEqual(ns2.param1, 456)
451 | self.assertEqual(ans.param1, 456)
452 |
453 | self.assertEqual(ns1.parama, 'a')
454 | self.assertFalse(hasattr(ns2, 'parama'))
455 | self.assertEqual(ans.parama, 'a')
456 |
457 | self.assertFalse(hasattr(ns1, 'paramb'))
458 | self.assertEqual(ns2.paramb, ('1', 2, 's'))
459 | self.assertEqual(ans.paramb, ('1', 2, 's'))
460 |
461 |
--------------------------------------------------------------------------------
/wiki/pics/database/open-database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/wiki/pics/database/open-database.png
--------------------------------------------------------------------------------
/wiki/pics/database/tree-view-and-data-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/wiki/pics/database/tree-view-and-data-tab.png
--------------------------------------------------------------------------------
/wiki/pics/database/view-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/wiki/pics/database/view-image.png
--------------------------------------------------------------------------------
/wiki/pics/database/view-record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/wiki/pics/database/view-record.png
--------------------------------------------------------------------------------
/wiki/pics/install-startup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/wiki/pics/install-startup.png
--------------------------------------------------------------------------------
/wiki/pics/startup-property.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/genzj/pybingwallpaper/5150d72e4427cf6ca0eb441a62961682de11839f/wiki/pics/startup-property.png
--------------------------------------------------------------------------------