├── .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 --------------------------------------------------------------------------------