mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-04-23 00:07:50 +02:00
Compare commits
No commits in common. "gh-pages" and "v0.80.0" have entirely different histories.
3
.cvsignore
Normal file
3
.cvsignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
@ -1,8 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
insert_final_newline = true
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
|||||||
* text=auto eol=lf
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -1 +0,0 @@
|
|||||||
* @Mikaela
|
|
5
.github/renovate.json5
vendored
5
.github/renovate.json5
vendored
@ -1,5 +0,0 @@
|
|||||||
/** @format */
|
|
||||||
|
|
||||||
{
|
|
||||||
extends: ["local>Mikaela/shell-things:.renovate-shared"],
|
|
||||||
}
|
|
25
.github/workflows/html5validator.yml.disabled
vendored
25
.github/workflows/html5validator.yml.disabled
vendored
@ -1,25 +0,0 @@
|
|||||||
name: HTML5 Validator
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4 # Required will all actions
|
|
||||||
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: 3.1
|
|
||||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
||||||
|
|
||||||
- name: Jekyll build
|
|
||||||
run: |
|
|
||||||
bundle exec jekyll build --drafts --profile
|
|
||||||
|
|
||||||
- name: Checks HTML5 validity
|
|
||||||
uses: Cyb3r-Jak3/html5validator-action@v7.2.0
|
|
||||||
with:
|
|
||||||
root: _site/
|
|
||||||
blacklist: n r or ir
|
|
32
.gitignore
vendored
32
.gitignore
vendored
@ -1,32 +0,0 @@
|
|||||||
*.mo
|
|
||||||
*.py[cdo]
|
|
||||||
*~
|
|
||||||
.*.swp
|
|
||||||
.swp
|
|
||||||
MANIFEST
|
|
||||||
backup/
|
|
||||||
build/
|
|
||||||
debian/compat
|
|
||||||
debian/files
|
|
||||||
debian/limnoria*
|
|
||||||
debian/python-module-stampdir/
|
|
||||||
dist/
|
|
||||||
docs/_build/
|
|
||||||
docs/plugins/
|
|
||||||
limnoria.egg-info/
|
|
||||||
merge.sh
|
|
||||||
nano.save
|
|
||||||
push.sh
|
|
||||||
py3k/
|
|
||||||
src/version.py
|
|
||||||
supybot.egg-info/
|
|
||||||
test-conf/
|
|
||||||
test-data/
|
|
||||||
test-logs/
|
|
||||||
src/version.py
|
|
||||||
_site
|
|
||||||
.sass-cache
|
|
||||||
vendor/
|
|
||||||
.bundle
|
|
||||||
node_modules/
|
|
||||||
pnpm-lock.yaml
|
|
@ -1,83 +0,0 @@
|
|||||||
# @format
|
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2023 Aminda Suomalainen <suomalainen+git@mikaela.info>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
|
||||||
|
|
||||||
# See https://pre-commit.com for more information
|
|
||||||
# See https://pre-commit.ci for more information
|
|
||||||
ci:
|
|
||||||
# I don't need so many duplicated notifications on the same thing as I keep
|
|
||||||
# autoupdating manually too. Besides it just creates extra branch I never
|
|
||||||
# touch.
|
|
||||||
# https://github.com/pre-commit-ci/issues/issues/83
|
|
||||||
autoupdate_schedule: quarterly
|
|
||||||
skip: [pnpm-install-dev, prettier]
|
|
||||||
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v5.0.0
|
|
||||||
hooks:
|
|
||||||
- id: trailing-whitespace
|
|
||||||
args: ["--markdown-linebreak-ext", "md,markdown"]
|
|
||||||
exclude_types: [svg, tsv]
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- id: check-yaml
|
|
||||||
- id: check-added-large-files
|
|
||||||
- id: check-case-conflict
|
|
||||||
- id: check-executables-have-shebangs
|
|
||||||
- id: check-json
|
|
||||||
- id: check-merge-conflict
|
|
||||||
- id: check-shebang-scripts-are-executable
|
|
||||||
- id: destroyed-symlinks
|
|
||||||
- id: detect-private-key
|
|
||||||
- id: fix-byte-order-marker
|
|
||||||
- id: check-merge-conflict
|
|
||||||
- id: mixed-line-ending
|
|
||||||
args: [--fix=auto]
|
|
||||||
- id: pretty-format-json
|
|
||||||
args: [--autofix, --no-ensure-ascii]
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit-ci/pre-commit-ci-config
|
|
||||||
rev: v1.6.1
|
|
||||||
hooks:
|
|
||||||
- id: check-pre-commit-ci-config
|
|
||||||
|
|
||||||
- repo: https://github.com/thlorenz/doctoc
|
|
||||||
rev: v2.2.0
|
|
||||||
hooks:
|
|
||||||
- id: doctoc
|
|
||||||
args: [--update-only, --notitle]
|
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
||||||
rev: 0.33.0
|
|
||||||
hooks:
|
|
||||||
- id: check-dependabot
|
|
||||||
- id: check-github-workflows
|
|
||||||
- id: check-gitlab-ci
|
|
||||||
|
|
||||||
# - repo: https://github.com/fsfe/reuse-tool
|
|
||||||
# rev: v3.0.2
|
|
||||||
# hooks:
|
|
||||||
# - id: reuse
|
|
||||||
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: pnpm-install-dev
|
|
||||||
name: Install pnpm dev dependencies
|
|
||||||
entry: corepack pnpm install -D
|
|
||||||
language: system
|
|
||||||
always_run: true
|
|
||||||
#verbose: true
|
|
||||||
pass_filenames: false
|
|
||||||
- id: prettier
|
|
||||||
name: prettier
|
|
||||||
entry: corepack pnpm exec prettier --cache --ignore-unknown --write
|
|
||||||
language: system
|
|
||||||
|
|
||||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
|
||||||
rev: "3.2.1"
|
|
||||||
hooks:
|
|
||||||
- id: editorconfig-checker
|
|
||||||
alias: ec
|
|
||||||
args: [-disable-max-line-length]
|
|
@ -1,5 +0,0 @@
|
|||||||
_includes/
|
|
||||||
_layouts/
|
|
||||||
_sass/
|
|
||||||
css/
|
|
||||||
feed.xml
|
|
@ -1 +0,0 @@
|
|||||||
{}
|
|
@ -1 +0,0 @@
|
|||||||
3.4.2
|
|
@ -1,4 +0,0 @@
|
|||||||
# @format
|
|
||||||
|
|
||||||
language: ruby
|
|
||||||
script: "bundle exec jekyll build"
|
|
7
ACKS
Normal file
7
ACKS
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
johhnyace, who gave me the modem that helped me tremendously in development.
|
||||||
|
bwp, who rewrote the Http.weather command, and also hosted the example
|
||||||
|
"supybot" in #supybot on OFTC and Freenode for quite some time.
|
||||||
|
sweede, for hosting the "main" supybot for awhile.
|
||||||
|
HostPC.com, for hosting the current example "supybot" and for graciously
|
||||||
|
providing DNS services and email.
|
||||||
|
|
10
BUGS
Normal file
10
BUGS
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
We're sure there are tons of them. When you find them, send them to us and
|
||||||
|
we'll fix them ASAP. We'd love to have a bugless bot someday...
|
||||||
|
|
||||||
|
Incidentally, the way to "send the bugs to us" is via SourceForge:
|
||||||
|
<http://sourceforge.net/tracker/?atid=489447&group_id=58965&func=browse>
|
||||||
|
|
||||||
|
Known bugs that probably won't get fixed:
|
||||||
|
|
||||||
|
BadWords' outFilter filters colors. It's not a high priority to get
|
||||||
|
that fixed.
|
67
DEVS
Normal file
67
DEVS
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
These are the developers of Supybot, in approximate order of ____.
|
||||||
|
|
||||||
|
Jeremy Fincher (jemfinch) is a Computer Science (and possibly
|
||||||
|
philosophy) student at The Ohio State University. He spends most of
|
||||||
|
his free time with his girlfriend Meg, but also...well, there's not
|
||||||
|
much also :) He hopes to graduate with good enough grades to go to
|
||||||
|
law school or seminary at some point in the future. He initially
|
||||||
|
wrote the majority of the Supybot framework and standard plugins,
|
||||||
|
though he's been trying to slowly phase himself out of plugin-writing
|
||||||
|
and more into framework-enhancement. Rather than list the specific
|
||||||
|
things he's done, you can just assume that if someone else isn't
|
||||||
|
claiming it, it was probably done by him.
|
||||||
|
|
||||||
|
Daniel DiPaolo (Strike/ddipaolo) is a lazy Texan punk with a job as an IT
|
||||||
|
monkey who spends his free time coding, playing ultimate frisbee, and arguing
|
||||||
|
pointless things on the internet. As far as the bot goes, he's mainly a
|
||||||
|
plugin developer but he has helped here and there with various under-the-hood
|
||||||
|
things and is one of the few people (other than jemfinch) who understands the
|
||||||
|
inner workings of Supybot. His biggest plugin contribution (in terms of sheer
|
||||||
|
lines of code) has been the MoobotFactoids plugin and all the workd involved
|
||||||
|
in getting that plugin to work, but he has also helped with a lot of testing,
|
||||||
|
debugging, and brainstorming. He also wrote the Dunno, News, and Todo plugins
|
||||||
|
and is responsible for a significant amount of code in the Poll, Debian,
|
||||||
|
QuoteGrabs, Karma, and ChannelDB plugins.
|
||||||
|
|
||||||
|
James Vega (jamessan) is an Electrical Engineering/Computer Science student at
|
||||||
|
Northeastern University. He wrote the Sourceforge and Ebay plugins as well as
|
||||||
|
the first incarnation of the Babelfish commands and most of Amazon. He has
|
||||||
|
also performed a significant amount of maintenance and refactoring of plugins
|
||||||
|
in general. Some of the plugins that were affected the most are Debian,
|
||||||
|
FunDB, Gameknot, Http, Note, and Quote. All of the link snarfers, save
|
||||||
|
Bugzilla's, were also written by jamessan. His meddlings have prompted the
|
||||||
|
implementation of Toggleables, which eventually evolved to Configurables and
|
||||||
|
then to the current registry system. As well as being the current webmaster,
|
||||||
|
he also overhauled the tool which is used to generate the site's HTML
|
||||||
|
documentation for Supybot and setup the weekly creation of CVS snapshots.
|
||||||
|
|
||||||
|
Brett Kelly (inkedmn) is a hobbyist (soon to be professional :)) coder
|
||||||
|
from southern California who enjoys collecting tattoos (on his body) and
|
||||||
|
drinking coffee with his wife. He initially wrote the Note plugin as well
|
||||||
|
as several commands in the Http plugin.
|
||||||
|
|
||||||
|
Vincent Foley-Bourgon is a recently-graduated student from Quebec who
|
||||||
|
enjoys anything pointless, unprofitable, and generally useless. Recently
|
||||||
|
returning to Supybot development (after writing the original freshmeat
|
||||||
|
command for the Http plugin) he wrote the entire Hangman infrastructure
|
||||||
|
for the Words plugin.
|
||||||
|
|
||||||
|
Daniel Berlin is a soon to be lawyer with a background in computer science
|
||||||
|
and compilers. He enjoys selling crack to young homeless orphans, and works
|
||||||
|
on Supybot when he's not lawyering or hacking on gcc.
|
||||||
|
|
||||||
|
Keith Jones (kmj) dislikes talking about himself in the third person. He
|
||||||
|
has an MS in Computer Science, and has decided to see how long he can go
|
||||||
|
without using that in any kind of professional capacity. To that end he
|
||||||
|
is currently taking some math classes and applying to math Ph.D programs
|
||||||
|
so some day he can be a professor at a college near a snowy mountain where
|
||||||
|
he will ski every morning. So far, he hasn't done much for the project
|
||||||
|
except squeeze Doug Bell's GPL'd unit conversion code into a supybot plugin.
|
||||||
|
|
||||||
|
Stéphan Kochen (G-LiTe) is a lazy (soon to be) computer science student.
|
||||||
|
He's usually just freelancing and submitting patches here and there when he
|
||||||
|
bumps into a bug that bothers him, but Supybot is one of the first projects
|
||||||
|
he semi-actively tries to work on. ;) His biggest contribution has been the
|
||||||
|
refactoring of the supybot-wizard script to use the registry, though he also
|
||||||
|
likes to track down those nasty obscure bugs which haunt many of our fine
|
||||||
|
applications these days.
|
8
Gemfile
8
Gemfile
@ -1,8 +0,0 @@
|
|||||||
source "https://rubygems.org"
|
|
||||||
ruby file: ".ruby-version"
|
|
||||||
# For now this is a GitHub Pages hosted website.
|
|
||||||
# Ref: https://github.com/Mikaela/mikaela.github.io/issues/153
|
|
||||||
gem 'github-pages', group: :jekyll_plugins
|
|
||||||
gem 'jekyll-seo-tag'
|
|
||||||
# Required for `bundle exec jekyll serve`
|
|
||||||
gem "webrick"
|
|
286
Gemfile.lock
286
Gemfile.lock
@ -1,286 +0,0 @@
|
|||||||
GEM
|
|
||||||
remote: https://rubygems.org/
|
|
||||||
specs:
|
|
||||||
activesupport (8.0.2)
|
|
||||||
base64
|
|
||||||
benchmark (>= 0.3)
|
|
||||||
bigdecimal
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
||||||
connection_pool (>= 2.2.5)
|
|
||||||
drb
|
|
||||||
i18n (>= 1.6, < 2)
|
|
||||||
logger (>= 1.4.2)
|
|
||||||
minitest (>= 5.1)
|
|
||||||
securerandom (>= 0.3)
|
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
|
||||||
uri (>= 0.13.1)
|
|
||||||
addressable (2.8.7)
|
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
|
||||||
base64 (0.2.0)
|
|
||||||
benchmark (0.4.0)
|
|
||||||
bigdecimal (3.1.9)
|
|
||||||
coffee-script (2.4.1)
|
|
||||||
coffee-script-source
|
|
||||||
execjs
|
|
||||||
coffee-script-source (1.12.2)
|
|
||||||
colorator (1.1.0)
|
|
||||||
commonmarker (0.23.11)
|
|
||||||
concurrent-ruby (1.3.5)
|
|
||||||
connection_pool (2.5.0)
|
|
||||||
csv (3.3.3)
|
|
||||||
dnsruby (1.72.4)
|
|
||||||
base64 (~> 0.2.0)
|
|
||||||
logger (~> 1.6.5)
|
|
||||||
simpleidn (~> 0.2.1)
|
|
||||||
drb (2.2.1)
|
|
||||||
em-websocket (0.5.3)
|
|
||||||
eventmachine (>= 0.12.9)
|
|
||||||
http_parser.rb (~> 0)
|
|
||||||
ethon (0.16.0)
|
|
||||||
ffi (>= 1.15.0)
|
|
||||||
eventmachine (1.2.7)
|
|
||||||
execjs (2.10.0)
|
|
||||||
faraday (2.13.0)
|
|
||||||
faraday-net_http (>= 2.0, < 3.5)
|
|
||||||
json
|
|
||||||
logger
|
|
||||||
faraday-net_http (3.4.0)
|
|
||||||
net-http (>= 0.5.0)
|
|
||||||
ffi (1.17.1-x86_64-linux-gnu)
|
|
||||||
forwardable-extended (2.6.0)
|
|
||||||
gemoji (4.1.0)
|
|
||||||
github-pages (232)
|
|
||||||
github-pages-health-check (= 1.18.2)
|
|
||||||
jekyll (= 3.10.0)
|
|
||||||
jekyll-avatar (= 0.8.0)
|
|
||||||
jekyll-coffeescript (= 1.2.2)
|
|
||||||
jekyll-commonmark-ghpages (= 0.5.1)
|
|
||||||
jekyll-default-layout (= 0.1.5)
|
|
||||||
jekyll-feed (= 0.17.0)
|
|
||||||
jekyll-gist (= 1.5.0)
|
|
||||||
jekyll-github-metadata (= 2.16.1)
|
|
||||||
jekyll-include-cache (= 0.2.1)
|
|
||||||
jekyll-mentions (= 1.6.0)
|
|
||||||
jekyll-optional-front-matter (= 0.3.2)
|
|
||||||
jekyll-paginate (= 1.1.0)
|
|
||||||
jekyll-readme-index (= 0.3.0)
|
|
||||||
jekyll-redirect-from (= 0.16.0)
|
|
||||||
jekyll-relative-links (= 0.6.1)
|
|
||||||
jekyll-remote-theme (= 0.4.3)
|
|
||||||
jekyll-sass-converter (= 1.5.2)
|
|
||||||
jekyll-seo-tag (= 2.8.0)
|
|
||||||
jekyll-sitemap (= 1.4.0)
|
|
||||||
jekyll-swiss (= 1.0.0)
|
|
||||||
jekyll-theme-architect (= 0.2.0)
|
|
||||||
jekyll-theme-cayman (= 0.2.0)
|
|
||||||
jekyll-theme-dinky (= 0.2.0)
|
|
||||||
jekyll-theme-hacker (= 0.2.0)
|
|
||||||
jekyll-theme-leap-day (= 0.2.0)
|
|
||||||
jekyll-theme-merlot (= 0.2.0)
|
|
||||||
jekyll-theme-midnight (= 0.2.0)
|
|
||||||
jekyll-theme-minimal (= 0.2.0)
|
|
||||||
jekyll-theme-modernist (= 0.2.0)
|
|
||||||
jekyll-theme-primer (= 0.6.0)
|
|
||||||
jekyll-theme-slate (= 0.2.0)
|
|
||||||
jekyll-theme-tactile (= 0.2.0)
|
|
||||||
jekyll-theme-time-machine (= 0.2.0)
|
|
||||||
jekyll-titles-from-headings (= 0.5.3)
|
|
||||||
jemoji (= 0.13.0)
|
|
||||||
kramdown (= 2.4.0)
|
|
||||||
kramdown-parser-gfm (= 1.1.0)
|
|
||||||
liquid (= 4.0.4)
|
|
||||||
mercenary (~> 0.3)
|
|
||||||
minima (= 2.5.1)
|
|
||||||
nokogiri (>= 1.16.2, < 2.0)
|
|
||||||
rouge (= 3.30.0)
|
|
||||||
terminal-table (~> 1.4)
|
|
||||||
webrick (~> 1.8)
|
|
||||||
github-pages-health-check (1.18.2)
|
|
||||||
addressable (~> 2.3)
|
|
||||||
dnsruby (~> 1.60)
|
|
||||||
octokit (>= 4, < 8)
|
|
||||||
public_suffix (>= 3.0, < 6.0)
|
|
||||||
typhoeus (~> 1.3)
|
|
||||||
html-pipeline (2.14.3)
|
|
||||||
activesupport (>= 2)
|
|
||||||
nokogiri (>= 1.4)
|
|
||||||
http_parser.rb (0.8.0)
|
|
||||||
i18n (1.14.7)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
jekyll (3.10.0)
|
|
||||||
addressable (~> 2.4)
|
|
||||||
colorator (~> 1.0)
|
|
||||||
csv (~> 3.0)
|
|
||||||
em-websocket (~> 0.5)
|
|
||||||
i18n (>= 0.7, < 2)
|
|
||||||
jekyll-sass-converter (~> 1.0)
|
|
||||||
jekyll-watch (~> 2.0)
|
|
||||||
kramdown (>= 1.17, < 3)
|
|
||||||
liquid (~> 4.0)
|
|
||||||
mercenary (~> 0.3.3)
|
|
||||||
pathutil (~> 0.9)
|
|
||||||
rouge (>= 1.7, < 4)
|
|
||||||
safe_yaml (~> 1.0)
|
|
||||||
webrick (>= 1.0)
|
|
||||||
jekyll-avatar (0.8.0)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-coffeescript (1.2.2)
|
|
||||||
coffee-script (~> 2.2)
|
|
||||||
coffee-script-source (~> 1.12)
|
|
||||||
jekyll-commonmark (1.4.0)
|
|
||||||
commonmarker (~> 0.22)
|
|
||||||
jekyll-commonmark-ghpages (0.5.1)
|
|
||||||
commonmarker (>= 0.23.7, < 1.1.0)
|
|
||||||
jekyll (>= 3.9, < 4.0)
|
|
||||||
jekyll-commonmark (~> 1.4.0)
|
|
||||||
rouge (>= 2.0, < 5.0)
|
|
||||||
jekyll-default-layout (0.1.5)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-feed (0.17.0)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-gist (1.5.0)
|
|
||||||
octokit (~> 4.2)
|
|
||||||
jekyll-github-metadata (2.16.1)
|
|
||||||
jekyll (>= 3.4, < 5.0)
|
|
||||||
octokit (>= 4, < 7, != 4.4.0)
|
|
||||||
jekyll-include-cache (0.2.1)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-mentions (1.6.0)
|
|
||||||
html-pipeline (~> 2.3)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-optional-front-matter (0.3.2)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-paginate (1.1.0)
|
|
||||||
jekyll-readme-index (0.3.0)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-redirect-from (0.16.0)
|
|
||||||
jekyll (>= 3.3, < 5.0)
|
|
||||||
jekyll-relative-links (0.6.1)
|
|
||||||
jekyll (>= 3.3, < 5.0)
|
|
||||||
jekyll-remote-theme (0.4.3)
|
|
||||||
addressable (~> 2.0)
|
|
||||||
jekyll (>= 3.5, < 5.0)
|
|
||||||
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
|
||||||
rubyzip (>= 1.3.0, < 3.0)
|
|
||||||
jekyll-sass-converter (1.5.2)
|
|
||||||
sass (~> 3.4)
|
|
||||||
jekyll-seo-tag (2.8.0)
|
|
||||||
jekyll (>= 3.8, < 5.0)
|
|
||||||
jekyll-sitemap (1.4.0)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-swiss (1.0.0)
|
|
||||||
jekyll-theme-architect (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-cayman (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-dinky (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-hacker (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-leap-day (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-merlot (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-midnight (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-minimal (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-modernist (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-primer (0.6.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-github-metadata (~> 2.9)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-slate (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-tactile (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-time-machine (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-titles-from-headings (0.5.3)
|
|
||||||
jekyll (>= 3.3, < 5.0)
|
|
||||||
jekyll-watch (2.2.1)
|
|
||||||
listen (~> 3.0)
|
|
||||||
jemoji (0.13.0)
|
|
||||||
gemoji (>= 3, < 5)
|
|
||||||
html-pipeline (~> 2.2)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
json (2.10.2)
|
|
||||||
kramdown (2.4.0)
|
|
||||||
rexml
|
|
||||||
kramdown-parser-gfm (1.1.0)
|
|
||||||
kramdown (~> 2.0)
|
|
||||||
liquid (4.0.4)
|
|
||||||
listen (3.9.0)
|
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
|
||||||
logger (1.6.6)
|
|
||||||
mercenary (0.3.6)
|
|
||||||
minima (2.5.1)
|
|
||||||
jekyll (>= 3.5, < 5.0)
|
|
||||||
jekyll-feed (~> 0.9)
|
|
||||||
jekyll-seo-tag (~> 2.1)
|
|
||||||
minitest (5.25.5)
|
|
||||||
net-http (0.6.0)
|
|
||||||
uri
|
|
||||||
nokogiri (1.18.7-x86_64-linux-gnu)
|
|
||||||
racc (~> 1.4)
|
|
||||||
octokit (4.25.1)
|
|
||||||
faraday (>= 1, < 3)
|
|
||||||
sawyer (~> 0.9)
|
|
||||||
pathutil (0.16.2)
|
|
||||||
forwardable-extended (~> 2.6)
|
|
||||||
public_suffix (5.1.1)
|
|
||||||
racc (1.8.1)
|
|
||||||
rb-fsevent (0.11.2)
|
|
||||||
rb-inotify (0.11.1)
|
|
||||||
ffi (~> 1.0)
|
|
||||||
rexml (3.4.1)
|
|
||||||
rouge (3.30.0)
|
|
||||||
rubyzip (2.4.1)
|
|
||||||
safe_yaml (1.0.5)
|
|
||||||
sass (3.7.4)
|
|
||||||
sass-listen (~> 4.0.0)
|
|
||||||
sass-listen (4.0.0)
|
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
|
||||||
sawyer (0.9.2)
|
|
||||||
addressable (>= 2.3.5)
|
|
||||||
faraday (>= 0.17.3, < 3)
|
|
||||||
securerandom (0.4.1)
|
|
||||||
simpleidn (0.2.3)
|
|
||||||
terminal-table (1.8.0)
|
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
|
||||||
typhoeus (1.4.1)
|
|
||||||
ethon (>= 0.9.0)
|
|
||||||
tzinfo (2.0.6)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
unicode-display_width (1.8.0)
|
|
||||||
uri (1.0.3)
|
|
||||||
webrick (1.9.1)
|
|
||||||
|
|
||||||
PLATFORMS
|
|
||||||
x86_64-linux
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
github-pages
|
|
||||||
jekyll-seo-tag
|
|
||||||
webrick
|
|
||||||
|
|
||||||
RUBY VERSION
|
|
||||||
ruby 3.4.2p28
|
|
||||||
|
|
||||||
BUNDLED WITH
|
|
||||||
2.6.2
|
|
110
INSTALL
Normal file
110
INSTALL
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
So what do you do? That depends on which operating system you're
|
||||||
|
running. We've split this document up to address the different
|
||||||
|
methods, so find the section for your operating system and continue
|
||||||
|
from there. First let's start with the parts that are common to all
|
||||||
|
OSes.
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# COMMON:
|
||||||
|
###
|
||||||
|
|
||||||
|
First things first: Supybot *requires* at least Python 2.3. There ain't
|
||||||
|
no getting around it. We do not require any version greater than 2.3,
|
||||||
|
but we will be compatible with any version of Python >= 2.3. If you're
|
||||||
|
a Python developer, you probably know how superior 2.3 is to previous
|
||||||
|
incarnations. If you're not, just think about the difference between a
|
||||||
|
bowl of plain vanilla ice cream and a banana split. Or something like
|
||||||
|
that. Either way, *We're* Python developers and we like banana splits.
|
||||||
|
So, be sure to install python2.3 or greater before continuing. You can
|
||||||
|
get it from http://www.python.org/
|
||||||
|
|
||||||
|
For more information and help on how to use Supybot, checkout
|
||||||
|
the documents under docs/ (especially GETTING_STARTED and CONFIGURATION).
|
||||||
|
Our forums (http://forums.supybot.org/) may also be of use, especially
|
||||||
|
the "Tips and Tricks" topic under "Supybot User Discussion".
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# UNIX/Linux/*BSD:
|
||||||
|
###
|
||||||
|
|
||||||
|
If you're installing Python using your distributor's packages, you may
|
||||||
|
need a python-dev package installed, too. If you don't have a
|
||||||
|
/usr/lib/python2.3/distutils directory (assuming /usr/lib/python2.3 is
|
||||||
|
where your Python libs are installed), then you will need a python-dev
|
||||||
|
package.
|
||||||
|
|
||||||
|
After you extract Supybot and cd into the supybot directory just
|
||||||
|
created, you'll want to run (as root) "python setup.py install". This
|
||||||
|
will install Supybot globally. If you need to install locally for
|
||||||
|
whatever reason, see the addendum near the end of this document.
|
||||||
|
You'll then have several new programs installed where Python scripts
|
||||||
|
are normally installed on your system (/usr/bin or /usr/local/bin are
|
||||||
|
common on UNIX systems). The two that might be of particular interest
|
||||||
|
to you, the new user, are "supybot" and "supybot-wizard". The former
|
||||||
|
("supybot") is the script to run an actual bot; the latter
|
||||||
|
("supybot-wizard") is an in-depth wizard that provides a nice user
|
||||||
|
interface for creating a registry file for your bot.
|
||||||
|
|
||||||
|
So after running supybot-wizard, you've got a nice registry file
|
||||||
|
handy. If you're not satisfied with your answers to any of the
|
||||||
|
questions you were asked, feel free to run the program again until
|
||||||
|
you're satisfied with all your answers. Once you're satisfied,
|
||||||
|
though, run the "supybot" program with the registry file you created
|
||||||
|
as an argument. This will start the bot; unless you turned off
|
||||||
|
logging to stdout, you'll see some nice log messages describing what
|
||||||
|
the bot is doing at any particular moment; it may pause for a
|
||||||
|
significant amount of time after saying "Connecting to ..." while the
|
||||||
|
server tries to check its ident.
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Windows:
|
||||||
|
###
|
||||||
|
|
||||||
|
*** If you are using an IPV6 connection, you will not be able to run
|
||||||
|
Supybot under Windows (unless Python has fixed things). Current
|
||||||
|
versions of Python for Windows are *not* built with IPV6 support. This
|
||||||
|
isn't expected to be fixed until Python 2.4, at the earliest.
|
||||||
|
|
||||||
|
Now that you have Python installed, open up a command prompt. The
|
||||||
|
easiest way to do this is to open the run dialog (Programs -> run) and
|
||||||
|
type "cmd" (for Windows 2000/XP/2003) or "command" (for Windows 9x).
|
||||||
|
In order to reduce the amount of typing you need to do, I suggest
|
||||||
|
adding Python's directory to your path. If you installed Python using
|
||||||
|
the default settings, you would then do the following in the command
|
||||||
|
prompt (otherwise change the path to match your settings):
|
||||||
|
|
||||||
|
set PATH=%PATH%;C:\Python23\
|
||||||
|
|
||||||
|
You should now be able to type "python" to start the Python
|
||||||
|
interpreter (CTRL-Z and Return to exit). Now that that's setup,
|
||||||
|
you'll want to cd into the directory that was created when you
|
||||||
|
unzipped Supybot; I'll assume you unzipped it to C:\Supybot for these
|
||||||
|
instructions. From C:\Supybot, run "python setup.py install". This
|
||||||
|
will install Supybot under C:\Python23\. If you want to install
|
||||||
|
Supybot to a non-default location, see the addendum near the end of
|
||||||
|
this document. You will now have several new programs installed in
|
||||||
|
C:\Python23\Scripts\. The two that might be of particular interest to
|
||||||
|
you, the new user, are "supybot" and "supybot-wizard". The former
|
||||||
|
("supybot") is the script to run an actual bot; the latter
|
||||||
|
("supybot-wizard") is an in-depth wizard that provides a nice user
|
||||||
|
interface for creating a registry file for your bot.
|
||||||
|
|
||||||
|
Now you will want to run "python C:\Python23\Scripts\supybot-wizard"
|
||||||
|
to generate a registry file for your bot. So after running
|
||||||
|
supybot-wizard, you've got a nice registry file handy. If you're not
|
||||||
|
satisfied with your answers to any of the questions you were asked,
|
||||||
|
feel free to run the program again until you're satisfied with all
|
||||||
|
your answers. Once you're satisfied, though, run "python
|
||||||
|
C:\Python23\Scripts\supybot botname.conf". This will start the bot;
|
||||||
|
unless you turned off logging to stdout, you'll see some nice log
|
||||||
|
messages describing what the bot is doing at any particular moment; it
|
||||||
|
may pause for a significant amount of time after saying "Connecting
|
||||||
|
to ..." while the server tries to check its ident.
|
||||||
|
|
||||||
|
###
|
||||||
|
# Addenda
|
||||||
|
###
|
||||||
|
Local installs: See this forum post: http://tinyurl.com/2tb37
|
28
LICENSE
Normal file
28
LICENSE
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2002-2004 Jeremiah Fincher and others
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions, and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions, and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the author of this software nor the name of
|
||||||
|
contributors to this software may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Portions of the included source code are copyright by its original author(s)
|
||||||
|
and remain subject to its associated license.
|
38
README
Normal file
38
README
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
EVERYONE:
|
||||||
|
---------
|
||||||
|
Read LICENSE. It's a 2-clause BSD license, but you should read it anyway.
|
||||||
|
|
||||||
|
|
||||||
|
USERS:
|
||||||
|
------
|
||||||
|
If you're upgrading, read RELNOTES. If you're new to Supybot,
|
||||||
|
read docs/GETTING_STARTED for an introduction to the bot, and read
|
||||||
|
docs/CAPABILITIES to see how to use capabilities to your greater
|
||||||
|
benefit.
|
||||||
|
|
||||||
|
If you have any trouble, feel free to swing by #supybot on
|
||||||
|
irc.freenode.net or irc.oftc.net (we have a Supybot there relaying,
|
||||||
|
so either network works) and ask questions. We'll be happy to help
|
||||||
|
wherever we can. And by all means, if you find anything hard to
|
||||||
|
understand or think you know of a better way to do something,
|
||||||
|
*please* post it on Sourceforge.net so we can improve the bot!
|
||||||
|
|
||||||
|
WINDOWS USERS:
|
||||||
|
--------------
|
||||||
|
The wizards (supybot-wizard, supybot-newplugin, and
|
||||||
|
supybot-adduser) are all installed to your Python directory's
|
||||||
|
\Scripts. What that *probably* means is that you'll run them like
|
||||||
|
this: C:\Python23\python C:\Python23\Scripts\supybot-wizard
|
||||||
|
|
||||||
|
|
||||||
|
DEVELOPERS:
|
||||||
|
-----------
|
||||||
|
Read OVERVIEW to see what the modules are used for. Read PLUGIN-EXAMPLE
|
||||||
|
to see some examples of callbacks and commands written for the bot.
|
||||||
|
Read INTERFACES to see what kinds of objects you'll be dealing with.
|
||||||
|
Read STYLE if you wish to contribute; all contributed code must meet
|
||||||
|
the guidelines set forth there.
|
||||||
|
|
||||||
|
Be sure to run "test/test.py --help" to see what options are available
|
||||||
|
to you when testing. Windows users in particular should be sure to
|
||||||
|
exclude test_Debian.py and test_Unix.py.
|
11
README.md
11
README.md
@ -1,11 +0,0 @@
|
|||||||
<!-- @format -->
|
|
||||||
|
|
||||||
# Mikaela's fork of Limnoria.
|
|
||||||
|
|
||||||
There are mainly two branches. This one which you are looking at, gh-pages which
|
|
||||||
is the source of <https://supybot.mikaela.info/>.
|
|
||||||
|
|
||||||
**testing** which will be synced with [ProgVal/Limnoria] when needed. It is used
|
|
||||||
as base for my changes which will be pull requested.
|
|
||||||
|
|
||||||
[ProgVal/Limnoria]: https://github.com/ProgVal/Limnoria.git
|
|
257
RELNOTES
Normal file
257
RELNOTES
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
Version 0.80.0
|
||||||
|
|
||||||
|
We *finally* hit 0.80.0! This release is completely compatible with
|
||||||
|
the last release candidate.
|
||||||
|
|
||||||
|
An update to Babelfish may cause an error message to be displayed in
|
||||||
|
the console when the bot is first run. The error message should be
|
||||||
|
gone when the bot is restarted.
|
||||||
|
|
||||||
|
We also have a new community website at http://www.supybot.com/ where
|
||||||
|
our users can submit their own plugins, view/download other people's
|
||||||
|
plugins and discuss all things Supybot-related.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0rc3
|
||||||
|
|
||||||
|
Another bugfix release. This one was pretty important as it actually
|
||||||
|
makes supybot.database.plugins.channelSpecific work properly.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0rc2
|
||||||
|
|
||||||
|
supybot.databases.plugins.channelSpecific.channel was renamed to
|
||||||
|
supybot.databases.plugins.channelSpecific.link.
|
||||||
|
|
||||||
|
supybot.databases.plugins.channelSpecific.link.allow was added, which
|
||||||
|
determines whether a channel will allow other channels to link to its
|
||||||
|
database.
|
||||||
|
|
||||||
|
Infobot is no longer deprecated and the following changes were made to
|
||||||
|
its config variables:
|
||||||
|
supybot.plugins.Infobot.answerUnaddressedQuestions was renamed to
|
||||||
|
supybot.plugins.Infobot.unaddressed.answerQuestions.
|
||||||
|
supybot.plugins.Infobot.snarfUnaddressedDefinitions was renamed to
|
||||||
|
supybot.plugins.Infobot.unaddressed.snarfDefinitions.
|
||||||
|
supybot.plugins.Infobot.unaddressed.replyExistingFactoid was added to
|
||||||
|
determine whether the bot will reply when someone attempts to create a
|
||||||
|
duplicate factoid.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0pre6
|
||||||
|
|
||||||
|
Another bugfix release. No incompatibilities known. The only
|
||||||
|
registry change is that supybot.databases.users.hash has been
|
||||||
|
removed.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0pre5
|
||||||
|
|
||||||
|
Completely bugfix release. No incompatibilies known.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0pre4
|
||||||
|
|
||||||
|
Mainly a bug fix release. This will likely be the last release before
|
||||||
|
0.80.0final, but we're gonna let it stew for a couple weeks to attempt
|
||||||
|
to catch any lingering bugs.
|
||||||
|
|
||||||
|
ansycoreDrivers is now deprecated in favor of socketDrivers or
|
||||||
|
twistedDrivers.
|
||||||
|
|
||||||
|
supybot.databases.plugins.channelSpecific.channel is now a channelValue
|
||||||
|
so that you can link specific channels together (instead of all channels
|
||||||
|
being linked together).
|
||||||
|
|
||||||
|
For those of you that use eval and/or exec, they have been removed from
|
||||||
|
the Owner plugin and are now in sandbox/Debug.py (which you'll have to
|
||||||
|
grab from CVS).
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0pre3
|
||||||
|
|
||||||
|
The database format for the Note plugin has changed to a flatfile
|
||||||
|
format; use tools/noteConvert.py to convert it to the new format.
|
||||||
|
|
||||||
|
Ditto that for the URL database.
|
||||||
|
|
||||||
|
FunDB is deprecated and will be removed at the next major release;
|
||||||
|
use tools/fundbConvert.py to convert your old FunDB databases to Lart
|
||||||
|
and Praise databases.
|
||||||
|
|
||||||
|
If you had turned off supybot.databases.plugins.channelSpecific, your
|
||||||
|
non-channel-specific database files had gone directly into your data/
|
||||||
|
directory. We had some problems with poor interactions between that
|
||||||
|
configuration variable and channel capabilities, though, so we
|
||||||
|
changed the implementation so that non-channel-specific databases are
|
||||||
|
considered databases of a single (configurable) channel (defaulting
|
||||||
|
to "#"). This will also help others who are converting from
|
||||||
|
channel-specific to non-channel-specific databases, but for you
|
||||||
|
who've already made the switch, you'll need to move your database
|
||||||
|
files again, from data/ to data/# (or whatever channel you might
|
||||||
|
change that variable to).
|
||||||
|
|
||||||
|
supybot.channels doesn't exist anymore; now the only list of channels
|
||||||
|
to join is per-network, in supybot.networks.<network>.channels.
|
||||||
|
|
||||||
|
We weren't serializing supybot.replies.* properly in older versions.
|
||||||
|
Now we are, but the old, improperly serialized versions won't work
|
||||||
|
properly. Remove from your configuration file all variables
|
||||||
|
beginning with "supybot.replies" before you start the bot.
|
||||||
|
|
||||||
|
The URL database has been changed again, but it will use a different
|
||||||
|
filename so you shouldn't run into conflicts, just a newly-empty
|
||||||
|
database.
|
||||||
|
|
||||||
|
We upgraded the SOAP stuff in others; you may do well to do a
|
||||||
|
setup.py install --clean this time around.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0pre2
|
||||||
|
|
||||||
|
Many more bugs have been fixed. A few more plugins have been updated
|
||||||
|
to use our new-style database abstraction. If it seems like your
|
||||||
|
databases are suddenly empty, look for a new database file named
|
||||||
|
Plugin.dbtype.db. We've also added a few more configuration variables.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.80.0pre1
|
||||||
|
|
||||||
|
Tons of bugs fixed, many features and plugins added. Everything
|
||||||
|
should be entirely compatible; many more configuration variables have
|
||||||
|
been added.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.79.9999
|
||||||
|
|
||||||
|
Some more bugs fixed, added a few features and a couple configuration
|
||||||
|
variabless. This should hopefully be the last release before 0.80.0,
|
||||||
|
which will finally bring us to pure Beta status.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.79.999
|
||||||
|
|
||||||
|
Some bugs fixed, but the ones that were fixed were pretty big. This
|
||||||
|
is, of course, completely compatible with the last release.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.79.99
|
||||||
|
|
||||||
|
Many bugs fixed, thanks to the users who reported them. We're
|
||||||
|
getting asymptotically closer to 0.80.0 -- maybe this'll be the last
|
||||||
|
one, maybe we'll have to release an 0.79.999 -- either way, we're
|
||||||
|
getting close :) Check out the ChangeLog for the fixes and a few new
|
||||||
|
features.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.79.9
|
||||||
|
|
||||||
|
We've changed so much stuff in this release that we've given up on
|
||||||
|
users upgrading their configuration files for the new release. So
|
||||||
|
do a clean install (python2.3 setup.py install --clean), run the
|
||||||
|
wizard again, and kick some butt.
|
||||||
|
|
||||||
|
(It's rumored that you can save most of your old configuration by
|
||||||
|
appending your new configuration at the end of your old configuration
|
||||||
|
and running supybot with that new configuration file. This, of
|
||||||
|
course, comes with no warranty or guarantee of utility -- try it if
|
||||||
|
you want, but backup your original configuration file!)
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.77.2
|
||||||
|
|
||||||
|
This is a drop-in replacement for 0.77.1, with two exceptions. The
|
||||||
|
configuration variable formerly known as
|
||||||
|
"supybot.plugins.Services.password" is now known as
|
||||||
|
"supybot.plugins.Services.NickServ.password", due to the fact that
|
||||||
|
there might be different passwords for NickServ and ChanServ (and
|
||||||
|
ChanServ passwords are per-channel, whereas NickServ passwords are
|
||||||
|
global). If you're using the Services plugin, you'll need to make
|
||||||
|
this change in order to continue identifying with services. The
|
||||||
|
configuration variable formerly known as
|
||||||
|
"supybot.plugins.Babelfish.disabledLanguages" is now known as
|
||||||
|
"supybot.plugins.Babelfish.languages". The configuration variable now
|
||||||
|
accepts the languages that *will* be translated as opposed to ones
|
||||||
|
that are *not* translated.
|
||||||
|
|
||||||
|
Tests and the developer sandbox are not longer delivered with our
|
||||||
|
release tarballs. If you're a developer and you want these, you
|
||||||
|
should either check out CVS or download one of our weekly CVS
|
||||||
|
snapshots, available at http://supybot.sourceforge.net/snapshots/ .
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.77.1
|
||||||
|
|
||||||
|
This is a drop-in replacement for 0.77.0 -- no incompatibilities, to
|
||||||
|
out knowledge. Simply install over your old installation and restart
|
||||||
|
your bot :)
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.77.0
|
||||||
|
|
||||||
|
Setup.py will automatically remove your old installations for you, no
|
||||||
|
need to worry about that yourself.
|
||||||
|
|
||||||
|
Configuration has been *entirely* redone. Read the new
|
||||||
|
GETTING_STARTED document to see how to work with configuration
|
||||||
|
variables now. Your old botscripts from earlier versions *will not*
|
||||||
|
work with the new configuration method. We'd appreciate it if you'd
|
||||||
|
rerun the wizard in order for us to find any bugs that remain in it
|
||||||
|
before we officially declare ourselves Beta. Note also that because
|
||||||
|
of the new configuration method, the interface for plugins' configure
|
||||||
|
function has changed: there are no longer any onStart or afterConnect
|
||||||
|
arguments, so all configuration should be performed via the registry.
|
||||||
|
|
||||||
|
Channel capabilities have been changed; rather than being
|
||||||
|
#channel.capability, they're now #channel,capability. It's a bit
|
||||||
|
uglier, we know, but dots can be valid in channel names, and we
|
||||||
|
needed the dot for handling plugin.command capabilities.
|
||||||
|
tools/ircdbConvert.py should update this for you.
|
||||||
|
|
||||||
|
The on-disk format of the user/channel databases has changed to be far
|
||||||
|
more readable. A conversion utility is included, as mentioned before:
|
||||||
|
tools/ircdbConvert.py. Run this with no arguments to see the
|
||||||
|
directions for using it.
|
||||||
|
|
||||||
|
Uh, we were just kidding about the upgrade script in 0.76.0 :) It'll
|
||||||
|
be a little while longer. We do have several little upgrade scripts,
|
||||||
|
though.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.76.1
|
||||||
|
|
||||||
|
Almost entirely bugfixes, just some minor (and some less minor) bugs
|
||||||
|
that need to get in before we really start hacking on the next
|
||||||
|
version. Should be *entirely* compatible with 0.76.0.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.76.0
|
||||||
|
|
||||||
|
Major bugfix release. A great number of bugs fixed. This is the last
|
||||||
|
release without an upgrade script.
|
||||||
|
|
||||||
|
The only hiccup in the upgrade from 0.75.0 should be that you'll need
|
||||||
|
to update your botscript to reflect the removal of the debug module.
|
||||||
|
We'd rather you use supybot-wizard to generate a new botscript, of
|
||||||
|
course, but if you insist on modifying your existing botscript, take a
|
||||||
|
look at
|
||||||
|
<http://cvs.sourceforge.net/viewcvs.py/supybot/supybot/src/template.py?r1=1.20&r2=1.21>
|
||||||
|
to see what you need to do.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.75.0
|
||||||
|
|
||||||
|
Don't forget to reinstall (i.e., run "python setup.py install" as
|
||||||
|
root). Sometimes it even does good to remove the old installation;
|
||||||
|
$PYTHON/site-packages/supybot can be removed with no problems
|
||||||
|
whatsoever.
|
||||||
|
|
||||||
|
You will need to re-run supybot-wizard and generate a new botscript.
|
||||||
|
|
||||||
|
The Infobot plugin has been removed from this release; it's not ready
|
||||||
|
for prime time. If you're interested in getting it running (i.e., you
|
||||||
|
want full Infobot compatibility and aren't satisfied with either
|
||||||
|
MoobotFactoids or Factoids) then swing over to #supybot and we can
|
||||||
|
discuss the tests. We simply don't know enough about Infobot to make
|
||||||
|
sure our Infobot plugin is an exact replica, and need someone's help
|
||||||
|
with making the changes necessary for that.
|
@ -1,71 +0,0 @@
|
|||||||
---
|
|
||||||
layout: page
|
|
||||||
title: Ignoring RelayBot
|
|
||||||
permalink: /Relaybot.html
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- @format -->
|
|
||||||
|
|
||||||
RelayBot is the bot which relays between #supybot,#limnoria at a couple of
|
|
||||||
networks (TODO/FIXME, which ones?). It is currently using the
|
|
||||||
[LinkRelay](https://github.com/ProgVal/Supybot-plugins/tree/master/LinkRelay)
|
|
||||||
plugin to do this.
|
|
||||||
|
|
||||||
It's sometimes considered as annoyance as it has lately mostly spammed with join
|
|
||||||
(part messages aren't working, because of a bug (2014-06-23)) messages of people
|
|
||||||
who usually say nothing and this is why this page is here to tell how to ignore
|
|
||||||
it on various client.
|
|
||||||
|
|
||||||
We(who? I?) encourage you to ignore only notices from RelayBot instead of
|
|
||||||
everything as there are people whom should be heard at OFTC (mainly main Supybot
|
|
||||||
developer). (TODO/FIXME: is this the case in 2021?)
|
|
||||||
|
|
||||||
Related links:
|
|
||||||
|
|
||||||
- [LinkRelay plugin](https://github.com/ProgVal/Supybot-plugins/tree/master/LinkRelay)
|
|
||||||
- [Feature request for smart filtering of joins/quits/parts](https://github.com/ProgVal/Supybot-plugins/issues/66)
|
|
||||||
- [Feature request for RELAYMSG for more native look&feel](https://github.com/ProgVal/Supybot-plugins/issues/338)
|
|
||||||
|
|
||||||
Hostmask of RelayBot on Libera.Chat 2021-06-06:
|
|
||||||
|
|
||||||
- `RelayBot!~limnoria@helium.progval.net`
|
|
||||||
- This is absolute hostmask, also known as NUH (`nick!user@host`)
|
|
||||||
- `RelayBot*!*@helium.progval.net`
|
|
||||||
- This is recommended hostmask as it matches RelayBot even if it cannot use
|
|
||||||
it's primary nickname or networks cannot connect to it's identd.
|
|
||||||
|
|
||||||
## HexChat
|
|
||||||
|
|
||||||
From the "Window" menu you can find "Ignore list". Click "Add" and add one of
|
|
||||||
the hostmasks mentioned above (the lower is recommended).
|
|
||||||
|
|
||||||
Uncheck the other checkboxes than "Notice" and you can close the window and you
|
|
||||||
won't see spamming.
|
|
||||||
|
|
||||||
## KVIRC
|
|
||||||
|
|
||||||
I am not primarily KVIRC user and I cannot say anything else than right click
|
|
||||||
RelayBot and select something that matches only RelayBot.
|
|
||||||
|
|
||||||
**WARNING: KVIRC makes it very easy to also ignore pinkieval which you don't
|
|
||||||
want to do as they are author of Limnoria and help people often!**
|
|
||||||
|
|
||||||
## Linkinus
|
|
||||||
|
|
||||||
According to another person, there is a GUI where you can easily ignore notices
|
|
||||||
from specific hostmask.
|
|
||||||
|
|
||||||
## WeeChat
|
|
||||||
|
|
||||||
`/filter add relaybotnotices * irc_notice+nick_RelayBot *`
|
|
||||||
|
|
||||||
This creates a new filter with the name "relaybotnotices" which filters all
|
|
||||||
notices from the nickname "RelayBot".
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This page is very likely missing many IRC clients. Could you
|
|
||||||
[open an issue](https://github.com/mikaela/limnoria/issues) about how to do this
|
|
||||||
with your IRC client that isn't mentioned here?
|
|
||||||
|
|
||||||
---
|
|
145
Supybot.markdown
145
Supybot.markdown
@ -1,145 +0,0 @@
|
|||||||
---
|
|
||||||
layout: page
|
|
||||||
title: Security issues
|
|
||||||
permalink: /Supybot.html
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- @format -->
|
|
||||||
|
|
||||||
Supybot git repository was declared dead on 2018-05-10 and archived on GitHub.
|
|
||||||
[v0.84.0 was the last release at that time](https://github.com/Supybot/Supybot/releases/tag/v0.84.0).
|
|
||||||
0.83.4.1 used to be a very common release available through several Linux
|
|
||||||
distributions for years and thus I made this page, which I guess is now
|
|
||||||
available more of for historical reasons.
|
|
||||||
|
|
||||||
**_WARNING: most of the content originates from 2014!_**
|
|
||||||
|
|
||||||
## The issues of 0.83.4.1.
|
|
||||||
|
|
||||||
### 1. Anyone can crash it and computer where it's running on
|
|
||||||
|
|
||||||
And this is very easy. Just run the command
|
|
||||||
|
|
||||||
`!misc last --regexp m/(.*\w){512}/`
|
|
||||||
|
|
||||||
where ! is the prefix character.
|
|
||||||
|
|
||||||
Misc is loaded by default and cannot be unloaded without modifying the config.
|
|
||||||
|
|
||||||
- [Limnoria issue #157](https://github.com/ProgVal/Limnoria/issues/157)
|
|
||||||
- Fixing commits:
|
|
||||||
[3526d5d](https://github.com/ProgVal/Limnoria/commit/3526d5dabf587457a43af8bee8d4db21986e8222)
|
|
||||||
&
|
|
||||||
[e11dc28](https://github.com/ProgVal/Limnoria/commit/e11dc28025de877b1b6cf059013eef88337b7e44)
|
|
||||||
- [Ubuntu bug #996947](https://bugs.launchpad.net/ubuntu/+source/supybot/+bug/996947)
|
|
||||||
- [Debian bug #672214](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=672214)
|
|
||||||
|
|
||||||
### 2. The previous wasn't the only way to do this
|
|
||||||
|
|
||||||
Everyone can also make the bot count an equation, which brings it and the host
|
|
||||||
computer down.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
`!math calc factorial(999999)`
|
|
||||||
|
|
||||||
This requires Math plugin which comes with Supybot, but isn't load by default.
|
|
||||||
|
|
||||||
- [Limnoria issue #354](https://github.com/ProgVal/Limnoria/issues/354)
|
|
||||||
- Fixing commit:
|
|
||||||
[695078e](https://github.com/ProgVal/Limnoria/commit/695078edeb91e5ff1eec728fedf0e0c27b55c505)
|
|
||||||
- [Ubuntu bug #996950](https://bugs.launchpad.net/ubuntu/+source/supybot/+bug/996950)
|
|
||||||
- [Debian bug 672215](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=672215)
|
|
||||||
|
|
||||||
### 3. Anyone can access network services via the bot.
|
|
||||||
|
|
||||||
I don't have example command for this, but it happens by nesting "format cut"
|
|
||||||
and "misc tell".
|
|
||||||
|
|
||||||
What does this mean? Anyone can tell the bot to ghost someone else on same
|
|
||||||
account, take over a channel by telling the bot to give flags (if it has correct
|
|
||||||
flags), change password of the account and everything else what you do with
|
|
||||||
network services.
|
|
||||||
|
|
||||||
- _This was only reported at IRC and I am unable to find issue report or fixing
|
|
||||||
commit. ~~Mikaela on 2015-01-04._
|
|
||||||
|
|
||||||
### 4. Web page with special characters in \<title\> can be used to send DCC/CTCP commands.
|
|
||||||
|
|
||||||
This doesn't mean only things like CTCP actions (also known as /me), but known
|
|
||||||
problems with old routers ( `FF ? DCC SEND “ff???f??????????????” 0 0 0` ) which
|
|
||||||
make them reconnect to the internet.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
- `!web title <malicious.page.here>`
|
|
||||||
- `!web fetch <malicious.page.here>`
|
|
||||||
|
|
||||||
_This was only reported at IRC and I am unable to find issue report or fixing
|
|
||||||
commit. ~~Mikaela on 2015-01-04._
|
|
||||||
|
|
||||||
### 5. Web Titlte/Fetch can be used for DoS
|
|
||||||
|
|
||||||
They are vulnerable to queries to servers which have custom headers which can
|
|
||||||
lead to DoS.
|
|
||||||
|
|
||||||
_This was only reported at IRC and I am unable to find issue report or fixing
|
|
||||||
commit. ~~Mikaela on 2015-01-04._
|
|
||||||
|
|
||||||
### 6. QuoteGrabs grab command also works in PM
|
|
||||||
|
|
||||||
and can grab private content such as `user register` or `user identify` or with
|
|
||||||
the case of owner possibly NickServ passwords and others not so nice things.
|
|
||||||
|
|
||||||
- _It appears this issue was only reported at IRC._
|
|
||||||
- Fixing commit:
|
|
||||||
[a3346343679f3bdf8c77d9efb5a2097e215d51df](https://github.com/ProgVal/Limnoria/commit/a3346343679f3bdf8c77d9efb5a2097e215d51df)
|
|
||||||
|
|
||||||
### Are these issues publicly known?
|
|
||||||
|
|
||||||
**Of course they are.** Issue reports are below the actual issues.
|
|
||||||
|
|
||||||
The first issue has been also used to take down some of
|
|
||||||
[Ubuntu IRC bots](https://wiki.ubuntu.com/IRC/Bots) several times. At least
|
|
||||||
UbotX (I don't remember the number) and meetingology.
|
|
||||||
|
|
||||||
Some of these issues are fixed in git repository, but most people aren't using
|
|
||||||
it. If you wish to start using it, please scroll down to installation
|
|
||||||
instructions lower this page even though [Limnoria] and [gribble] are more
|
|
||||||
recommended.
|
|
||||||
|
|
||||||
### How to avoid them?
|
|
||||||
|
|
||||||
You can add anticapability for these commands using `owner defaultcapability`,
|
|
||||||
but that is only a temporary solution. There can also be other issues.
|
|
||||||
|
|
||||||
There are also two active Supybot forks, known as [Limnoria] and [Gribble],
|
|
||||||
which are actively developed and have fixed these issues. If you want permanent
|
|
||||||
solution, you should install either of them.
|
|
||||||
|
|
||||||
## Possibly interesting links
|
|
||||||
|
|
||||||
- [Comparsion of commit activity between Limnoria, Gribble and Supybot](https://www.openhub.net/p/compare?project_0=Limnoria&project_1=Gribble%3A+Support+Bottie&project_2=Supybot).
|
|
||||||
- [Gribble's modifications to stock Supybot](https://sourceforge.net/p/gribble/wiki/Gribble_Project_Git_Repository/)
|
|
||||||
- [Limnoria's modifications to Gribble.](https://github.com/ProgVal/Limnoria/wiki/LGC)
|
|
||||||
- Features of Gribble are fully merged to Limnoria.
|
|
||||||
|
|
||||||
Your current botname.conf is **100% compatible with forks**.
|
|
||||||
|
|
||||||
[Join Supybot channels on LiberaChat!](ircs://irc.libera.chat:6697/#supybot,#gribble,#limnoria)
|
|
||||||
|
|
||||||
[Limnoria]: https://github.com/ProgVal/Limnoria
|
|
||||||
[Gribble]: http://github.com/nanotube/supybot_fixes
|
|
||||||
|
|
||||||
## Installing forks
|
|
||||||
|
|
||||||
_This section has been removed in order to not duplicate
|
|
||||||
[Limnoria's documentation.](http://doc.supybot.aperio.fr/en/latest/use/install.html)_
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Do you know issue that isn't mentioned here? If it's not already reported,
|
|
||||||
please report it
|
|
||||||
on [Limnoria's issue tracker.](https://github.com/ProgVal/Limnoria/issues) If
|
|
||||||
it's known, but just not reported here,
|
|
||||||
[please feel free to add it.](https://github.com/Mikaela/limnoria/edit/gh-pages/Supybot.markdown)
|
|
40
TODO
Normal file
40
TODO
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
Roughly in order of precedence (the closer to the front of the file,
|
||||||
|
the more likely it'll be done before next release):
|
||||||
|
|
||||||
|
* We should have a channel-global value in ircdb.IrcChannel for
|
||||||
|
ignoring unregistered users.
|
||||||
|
|
||||||
|
* We need to note when bans expire on a channel and send the unban,
|
||||||
|
that way plugins can use the ircdb ban stuff and not worry about
|
||||||
|
sending or scheduling unbans.
|
||||||
|
|
||||||
|
* We should probably add a "hello" command to make things more
|
||||||
|
compatible with Eggdrop, since we've been replacing many eggdrop
|
||||||
|
bots lately.
|
||||||
|
|
||||||
|
* We should be able to set the log level for plugins individually.
|
||||||
|
|
||||||
|
* Rbot has a "remind" plugin that seems pretty cool, we should get
|
||||||
|
one as well. It might do well as a command in the Later plugin.
|
||||||
|
|
||||||
|
* MozBot has a "list" plugin that seems pretty cool, we should get
|
||||||
|
one as well.
|
||||||
|
|
||||||
|
Problems that involve a lot of tedious minutiae, but really need to
|
||||||
|
be done at some point:
|
||||||
|
|
||||||
|
Hard problems that won't get done until someone really wants to have
|
||||||
|
some fun:
|
||||||
|
|
||||||
|
* Redundant relaying -- having more than one bot handle relaying,
|
||||||
|
where a secondary one will automatically take over if the first
|
||||||
|
becomes incapacitated.
|
||||||
|
|
||||||
|
* Have the bot detect when other bots are in the channel responding
|
||||||
|
to the same prefix character, and do something based on that.
|
||||||
|
|
||||||
|
* Unicode support. Basically, we'd have to have a way to set the
|
||||||
|
encoding for the server, and use unicode throughout internally.
|
||||||
|
The real issue is how much it would affect the current code to
|
||||||
|
switch it over to unicode, and what kind of burden it would put on
|
||||||
|
plugin authors to deal with that issue.
|
38
_config.yml
38
_config.yml
@ -1,38 +0,0 @@
|
|||||||
# @format
|
|
||||||
|
|
||||||
theme: minima
|
|
||||||
title: Mikaela's Supybot site
|
|
||||||
tagline: Things official documentation may not tell you
|
|
||||||
author:
|
|
||||||
name: "Aminda Suomalainen"
|
|
||||||
url: "https://aminda.eu/"
|
|
||||||
description: > # this means to ignore newlines until "baseurl:"
|
|
||||||
Mikaela's Supybot site where nowadays the only content is security issues of
|
|
||||||
stock Supybot.
|
|
||||||
baseurl: "" # the subpath of your site, e.g. /blog/
|
|
||||||
url: "https://supybot.mikaela.info/" # the base hostname & protocol for your site
|
|
||||||
github_username: Mikaela
|
|
||||||
lang: en
|
|
||||||
timezone: Etc/UTC
|
|
||||||
encoding: utf-8
|
|
||||||
plugins:
|
|
||||||
# - jekyll-mentions
|
|
||||||
- jekyll-redirect-from
|
|
||||||
- jekyll-sitemap
|
|
||||||
- jekyll-seo-tag
|
|
||||||
sitemap:
|
|
||||||
file: "/sitemap.xml"
|
|
||||||
include: [robots.txt]
|
|
||||||
robots: nofollow, noai
|
|
||||||
icon: https://github.com/ProgVal/Supybot-website/raw/master/static/logo.png
|
|
||||||
markdown: kramdown
|
|
||||||
kramdown:
|
|
||||||
parse_block_html: true
|
|
||||||
#webmaster_verifications:
|
|
||||||
#google:
|
|
||||||
#bing:
|
|
||||||
defaults:
|
|
||||||
- scope:
|
|
||||||
path: "*"
|
|
||||||
values:
|
|
||||||
image: https://github.com/ProgVal/Supybot-website/raw/master/static/logo.png
|
|
@ -1,32 +0,0 @@
|
|||||||
<footer class="site-footer h-card">
|
|
||||||
<data class="u-url" href="{{ "/" | relative_url }}"></data>
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
|
|
||||||
<h2 class="footer-heading">{{ site.title | escape }}</h2>
|
|
||||||
|
|
||||||
<div class="footer-col-wrapper">
|
|
||||||
<div class="footer-col footer-col-1">
|
|
||||||
<ul class="contact-list">
|
|
||||||
<li class="p-name">
|
|
||||||
<a rel="me prefetch" href="{{ site.author.url }}">{{ site.author.name }}</a><br>
|
|
||||||
{{ site.title | escape }}
|
|
||||||
</li>
|
|
||||||
{%- if site.email -%}
|
|
||||||
<li><a class="u-email" href="mailto:{{ site.email }}">{{ site.email }}</a></li>
|
|
||||||
{%- endif -%}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-col footer-col-2">
|
|
||||||
{%- include social.html -%}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-col footer-col-3">
|
|
||||||
<p>{{- site.description | escape -}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</footer>
|
|
@ -1,15 +0,0 @@
|
|||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<!---->
|
|
||||||
{%- seo -%}
|
|
||||||
<!---->
|
|
||||||
<meta name="robots" content="{% if page.robots %}{{ page.robots }} {% else %}{{ site.robots }} {% endif %}">
|
|
||||||
<link rel="icon" href="https://github.com/ProgVal/Supybot-website/raw/master/static/logo.png">
|
|
||||||
<meta name="color-scheme" content="dark light">
|
|
||||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#ffb700">
|
|
||||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fdfdfd">
|
|
||||||
<link rel="stylesheet" href="{{ "/assets/main.css" | prepend: site.baseurl }}">
|
|
||||||
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}">
|
|
||||||
</head>
|
|
210
_sass/_base.scss
210
_sass/_base.scss
@ -1,210 +0,0 @@
|
|||||||
:root {
|
|
||||||
color-scheme: dark light;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset some basic elements
|
|
||||||
*/
|
|
||||||
body, h1, h2, h3, h4, h5, h6,
|
|
||||||
p, blockquote, pre, hr,
|
|
||||||
dl, dd, ol, ul, figure {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic styling
|
|
||||||
*/
|
|
||||||
body {
|
|
||||||
font-family: $base-font-family;
|
|
||||||
font-size: $base-font-size;
|
|
||||||
line-height: $base-line-height;
|
|
||||||
font-weight: 300;
|
|
||||||
color: $text-color;
|
|
||||||
background-color: $background-color;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set `margin-bottom` to maintain vertical rhythm
|
|
||||||
*/
|
|
||||||
h1, h2, h3, h4, h5, h6,
|
|
||||||
p, blockquote, pre,
|
|
||||||
ul, ol, dl, figure,
|
|
||||||
%vertical-rhythm {
|
|
||||||
margin-bottom: $spacing-unit / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Images
|
|
||||||
*/
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Figures
|
|
||||||
*/
|
|
||||||
figure > img {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
figcaption {
|
|
||||||
font-size: $small-font-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists
|
|
||||||
*/
|
|
||||||
ul, ol {
|
|
||||||
margin-left: $spacing-unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
> ul,
|
|
||||||
> ol {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Headings
|
|
||||||
*/
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Links
|
|
||||||
*/
|
|
||||||
a {
|
|
||||||
color: $brand-color;
|
|
||||||
//text-decoration: none;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&:visited {
|
|
||||||
//color: darken($brand-color, 15%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $text-color;
|
|
||||||
//text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blockquotes
|
|
||||||
*/
|
|
||||||
blockquote {
|
|
||||||
color: $grey-color;
|
|
||||||
border-left: 4px solid $grey-color-light;
|
|
||||||
padding-left: $spacing-unit / 2;
|
|
||||||
font-size: 18px;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
font-style: italic;
|
|
||||||
|
|
||||||
> :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Code formatting
|
|
||||||
*/
|
|
||||||
pre,
|
|
||||||
code {
|
|
||||||
font-family: $monospace-font-family;
|
|
||||||
font-size: 15px;
|
|
||||||
border: 1px solid; //$grey-color-light;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: revert; //#eef;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding: 1px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding: 8px 12px;
|
|
||||||
overflow-x: scroll;
|
|
||||||
|
|
||||||
> code {
|
|
||||||
border: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper
|
|
||||||
*/
|
|
||||||
.wrapper {
|
|
||||||
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
|
|
||||||
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
padding-right: $spacing-unit;
|
|
||||||
padding-left: $spacing-unit;
|
|
||||||
@extend %clearfix;
|
|
||||||
|
|
||||||
@include media-query($on-laptop) {
|
|
||||||
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
|
|
||||||
max-width: calc(#{$content-width} - (#{$spacing-unit}));
|
|
||||||
padding-right: $spacing-unit / 2;
|
|
||||||
padding-left: $spacing-unit / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clearfix
|
|
||||||
*/
|
|
||||||
%clearfix {
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icons
|
|
||||||
*/
|
|
||||||
.icon {
|
|
||||||
|
|
||||||
> svg {
|
|
||||||
display: inline-block;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
path {
|
|
||||||
fill: $grey-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,236 +0,0 @@
|
|||||||
/**
|
|
||||||
* Site header
|
|
||||||
*/
|
|
||||||
.site-header {
|
|
||||||
border-top: 5px solid $grey-color-dark;
|
|
||||||
border-bottom: 1px solid $grey-color-light;
|
|
||||||
min-height: 56px;
|
|
||||||
|
|
||||||
// Positioning context for the mobile navigation icon
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title {
|
|
||||||
font-size: 26px;
|
|
||||||
line-height: 56px;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
float: left;
|
|
||||||
|
|
||||||
&,
|
|
||||||
&:visited {
|
|
||||||
color: $grey-color-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-nav {
|
|
||||||
float: right;
|
|
||||||
line-height: 56px;
|
|
||||||
|
|
||||||
.menu-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-link {
|
|
||||||
color: $text-color;
|
|
||||||
line-height: $base-line-height;
|
|
||||||
|
|
||||||
// Gaps between nav items, but not on the first one
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-query($on-palm) {
|
|
||||||
position: absolute;
|
|
||||||
top: 9px;
|
|
||||||
right: 30px;
|
|
||||||
background-color: $background-color;
|
|
||||||
border: 1px solid $grey-color-light;
|
|
||||||
border-radius: 5px;
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
.menu-icon {
|
|
||||||
display: block;
|
|
||||||
float: right;
|
|
||||||
width: 36px;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 0;
|
|
||||||
padding-top: 10px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> svg {
|
|
||||||
width: 18px;
|
|
||||||
height: 15px;
|
|
||||||
|
|
||||||
path {
|
|
||||||
fill: $grey-color-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger {
|
|
||||||
clear: both;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .trigger {
|
|
||||||
display: block;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-link {
|
|
||||||
display: block;
|
|
||||||
padding: 5px 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Site footer
|
|
||||||
*/
|
|
||||||
.site-footer {
|
|
||||||
border-top: 1px solid $grey-color-light;
|
|
||||||
padding: $spacing-unit 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-heading {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: $spacing-unit / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-list,
|
|
||||||
.social-media-list {
|
|
||||||
list-style: none;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col-wrapper {
|
|
||||||
font-size: 15px;
|
|
||||||
color: $grey-color;
|
|
||||||
margin-left: -$spacing-unit / 2;
|
|
||||||
@extend %clearfix;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col {
|
|
||||||
float: left;
|
|
||||||
margin-bottom: $spacing-unit / 2;
|
|
||||||
padding-left: $spacing-unit / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col-1 {
|
|
||||||
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
|
|
||||||
width: calc(35% - (#{$spacing-unit} / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col-2 {
|
|
||||||
width: -webkit-calc(20% - (#{$spacing-unit} / 2));
|
|
||||||
width: calc(20% - (#{$spacing-unit} / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col-3 {
|
|
||||||
width: -webkit-calc(45% - (#{$spacing-unit} / 2));
|
|
||||||
width: calc(45% - (#{$spacing-unit} / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-query($on-laptop) {
|
|
||||||
.footer-col-1,
|
|
||||||
.footer-col-2 {
|
|
||||||
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
|
|
||||||
width: calc(50% - (#{$spacing-unit} / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-col-3 {
|
|
||||||
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
|
|
||||||
width: calc(100% - (#{$spacing-unit} / 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-query($on-palm) {
|
|
||||||
.footer-col {
|
|
||||||
float: none;
|
|
||||||
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
|
|
||||||
width: calc(100% - (#{$spacing-unit} / 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page content
|
|
||||||
*/
|
|
||||||
.page-content {
|
|
||||||
padding: $spacing-unit 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-heading {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-list {
|
|
||||||
margin-left: 0;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
> li {
|
|
||||||
margin-bottom: $spacing-unit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-meta {
|
|
||||||
font-size: $small-font-size;
|
|
||||||
color: $grey-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-link {
|
|
||||||
display: block;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Posts
|
|
||||||
*/
|
|
||||||
.post-header {
|
|
||||||
margin-bottom: $spacing-unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-title {
|
|
||||||
font-size: 42px;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
@include media-query($on-laptop) {
|
|
||||||
font-size: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-content {
|
|
||||||
margin-bottom: $spacing-unit;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 32px;
|
|
||||||
|
|
||||||
@include media-query($on-laptop) {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 26px;
|
|
||||||
|
|
||||||
@include media-query($on-laptop) {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
@include media-query($on-laptop) {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* Syntax highlighting styles
|
|
||||||
*/
|
|
||||||
.highlight {
|
|
||||||
background: #fff;
|
|
||||||
@extend %vertical-rhythm;
|
|
||||||
|
|
||||||
.c { color: #998; font-style: italic } // Comment
|
|
||||||
.err { color: #a61717; background-color: #e3d2d2 } // Error
|
|
||||||
.k { font-weight: bold } // Keyword
|
|
||||||
.o { font-weight: bold } // Operator
|
|
||||||
.cm { color: #998; font-style: italic } // Comment.Multiline
|
|
||||||
.cp { color: #999; font-weight: bold } // Comment.Preproc
|
|
||||||
.c1 { color: #998; font-style: italic } // Comment.Single
|
|
||||||
.cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
|
|
||||||
.gd { color: #000; background-color: #fdd } // Generic.Deleted
|
|
||||||
.gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
|
|
||||||
.ge { font-style: italic } // Generic.Emph
|
|
||||||
.gr { color: #a00 } // Generic.Error
|
|
||||||
.gh { color: #999 } // Generic.Heading
|
|
||||||
.gi { color: #000; background-color: #dfd } // Generic.Inserted
|
|
||||||
.gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
|
|
||||||
.go { color: #888 } // Generic.Output
|
|
||||||
.gp { color: #555 } // Generic.Prompt
|
|
||||||
.gs { font-weight: bold } // Generic.Strong
|
|
||||||
.gu { color: #aaa } // Generic.Subheading
|
|
||||||
.gt { color: #a00 } // Generic.Traceback
|
|
||||||
.kc { font-weight: bold } // Keyword.Constant
|
|
||||||
.kd { font-weight: bold } // Keyword.Declaration
|
|
||||||
.kp { font-weight: bold } // Keyword.Pseudo
|
|
||||||
.kr { font-weight: bold } // Keyword.Reserved
|
|
||||||
.kt { color: #458; font-weight: bold } // Keyword.Type
|
|
||||||
.m { color: #099 } // Literal.Number
|
|
||||||
.s { color: #d14 } // Literal.String
|
|
||||||
.na { color: #008080 } // Name.Attribute
|
|
||||||
.nb { color: #0086B3 } // Name.Builtin
|
|
||||||
.nc { color: #458; font-weight: bold } // Name.Class
|
|
||||||
.no { color: #008080 } // Name.Constant
|
|
||||||
.ni { color: #800080 } // Name.Entity
|
|
||||||
.ne { color: #900; font-weight: bold } // Name.Exception
|
|
||||||
.nf { color: #900; font-weight: bold } // Name.Function
|
|
||||||
.nn { color: #555 } // Name.Namespace
|
|
||||||
.nt { color: #000080 } // Name.Tag
|
|
||||||
.nv { color: #008080 } // Name.Variable
|
|
||||||
.ow { font-weight: bold } // Operator.Word
|
|
||||||
.w { color: #bbb } // Text.Whitespace
|
|
||||||
.mf { color: #099 } // Literal.Number.Float
|
|
||||||
.mh { color: #099 } // Literal.Number.Hex
|
|
||||||
.mi { color: #099 } // Literal.Number.Integer
|
|
||||||
.mo { color: #099 } // Literal.Number.Oct
|
|
||||||
.sb { color: #d14 } // Literal.String.Backtick
|
|
||||||
.sc { color: #d14 } // Literal.String.Char
|
|
||||||
.sd { color: #d14 } // Literal.String.Doc
|
|
||||||
.s2 { color: #d14 } // Literal.String.Double
|
|
||||||
.se { color: #d14 } // Literal.String.Escape
|
|
||||||
.sh { color: #d14 } // Literal.String.Heredoc
|
|
||||||
.si { color: #d14 } // Literal.String.Interpol
|
|
||||||
.sx { color: #d14 } // Literal.String.Other
|
|
||||||
.sr { color: #009926 } // Literal.String.Regex
|
|
||||||
.s1 { color: #d14 } // Literal.String.Single
|
|
||||||
.ss { color: #990073 } // Literal.String.Symbol
|
|
||||||
.bp { color: #999 } // Name.Builtin.Pseudo
|
|
||||||
.vc { color: #008080 } // Name.Variable.Class
|
|
||||||
.vg { color: #008080 } // Name.Variable.Global
|
|
||||||
.vi { color: #008080 } // Name.Variable.Instance
|
|
||||||
.il { color: #099 } // Literal.Number.Integer.Long
|
|
||||||
}
|
|
145
assets/main.scss
145
assets/main.scss
@ -1,145 +0,0 @@
|
|||||||
---
|
|
||||||
# front-matter
|
|
||||||
---
|
|
||||||
|
|
||||||
@charset "utf-8";
|
|
||||||
|
|
||||||
// Font specifications. I keep changing my mind on what are the most pleasant
|
|
||||||
// fonts to my eyes, so I won't bother commenting them here.'
|
|
||||||
$serif-font-family:
|
|
||||||
ui-serif, "Roboto Serif", "Noto Serif", Tinos, serif, "Noto Emoji",
|
|
||||||
"Noto Color Emoji", "Segoe UI Emoji", emoji;
|
|
||||||
$sans-serif-font-family:
|
|
||||||
"Inclusive Sans", ui-sans-serif, "Roboto Flex", "Segoe UI Variable", Roboto,
|
|
||||||
"Noto Sans", Arimo, sans-serif, "Noto Emoji", "Noto Color Emoji",
|
|
||||||
"Segoe UI Emoji", emoji;
|
|
||||||
$monospace-font-family:
|
|
||||||
"Comic Shanns Mono", ui-monospace, "Roboto Mono", "Segoe UI Mono",
|
|
||||||
"Noto Mono", Cousine, monospace, "Noto Emoji", "Noto Color Emoji",
|
|
||||||
"Segoe UI Emoji", emoji;
|
|
||||||
// Must be in the end under threat of undefined variable error.
|
|
||||||
$base-font-family: $sans-serif-font-family;
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Inclusive Sans";
|
|
||||||
src: url("https://raw.githubusercontent.com/LivKing/Inclusive-Sans/refs/heads/main/fonts/webfonts/InclusiveSans[wght].woff2")
|
|
||||||
format("woff2");
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Inclusive Sans Italic";
|
|
||||||
src: url("https://raw.githubusercontent.com/LivKing/Inclusive-Sans/refs/heads/main/fonts/webfonts/InclusiveSans-Italic[wght].woff2")
|
|
||||||
format("woff2");
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Comic Shanns Mono";
|
|
||||||
src: url("https://raw.githubusercontent.com/jesusmgg/comic-shanns-mono/refs/heads/master/fonts/ComicShannsMono-Regular.otf")
|
|
||||||
format("opentype");
|
|
||||||
}
|
|
||||||
|
|
||||||
@import "{{ site.theme }}";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
color-scheme: dark light !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
// box-sizing: border-box !important;
|
|
||||||
color: revert !important;
|
|
||||||
background-color: revert !important;
|
|
||||||
//margin: auto !important;
|
|
||||||
// line-height: 1.2 !important;
|
|
||||||
// A4 paper
|
|
||||||
//max-width: 210mm !important;
|
|
||||||
font-size: revert;
|
|
||||||
//padding: auto !important;
|
|
||||||
overflow-wrap: break-word !important;
|
|
||||||
hyphens: auto !important;
|
|
||||||
// Experimental trick to make all emojis text if supported.
|
|
||||||
//font-variant-emoji: text;
|
|
||||||
/* WCAG minimum suggestions */
|
|
||||||
margin-bottom: 2 !important;
|
|
||||||
line-height: 1.5 !important;
|
|
||||||
letter-spacing: 0.12 !important;
|
|
||||||
word-spacing: 0.16 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.site-title {
|
|
||||||
font-family: $serif-font-family;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monospace preferred for code
|
|
||||||
code,
|
|
||||||
pre {
|
|
||||||
font-family: $monospace-font-family !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// So it will not look bigger than normal text
|
|
||||||
code {
|
|
||||||
font-size: 0.8em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sans-Serif for headings to constrast with aminda.eu
|
|
||||||
// h2,
|
|
||||||
// h3,
|
|
||||||
// h4,
|
|
||||||
// h5,
|
|
||||||
// h6 {
|
|
||||||
// font-family: $sans-serif-font-family !important;
|
|
||||||
// }
|
|
||||||
|
|
||||||
h2.footer-heading {
|
|
||||||
font-family: $sans-serif-font-family !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The introduction on top
|
|
||||||
#bio {
|
|
||||||
text-align: center;
|
|
||||||
font-style: italic;
|
|
||||||
font-family: ui-cursive, $sans-serif-font-family;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border-radius: 50% !important;
|
|
||||||
display: block;
|
|
||||||
margin-left: auto !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
@media (min-width: 395px) {
|
|
||||||
display: float !important;
|
|
||||||
float: right !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.linklist {
|
|
||||||
list-style: none inside;
|
|
||||||
font-family: $monospace-font-family;
|
|
||||||
//font-variant: small-caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Customize the dark theme to be more me
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
* {
|
|
||||||
color: #ffb700 !important;
|
|
||||||
border-color: #ffb700 !important;
|
|
||||||
background-color: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-nav {
|
|
||||||
color-scheme: only dark !important;
|
|
||||||
color: #ffb700 !important;
|
|
||||||
background-color: #000000 !important;
|
|
||||||
color: #ffb700 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// I don't want links to be restored to amber'
|
|
||||||
a {
|
|
||||||
color: revert !important;
|
|
||||||
}
|
|
||||||
}
|
|
63
debian/changelog
vendored
Normal file
63
debian/changelog
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
supybot (0.79.9999+0.80.0pre2-1) unstable; urgency=high
|
||||||
|
|
||||||
|
* New upstream
|
||||||
|
* Urgency high because it still fixes previous bugs.
|
||||||
|
|
||||||
|
-- James Vega <vega.james@gmail.com> Fri, 17 Sep 2004 11:18:43 -0400
|
||||||
|
|
||||||
|
supybot (0.79.9999-1) unstable; urgency=high
|
||||||
|
|
||||||
|
* New upstream
|
||||||
|
* Urgency high because it still fixes previous bugs.
|
||||||
|
|
||||||
|
-- James Vega <vega.james@gmail.com> Mon, 6 Sep 2004 15:40:13 -0400
|
||||||
|
|
||||||
|
supybot (0.79.999-1) unstable; urgency=high
|
||||||
|
|
||||||
|
* New upstream
|
||||||
|
* Urgency high because of a bug in the Ebay plugin that would hang
|
||||||
|
the bot.
|
||||||
|
|
||||||
|
-- James Vega <vega.james@gmail.com> Tue, 31 Aug 2004 21:58:59 -0400
|
||||||
|
|
||||||
|
supybot (0.79.99-1) unstable; urgency=high
|
||||||
|
|
||||||
|
* New upstream
|
||||||
|
* Urgency high as 0.79.9 had a serious bug in src/callbacks.py
|
||||||
|
* Added a watch file
|
||||||
|
|
||||||
|
-- James Vega <vega.james@gmail.com> Mon, 30 Aug 2004 09:20:48 -0400
|
||||||
|
|
||||||
|
supybot (0.79.9-1) unstable; urgency=high
|
||||||
|
|
||||||
|
* New upstream (Closes: #268311)
|
||||||
|
* New maintainer: James Vega <vega.james@gmail.com>
|
||||||
|
* Urgency high to get the RC fix into Sarge (hopefully)
|
||||||
|
* debian/control:
|
||||||
|
+ Downgrade Recommends: python-sqlite to Suggests: python-sqlite
|
||||||
|
+ Update Build-Depends: cdbs, debhelper (>= 4.1.67)
|
||||||
|
+ Bump Standard-Version to 3.6.1.0, no changes necessary
|
||||||
|
* Convert to cdbs
|
||||||
|
|
||||||
|
-- James Vega <vega.james@gmail.com> Thu, 26 Aug 2004 17:28:03 -0400
|
||||||
|
|
||||||
|
supybot (0.77.2-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* New upstream
|
||||||
|
|
||||||
|
-- Jonathan Hseu <vomjom@debian.org> Sun, 18 Apr 2004 02:58:25 -0500
|
||||||
|
|
||||||
|
supybot (0.77.1-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* New upstream
|
||||||
|
* Add python-dev as a build-dep (closes: Bug#242953)
|
||||||
|
|
||||||
|
-- Jonathan Hseu <vomjom@debian.org> Sat, 10 Apr 2004 13:06:38 -0500
|
||||||
|
|
||||||
|
supybot (0.77.0-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial Release.
|
||||||
|
* Paste LICENSE file into copyright
|
||||||
|
|
||||||
|
-- Jonathan Hseu <vomjom@debian.org> Tue, 10 Mar 2004 00:59:46 -0600
|
||||||
|
|
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
4
|
21
debian/control
vendored
Normal file
21
debian/control
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
Source: supybot
|
||||||
|
Section: net
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: James Vega <vega.james@gmail.com>
|
||||||
|
Build-Depends: debhelper (>= 4.1.67), cdbs, python (>= 2.3), python-dev (>= 2.3)
|
||||||
|
Standards-Version: 3.6.1.0
|
||||||
|
|
||||||
|
Package: supybot
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${python:Depends}
|
||||||
|
Suggests: python-twisted, python-sqlite
|
||||||
|
Description: robust and user friendly Python IRC bot
|
||||||
|
Supybot is a robust (it doesn't crash), user friendly (it's easy
|
||||||
|
to configure) and programmer friendly (plugins are *extremely*
|
||||||
|
easy to write) Python IRC bot. It aims to be an adequate
|
||||||
|
replacement for most existing IRC bots. It includes a very
|
||||||
|
flexible and powerful ACL system for controlling access to
|
||||||
|
commands, as well as more than 50 builtin plugins providing
|
||||||
|
around 400 actual commands.
|
||||||
|
.
|
||||||
|
Homepage: http://supybot.sourceforge.net/
|
53
debian/copyright
vendored
Normal file
53
debian/copyright
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
This package was debianized by Jonathan Hseu <vomjom@debian.org> on
|
||||||
|
Tue, 3 Feb 2004 21:45:46 -0600.
|
||||||
|
|
||||||
|
It was downloaded from http://supybot.sourceforge.net/
|
||||||
|
|
||||||
|
Upstream Authors:
|
||||||
|
|
||||||
|
/usr/share/doc/supybot/DEVS includes the names and descriptions of the
|
||||||
|
developers.
|
||||||
|
|
||||||
|
Copyright:
|
||||||
|
|
||||||
|
Files located in /usr/lib/python2.3/site-packages/supybot/others/ have their
|
||||||
|
own respective licenses, which are either commented at the top of the file, or
|
||||||
|
are held in the __license__ variable.
|
||||||
|
|
||||||
|
The licenses in that directory are either:
|
||||||
|
BSD-style, GPL, or the Python License
|
||||||
|
The first two can be found in /usr/share/common-licenses/
|
||||||
|
|
||||||
|
The Python License can be found in:
|
||||||
|
/usr/share/doc/python/copyright
|
||||||
|
|
||||||
|
The copyright for all other files are as follows (BSD-style):
|
||||||
|
|
||||||
|
Copyright (c) 2002, 2003, 2004 Jeremiah Fincher
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions, and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions, and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the author of this software nor the name of
|
||||||
|
contributors to this software may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Portions of the included source code are copyright by its original author(s)
|
||||||
|
and remains subject to its associated license.
|
15
debian/docs
vendored
Normal file
15
debian/docs
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
ACKS
|
||||||
|
BUGS
|
||||||
|
DEVS
|
||||||
|
README
|
||||||
|
RELNOTES
|
||||||
|
TODO
|
||||||
|
docs/CAPABILITIES
|
||||||
|
docs/CONFIGURATION
|
||||||
|
docs/FAQ
|
||||||
|
docs/GETTING_STARTED
|
||||||
|
docs/HACKING
|
||||||
|
docs/INTERFACES
|
||||||
|
docs/OVERVIEW
|
||||||
|
docs/PLUGIN-EXAMPLE
|
||||||
|
docs/STYLE
|
6
debian/rules
vendored
Executable file
6
debian/rules
vendored
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
include /usr/share/cdbs/1/rules/debhelper.mk
|
||||||
|
include /usr/share/cdbs/1/class/python-distutils.mk
|
||||||
|
|
||||||
|
DEB_INSTALL_MANPAGES_supybot := docs/man/supybot-adduser.1 docs/man/supybot-newplugin.1\
|
||||||
|
docs/man/supybot.1 docs/man/supybot-wizard.1
|
6
debian/watch
vendored
Normal file
6
debian/watch
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Example watch control file for uscan
|
||||||
|
# Rename this file to "watch" and then you can run the "uscan" command
|
||||||
|
# to check for upstream updates and more.
|
||||||
|
# Site Directory Pattern Version Script
|
||||||
|
version=2
|
||||||
|
http://osdn.dl.sourceforge.net/supybot/Supybot-(\d.\d\d.\d+(.\d+)?)\.tar\.gz
|
118
docs/CAPABILITIES
Normal file
118
docs/CAPABILITIES
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
Ok, some explanation of the capabilities system is probably in
|
||||||
|
order. With most IRC bots (including the ones I've written myself
|
||||||
|
prior to this one) "what a user can do" is set in one of two ways. On
|
||||||
|
the *really* simple bots, each user has a numeric "level" and commands
|
||||||
|
check to see if a user has a "high enough level" to perform some
|
||||||
|
operation. On bots that are slightly more complicated, users have a
|
||||||
|
list of "flags" whose meanings are hardcoded, and the bot checks to
|
||||||
|
see if a user possesses the necessary flag before performing some
|
||||||
|
operation. Both methods, IMO, are rather arbitrary, and force the
|
||||||
|
user and the programmer to be unduly confined to less expressive
|
||||||
|
constructs.
|
||||||
|
|
||||||
|
This bot is different. Every user has a set of "capabilities" that is
|
||||||
|
consulted every time they give the bot a command. Commands, rather
|
||||||
|
than checking for a user level of 100, or checking if the user has an
|
||||||
|
"o" flag, are instead able to check if a user has the "owner"
|
||||||
|
capability. At this point such a difference might not seem
|
||||||
|
revolutionary, but at least we can already tell that this method is
|
||||||
|
self-documenting, and easier for users and developers to understand
|
||||||
|
what's truly going on.
|
||||||
|
|
||||||
|
If that was all, well, the capability system would be "cool", but not
|
||||||
|
many people would say it was "awesome". But it *is* awesome! Several
|
||||||
|
things are happening behind the scene that make it awesome, and these
|
||||||
|
are things that couldn't happen if the bot was using numeric
|
||||||
|
userlevels or single-character flags. First, whenever a user issues
|
||||||
|
the bot a command, the command dispatcher checks to make sure the user
|
||||||
|
doesn't have the "anticapability" for that command. An anticapability
|
||||||
|
is a capability that, instead of saying "what a user can do", says
|
||||||
|
what a user *cannot* do. It's formed rather simply by adding a dash
|
||||||
|
("-") to the beginning of a capability; "rot13" is a capability, and
|
||||||
|
"-rot13" is an anticapability. Anyway, when a user issues the bot a
|
||||||
|
command, perhaps "calc" or "help", the bot first checks to make sure
|
||||||
|
the user doesn't have the "-calc" or the "-help" capabilities before
|
||||||
|
even considering responding to the user. So commands can be turned on
|
||||||
|
or off on a *per user* basis, offering fine-grained control not often
|
||||||
|
(if at all!) seen in other bots.
|
||||||
|
|
||||||
|
But that's not all! The capabilities system also supports *Channel*
|
||||||
|
capabilities, which are capabilities that only apply to a specific
|
||||||
|
channel; they're of the form "#channel,capability". Whenever a user
|
||||||
|
issues a command to the bot in a channel, the command dispatcher also
|
||||||
|
checks to make sure the user doesn't have the anticapability for that
|
||||||
|
command *in that channel*, and if the user does, the bot won't respond
|
||||||
|
to the user in the channel. Thus now, in addition to having the
|
||||||
|
ability to turn individual commands on or off for an individual user,
|
||||||
|
we can now turn commands on or off for an individual user on an
|
||||||
|
individual channel!
|
||||||
|
|
||||||
|
So when a user "foo" sends a command "bar" to the bot on channel
|
||||||
|
"#baz", first the bot checks to see if the user has the anticapability
|
||||||
|
for the command by itself, "-bar". If so, it returns right then and
|
||||||
|
there, completely ignoring the fact that the user issued that command
|
||||||
|
to it. If the user doesn't have that anticapability, then the bot
|
||||||
|
checks to see if the user issued the command over a channel, and if
|
||||||
|
so, checks to see if the user has the antichannelcapability for that
|
||||||
|
command, "#baz,-bar". If so, again, he returns right then and there
|
||||||
|
and doesn't even think about responding to the bot. If neither of
|
||||||
|
these anticapabilities are present, then the bot just responds to the
|
||||||
|
user like normal.
|
||||||
|
|
||||||
|
From a programming perspective, capabilties are easy to use and
|
||||||
|
flexible. Any command can check if a user has any capability, even
|
||||||
|
ones not thought of when the bot was originally written.
|
||||||
|
Commands/Callbacks can add their own capabilities -- it's as easy as
|
||||||
|
just checking for a capability and documenting somewhere that a user
|
||||||
|
needs that capability to do something.
|
||||||
|
|
||||||
|
From an end-user perspective, capabilities remove a lot of the mystery
|
||||||
|
and esotery of bot control, in addition to giving the user absolutely
|
||||||
|
finegrained control over what users are allowed to do with the bot.
|
||||||
|
Additionally, defaults can be set by the end-user for both individual
|
||||||
|
channels and for the bot as a whole, letting an end-user set the
|
||||||
|
policy he wants the bot to follow for users that haven't yet
|
||||||
|
registered in his user database. It's really a revolution!
|
||||||
|
|
||||||
|
There are several default capabilities the bot uses. The most
|
||||||
|
important of these is the "owner" capability. This capability allows
|
||||||
|
the person having it to use *any* command. It's best to keep this
|
||||||
|
capability reserved to people who actually have access to the shell
|
||||||
|
the bot is running on.
|
||||||
|
|
||||||
|
There is also the "admin" capability for non-owners that are highly
|
||||||
|
trusted to administer the bot appropriately. They can do things such
|
||||||
|
as change the bot's nick, globally enable/disable commands, cause the
|
||||||
|
bot to ignore a given user, set the prefixchar, report bugs, etc.
|
||||||
|
They generally cannot do administration related to channels, which is
|
||||||
|
reserved for people with the next capability.
|
||||||
|
|
||||||
|
People who are to administer channels with the bot should have the
|
||||||
|
#channel,op capability -- whatever channel they are to administrate,
|
||||||
|
they should have that channel capability for "op". For example, since
|
||||||
|
I want inkedmn to be an administrator in #supybot, I'll give him the
|
||||||
|
#supybot,op capability. This is in addition to his admin capability,
|
||||||
|
since the admin capability doesn't give the person having it control
|
||||||
|
over channels. #channel,op is used for such things as
|
||||||
|
giving/receiving ops, kickbanning people, lobotomizing the bot,
|
||||||
|
ignoring users in the channel, and managing the channel capabilities.
|
||||||
|
The #channel,op capability is also basically the equivalent of the
|
||||||
|
owner capability for capabilities involving #channel -- basically
|
||||||
|
anyone with the #channel,op capability is considered to have all
|
||||||
|
positive capabilities and no negative capabilities for #channel.
|
||||||
|
|
||||||
|
One other globally important capability exists: "trusted". This is a
|
||||||
|
command that basically says "This user can be trusted not to try and
|
||||||
|
crash the bot." It allows users to call commands like Math.icalc,
|
||||||
|
which potentially could cause the bot to begin a calculation that
|
||||||
|
could potentially never return (a calculation like 10**10**10**10).
|
||||||
|
Another command that requires the trusted capability is Utilties.re,
|
||||||
|
which (due to the regular expression implementation in Python (and any
|
||||||
|
other language that uses NFA regular expressions, like Perl or Ruby or
|
||||||
|
Lua or ...) which can allow a regular expression to take exponential
|
||||||
|
time to process). Consider what would happen if someone gave the
|
||||||
|
bot the command 're [format join "" s/./ [dict go] /] [dict go]'
|
||||||
|
|
||||||
|
Other plugins may require different capabilities; the Factoids plugin
|
||||||
|
requires #channel,factoids, the Topic plugin requires #channel,topic,
|
||||||
|
etc.
|
174
docs/CONFIGURATION
Normal file
174
docs/CONFIGURATION
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
So you've got your Supybot up and running and there are some things
|
||||||
|
you don't like about it. Fortunately for you, chances are that these
|
||||||
|
things are configurable, and this document is here to tell you how to
|
||||||
|
configure them.
|
||||||
|
|
||||||
|
Configuration of Supybot is handled via the Config plugin, which
|
||||||
|
controls runtime access to Supybot's registry (the configuration file
|
||||||
|
generated by the supybot-wizard program you ran). The Config plugin
|
||||||
|
provides a way to get or set variables, to list the available
|
||||||
|
variables, and even to get help for certain variables. Take a moment
|
||||||
|
now to read the help for each of those commands: config, list, and
|
||||||
|
help. If you don't know how to get help on those commands, go ahead
|
||||||
|
and read our GETTING_STARTED document before this one.
|
||||||
|
|
||||||
|
Now, if you're used to the Windows registry, don't worry, Supybot's
|
||||||
|
registry is completely different. For one, it's completely plain
|
||||||
|
text. There's no binary database sensitive to corruption, it's not
|
||||||
|
necessary to use another program to edit it -- all you need is a
|
||||||
|
simple text editor. But there is at least one good idea in Windows'
|
||||||
|
registry: hierarchical configuration. Supybot's configuration
|
||||||
|
variables are organized in a hierarchy: variables having to do with
|
||||||
|
the way Supybot makes replies all start with supybot.reply; variables
|
||||||
|
having to do with the way a plugin works all start with
|
||||||
|
supybot.plugins.Plugin (where Plugin is the name of the plugin in
|
||||||
|
question). This hierarchy is nice because it means the user isn't
|
||||||
|
inundated with hundreds of unrelated and unsorted configuration
|
||||||
|
variables.
|
||||||
|
|
||||||
|
Some of the more important configuration values are located directly
|
||||||
|
under the base group, supybot. Things like the bot's nick, its ident,
|
||||||
|
etc. Along with these config values are a few subgroups that contain
|
||||||
|
other values. Some of the more prominent subgroups are: plugins
|
||||||
|
(where all the plugin-specific configuration is held), reply (where
|
||||||
|
variables affecting the way a Supybot makes its replies resides),
|
||||||
|
replies (where all the specific standard replies are kept), and
|
||||||
|
directories (where all the directories a Supybot uses are defined).
|
||||||
|
There are other subgroups as well, but these are the ones we'll use in
|
||||||
|
our example.
|
||||||
|
|
||||||
|
Using the Config plugin, you can list the values in a subgroup and get
|
||||||
|
or set any of the values anywhere in the configuration hierarchy. For
|
||||||
|
example, let's say you wanted to see what configuration values were
|
||||||
|
under the "supybot" (the base group) hierarchy. You would simply
|
||||||
|
issue this command:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config list supybot
|
||||||
|
<supybot> @capabilities, @commands, @databases, @debug, @directories, @drivers,
|
||||||
|
@log, @networks, @nick, @plugins, @protocols, @replies, @reply,
|
||||||
|
alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout,
|
||||||
|
externalIP, flush, followIdentificationThroughNickChanges,
|
||||||
|
humanTimestampFormat, ident, pidFile, snarfThrottle, upkeepInterval,
|
||||||
|
and user
|
||||||
|
|
||||||
|
These are all the configuration groups and values which are under the
|
||||||
|
base "supybot" group. Actually, their full names would each have a
|
||||||
|
"supybot." appended on to the front of them, but it is omitted in the
|
||||||
|
listing in order to shorten the output. The first entries in the output are
|
||||||
|
the groups (distinguished by the @ symbol in front of them), and the rest are
|
||||||
|
the configuration values.
|
||||||
|
|
||||||
|
Okay, now that you've used the Config plugin to list configuration
|
||||||
|
variables, it's time that we start looking at individual variables and
|
||||||
|
their values.
|
||||||
|
|
||||||
|
The first (and perhaps most important) thing you should know about
|
||||||
|
each configuration variable is that they all have an associated help
|
||||||
|
string to tell you what they represent. So the first command we'll
|
||||||
|
cover is "config help". To see the help string for any value or
|
||||||
|
group, simply use the "config help" command. For example, to see what
|
||||||
|
this "supybot.snarfThrottle" configuration variable is all about, we'd
|
||||||
|
do this:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config help supybot.snarfThrottle
|
||||||
|
<supybot> jemfinch|lambda: A floating point number of seconds to throttle snarfed
|
||||||
|
URLs, in order to prevent loops between two bots snarfing the same URLs and
|
||||||
|
having the snarfed URL in the output of the snarf message. (Current value:
|
||||||
|
10.0)
|
||||||
|
|
||||||
|
Pretty simple, eh?
|
||||||
|
|
||||||
|
Now, if you're curious what the current value of a configuration
|
||||||
|
variable is, you'll use the "config" command with one argument, the
|
||||||
|
name of the variable you want to see the value of:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars
|
||||||
|
<supybot> jemfinch|lambda: '@'
|
||||||
|
|
||||||
|
To set this value, just stick an extra argument after the name:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars @$
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
|
||||||
|
Now, check this out:
|
||||||
|
|
||||||
|
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars
|
||||||
|
<supybot> jemfinch|lambda: '@$'
|
||||||
|
|
||||||
|
Note that we used $ as our prefix character, and that the value of the
|
||||||
|
configuration variable changed. If I were to use the "flush" command
|
||||||
|
now, this change would be flushed to the registry file on disk (this
|
||||||
|
would also happen if I made the bot quit, or pressed Ctrl-C in the
|
||||||
|
terminal the bot was running in). Instead, I'll revert the change:
|
||||||
|
|
||||||
|
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars @
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> $note that this makes no response.
|
||||||
|
|
||||||
|
If you're ever curious what the default for a given configuration
|
||||||
|
variable is, use the "config default" command:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config default supybot.reply.whenAddressedBy.chars
|
||||||
|
<supybot> jemfinch|lambda: ''
|
||||||
|
|
||||||
|
Thus, to reset a configuration variable to its default value, you can
|
||||||
|
simply say:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars [config
|
||||||
|
default supybot.reply.whenAddressedBy.chars]
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> @note that this does nothing
|
||||||
|
|
||||||
|
Simple, eh?
|
||||||
|
|
||||||
|
Now, let's say you want to find all configuration variables that might
|
||||||
|
be even remotely related to opping. For that, you'll want the "config
|
||||||
|
search" command. Check this out:
|
||||||
|
|
||||||
|
<jemfinch|lambda> @config search op
|
||||||
|
<supybot> jemfinch|lambda:
|
||||||
|
supybot.plugins.Enforcer.autoOp,
|
||||||
|
supybot.plugins.Enforcer.autoHalfop,
|
||||||
|
supybot.plugins.Enforcer.takeRevenge.onOps,
|
||||||
|
supybot.plugins.Enforcer.cycleToGetOps,
|
||||||
|
supybot.plugins.Topic, supybot.plugins.Topic.public,
|
||||||
|
supybot.plugins.Topic.separator,
|
||||||
|
supybot.plugins.Topic.format,
|
||||||
|
supybot.plugins.Topic.recognizeTopiclen,
|
||||||
|
supybot.plugins.Topic.default,
|
||||||
|
supybot.plugins.Topic.undo.maz, and
|
||||||
|
supybot.plugins.Relay.topicSync
|
||||||
|
|
||||||
|
Sure, it showed up all the topic-related stuff in there, but it also
|
||||||
|
showed you all the op-related stuff, too. Do note, however, that you
|
||||||
|
can only see configuration variables for plugins that you have loaded
|
||||||
|
or that you loaded in the past; if you've never loaded a plugin,
|
||||||
|
there's no way for the bot to know what configuration variables it
|
||||||
|
registers.
|
||||||
|
|
||||||
|
Some people might like editing their registry file directly rather
|
||||||
|
than manipulating all these things through the bot. For those people,
|
||||||
|
we offer the "config reload" command, which reloads both registry
|
||||||
|
configuration and user/channel/ignore database configuration. Just
|
||||||
|
edit the interesting files and then give the bot the "config reload"
|
||||||
|
command and it'll work as expected. Do note, however, that Supybot
|
||||||
|
flushes his configuration files and databases to disk every hour or
|
||||||
|
so, and if this happens after you've edited your configuration files
|
||||||
|
but before you reload your changes, you could lose the changes you
|
||||||
|
made. To prevent this, set the supybot.flush value to Off, and no
|
||||||
|
automatic flushing will occur.
|
||||||
|
|
||||||
|
Many configuration variables can be specific to individual channels.
|
||||||
|
The Config plugin provides an easy way to configure something for a
|
||||||
|
specific channel; for instance, in order to set the prefix chars for a
|
||||||
|
specific channel, do this in that channel:
|
||||||
|
|
||||||
|
config channel supybot.reply.whenAddressedBy.chars !
|
||||||
|
|
||||||
|
That'll set the prefix chars in the channel that message is sent in to
|
||||||
|
!. Voila, channel-specific values! Also, note that when using the
|
||||||
|
Config plugin's list command, channel-specific values are preceded by
|
||||||
|
a '#' character to indicate such.
|
||||||
|
|
||||||
|
Anyway, that's about it for configuration. Have fun, and enjoy your
|
||||||
|
configurable bot!
|
25
docs/DocBook/Makefile
Normal file
25
docs/DocBook/Makefile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
JADE=/usr/bin/jade
|
||||||
|
JADETEX=/usr/bin/jadetex
|
||||||
|
DVIPDF=/usr/bin/dvipdfm
|
||||||
|
HTMLSTYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/html/docbook.dsl
|
||||||
|
PRINTSTYLESHEET=/usr/share/sgml/docbook/stylesheet/dsssl/modular/print/docbook.dsl
|
||||||
|
|
||||||
|
html: example.sgml capabilities.sgml
|
||||||
|
$(JADE) -t xml -d $(HTMLSTYLESHEET) $<
|
||||||
|
|
||||||
|
example.dvi: example.sgml
|
||||||
|
$(JADE) -t tex -d $(PRINTSTYLESHEET) $<
|
||||||
|
$(JADETEX) $(addsuffix .tex, $(basename $<))
|
||||||
|
|
||||||
|
example.pdf: example.dvi
|
||||||
|
$(DVIPDF) -o $(addsuffix .pdf, $(basename $<)) $<
|
||||||
|
|
||||||
|
capabilities.dvi: capabilities.sgml
|
||||||
|
$(JADE) -t tex -d $(PRINTSTYLESHEET) $<
|
||||||
|
$(JADETEX) $(addsuffix .tex, $(basename $<))
|
||||||
|
|
||||||
|
capabilities.pdf: capabilities.dvi
|
||||||
|
$(DVIPDF) -o $(addsuffix .pdf, $(basename $<)) $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.html
|
177
docs/DocBook/README.DocBook
Normal file
177
docs/DocBook/README.DocBook
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
The Official Supybot DocBook Metadocumentation
|
||||||
|
(or, How Does One SGML File Turn Into All Those Document Formats?)
|
||||||
|
|
||||||
|
Okay, though this isn't the case yet, ideally all of Supybot's documentation
|
||||||
|
can and will be done using DocBook and DocBook-related tools as well as a few
|
||||||
|
custom extensions that I've written.
|
||||||
|
|
||||||
|
- How does DocBook work?
|
||||||
|
First things first, you have to understand sort of how DocBook works in order
|
||||||
|
to figure out how our documentation gets generated from just one file. What
|
||||||
|
DocBook is, basically, is just a DTD (Document Type Definition). What that
|
||||||
|
means is that it simply specifies how a document can be structured and still be
|
||||||
|
considered a valid document by placing restrictions on what elements go where.
|
||||||
|
It's a popular DTD because it is structured very well and it's not only fairly
|
||||||
|
generic, but it also has nice elements that make documenting things (such as
|
||||||
|
supybot) rather easy. It focuses on structure and content instead of
|
||||||
|
presentation which is what makes it nice for writing things which are output
|
||||||
|
format agnostic.
|
||||||
|
|
||||||
|
So, let's say we've written a proper DocBook document, now what? Well, using
|
||||||
|
an output formatting tool and a stylesheet, you create whatever form of output
|
||||||
|
you want. What we use for producing the outptut is jade, and DocBook comes
|
||||||
|
with a few stylesheets that work with that tool to create output formats like
|
||||||
|
HTML and TeX. From the TeX file we produce a DVI (device independent) file
|
||||||
|
with latex, and from that we produce the print formats of our documents, like
|
||||||
|
PDF and Postscript using tools like dvips and dvipdfm.
|
||||||
|
|
||||||
|
- What extra stuff do we do?
|
||||||
|
Well, since our documents all have to do with an IRC bot, there are some very
|
||||||
|
common things that we talk about a lot that we might like to format specially.
|
||||||
|
For example, when we discuss a particular command for the bot we might like to
|
||||||
|
have that text appear slightly different to emphasize the fact that it is
|
||||||
|
special. So, for the commonly used items that weren't already covered by
|
||||||
|
DocBook's DTD, I added elements into a new DTD which just extends DocBook's
|
||||||
|
DTD. So now we have elements like <nick> and <channel> and <botcommand> that
|
||||||
|
we can use for our documentation.
|
||||||
|
|
||||||
|
Of course, now that we have used a DTD with more stuff in it (than DocBook),
|
||||||
|
the stylesheets that DocBook provides won't do any special formatting for those
|
||||||
|
new elements so we have to write new stylesheets as well. Once again I just
|
||||||
|
extended the existing ones with formatting instructions on how to treat the new
|
||||||
|
elements. So with this done, now our HTML and TeX (and whatever else) output
|
||||||
|
will be properly formatted.
|
||||||
|
|
||||||
|
- How do I make my own changes to the DTD and stylesheets?
|
||||||
|
Primarily, you don't :) Ask me (Strike) first about it, and I will generally
|
||||||
|
write them for you or explain a better way of doing things. This is especially
|
||||||
|
true for the DTD, because that must remain consistent everywhere we write/read
|
||||||
|
supybot docs based on it. The stylesheets are more lax and can be modified to
|
||||||
|
produce whatever kind of output you wish.
|
||||||
|
|
||||||
|
So, with that warning/reminder out of the way, here's how to modify each
|
||||||
|
anyway. This doesn't really assume any knowledge of how to write a DTD, nor is
|
||||||
|
it an exhaustive reference on writing one, so don't treat it as such. I'm
|
||||||
|
basically just going to explain how to add extra elements that will play well
|
||||||
|
with the DocBook DTD.
|
||||||
|
|
||||||
|
-- Adding an element to the DTD
|
||||||
|
If you've decided that there's a certain "thing" that's mentioned a lot in the
|
||||||
|
documentation and deserves classification (for potential special formatting),
|
||||||
|
you'll probably want to create a new element (or set of elements) for it. I'll
|
||||||
|
walk you through how I added the "nick" element to our DTD (though many/most of
|
||||||
|
the elements I added follow an identical process).
|
||||||
|
|
||||||
|
The very first thing you need to figure out is: where in my document does this
|
||||||
|
element "fit". That is to say, what elements should/can rightly contain this
|
||||||
|
particular one? In the case of the "nick" element, it's basically always an
|
||||||
|
inline-formatted deal that belongs in paragraphs for the most part. For those
|
||||||
|
of you scratching your heads at that last sentence, perhaps thinking "okay, so
|
||||||
|
how are we supposed to know what is relevant?" I say, "don't worry, I learned
|
||||||
|
by example as well." Basically, I just looked through the DocBook DTD and
|
||||||
|
figured out where things belong. Now, even if you don't know the DocBook DTD
|
||||||
|
front-to-back, you can still peruse it to figure out where your new element
|
||||||
|
belongs. Obviously, you should probably know *some* DocBook to figure out what
|
||||||
|
each element means, but luckily all of our docs have been converted to DocBook
|
||||||
|
and serve as nice examples of the usage of many elements :)
|
||||||
|
|
||||||
|
Now, to figure out where something like "nick" belongs. In many ways, a nick
|
||||||
|
is sort of like a variable name (at least in documentation usage). So, the
|
||||||
|
element I chose to base it off of was "varname". If you have the DocBook DTD
|
||||||
|
installed (as you should if you intend on making extensions to it), the varname
|
||||||
|
element definition is contained in the dbpoolx.mod filename (in Debian, it's
|
||||||
|
under /usr/share/sgml/docbook/dtd/4.2). How did I know this? Well, grep is
|
||||||
|
your friend and mine too, and dbpoolx is the only filename that shows up when
|
||||||
|
grepping for "varname" in the DocBook DTD directory. So, we open up dbpoolx.mod and search for varname. The first thing we find it in looks like this:
|
||||||
|
|
||||||
|
<!ENTITY % tech.char.class
|
||||||
|
"action|application
|
||||||
|
|classname|methodname|interfacename|exceptionname
|
||||||
|
|ooclass|oointerface|ooexception
|
||||||
|
|command|computeroutput
|
||||||
|
|database|email|envar|errorcode|errorname|errortype|errortext|filename
|
||||||
|
|function|guibutton|guiicon|guilabel|guimenu|guimenuitem
|
||||||
|
|guisubmenu|hardware|interface|keycap
|
||||||
|
|keycode|keycombo|keysym|literal|constant|markup|medialabel
|
||||||
|
|menuchoice|mousebutton|option|optional|parameter
|
||||||
|
|prompt|property|replaceable|returnvalue|sgmltag|structfield
|
||||||
|
|structname|symbol|systemitem|token|type|userinput|varname
|
||||||
|
%ebnf.inline.hook;
|
||||||
|
%local.tech.char.class;">
|
||||||
|
|
||||||
|
Hmm, this doesn't look like a definition of varname (to me, but I sort of
|
||||||
|
cheated by having read about DocBook before-hand ;)), but it will be important
|
||||||
|
to remember for later. Let's try and find the element definition for varname
|
||||||
|
(so, basically, let's look for the first line that starts with "<!ELEMENT ").
|
||||||
|
The first line I come up with when I search is:
|
||||||
|
|
||||||
|
<!ELEMENT varname %ho; (%smallcptr.char.mix;)*>
|
||||||
|
|
||||||
|
Rather than write a separate tutorial for interpreting DTDs, I found a good
|
||||||
|
SGML tutorial online that explains everything necessary to help you parse the
|
||||||
|
DocBook DTD to figure out what the varname element really is, as well as to
|
||||||
|
help you learn all the stuff necessary for what we will cover in creating our
|
||||||
|
new nick element. That tutorial is at
|
||||||
|
http://www.w3.org/TR/WD-html40-970708/intro/sgmltut.html#howtodtd (it's for
|
||||||
|
reading the HTML DTD, but it applies to any DTD).
|
||||||
|
|
||||||
|
So, now that we understand how to write/read things for a DTD, we arrive at the
|
||||||
|
time where we can write the actual definition of our "nick" element:
|
||||||
|
|
||||||
|
<!ELEMENT Nick - - ((%smallcptr.char.mix;)+)>
|
||||||
|
|
||||||
|
As we learned in the above tutorial, this means that we are creating an element
|
||||||
|
named "nick", which must have start and end tags, and is defined to contain one
|
||||||
|
or more of whatever is in "smallcptr.char.mix". And rather than hunt through
|
||||||
|
the DocBook DTD to figure out what that is, for now we'll just live with the
|
||||||
|
fact that whatever can go into a DocBook varname can go into our new nick
|
||||||
|
element. If you feel so inclined, feel free to try and define the content
|
||||||
|
model for nick to only include valid nick characters. It's perfectly doable,
|
||||||
|
and I'll probably do it at some point but I haven't yet.
|
||||||
|
|
||||||
|
Since we're extending the DocBook DTD, I also decided that it'd be nice to
|
||||||
|
follow the element creation conventions observed in their DTD, so there are a
|
||||||
|
few more lines associated with our new nick element. All of them are related
|
||||||
|
to the attributes of the element, and allowing for them to be extended by
|
||||||
|
external DTDs (much like we are doing, only we aren't changing attributes of
|
||||||
|
existing elements, just adding our own). The first one is:
|
||||||
|
|
||||||
|
<!ENTITY % local.nick.attrib "">
|
||||||
|
|
||||||
|
This basically defines an empty entity named local.nick.attrib which we will
|
||||||
|
include so that if anyone chooses to extend the nick attributes, all they have
|
||||||
|
to do is redefine local.nick.attrib.
|
||||||
|
|
||||||
|
<!ENTITY % nick.role.attrib "%role.attrib;">
|
||||||
|
|
||||||
|
To tell you the truth, I'm not entirely sure what this is for, but it follows the DocBook convention :)
|
||||||
|
|
||||||
|
<!ATTLIST Nick
|
||||||
|
%common.attrib;
|
||||||
|
%local.nick.attrib;
|
||||||
|
%nick.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
This is, of course, our attribute list for our nick element. It consists of
|
||||||
|
the two things we just defined as well as common.attrib which contains things
|
||||||
|
like "id" and whatnot which all DocBook elements are expected to have.
|
||||||
|
|
||||||
|
-- Extending the DocBook DTD to recognize new elements
|
||||||
|
So, that's all you need to define your new element. But, we're not done just
|
||||||
|
yet! We're almost there, we just need to make it so that it works with the
|
||||||
|
existing DocBook elements, otherwise it's no good to us. Since we defined our
|
||||||
|
element to esentially be the same as varname, it probably belongs at the same
|
||||||
|
place within the DocBook schema as varname. Do you remember when we had that
|
||||||
|
large entity definition that wasn't what we were looking for at the time though
|
||||||
|
I said it'd be important later? Well, later is now. So, what that line tells
|
||||||
|
us is what class of elements DocBook has varname in, which is
|
||||||
|
"tech.char.class". And thanks to the DocBook convention of defining a
|
||||||
|
local.<classname> entity that we can extend, all we have to do is redefine
|
||||||
|
local.tech.char.class to contain "nick", and we are done.
|
||||||
|
|
||||||
|
You may notice, however, that we don't actually put varname right into the
|
||||||
|
local.tech.char.class entity, but instead we create our own
|
||||||
|
supybot.tech.char.class class of elements that are supybot-specific (and are
|
||||||
|
the equivalent of DocBook's tech.char.class elements) and instead, put all of
|
||||||
|
those into the local.tech.char.class entity. Basically, we just go through one
|
||||||
|
more level of indirection.
|
221
docs/DocBook/capabilities.sgml
Normal file
221
docs/DocBook/capabilities.sgml
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<articleinfo>
|
||||||
|
<authorgroup>
|
||||||
|
<author>
|
||||||
|
<firstname>Jeremiah</firstname>
|
||||||
|
<surname>Fincher</surname>
|
||||||
|
</author>
|
||||||
|
<editor>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
<contrib>DocBook translator</contrib>
|
||||||
|
</editor>
|
||||||
|
</authorgroup>
|
||||||
|
<title>Supybot capabilities system explanation</title>
|
||||||
|
<revhistory>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.1</revnumber>
|
||||||
|
<date>18 Feb 2004</date>
|
||||||
|
<revremark>Initial Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.2</revnumber>
|
||||||
|
<date>04 Sep 2004</date>
|
||||||
|
<revremark>Update Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
</revhistory>
|
||||||
|
</articleinfo>
|
||||||
|
<sect1>
|
||||||
|
<title>Introduction</title>
|
||||||
|
<subtitle>
|
||||||
|
Supybot's capabilities overview and comparisons to other bots
|
||||||
|
</subtitle>
|
||||||
|
<para>
|
||||||
|
Ok, some some explanation of the capabilities system is probably
|
||||||
|
in order. With most IRC bots (including the ones I've written
|
||||||
|
myself prior to this one) “what a user can do” is set
|
||||||
|
in one of two ways. On the <emphasis>really</emphasis> simple
|
||||||
|
bots, each user has a numeric “level” and commands
|
||||||
|
check to see if a user has a “high enough level” to
|
||||||
|
perform some operation. On bots that are slightly more
|
||||||
|
complicated, users have a list of “flags” whose
|
||||||
|
meanings are hardcoded, and the bot checks to see if a user
|
||||||
|
possesses the necessary flag before performing some operation.
|
||||||
|
Both methods, IMO, are rather arbitrary, and force the user and
|
||||||
|
the programmer to be unduly confined to less expressive
|
||||||
|
constructs.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This bot is different. Every user has a set of
|
||||||
|
“capabilities” that is consulted every time they give
|
||||||
|
the bot a command. Commands, rather than checking for a user
|
||||||
|
level of 100, or checking if the user has an <varname>o</varname>
|
||||||
|
flag, are instead able to check if a user has the
|
||||||
|
<capability>owner</capability> capability. At this point such a
|
||||||
|
difference might not seem revolutionary, but at least we can
|
||||||
|
already tell that this method is self-documenting, and easier for
|
||||||
|
users and developers to understand what's truly going on.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>What sets supybot's capabilities apart</title>
|
||||||
|
<para>
|
||||||
|
If that was all, well, the capability system would be
|
||||||
|
“cool”, but not many people would say it was
|
||||||
|
“awesome”. But it <emphasis>is</emphasis> awesome!
|
||||||
|
Several things are happening behind the scene that make it
|
||||||
|
awesome, and these are things that couldn't happen if the bot was
|
||||||
|
using numeric userlevels or single-character flags. First,
|
||||||
|
whenever a user issues the bot a command, the command dispatcher
|
||||||
|
checks to make sure the user doesn't have the
|
||||||
|
“anticapability” for that command. An anticapability is
|
||||||
|
a capability that, instead of saying “what a user can
|
||||||
|
do”, says what a user <emphasis>cannot</emphasis> do. It's
|
||||||
|
formed rather simply by adding a dash (“-”) to the
|
||||||
|
beginning of a capability; <botcommand>rot13</botcommand> is a
|
||||||
|
capability, and <botcommand>-rot13</botcommand> is an
|
||||||
|
anticapability. Anyway, when a user issues the bot a command,
|
||||||
|
perhaps <botcommand>calc</botcommand> or
|
||||||
|
<botcommand>help</botcommand>, the bot first checks to make sure
|
||||||
|
the user doesn't have the <capability>-calc</capability> or the
|
||||||
|
<capability>-help</capability> capabilities before even
|
||||||
|
considering responding to the user. So commands can be turned on
|
||||||
|
or off on a <emphasis>per user</emphasis> basis, offering
|
||||||
|
finegrained control not often (if at all!) seen in other bots.
|
||||||
|
</para>
|
||||||
|
<sect2>
|
||||||
|
<title>Channel capabilities</title>
|
||||||
|
<para>
|
||||||
|
But that's not all! The capabilities system also supports
|
||||||
|
<emphasis>Channel</emphasis> capabilities, which are
|
||||||
|
capabilities that only apply to a specific channel; they're of
|
||||||
|
the form <capability>#channel,capability</capability>.
|
||||||
|
Whenever a user issues a command to the bot in a channel, the
|
||||||
|
command dispatcher also checks to make sure the user doesn't
|
||||||
|
have the anticapability for that command <emphasis>in that
|
||||||
|
channel</emphasis> and if the user does, the bot won't respond
|
||||||
|
to the user in the channel. Thus now, in addition to having
|
||||||
|
the ability to turn individual commands on or off for an
|
||||||
|
individual user, we can now turn commands on or off for an
|
||||||
|
individual user on an individual channel!
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So when a user <nick>foo</nick> sends a command
|
||||||
|
<botcommand>bar</botcommand> to the bot on channel
|
||||||
|
<channel>#baz</channel>, first the bot checks to see if the
|
||||||
|
user has the anticapability for the command by itself,
|
||||||
|
<capability>-bar</capability>. If so, it returns right then
|
||||||
|
and there, compltely ignoring the fact that the user issued
|
||||||
|
that command to it. If the user doesn't have that
|
||||||
|
anticapability, then the bot checks to see if the user issued
|
||||||
|
the command over a channel, and if so, checks to see if the
|
||||||
|
user has the antichannelcapability for that command,
|
||||||
|
<capability>#baz,-bar</capability>. If so, again, he returns
|
||||||
|
right then and there and doesn't even think about responding
|
||||||
|
to the bot. If neither of these anticapabilities are present,
|
||||||
|
then the bot just responds to the user like normal.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Motivations behind the capabilities system</title>
|
||||||
|
<sect2>
|
||||||
|
<title>A programmer's perspective</title>
|
||||||
|
<para>
|
||||||
|
From a programming perspective, capabilties are easy to use
|
||||||
|
and flexible. Any command can check if a user has any
|
||||||
|
capability, even ones not thought of when the bot was
|
||||||
|
originally written. Commands/Callbacks can add their own
|
||||||
|
capabilities – it's as easy as just checking for a
|
||||||
|
capability and documenting somewhere that a user needs that
|
||||||
|
capability to do something.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>An end-user's perspective</title>
|
||||||
|
<para>
|
||||||
|
From an end-user perspective, capabilities remove a lot of the
|
||||||
|
mystery and esotery of bot control, in addition to giving the
|
||||||
|
user absolutely finegrained control over what users are
|
||||||
|
allowed to do with the bot. Additionally, defaults can be set
|
||||||
|
by the end-user for both individual channels and for the bot
|
||||||
|
as a whole, letting an end-user set the policy he wants the
|
||||||
|
bot to follow for users that haven't yet registered in his
|
||||||
|
user database.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
<para>
|
||||||
|
It's really a revolution!
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Hard-coded supybot capabilities</title>
|
||||||
|
<para>
|
||||||
|
There are several default capabilities the bot uses. The most
|
||||||
|
important of these is the <capability>owner</capability>
|
||||||
|
capability. This capability allows the person having it to use
|
||||||
|
<emphasis>any</emphasis> command. It's best to keep this
|
||||||
|
capability reserved to people who actually have access to the
|
||||||
|
shell the bot is running on.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
There is also the <capability>admin</capability> capability for
|
||||||
|
non-owners that are highly trusted to administer the bot
|
||||||
|
appropriately. They can do things such as change the bot's nick,
|
||||||
|
globally enable/disable commands, cause the bot to ignore a given
|
||||||
|
user, set the prefixchar, report bugs, etc. They generally cannot
|
||||||
|
do administration related to channels, which is reserved for
|
||||||
|
people with the next capability.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
People who are to administer channels with the bot should have the
|
||||||
|
<capability>#channel,op</capability> capability – whatever
|
||||||
|
channel they are to administrate, they should have that channel
|
||||||
|
capability for <capability>op</capability>. For example, since I
|
||||||
|
want <nick>inkedmn</nick> to be an administrator in
|
||||||
|
<channel>#supybot</channel>, I'll give him the
|
||||||
|
<capability>#supybot,op</capability> capability. This is in
|
||||||
|
addition to his <capability>admin</capability> capability, since
|
||||||
|
the <capability>admin</capability> capability doesn't give the
|
||||||
|
person having it control over channels.
|
||||||
|
<capability>#channel.op</capability> is used for such things as
|
||||||
|
giving/receiving ops, kickbanning people, lobotomizing the bot,
|
||||||
|
ignoring users in the channel, and managing the channel
|
||||||
|
capabilities. The <capability>#channel,op</capability> capability
|
||||||
|
is also basically the equivalent of the owner capability for
|
||||||
|
capabilities involving <channel>#channel</channel> –
|
||||||
|
basically anyone with the <capability>#channel,op</capability>
|
||||||
|
capability is considered to have all positive capabilities and no
|
||||||
|
negative capabilities for <channel>#channel</channel>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
One other globally important capability exists:
|
||||||
|
<capability>trusted</capability>. This is a command that
|
||||||
|
basically says “This user can be trusted not to try and
|
||||||
|
crash the bot.” It allows users to call commands like
|
||||||
|
<botcommand>Math.icalc</botcommand>, which potentially could cause the
|
||||||
|
bot to begin a calculation that could potentially never return (a
|
||||||
|
calculation like 10**10**10**10). Another command that requires
|
||||||
|
the trusted capability is <botcommand>Utilties.re</botcommand>, which
|
||||||
|
(due to the regular expression implementation in Python (and any
|
||||||
|
other language that uses NFA regular expressions, like Perl or
|
||||||
|
Ruby or Lua or …) which can allow a regular expression to
|
||||||
|
take exponential time to process). Consider what would happen if
|
||||||
|
the someone gave the bot the command <literal>re [strjoin "" s/./
|
||||||
|
[dict go] /] [dict go]</literal>.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Other capabilities</title>
|
||||||
|
<para>
|
||||||
|
Other plugins may require different capabilities; the
|
||||||
|
<plugin>Factoids</plugin> plugin requires
|
||||||
|
<capability>#channel,factoids</capability>, the <plugin>Topic</plugin>
|
||||||
|
plugin requires <capability>#channel,topic</capability>, etc.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
|
316
docs/DocBook/configuration.sgml
Normal file
316
docs/DocBook/configuration.sgml
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<articleinfo>
|
||||||
|
<authorgroup>
|
||||||
|
<author>
|
||||||
|
<firstname>Jeremiah</firstname>
|
||||||
|
<surname>Fincher</surname>
|
||||||
|
</author>
|
||||||
|
<author>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
</author>
|
||||||
|
<editor>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
<contrib>DocBook translator</contrib>
|
||||||
|
</editor>
|
||||||
|
</authorgroup>
|
||||||
|
<title>Supybot configuration system explanation</title>
|
||||||
|
<revhistory>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.1</revnumber>
|
||||||
|
<date>18 Feb 2004</date>
|
||||||
|
<revremark>Initial Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.2</revnumber>
|
||||||
|
<date>26 Feb 2004</date>
|
||||||
|
<revremark>Conversion to Supybot DTD</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.3</revnumber>
|
||||||
|
<date>4 Sep 2004</date>
|
||||||
|
<revremark>Update Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
</revhistory>
|
||||||
|
</articleinfo>
|
||||||
|
<sect1>
|
||||||
|
<title>Introduction</title>
|
||||||
|
<para>
|
||||||
|
So you've got your Supybot up and running and there are some
|
||||||
|
things you don't like about it. Fortunately for you, chances are
|
||||||
|
that these things are configurable, and this document is here to
|
||||||
|
tell you how to configure them.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Configuration of Supybot is handled via the
|
||||||
|
<plugin>Config</plugin> plugin, which controls runtime access to
|
||||||
|
Supybot's registry (the configuration file generated by the
|
||||||
|
<script>supybot-wizard</script> program you ran). The
|
||||||
|
<plugin>Config</plugin> plugin provides a way to get or set
|
||||||
|
variables, to list the available variables, and even to get help
|
||||||
|
for certain variables. Take a moment now to read the help for
|
||||||
|
each of those commands: <botcommand>config</botcommand>,
|
||||||
|
<botcommand>list</botcommand>, and
|
||||||
|
<botcommand>help</botcommand>. If you don't know how to get help on
|
||||||
|
those commands, go ahead and read our
|
||||||
|
<filename>GETTING_STARTED</filename> document before this one.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Supybot's registry</title>
|
||||||
|
<para>
|
||||||
|
Now, if you're used to the Windows registry, don't worry,
|
||||||
|
Supybot's registry is completely different. For one, it's
|
||||||
|
completely plain text. There's no binary database sensitive to
|
||||||
|
corruption, it's not necessary to use another program to edit it
|
||||||
|
– all you need is a simple text editor. But there is at
|
||||||
|
least one good idea in Windows' registry: hierarchical
|
||||||
|
configuration. Supybot's configuration variables are organized in
|
||||||
|
a hierarchy: variables having to do with the way Supybot makes
|
||||||
|
replies all start with
|
||||||
|
<registrygroup>supybot.reply</registrygroup>; variables having to
|
||||||
|
do with the way a plugin works all start with
|
||||||
|
<registrygroup>supybot.plugins.Plugin</registrygroup> (where
|
||||||
|
<plugin>Plugin</plugin> is the name of the plugin in question).
|
||||||
|
This hierarchy is nice because it means the user isn't inundated
|
||||||
|
with hundreds of unrelated and unsorted configuration variables.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Some of the more important configuration values are located
|
||||||
|
directly under the base group,
|
||||||
|
<registrygroup>supybot</registrygroup>. Things like the bot's
|
||||||
|
nick, its ident, etc. Along with these config values are a few
|
||||||
|
subgroups that contain other values. Some of the more prominent
|
||||||
|
subgroups are: <registrygroup>plugins</registrygroup> (where all
|
||||||
|
the plugin-specific configuration is held),
|
||||||
|
<registrygroup>reply</registrygroup> (where variables affecting
|
||||||
|
the way a Supybot makes its replies resides),
|
||||||
|
<registrygroup>replies</registrygroup> (where all the specific
|
||||||
|
standard replies are kept), and
|
||||||
|
<registrygroup>directories</registrygroup> (where all the
|
||||||
|
directories a Supybot uses are defined). There are other
|
||||||
|
subgroups as well, but these are the ones we'll use in our
|
||||||
|
example.
|
||||||
|
</para>
|
||||||
|
<sect2>
|
||||||
|
<title>Config plugin commands</title>
|
||||||
|
<sect3>
|
||||||
|
<title>Listing registry contents</title>
|
||||||
|
<para>
|
||||||
|
Using the <plugin>Config</plugin> plugin, you can list
|
||||||
|
the values in a subgroup and get or set any of the values
|
||||||
|
anywhere in the configuration hierarchy. For example,
|
||||||
|
let's say you wanted to see what configuration values were
|
||||||
|
under the <registrygroup>supybot</registrygroup> (the base
|
||||||
|
group) hierarchy. You would simply issue this command:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config list supybot
|
||||||
|
<supybot> @capabilities, @commands, @databases, @debug, @directories, @drivers,
|
||||||
|
@log, @networks, @nick, @plugins, @protocols, @replies, @reply,
|
||||||
|
alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout,
|
||||||
|
externalIP, flush, followIdentificationThroughNickChanges,
|
||||||
|
humanTimestampFormat, ident, pidFile, snarfThrottle, upkeepInterval,
|
||||||
|
and user
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
These are all the configuration groups and values which
|
||||||
|
are under the base <registrygroup>supybot</registrygroup>
|
||||||
|
group. Actually, their full names would each have a
|
||||||
|
“supybot.” appended on to the front of them,
|
||||||
|
but it is omitted in the listing in order to shorten the
|
||||||
|
output. The first entries in the output are the groups
|
||||||
|
(distinguished by the @ symbol in front of them), and the
|
||||||
|
rest are the configuration values.
|
||||||
|
</para>
|
||||||
|
</sect3>
|
||||||
|
<sect2>
|
||||||
|
<title>Supybot's registry</title>
|
||||||
|
<sect3>
|
||||||
|
<title>Dealing with registry values</title>
|
||||||
|
<para>
|
||||||
|
Okay, now that you've used the <plugin>Config</plugin>
|
||||||
|
plugin to list configuration variables, it's time that we
|
||||||
|
start looking at individual variables and their values.
|
||||||
|
</para>
|
||||||
|
<sect4>
|
||||||
|
<title>Built-in help for registry values</title>
|
||||||
|
<para>
|
||||||
|
The first (and perhaps most important) thing you
|
||||||
|
should know about each configuration variable is that
|
||||||
|
they all have an associated help string to tell you
|
||||||
|
what they represent. So the first command we'll cover
|
||||||
|
is <botcommand>config help</botcommand>. To see the
|
||||||
|
help string for any value or group, simply use the
|
||||||
|
<botcommand>config help</botcommand> command. For
|
||||||
|
example, to see what this
|
||||||
|
<registrygroup>supybot.snarfThrottle</registrygroup>
|
||||||
|
configuration variable is all about, we'd do this:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config help supybot.snarfThrottle
|
||||||
|
<supybot> jemfinch|lambda: A floating point number of seconds to throttle snarfed
|
||||||
|
URLs, in order to prevent loops between two bots snarfing the same URLs and
|
||||||
|
having the snarfed URL in the output of the snarf message. (Current value:
|
||||||
|
10.0)
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Pretty simple, eh?
|
||||||
|
</para>
|
||||||
|
</sect4>
|
||||||
|
<sect4>
|
||||||
|
<title>Getting/setting registry values</title>
|
||||||
|
<para>
|
||||||
|
Now, if you're curious what the current value of a
|
||||||
|
configuration variable is, you'll use the
|
||||||
|
<botcommand>config</botcommand> command with one
|
||||||
|
argument, the name of the variable you want to see the
|
||||||
|
value of:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars
|
||||||
|
<supybot> jemfinch|lambda: '@'
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
To set this value, just stick an extra argument after
|
||||||
|
the name:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars @$
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Now, check this out:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars
|
||||||
|
<supybot> jemfinch|lambda: '@$'
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Note that we used <literal>$</literal> as our prefix
|
||||||
|
character, and that the value of the configuration
|
||||||
|
variable changed. If I were to use the
|
||||||
|
<botcommand>flush</botcommand> command now, this
|
||||||
|
change would be flushed to the registry file on disk
|
||||||
|
(this would also happen if I made the bot quit, or
|
||||||
|
pressed
|
||||||
|
<keycombo>
|
||||||
|
<keycap>Ctrl</keycap>
|
||||||
|
<keycap>C</keycap>
|
||||||
|
</keycombo>
|
||||||
|
in the terminal the bot was running in). Instead,
|
||||||
|
I'll revert the change:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> $config supybot.reply.whenAddressedBy.chars @
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> $note that this makes no response.
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
If you're ever curious what the default for a given
|
||||||
|
configuration variable is, use the <botcommand>config
|
||||||
|
default</botcommand> command:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config default supybot.reply.whenAddressedBy.chars
|
||||||
|
<supybot> jemfinch|lambda: ''
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Thus, to reset a configuration variable to its default
|
||||||
|
value, you can simply say:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config supybot.reply.whenAddressedBy.chars [config default
|
||||||
|
supybot.reply.whenAddressedBy.chars]
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> @note that this does nothing
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Simple, eh?
|
||||||
|
</para>
|
||||||
|
</sect4>
|
||||||
|
</sect3>
|
||||||
|
<sect3>
|
||||||
|
<title>Searching the registry</title>
|
||||||
|
<para>
|
||||||
|
Now, let's say you want to find all configuration
|
||||||
|
variables that might be even remotely related to opping.
|
||||||
|
For that, you'll want the <botcommand>config
|
||||||
|
search</botcommand> command. Check this out:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> @config search op
|
||||||
|
<supybot> jemfinch|lambda:
|
||||||
|
supybot.plugins.Enforcer.autoOp,
|
||||||
|
supybot.plugins.Enforcer.autoHalfop,
|
||||||
|
supybot.plugins.Enforcer.takeRevenge.onOps,
|
||||||
|
supybot.plugins.Enforcer.cycleToGetOps,
|
||||||
|
supybot.plugins.Topic, supybot.plugins.Topic.public,
|
||||||
|
supybot.plugins.Topic.separator,
|
||||||
|
supybot.plugins.Topic.format,
|
||||||
|
supybot.plugins.Topic.recognizeTopiclen,
|
||||||
|
supybot.plugins.Topic.default,
|
||||||
|
supybot.plugins.Topic.undo.maz, and
|
||||||
|
supybot.plugins.Relay.topicSync
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Sure, it showed up all the topic-related stuff in there,
|
||||||
|
but it also showed you all the op-related stuff, too. Do
|
||||||
|
note, however, that you can only see configuration
|
||||||
|
variables for plugins that you have loaded or that you
|
||||||
|
loaded in the past; if you've never loaded a plugin,
|
||||||
|
there's no way for the bot to know what configuration
|
||||||
|
variables it registers.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Some people might like editing their registry file
|
||||||
|
directly rather than manipulating all these things through
|
||||||
|
the bot. For those people, we offer the
|
||||||
|
<botcommand>config reload</botcommand> command, which
|
||||||
|
reloads both registry configuration and
|
||||||
|
user/channel/ignore database configuration. Just edit the
|
||||||
|
interesting files and then give the bot the
|
||||||
|
<botcommand>config reload</botcommand> command and it'll
|
||||||
|
work as expected. Do note, however, that Supybot flushes
|
||||||
|
his configuration files and databases to disk every hour
|
||||||
|
or so, and if this happens after you've edited your
|
||||||
|
configuration files but before you reload your changes,
|
||||||
|
you could lose the changes you made. To prevent this, set
|
||||||
|
the <registrygroup>supybot.flush</registrygroup> value to
|
||||||
|
<literal>Off</literal>, and no automatic flushing will
|
||||||
|
occur.
|
||||||
|
</para>
|
||||||
|
</sect3>
|
||||||
|
<sect3>
|
||||||
|
<title>Channel-specific configuration</title>
|
||||||
|
<para>
|
||||||
|
Many configuration variables can be specific to individual
|
||||||
|
channels. The <plugin>Config</plugin> plugin provides an
|
||||||
|
easy way to configure something for a specific channel;
|
||||||
|
for instance, in order to set the prefix chars for a
|
||||||
|
specific channel, do this in that channel:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
config channel supybot.reply.whenAddressedBy.chars !
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
That'll set the prefix chars in the channel that message
|
||||||
|
is sent in to <literal>!</literal>. Voila,
|
||||||
|
channel-specific values!
|
||||||
|
</para>
|
||||||
|
</sect3>
|
||||||
|
</sect2>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>All done!</title>
|
||||||
|
<para>
|
||||||
|
Anyway, that's about it for configuration. Have fun, and enjoy
|
||||||
|
your configurable bot!
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
|
359
docs/DocBook/faq.sgml
Normal file
359
docs/DocBook/faq.sgml
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||||
|
<article class="faq">
|
||||||
|
<articleinfo>
|
||||||
|
<authorgroup>
|
||||||
|
<author>
|
||||||
|
<firstname>Jeremiah</firstname>
|
||||||
|
<surname>Fincher</surname>
|
||||||
|
</author>
|
||||||
|
<editor>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
<contrib>DocBook translator</contrib>
|
||||||
|
</editor>
|
||||||
|
</authorgroup>
|
||||||
|
<title>Supybot Frequently Asked Questions</title>
|
||||||
|
<revhistory>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.1</revnumber>
|
||||||
|
<date>18 Feb 2004</date>
|
||||||
|
<revremark>Initial Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.2</revnumber>
|
||||||
|
<date>26 Feb 2004</date>
|
||||||
|
<revremark>Changed to Supybot DTD</revremark>
|
||||||
|
</revision>
|
||||||
|
</revhistory>
|
||||||
|
</articleinfo>
|
||||||
|
<qandaset defaultlabel="qanda">
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Why does my bot not recognize me or tell me that I don't
|
||||||
|
have the <capability>owner</capability> capability?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Because you've not given it anything to recognize you
|
||||||
|
from! You'll need to identify with the bot
|
||||||
|
(<botcommand>help identify</botcommand> to see how that
|
||||||
|
works) or add your hostmask to your user record
|
||||||
|
(<botcommand>help addhostmask</botcommand> to see how that
|
||||||
|
works) for it to know that you're you. You may wish to
|
||||||
|
note that <botcommand>addhostmask</botcommand> can accept
|
||||||
|
a password; rather than identify, you can send the command
|
||||||
|
<botcommand>addhostmask myOwnerUser [hostmask]
|
||||||
|
myOwnerUserPassword</botcommand> and the bot will add your
|
||||||
|
current hostmask to your owner user (of course, you should
|
||||||
|
change <literal>myOwnerUser</literal> and
|
||||||
|
<literal>myOwnerUserPassword</literal> appropriately for
|
||||||
|
your bot).
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
How do I make Supybot op my users?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
First, you'll have to make sure that your users register
|
||||||
|
with the bot. They can do this with the
|
||||||
|
<botcommand>register</botcommand> command. After they do
|
||||||
|
so, you'll want to add the
|
||||||
|
<capability>#channel,op</capability> capability to their
|
||||||
|
user. Use the <botcommand>channel
|
||||||
|
addcapability</botcommand> command to do this. After
|
||||||
|
that, your users should be able to use the
|
||||||
|
<botcommand>op</botcommand> command to get ops.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
If you want your users to be auto-opped when they join the
|
||||||
|
channel, you'll need to load the <plugin>Enforcer</plugin>
|
||||||
|
plugin and turn its <registrygroup>autoOp</registrygroup>
|
||||||
|
configuration variable on. Use the
|
||||||
|
<botcommand>config</botcommand> command to do so. Here's
|
||||||
|
an example of how to do these steps:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch|lambda> I'm going to make an example session for giving
|
||||||
|
you auto-ops, for our FAQ.
|
||||||
|
<dunk1> ah ok ;]
|
||||||
|
<jemfinch|lambda> First, I need you to register with supybot, using
|
||||||
|
the "register" command (remember to send it in private).
|
||||||
|
<dunk1> done
|
||||||
|
<jemfinch|lambda> what name are you registered under?
|
||||||
|
<dunk1> dunk1
|
||||||
|
<jemfinch|lambda> ok, cool.
|
||||||
|
<jemfinch|lambda> @channel addcapability dunk1 op
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> now use the "op" command to get ops.
|
||||||
|
<dunk1> @op
|
||||||
|
— supybot gives channel operator status to dunk1
|
||||||
|
<dunk1> works!
|
||||||
|
<dunk1> ;]
|
||||||
|
<jemfinch|lambda> @load Enforcer
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> @config channel supybot.plugins.Enforcer.autoOp On
|
||||||
|
<supybot> jemfinch|lambda: The operation succeeded.
|
||||||
|
<jemfinch|lambda> ok, now cycle the channel (part and then rejoin)
|
||||||
|
<– dunk1 (dunker@freebsd.nl) has left #supybot
|
||||||
|
–> dunk1 (dunker@freebsd.nl) has joined #supybot
|
||||||
|
— supybot gives channel operator status to dunk1
|
||||||
|
<jemfinch|lambda> cool, thanks :)
|
||||||
|
</ircsession>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Can users with the <capability>admin</capability>
|
||||||
|
capability change configuration variables?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Currently, no. Since this is the first release of Supybot
|
||||||
|
that uses the registry, we wanted to stay on the
|
||||||
|
conservative side and require the
|
||||||
|
<capability>owner</capability> capability for changing all
|
||||||
|
non-channel-related configuration variables. Feel free to
|
||||||
|
make your case to us as to why a certain configuration
|
||||||
|
variable should only require the
|
||||||
|
<capability>admin</capability> capability instead of the
|
||||||
|
<capability>owner</capability> capability, and if we agree
|
||||||
|
with you, we'll change it for the next release.
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Can Supybot do factoids?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Supybot most certainly can! In fact, we offer three
|
||||||
|
full-fledged factoids-related plugins!
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
<plugin>Factoids</plugin> (written by
|
||||||
|
<nick>jemfinch</nick>) is Supybot's original
|
||||||
|
factoids-related plugin. It offers full integration with
|
||||||
|
Supybot's nested commands as well as a complete 1:n key to
|
||||||
|
factoid ratio, with lookup by individual number. Factoids
|
||||||
|
also uses a channel-specific database instead of a global
|
||||||
|
database though that's configurable with the
|
||||||
|
<registrygroup>supybot.databases.plugins.channelSpecific</registrygroup>
|
||||||
|
configuration variable.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
<plugin>MoobotFactoids</plugin> (written by
|
||||||
|
<nick>Strike</nick>) is much more full-featured, offering
|
||||||
|
users the ability to define factoids in a slightly more
|
||||||
|
user-friendly way, as well as parsing factoids to handle
|
||||||
|
<reply>, <action>, and alternations (defining
|
||||||
|
a factoid “test” as
|
||||||
|
“<reply>(foo|bar|baz)” will make the bot
|
||||||
|
send “foo” or “bar” or
|
||||||
|
“baz” to the channel (without the normal
|
||||||
|
“test is ” at the beginning)). If you're
|
||||||
|
accustomed to Moobot's factoids or Blootbot's factoids,
|
||||||
|
then this is the Factoids plugin for you. Unfortunately,
|
||||||
|
due to the more natural definition syntax (required to be
|
||||||
|
comaptible with Moobot) you can't define Factoids with
|
||||||
|
nested commands; you'll have to evaluate the command first
|
||||||
|
and then copy the result into your factoid definition.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
<plugin>Infobot</plugin> (written by
|
||||||
|
<nick>jamessan</nick>) is used for Infobot compatibility;
|
||||||
|
if you still want the basic functionality of Infobot, this
|
||||||
|
is the plugin to use.
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Can I import my Infobot/Blootbot/Moobot factoids into
|
||||||
|
Supybot?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
As of present, we have no automated way to do so.
|
||||||
|
<nick>Strike</nick> has written a few scripts for
|
||||||
|
importing a Moobot database into MoobotFactoids, however,
|
||||||
|
so you'll want to talk to him about helping you with that.
|
||||||
|
We're certainly happy to help you convert such databases;
|
||||||
|
if you can provide us with such a database exported to a
|
||||||
|
flat file, we can probably do the rest of the work to
|
||||||
|
write a script that imports it into a database for one of
|
||||||
|
our factoids-related plugins.
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Do I really have to use separate databases for each
|
||||||
|
channel?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Of course not! We default to separate databases for each
|
||||||
|
channel because, well, that's what <nick>jemfinch</nick>
|
||||||
|
always thought was reasonable. Anyway, if you change the
|
||||||
|
configuration variable
|
||||||
|
<registrygroup>supybot.databases.plugins.channelSpecific</registrygroup>
|
||||||
|
to <literal>False</literal> instead of
|
||||||
|
<literal>True</literal>, for <emphasis>most</emphasis>
|
||||||
|
databases, each channel will share the same database (the
|
||||||
|
exceptions are <plugin>ChannelStats</plugin>,
|
||||||
|
<plugin>Herald</plugin>, <plugin>Seen</plugin>, and
|
||||||
|
<plugin>WordStats</plugin>, which are inherently rather
|
||||||
|
channel-based).
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Karma doesn't seem to work for me.
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
<plugin>Karma</plugin> by default doesn't acknowledge
|
||||||
|
karma updates. If you check the karma of whatever you
|
||||||
|
increased/decreased, you'll note that your increment or
|
||||||
|
decrement still took place. If you'd rather
|
||||||
|
<plugin>Karma</plugin> acknowledge karma updates, change
|
||||||
|
the
|
||||||
|
<registrygroup>supybot.plugins.Karma.response</registrygroup>
|
||||||
|
configuration variable to <literal>On</literal>.
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
I added an alias, but it doesn't work!
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Take a look at <botcommand>help <alias you
|
||||||
|
added></botcommand>. If the alias the bot has listed
|
||||||
|
doesn't match what you're giving it, chances re you need
|
||||||
|
to quote your alias in order for the brackets not to be
|
||||||
|
evaluated. For instance, if you're adding an alias to
|
||||||
|
give you a link to your homepage, you need to say:
|
||||||
|
<para>
|
||||||
|
<ircsession>
|
||||||
|
alias add mylink "format concat http://myhost.com/
|
||||||
|
[urlquote $1]"
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
and not:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
alias add mylink format concat http://myhost.com/
|
||||||
|
[urlquote $1]
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
The first version works; the second version will always
|
||||||
|
return the same url.
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
Is there a command that can tell me what capability
|
||||||
|
another command requires?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
No, there isn't, and there probably never will be.
|
||||||
|
Commands have the flexibility to check any capabilities
|
||||||
|
they wish to check; while this flexibility is useful, it
|
||||||
|
also makes it hard to guess what capability a certain
|
||||||
|
command requires. We could make a solution that would
|
||||||
|
work in a large majority of cases, but it wouldn't (and
|
||||||
|
couldn't!) be absolutely correct in all circumstances, and
|
||||||
|
since we're anal and we hate doing things halfway, we
|
||||||
|
probably won't ever add this partial solution.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Besides, is the error message so bad? If we did have such
|
||||||
|
a command, many users would call the command, see that
|
||||||
|
they could perform it, and then run the command, thus
|
||||||
|
doubling the activity in the channel. Is that something
|
||||||
|
you want?
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
How do I make my Supybot connect to multiple servers?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Just use the <botcommand>connect</botcommand> command in
|
||||||
|
the <plugin>Owner</plugin> plugin. Easy as pie!
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
<qandaentry>
|
||||||
|
<question>
|
||||||
|
<para>
|
||||||
|
I found a bug, what do I do?
|
||||||
|
</para>
|
||||||
|
</question>
|
||||||
|
<answer>
|
||||||
|
<para>
|
||||||
|
Submit it on Sourceforge through our Sourceforge project
|
||||||
|
page:
|
||||||
|
<ulink
|
||||||
|
url="http://sourceforge.net/tracker/?group_id=58965&atid=489447">
|
||||||
|
http://sourceforge.net/tracker/?group_id=58965&atid=489447
|
||||||
|
</ulink>. If Sourceforge happens to be down when you try
|
||||||
|
to submit your bug, then post it in the "Supybot Developer
|
||||||
|
Discussion" forum at our forums at
|
||||||
|
<ulink url="http://forums.supybot.org">
|
||||||
|
http://forums.supybot.org/
|
||||||
|
</ulink>. If that doesn't work, email
|
||||||
|
<email>supybot-bugs@lists.sourceforge.net</email>. If
|
||||||
|
that doesn't work, email
|
||||||
|
<email>jemfinch@supybot.org</email>. If that doesn't
|
||||||
|
work, find yourself some carrier pigeons and … hah!
|
||||||
|
You thought I was serious!
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Anyway, when you submit your bug, we'll need several
|
||||||
|
things. If the bug involved an uncaught exception, we
|
||||||
|
need the traceback (basically the stuff from
|
||||||
|
“Uncaught exception in …” to the next
|
||||||
|
log entry). We'd also like to see the commands that
|
||||||
|
caused the bug, or happened around the time you saw the
|
||||||
|
bug. If the bug involved a database, we'd love to see the
|
||||||
|
database. Remember, it's always worse to send us too
|
||||||
|
little information in a bug report than too much.
|
||||||
|
</para>
|
||||||
|
</answer>
|
||||||
|
</qandaentry>
|
||||||
|
</qandaset>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
|
297
docs/DocBook/getting_started.sgml
Normal file
297
docs/DocBook/getting_started.sgml
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<articleinfo>
|
||||||
|
<authorgroup>
|
||||||
|
<author>
|
||||||
|
<firstname>Jeremiah</firstname>
|
||||||
|
<surname>Fincher</surname>
|
||||||
|
</author>
|
||||||
|
<editor>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
<contrib>DocBook translator</contrib>
|
||||||
|
</editor>
|
||||||
|
</authorgroup>
|
||||||
|
<title>Getting started with Supybot</title>
|
||||||
|
<revhistory>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.1</revnumber>
|
||||||
|
<date>18 Feb 2004</date>
|
||||||
|
<revremark>Initial Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
</revhistory>
|
||||||
|
</articleinfo>
|
||||||
|
<sect1>
|
||||||
|
<title>Introduction</title>
|
||||||
|
<para>
|
||||||
|
Ok, so you've decided to try out Supybot. That's great! The more
|
||||||
|
people who use Supybot, the more people can submit bugs and help
|
||||||
|
us to make it the best IRC bot in the world :)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
First things first: Supybot <emphasis>requires</emphasis> Python
|
||||||
|
2.3. There ain't no getting around it. If you're a Python
|
||||||
|
developer, you probably know how superior 2.3 is to previous
|
||||||
|
incarnations. If you're not, just think about the difference
|
||||||
|
between a bowl of plain vanilla ice cream and a banana split. Or
|
||||||
|
something like that. Either way, <emphasis>we're</emphasis>
|
||||||
|
Python developers and we like banana splits.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Installing the bot and its utilities</title>
|
||||||
|
<para>
|
||||||
|
So what do you do? First thing you'll want to do is run (with
|
||||||
|
root/admin privileges) <application>python setup.py
|
||||||
|
install</application>. This will install Supybot globally. If
|
||||||
|
you need to install locally for whatever reason, see this <ulink
|
||||||
|
url="http://tinyurl.com/2tb37">forum post</ulink> on how to do so.
|
||||||
|
You'll then have several new programs installed where Python
|
||||||
|
scripts are normally installed on your system
|
||||||
|
(<filename>/usr/bin</filename> or
|
||||||
|
<filename>/usr/local/bin</filename> are common on UNIX systems;
|
||||||
|
<filename>C:\Python23\Scripts</filename> is a common place on
|
||||||
|
Windows; and (watch out, this is a long one :))
|
||||||
|
<filename>/System/Library/Frameworks/Python.framework/Versions/2.3/bin</filename>
|
||||||
|
is a common place on MacOS X.). The two that might be of
|
||||||
|
particular interest to you, the new user, are
|
||||||
|
<script>supybot</script> and
|
||||||
|
<script>supybot-wizard</script> The former
|
||||||
|
(<script>supybot</script> is the script to run an actual
|
||||||
|
bot; the latter (<script>supybot-wizard</script> is an
|
||||||
|
in-depth wizard that provides a nice user interface for creating
|
||||||
|
configuration files for your bot. We'd prefer you to the use
|
||||||
|
<script>supybot-wizard</script>, but if you're in a
|
||||||
|
hurry or don't feel like being asked many questions, just run
|
||||||
|
supybot with no arguments and it'll ask you only the questions
|
||||||
|
necessary ")to run a bot.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Firing up the bot for the first time</title>
|
||||||
|
<para>
|
||||||
|
So after running either of those two programs, you've got a nice
|
||||||
|
registry file handy. If you're not satisfied with your answers
|
||||||
|
to any of the questions you were asked, feel free to run the
|
||||||
|
program again until you're satisfied with all your answers. Once
|
||||||
|
you're satisfied, though, run the
|
||||||
|
<script>supybot</script> program with the
|
||||||
|
registry file you created as an argument. This will start the
|
||||||
|
bot; unless you turned off logging to stdout, you'll see some nice
|
||||||
|
log messages describing what the bot is doing at any particular
|
||||||
|
moment; it may pause for a significant amount of time after saying
|
||||||
|
"Connecting to ..." while the server tries to check its ident.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Your first interactions with the bot</title>
|
||||||
|
<para>
|
||||||
|
Ok, so let's assume your bot connected to the server fine and
|
||||||
|
joined the channels you told it to join. For now we'll assume you
|
||||||
|
named your bot <nick>supybot</nick> (you probably didn't,
|
||||||
|
but it'll make it much clearer in the examples that follow to
|
||||||
|
assume that you did). We'll also assume that you told it to join
|
||||||
|
<channel>#channel</channel> (a nice generic name for a channel,
|
||||||
|
isn't it? :)) So what do you do with this bot that you just made
|
||||||
|
to join your channel? Try this in the channel:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
supybot: list
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Replacing <nick>supybot</nick> with the actual name you
|
||||||
|
picked for your bot, of course. Your bot should reply with a list
|
||||||
|
of the plugins he currently has loaded. At least
|
||||||
|
<plugin>Admin</plugin>, <plugin>Channel</plugin>,
|
||||||
|
<plugin>Config</plugin>, <plugin>Misc</plugin>,
|
||||||
|
<plugin>Owner</plugin>, and <plugin>User</plugin> should be
|
||||||
|
there; if you used <script>supybot-wizard</script> to
|
||||||
|
create your configuration file you may have many more plugins
|
||||||
|
loaded. The <botcommand>list</botcommand> command can also be used to
|
||||||
|
list the commands in a given plugin:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
supybot: list Misc
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Will list all the commands in the <plugin>Misc</plugin> plugin.
|
||||||
|
</para>
|
||||||
|
<sect2>
|
||||||
|
<title>Accessing the bot's online help</title>
|
||||||
|
<para>
|
||||||
|
If you want to see the help for any command, just use
|
||||||
|
the <botcommand>help</botcommand> command:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
supybot: help help
|
||||||
|
supybot: help list
|
||||||
|
supybot: help load
|
||||||
|
</ircsession>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>Dealing with ambiguous commands</title>
|
||||||
|
<para>
|
||||||
|
Sometimes more than one plugin will have a given command; for
|
||||||
|
instance, the <botcommand>list</botcommand> command exists in both
|
||||||
|
the <plugin>Misc</plugin> and <plugin>Config</plugin>
|
||||||
|
plugins (both loaded by default). <plugin>List</plugin>, in
|
||||||
|
this case, defaults to the <plugin>Misc</plugin> plugin, but
|
||||||
|
you may want to get the help for the
|
||||||
|
<botcommand>list</botcommand>
|
||||||
|
command in the <plugin>Config</plugin> plugin. In that
|
||||||
|
case, you'll want to give your command like this:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
supybot: help config list
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Anytime your bot tells you that a given command is defined in
|
||||||
|
several plugins, you'll want to use this syntax
|
||||||
|
(<botcommand>plugin command</botcommand>) to disambiguate which
|
||||||
|
plugin's command you wish to call. For instance, if you
|
||||||
|
wanted to call the <plugin>Config</plugin> plugin's
|
||||||
|
<botcommand>list</botcommand> command, then you'd need to say:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
supybot: config list
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Rather than just <botcommand>list</botcommand>.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>Loading plugins</title>
|
||||||
|
<para>
|
||||||
|
Now that you know how to deal with plugins having commands
|
||||||
|
with the same name, let's take a look at loading other
|
||||||
|
plugins. If you didn't use
|
||||||
|
<script>supybot-wizard</script>, though, you might
|
||||||
|
do well to try it before playing around with loading plugins
|
||||||
|
yourself: each plugin has its own
|
||||||
|
<function>configure</function> function that the wizard uses
|
||||||
|
to setup the appropriate registry entries if the plugin
|
||||||
|
requires any.
|
||||||
|
</para>
|
||||||
|
<sect3>
|
||||||
|
<title>Identifying yourself as the bot owner</title>
|
||||||
|
<para>
|
||||||
|
Now, if you do want to play around with loading plugins,
|
||||||
|
you're going to need to have the
|
||||||
|
<capability>owner</capability>
|
||||||
|
capability. If you ran the wizard, then chances are you
|
||||||
|
already added an owner user for yourself. If not,
|
||||||
|
however, you can add one via the handy-dandy
|
||||||
|
<script>supybot-adduser</script> script. You'll
|
||||||
|
want to run it while the bot is not running (otherwise it
|
||||||
|
could overwrite
|
||||||
|
<script>supybot-adduser</script>'s changes to
|
||||||
|
your user database before you get a chance to reload
|
||||||
|
them). Just follow the prompts, and when it asks if you
|
||||||
|
want to give the user any capabilities, say yes and then
|
||||||
|
give yourself the <capability>owner</capability> capability
|
||||||
|
(without the quotes), restart the bot and you'll be ready
|
||||||
|
to load some plugins!
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Now, in order for the bot to recognize you as your owner
|
||||||
|
user, you'll have to identify with the bot. Open up a
|
||||||
|
query window in your irc client (/query should do it; if
|
||||||
|
not, just know that you can't identify in a channel
|
||||||
|
because it requires sending your password to the bot).
|
||||||
|
Then type this:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
help identify
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
And follow the instructions; the command you send will
|
||||||
|
probably look like this, with your owner user and password
|
||||||
|
replaced:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
identify myowneruser myuserpassword
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
The bot will tell you that “The operation
|
||||||
|
succeeded” if you got the right name and password.
|
||||||
|
Now that you're identified, you can do anything that
|
||||||
|
requires any privilege: that includes all the commands in
|
||||||
|
the <plugin>Owner</plugin> and <plugin>Admin</plugin>
|
||||||
|
plugins, which you may want to take a look at (using the
|
||||||
|
<botcommand>list</botcommand> and
|
||||||
|
<botcommand>help</botcommand>
|
||||||
|
commands, of course). One command in particular that you
|
||||||
|
might want to use (it's from the <plugin>User</plugin>
|
||||||
|
plugin) is the <botcommand>addhostmask</botcommand> command: it
|
||||||
|
lets you add a hostmask to your user record so the bot
|
||||||
|
recognizes you by your hostmask instead of requiring you
|
||||||
|
to always identify with it before it recognizes you. Use
|
||||||
|
the <botcommand>help</botcommand> command to see how this
|
||||||
|
command works. Here's how I often use it:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
addhostmask myuser [hostmask] mypassword
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
You may not have seen that "[hostmask]" syntax before.
|
||||||
|
Supybot allows nested commands, which means that any
|
||||||
|
command's output can be nested as an argument to another
|
||||||
|
command. The hostmask command from the
|
||||||
|
<plugin>Misc</plugin> plugin returns the hostmask of a
|
||||||
|
given nick, but if given no arguments, it returns the
|
||||||
|
hostmask of the person giving the command. So the command
|
||||||
|
above adds the hostmask I'm currently using to my user's
|
||||||
|
list of recognized hostmasks. I'm only required to give
|
||||||
|
<literal>mypassword</literal> if I'm not already
|
||||||
|
identified with the bot.
|
||||||
|
</para>
|
||||||
|
</sect3>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>The <botcommand>more</botcommand> command</title>
|
||||||
|
<para>
|
||||||
|
Another command you might find yourself needing somewhat often
|
||||||
|
is the <botcommand>more</botcommand> command. The IRC protocol
|
||||||
|
limits messages to 512 bytes, 60 or so of which must be
|
||||||
|
devoted to some bookkeeping. Sometimes, however, Supybot
|
||||||
|
wants to send a message that's longer than that. What it
|
||||||
|
does, then, is break it into "chunks" and send the first one,
|
||||||
|
following it with "(X more messages)" where X is how many more
|
||||||
|
chunks there are. To get to these chunks, use the more
|
||||||
|
command. One way to try is to look at the listing of
|
||||||
|
configuration groups for the bot (more on this in the
|
||||||
|
CONFIGURATION document) by giving the command "config list
|
||||||
|
supybot". Last I checked, it'll overflow into a second chunk.
|
||||||
|
When you invoke this command, you should see output like:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<supybot> nick, ident, user, server, password, channels, prefixChars,
|
||||||
|
defaultCapabilities, defaultAllow, defaultIgnore,
|
||||||
|
humanTimestampFormat, externalIP, bracketSyntax, pipeSyntax,
|
||||||
|
followIdentificationThroughNickChanges, alwaysJoinOnInvite,
|
||||||
|
showSimpleSyntax, maxHistoryLength, nickmods, throttleTime,
|
||||||
|
snarfThrottle, threadAllCommands, pingServer, pingInterval,
|
||||||
|
upkeepInterval, flush, (1 more message)
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Now, to see the rest of the output, simply give the command
|
||||||
|
<botcommand>more</botcommand>, and it will show you the rest:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch> more
|
||||||
|
<supybot> httpPeekSize, and defaultSocketTimeout
|
||||||
|
</ircsession>
|
||||||
|
</sect2>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>You're ready!</title>
|
||||||
|
<para>
|
||||||
|
You should now have a solid foundation for using Supybot. Be sure
|
||||||
|
to check the help that is built-in to the bot itself if you have
|
||||||
|
any questions, and enjoy using Supybot!
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
|
585
docs/DocBook/interfaces.sgml
Normal file
585
docs/DocBook/interfaces.sgml
Normal file
@ -0,0 +1,585 @@
|
|||||||
|
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<articleinfo>
|
||||||
|
<authorgroup>
|
||||||
|
<author>
|
||||||
|
<firstname>Jeremiah</firstname>
|
||||||
|
<surname>Fincher</surname>
|
||||||
|
</author>
|
||||||
|
<editor>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
<contrib>DocBook translator</contrib>
|
||||||
|
</editor>
|
||||||
|
</authorgroup>
|
||||||
|
<title>Supybot developer interfaces</title>
|
||||||
|
<revhistory>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.1</revnumber>
|
||||||
|
<date>19 Feb 2004</date>
|
||||||
|
<revremark>Initial Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.2</revnumber>
|
||||||
|
<date>26 Feb 2004</date>
|
||||||
|
<revremark>Converted to Supybot DTD</revremark>
|
||||||
|
</revision>
|
||||||
|
</revhistory>
|
||||||
|
</articleinfo>
|
||||||
|
<sect1>
|
||||||
|
<title>Available interfaces</title>
|
||||||
|
<para>
|
||||||
|
These are the interfaces for some of the objects you'll deal with
|
||||||
|
if you code for Supybot.
|
||||||
|
</para>
|
||||||
|
<sect2>
|
||||||
|
<title><classname>ircmsgs.IrcMsg</classname>
|
||||||
|
<para>
|
||||||
|
This is the object that represents an IRC message. It has
|
||||||
|
several methods and attributes. The most important thing
|
||||||
|
about this class, however, is that it <emphasis>is</emphasis>
|
||||||
|
hashable, and thus <emphasis>cannot</emphasis> be modified.
|
||||||
|
Do not change any attributes; any code that modifies an IRC
|
||||||
|
message is <emphasis>broken</emphasis> and should not exist.
|
||||||
|
</para>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting methods</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>__init__</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
One of the more complex initializers in a class.
|
||||||
|
It can be used in three different ways:
|
||||||
|
</para>
|
||||||
|
<orderedlist numeration="arabic" spacing="normal">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
It can be given a string, as one received
|
||||||
|
from the server, which it will then parse
|
||||||
|
into its separate components and
|
||||||
|
instantiate the class with those
|
||||||
|
components as attributes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
It can be given a command, some (optional)
|
||||||
|
arguments, and a (optional) prefix, and
|
||||||
|
will instantiate the class with those
|
||||||
|
components as attributes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
It can be given, in addition to any of the
|
||||||
|
above arguments, a <varname>msg</varname>
|
||||||
|
keyword argument that will use the
|
||||||
|
attributes of msg as defaults. This
|
||||||
|
exists to make it easier to copy messages,
|
||||||
|
since the class is immutable.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>__str__</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This returns the message in a string form suitable
|
||||||
|
for sending to a server.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>__repr__</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This returns the message in a form suitable for
|
||||||
|
<function>eval()</function>, assuming the name
|
||||||
|
<varname>IrcMsg</varname> is in your namespace and
|
||||||
|
is bound to this class.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
<para>
|
||||||
|
The following attributes are the meat of this class. These
|
||||||
|
are generally what you'll be looking at with
|
||||||
|
<varname>IrcMsg</varname>s.
|
||||||
|
</para>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting attributes</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>command</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This is the command of the
|
||||||
|
<varname>IrcMsg</varname> –
|
||||||
|
<literal>PRIVMSG</literal>,
|
||||||
|
<literal>NOTICE</literal>,
|
||||||
|
<literal>WHOIS</literal>,
|
||||||
|
etc.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>args</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This is a tuple of the arguments to the
|
||||||
|
<varname>IrcMsg</varname>. Some messages have
|
||||||
|
arguments, some don't, depending on what command
|
||||||
|
they are. You are, of course, always assured that
|
||||||
|
<varname>args</varname> exists and is a tuple,
|
||||||
|
though it might be empty.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>prefix</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This is the hostmask of the person/server the
|
||||||
|
message is from. In general, you won't be setting
|
||||||
|
this on your outgoing messages, but incoming
|
||||||
|
messages will always have one. This is the whole
|
||||||
|
hostmask; if the message was received from a
|
||||||
|
server, it'll be the server's hostmask; if the
|
||||||
|
message was received from a user, it'll be the
|
||||||
|
whole user hostmask. In that case, however, it's
|
||||||
|
also parsed out into the
|
||||||
|
<varname>nick</varname>/<varname>user</varname>/<varname>host</varname>
|
||||||
|
attributes, which are probably more useful to
|
||||||
|
check for many purposes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>nick</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If the message was sent by a user, this will be
|
||||||
|
the nick of the user. If it was sent by a server,
|
||||||
|
this will be the server's name (something like
|
||||||
|
<literal>calvino.freenode.net</literal> or
|
||||||
|
similar).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>user</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If the message was sent by a user, this will be
|
||||||
|
the user string of the user – what they put
|
||||||
|
into their IRC client for their "full name." If
|
||||||
|
it was sent by a server, it'll be the server's
|
||||||
|
name, again.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>host</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If the message was sent by a user, this will be
|
||||||
|
the host portion of their hostmask. If it was
|
||||||
|
sent by a server, it'll be the server's name (yet
|
||||||
|
again :))
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title><classname>irclib.Irc</classname>
|
||||||
|
<para>
|
||||||
|
This is the object to handle everything about IRC except the
|
||||||
|
actual connection to the server itself.
|
||||||
|
(<emphasis>NOTE</emphasis> that the object actually received
|
||||||
|
by commands in subclasses of
|
||||||
|
<classname>callbacks.Privmsg</classname> is an
|
||||||
|
<classname>IrcObjectProxy</classname>, which is described
|
||||||
|
later. It augments the following interface with several
|
||||||
|
methods of its own to help plugin authors.)
|
||||||
|
</para>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting methods</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>queueMsg</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Queues a message for sending to the server. The
|
||||||
|
queue is generally FIFO, but it does prioritize
|
||||||
|
messages based on their command.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>sendMsg</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Queues a message for sending to the server prior
|
||||||
|
to any messages in the normal queue. This is
|
||||||
|
exactly a FIFO queue, no reordering is done at
|
||||||
|
all.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<!--<note>
|
||||||
|
<para>
|
||||||
|
The following two methods are the most important for
|
||||||
|
people writing new <varname>IrcDriver</varname>s.
|
||||||
|
Otherwise, you really don't need to pay attention to
|
||||||
|
them.
|
||||||
|
</para>
|
||||||
|
</note>-->
|
||||||
|
<varlistentry>
|
||||||
|
<term>feedMsg</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Feeds the <varname>Irc</varname> object a message
|
||||||
|
for it handle appropriately, as well as passing it
|
||||||
|
on to callbacks.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>takeMsg</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If the <varname>Irc</varname> object has a message
|
||||||
|
it's ready to send to the server, this will return
|
||||||
|
it. Otherwise, it will return
|
||||||
|
<literal>None</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<!--<note>
|
||||||
|
<para>
|
||||||
|
The next several methods are of far more marginal
|
||||||
|
utility. But someone may need them, so they're
|
||||||
|
documented here.
|
||||||
|
</para>
|
||||||
|
</note>-->
|
||||||
|
<varlistentry>
|
||||||
|
<term>addCallback</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Takes a callback to add to the list of callbacks
|
||||||
|
in the <varname>Irc</varname> object. See the
|
||||||
|
interface for <varname>IrcCallback</varname> for
|
||||||
|
more information.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>getCallback</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Gets a callback by name, if it is in the
|
||||||
|
<varname>Irc</varname> object's list of callbacks.
|
||||||
|
If it it isn't, returns <literal>None</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>removeCallback</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Removes a callback by name. Returns a list of the
|
||||||
|
callbacks removed (since it is technically
|
||||||
|
possible to have multiple callbacks with the same
|
||||||
|
name. This list may be empty.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>__init__</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Requires a <varname>nick</varname>. Optional
|
||||||
|
arguments include <varname>user</varname> and
|
||||||
|
<varname>ident</varname>, which default to the
|
||||||
|
nick given, <varname>password</varname>, which
|
||||||
|
defaults to the empty password, and
|
||||||
|
<varname>callbacks</varname>, a list of callbacks
|
||||||
|
(which defaults to nothing, an empty list).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>reset</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Resets the <varname>Irc</varname> object to its
|
||||||
|
original state, as well as sends a
|
||||||
|
<function>reset()</function> to every callbacks.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>die</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Kills the IRC object and all its callbacks.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting attributes</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>nick</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The current nick of the bot.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>prefix</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The current prefix of the bot.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>server</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The current server the bot is connected to.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>network</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The current network name the bot is connected to.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>afterConnect</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>False</literal> until the bot has
|
||||||
|
received a command sent after the connection is
|
||||||
|
finished – 376, 377, or 422.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>state</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
An <varname>IrcState</varname> object for this
|
||||||
|
particular connection. See the interface for the
|
||||||
|
<varname>IrcState</varname> object for more
|
||||||
|
information.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title><classname>irclib.IrcCallback</classname></title>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting Methods</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>name</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Returns the name of the callback. The default
|
||||||
|
implementation simply returns the name of the
|
||||||
|
class.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>__call__</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Called by the <varname>Irc</varname> object with
|
||||||
|
itself and the message whenever a message is fed
|
||||||
|
to the <varname>Irc</varname> object. Nothing is
|
||||||
|
done with the return value.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>inFilter</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Called by the <varname>Irc</varname> object with
|
||||||
|
itself and the message whenever a message is fed
|
||||||
|
to the <varname>Irc</varname> object. The return
|
||||||
|
value should be an <varname>IrcMsg</varname>
|
||||||
|
object to be passed to the next callback in the
|
||||||
|
<varname>Irc</varname>'s list of callbacks. If
|
||||||
|
<literal>None</literal> is returned, all
|
||||||
|
processing stops. This gives callbacks an
|
||||||
|
oppurtunity to "filter" incoming messages before
|
||||||
|
general callbacks are given them.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>outFilter</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Basically equivalent to
|
||||||
|
<varname>inFilter</varname>, except instead of
|
||||||
|
being called on messages as they enter the
|
||||||
|
<varname>Irc</varname> object, it's called on
|
||||||
|
messages as they leave the <varname>Irc</varname>
|
||||||
|
object.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>die</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Called when the parent <varname>Irc</varname> is
|
||||||
|
told to die. This gives callbacks an oppurtunity
|
||||||
|
to close open files, network connections, or
|
||||||
|
databases before they're deleted.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>reset</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Called when the parent <varname>Irc</varname> is
|
||||||
|
told to reset (which is generally when
|
||||||
|
reconnecting to the server). Most callbacks don't
|
||||||
|
need to define this.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting attributes</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>priority</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Determines the priority of the callback in the
|
||||||
|
<varname>Irc</varname> object's list of callbacks.
|
||||||
|
Defaults to <literal>99</literal>; the valid range
|
||||||
|
includes <literal>0</literal> through
|
||||||
|
<literal>sys.maxint-1</literal> (don't use
|
||||||
|
<literal>sys.maxint</literal> itself, that's
|
||||||
|
reserved for the <varname>Misc</varname> plugin).
|
||||||
|
The lower the number, the higher the priority.
|
||||||
|
High priority callbacks are called earlier in the
|
||||||
|
<varname>inFilter</varname> cycle, earlier in the
|
||||||
|
<varname>__call__</varname> cycle, and later in
|
||||||
|
the <varname>outFilter</varname> cycle –
|
||||||
|
basically, they're given the first chances on the
|
||||||
|
way in and the last chances on the way out.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title><classname>callbacks.IrcObjectProxy</classname></title>
|
||||||
|
<para>
|
||||||
|
<classname>IrcObjectProxy</classname> is a proxy for an
|
||||||
|
<classname>irclib.Irc</classname> instance that serves to
|
||||||
|
provide a much fuller interface for handling replies and
|
||||||
|
errors as well as to handle the nesting of commands. This is
|
||||||
|
what you'll be dealing with almost all the time when writing
|
||||||
|
commands; when writing <function>doCommand</function> methods
|
||||||
|
(the kind you read about in the interface description of
|
||||||
|
<classname>irclib.IrcCallback</classname>) you'll be dealing
|
||||||
|
with plain old <classname>irclib.Irc</classname> objects.
|
||||||
|
</para>
|
||||||
|
<variablelist>
|
||||||
|
<title>Interesting methods</title>
|
||||||
|
<varlistentry>
|
||||||
|
<term>reply</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Called to reply to the current message with a
|
||||||
|
string that is to be the reply. Uses the
|
||||||
|
<function>queueMsg</function> command discussed in
|
||||||
|
the <classname>irclib.Irc</classname> section.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>replySuccess</term>
|
||||||
|
<term>replyError</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
These reply with the configured responses for
|
||||||
|
success and generic error, respectively. If an
|
||||||
|
additional argument is given, it's (intelligently)
|
||||||
|
appended to the generic message to be more
|
||||||
|
specific.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>error</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Called to send an error reply to the current
|
||||||
|
message; not only does the response indicate an
|
||||||
|
error, but commands that error out break the
|
||||||
|
nested-command chain, which is generally useful
|
||||||
|
for not confusing the user :)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>errorNoCapability</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Like <function>error</function>, except it accepts
|
||||||
|
the capability that's missing and integrates it
|
||||||
|
into the configured error message for such things.
|
||||||
|
Also accepts an additional string for a more
|
||||||
|
descriptive message, if that's what you want.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>errorPossibleBug</term>
|
||||||
|
<term>errorNotRegistered</term>
|
||||||
|
<term>errorNoUser</term>
|
||||||
|
<term>errorRequiresPrivacy</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
These methods reply with the appropriate
|
||||||
|
configured error message for the conditions in
|
||||||
|
their names; they all take an additional arguments
|
||||||
|
to be more specific about the conditions they
|
||||||
|
indicate, but this argument is very rarely
|
||||||
|
necessary.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>getRealIrc</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Returns the actual <classname>Irc</classname>
|
||||||
|
object being proxied for.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect2>
|
||||||
|
</sect1>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
|
633
docs/DocBook/plugin-example.sgml
Normal file
633
docs/DocBook/plugin-example.sgml
Normal file
@ -0,0 +1,633 @@
|
|||||||
|
<!DOCTYPE article SYSTEM "supybot.dtd">
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<articleinfo>
|
||||||
|
<authorgroup>
|
||||||
|
<author>
|
||||||
|
<firstname>Jeremiah</firstname>
|
||||||
|
<surname>Fincher</surname>
|
||||||
|
</author>
|
||||||
|
<editor>
|
||||||
|
<firstname>Daniel</firstname>
|
||||||
|
<surname>DiPaolo</surname>
|
||||||
|
<contrib>DocBook translator</contrib>
|
||||||
|
</editor>
|
||||||
|
</authorgroup>
|
||||||
|
<title>Supybot plugin author example</title>
|
||||||
|
<revhistory>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.1</revnumber>
|
||||||
|
<date>13 Sep 2003</date>
|
||||||
|
<revremark>Initial revision</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.2</revnumber>
|
||||||
|
<date>14 Sep 2003</date>
|
||||||
|
<revremark>Converted to DocBook</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.3</revnumber>
|
||||||
|
<date>24 Nov 2003</date>
|
||||||
|
<revremark>
|
||||||
|
Updated to match EXAMPLE included with 0.75.0
|
||||||
|
</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.4</revnumber>
|
||||||
|
<date>26 Feb 2004</date>
|
||||||
|
<revremark>Converted to use Supybot DTD</revremark>
|
||||||
|
</revision>
|
||||||
|
<revision>
|
||||||
|
<revnumber>0.5</revnumber>
|
||||||
|
<date>4 Sep 2004</date>
|
||||||
|
<revremark>Updated Docbook translation</revremark>
|
||||||
|
</revision>
|
||||||
|
</revhistory>
|
||||||
|
</articleinfo>
|
||||||
|
<sect1>
|
||||||
|
<title>Introduction</title>
|
||||||
|
<para>
|
||||||
|
Ok, so you want to write a callback for supybot. Good, then this
|
||||||
|
is the place to be. We're going to start from the top (the
|
||||||
|
highest level, where supybot code does the most work for you) and
|
||||||
|
move lower after that.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So have you used supybot? If not, you need to go use it, get a
|
||||||
|
feel for it, see how the various commands work and such.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So now that we know you've used supybot, we'll start getting into
|
||||||
|
details.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
<sect1>
|
||||||
|
<title>Creating your own plugin</title>
|
||||||
|
<sect2>
|
||||||
|
<title>
|
||||||
|
Using <script>scripts/newplugin.py</script>
|
||||||
|
</title>
|
||||||
|
<para>
|
||||||
|
First, the easiest way to start writing a module is to use the
|
||||||
|
wizard provided, <script>scripts/newplugin.py</script>.
|
||||||
|
Here's an example session:
|
||||||
|
</para>
|
||||||
|
<screen>
|
||||||
|
functor% scripts/newplugin.py
|
||||||
|
What should the name of the plugin be? Random
|
||||||
|
Supybot offers two major types of plugins: command-based and regexp-
|
||||||
|
based. Command-based plugins are the kind of plugins you've seen most
|
||||||
|
when you've used supybot. They're also the most featureful and
|
||||||
|
easiest to write. Commands can be nested, for instance, whereas
|
||||||
|
regexp-based callbacks can't do nesting. That doesn't mean that
|
||||||
|
you'll never want regexp-based callbacks. They offer a flexibility
|
||||||
|
that command-based callbacks don't offer; however, they don't tie into
|
||||||
|
the whole system as well. If you need to combine a command-based
|
||||||
|
callback with some regexp-based methods, you can do so by subclassing
|
||||||
|
callbacks.PrivmsgCommandAndRegexp and then adding a class-level
|
||||||
|
attribute "regexps" that is a sets.Set of methods that are regexp-
|
||||||
|
based. But you'll have to do that yourself after this wizard is
|
||||||
|
finished :)
|
||||||
|
Do you want a command-based plugin or a regexp-based plugin? [command/
|
||||||
|
regexp] command
|
||||||
|
Sometimes you'll want a callback to be threaded. If its methods
|
||||||
|
(command or regexp-based, either one) will take a signficant amount
|
||||||
|
of time to run, you'll want to thread them so they don't block
|
||||||
|
the entire bot.
|
||||||
|
|
||||||
|
Does your plugin need to be threaded? [y/n] n
|
||||||
|
Your new plugin template is in plugins/Random.py
|
||||||
|
functor%
|
||||||
|
</screen>
|
||||||
|
<para>
|
||||||
|
So that's what it looks like. Now let's look at the source
|
||||||
|
code (if you'd like to look at it in your programming editor,
|
||||||
|
the whole plugin is available as
|
||||||
|
<filename>examples/Random.py</filename>):
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
###
|
||||||
|
# Copyright (c) 2004, Jeremiah Fincher
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
"""
|
||||||
|
Add the module docstring here. This will be used by the setup.py script.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__revision__ = "$Id$"
|
||||||
|
__author__ = ''
|
||||||
|
|
||||||
|
import supybot.plugins as plugins
|
||||||
|
|
||||||
|
import supybot.conf as conf
|
||||||
|
import supybot.utils as utils
|
||||||
|
import supybot.privmsgs as privmsgs
|
||||||
|
import supybot.callbacks as callbacks
|
||||||
|
|
||||||
|
|
||||||
|
def configure(onStart, afterConnect, advanced):
|
||||||
|
# This will be called by setup.py to configure this module. Advanced is
|
||||||
|
# a bool that specifies whether the user identified himself as an advanced
|
||||||
|
# user or not. You should effect your configuration by manipulating the
|
||||||
|
# registry as appropriate.
|
||||||
|
from questions import expect, anything, something, yn
|
||||||
|
conf.registerPlugin('Random', True)
|
||||||
|
|
||||||
|
class Random(callbacks.Privmsg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
Class = Random
|
||||||
|
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||||
|
</programlisting>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>Customizing the boilerplate code</title>
|
||||||
|
<para>
|
||||||
|
So a few notes, before we customize it.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You'll probably want to change the copyright notice to be your
|
||||||
|
name. It wouldn't stick even if you kept my name, so you
|
||||||
|
might as well :)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Describe what you want the plugin to do in the docstring.
|
||||||
|
This is used in <script>scripts/setup.py</script> in
|
||||||
|
order to explain to the user the purpose of the module. It's
|
||||||
|
also returned when someone asks the bot for help for a given
|
||||||
|
module (instead of help for a certain command). We'll change
|
||||||
|
this one to <literal>"Lots of stuff relating to random
|
||||||
|
numbers."</literal>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Then there are the imports. The
|
||||||
|
<module>callbacks</module>
|
||||||
|
module is used (the class you're given subclasses
|
||||||
|
<classname>callbacks.Privmsg</classname>) but the
|
||||||
|
<module>privmsgs</module> module isn't used. That's
|
||||||
|
alright; we can almost guarantee you'll use it, so we go ahead
|
||||||
|
and add the import to the template.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Then you see a <function>configure</function> function. This
|
||||||
|
the function that's called when users decide to add your
|
||||||
|
module in <script>scripts/setup.py</script>. You'll
|
||||||
|
note that by default it simply registers the plugin to be
|
||||||
|
automatically loaded on startup. For many
|
||||||
|
plugins this is all you need; for more complex plugins, you
|
||||||
|
might need to ask questions and add commands based on the
|
||||||
|
answers.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>Digging in: customizing the plugin class</title>
|
||||||
|
<para>
|
||||||
|
Now comes the meat of the plugin: the plugin class.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
What you're given is a skeleton: a simple subclass of
|
||||||
|
<classname>callbacks.Privmsg</classname> for you to start
|
||||||
|
with. Now let's add a command.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
I don't know what you know about random number generators, but
|
||||||
|
the short of it is that they start at a certain number (a
|
||||||
|
seed) and they continue (via some somewhat
|
||||||
|
complicated/unpredictable algorithm) from there. This seed
|
||||||
|
(and the rest of the sequence, really) is all nice and
|
||||||
|
packaged up in Python's <module>random</module> module, the
|
||||||
|
<varname>Random</varname> object. So the first thing we're
|
||||||
|
going to have to do is give our plugin a
|
||||||
|
<varname>Random</varname> object.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Normally, when we want to give instances of a class an object,
|
||||||
|
we'll do so in the <function>__init__</function> method. And
|
||||||
|
that works great for plugins, too. The one thing you have to
|
||||||
|
be careful of is that you call the superclass
|
||||||
|
<function>__init__</function> method at the end of your own
|
||||||
|
<function>__init__</function>. So to add this
|
||||||
|
<classname>random.Random</classname> object to our plugin, we
|
||||||
|
can replace the <keyword>pass</keyword> statement with
|
||||||
|
this:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def __init__(self):
|
||||||
|
self.rng = random.Random()
|
||||||
|
callbacks.Privmsg.__init__(self)
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
(<varname>rng</varname>is an abbreviation for "random number
|
||||||
|
generator," in case you were curious)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Do be careful not to give your <function>__init__</function>
|
||||||
|
any arguments (other than <varname>self</varname>, of course).
|
||||||
|
There's no way anything will ever get to them! If you have
|
||||||
|
some sort of initial values you need to get to your plugin
|
||||||
|
before it can do anything interesting, you should get those
|
||||||
|
values from the registry.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
There's an easier way to get our plugin to have its own rng
|
||||||
|
than to define an <function>__init__</function>. Plugins are
|
||||||
|
unique among classes because we're always certain that there
|
||||||
|
will only be one instance -- supybot doesn't allow us to load
|
||||||
|
multiple instances of a single plugin. So instead of adding
|
||||||
|
the rng in <function>__init__</function>, we can just add it
|
||||||
|
as a attribute to the class itself. Like so (replacing the
|
||||||
|
<function>pass</function> statement again):
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
rng = random.Random()
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
And we save two lines of code and make our code a little more
|
||||||
|
clear :)
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Now that we have an RNG, we need some way to get random
|
||||||
|
numbers. So first, we'll add a command that simply gets the
|
||||||
|
next random number and gives it back to the user. It takes no
|
||||||
|
arguments, of course (what would you give it?). Here's the
|
||||||
|
command, and I'll follow that with the explanation of what
|
||||||
|
each part means.
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def random(self, irc, msg, args):
|
||||||
|
"""takes no arguments
|
||||||
|
|
||||||
|
Returns the next random number generated by the random number
|
||||||
|
generator.
|
||||||
|
"""
|
||||||
|
irc.reply(str(self.rng.random()))
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
And that's it! Pretty simple, huh? Anyway, you're probably
|
||||||
|
wondering what all that <emphasis>means</emphasis>. We'll
|
||||||
|
start with the <keyword>def</keyword> statement:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def random(self, irc, msg, args):
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
What that does is define a command
|
||||||
|
<botcommand>random</botcommand>. You can call it by saying
|
||||||
|
"@random" (or whatever prefix character your specific bot
|
||||||
|
uses). The arguments are a bit less obvious.
|
||||||
|
<varname>self</varname> is self-evident (hah!).
|
||||||
|
<varname>irc</varname> is the <classname>Irc</classname>
|
||||||
|
object passed to the command; <varname>msg</varname> is the
|
||||||
|
original <classname>IrcMsg</classname> object. But you're
|
||||||
|
really not going to have to deal with either of these too much
|
||||||
|
(with the exception of calling <function>irc.reply</function>
|
||||||
|
or <function>irc.error</function>). What you're
|
||||||
|
<emphasis>really</emphasis> interested in is the
|
||||||
|
<varname>args</varname> arg. That is a list of all the
|
||||||
|
arguments passed to your command, pre-parsed and already
|
||||||
|
evaluated (i.e., you never have to worry about nested
|
||||||
|
commands, or handling double quoted strings, or splitting on
|
||||||
|
whitespace – the work has already been done for you).
|
||||||
|
You can read about the <classname>Irc</classname> object in
|
||||||
|
<filename>irclib.py</filename> (you won't find
|
||||||
|
<function>.reply</function> or <function>.error</function>
|
||||||
|
there, though, because you're actually getting an
|
||||||
|
<classname>IrcObjectProxy</classname>, but that's beyond the
|
||||||
|
level we want to describe here :)). You can read about the
|
||||||
|
<varname>msg</varname> object in
|
||||||
|
<filename>ircmsgs.py</filename>. But again, you'll very
|
||||||
|
rarely be using these objects.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
(In case you're curious, the answer is yes, you
|
||||||
|
<emphasis>must</emphasis> name your arguments <varname>(self,
|
||||||
|
irc, msg, args)</varname>. The names of those arguments is
|
||||||
|
one of the ways that supybot uses to determine which methods
|
||||||
|
in a plugin class are commands and which aren't. And while
|
||||||
|
we're talking about naming restrictions, all your commands
|
||||||
|
should be named in all-lowercase with no underscores. Before
|
||||||
|
calling a command, supybot always converts the command name to
|
||||||
|
lowercase and removes all dashes and underscores. On the
|
||||||
|
other hand, you now know an easy way to make sure a method is
|
||||||
|
never called (even if its arguments are <varname>(self, irc,
|
||||||
|
msg, args)</varname>, however unlikely that may be). Just
|
||||||
|
name it with an underscore or an uppercase letter in it :))
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You'll also note that the docstring is odd. The wonderful
|
||||||
|
thing about the supybot framework is that it's easy to write
|
||||||
|
complete commands with help and everything: the docstring
|
||||||
|
<emphasis>is</emphasis> the help! Given the above docstring,
|
||||||
|
this is what a supybot does:
|
||||||
|
</para>
|
||||||
|
<ircsession>
|
||||||
|
<jemfinch> @help random
|
||||||
|
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||||
|
next random number from the random number generator.
|
||||||
|
</ircsession>
|
||||||
|
<para>
|
||||||
|
Now on to the actual body of the function:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
irc.reply(msg, str(self.rng.random()))
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
<function>irc.reply</function> simply takes one simple
|
||||||
|
argument: a string The string is the reply to be sent. Don't
|
||||||
|
worry about length restrictions or anything
|
||||||
|
– if the string you want to send is too big for an IRC
|
||||||
|
message (and oftentimes that turns out to be the case :)) the
|
||||||
|
Supybot framework handles that entirely transparently to you.
|
||||||
|
Do make sure, however, that you give
|
||||||
|
<function>irc.reply</function> a string. It doesn't take
|
||||||
|
anything else (sometimes even unicode fails!). That's why we
|
||||||
|
have "str(self.rng.random())" instead of simply
|
||||||
|
"self.rng.random()" – we had to give
|
||||||
|
<function>irc.reply</function> a string.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Anyway, now that we have an RNG, we have a need for seed! Of
|
||||||
|
course, Python gives us a good seed already (it uses the
|
||||||
|
current time as a seed if we don't give it one) but users
|
||||||
|
might want to be able to repeat "random" sequences, so letting
|
||||||
|
them set the seed is a good thing. So we'll add a seed
|
||||||
|
command to give the RNG a specific seed:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def seed(self, irc, msg, args):
|
||||||
|
"""<seed>
|
||||||
|
|
||||||
|
Sets the seed of the random number generator. <seed> must be
|
||||||
|
an int or a long.
|
||||||
|
"""
|
||||||
|
seed = privmsgs.getArgs(args)
|
||||||
|
try:
|
||||||
|
seed = long(seed)
|
||||||
|
except ValueError:
|
||||||
|
# It wasn't a valid long!
|
||||||
|
irc.error(msg, '<seed> must be a valid int or long.')
|
||||||
|
return
|
||||||
|
self.rng.seed(seed)
|
||||||
|
irc.replySuccess()
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
So this one's a bit more complicated. But it's still pretty
|
||||||
|
simple. The method name is <botcommand>seed</botcommand> so
|
||||||
|
that'll be the command name. The arguments are the same, the
|
||||||
|
docstring is of the same form, so we don't need to go over
|
||||||
|
that again. The body of the function, however, is
|
||||||
|
significantly different.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
<function>privmsgs.getArgs</function> is a function you're
|
||||||
|
going to be seeing a lot of when you write plugins for
|
||||||
|
supybot. What it does is basically give you the right number
|
||||||
|
of arguments for your comamnd. In this case, we want one
|
||||||
|
argument. But we might have been given any number of
|
||||||
|
arguments by the user. So
|
||||||
|
<function>privmsgs.getArgs</function> joins them
|
||||||
|
appropriately, leaving us with one single "seed" argument (by
|
||||||
|
default, it returns one argument as a single value; more
|
||||||
|
arguments are returned in a tuple/list). Yes, we could've
|
||||||
|
just said "seed = args[0]" and gotten the first argument, but
|
||||||
|
what if the user didn't pass us an argument at all? Then
|
||||||
|
we've got to catch the <classname>IndexError</classname> from
|
||||||
|
<varname>args[0]</varname> and complain to the user about it.
|
||||||
|
<function>privmsgs.getArgs</function>, on the other hand,
|
||||||
|
handles all that for us. If the user didn't give us enough
|
||||||
|
arguments, it'll reply with the help string for the command,
|
||||||
|
thus saving us the effort.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So we have the seed from
|
||||||
|
<function>privmsgs.getArgs</function>. But it's a string.
|
||||||
|
The next three lines is pretty darn obvious: we're just
|
||||||
|
converting the string to a int of some sort. But if it's not,
|
||||||
|
that's when we're going to call
|
||||||
|
<function>irc.error</function>. It has the same interface as
|
||||||
|
we saw before in <function>irc.reply</function>, but it makes
|
||||||
|
sure to remind the user that an error has been encountered
|
||||||
|
(currently, that means it puts <literal>"Error: "</literal> at
|
||||||
|
the beginning of the message). After erroring, we return.
|
||||||
|
It's important to remember this <keyword>return</keyword>
|
||||||
|
here; otherwise, we'll just keep going down through the
|
||||||
|
function and try to use this <varname>seed</varname> variable
|
||||||
|
that never got assigned. A good general rule of thumb is that
|
||||||
|
any time you use <function>irc.error</function>, you'll want
|
||||||
|
to return immediately afterwards.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Then we set the seed – that's a simple function on our
|
||||||
|
rng object. Assuming that succeeds (and doesn't raise an
|
||||||
|
exception, which it shouldn't, because we already read the
|
||||||
|
documentation and know that it should work) we reply to say
|
||||||
|
that everything worked fine. That's what
|
||||||
|
<function>irc.replySuccess</function> says. By default, it
|
||||||
|
has the very dry (and appropriately robot-like) "The operation
|
||||||
|
succeeded." but you're perfectly welcome to customize it
|
||||||
|
yourself – the registry was written to be modified!
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So that's a bit more complicated command. But we still
|
||||||
|
haven't dealt with multiple arguments. Let's do that
|
||||||
|
next.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So these random numbers are useful, but they're not the kind
|
||||||
|
of random numbers we usually want in Real Life. In Real Life,
|
||||||
|
we like to tell someone to "pick a number between 1 and 10."
|
||||||
|
So let's write a function that does that. Of course, we won't
|
||||||
|
hardcode the 1 or the 10 into the function, but we'll take
|
||||||
|
them as arguments. First the function:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def range(self, irc, msg, args):
|
||||||
|
"""<start> <end>
|
||||||
|
|
||||||
|
Returns a number between <start> and <end>, inclusive (i.e., the number
|
||||||
|
can be either of the endpoints.
|
||||||
|
"""
|
||||||
|
(start, end) = privmsgs.getArgs(args, required=2)
|
||||||
|
try:
|
||||||
|
end = int(end)
|
||||||
|
start = int(start)
|
||||||
|
except ValueError:
|
||||||
|
irc.error(msg, '<start> and <end> must both be integers.')
|
||||||
|
return
|
||||||
|
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||||
|
irc.reply(msg, str(self.rng.randrange(start, end+1)))
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
Pretty simple. This is becoming old hat by now. The only new
|
||||||
|
thing here is the call to
|
||||||
|
<function>privmsgs.getArgs</function>. We have to make sure,
|
||||||
|
since we want two values, to pass a keyword parameter
|
||||||
|
"required" into <function>privmsgs.getArgs</function>. Of
|
||||||
|
course, <function>privmsgs.getArgs</function> handles all the
|
||||||
|
checking for missing arguments and whatnot so we don't have
|
||||||
|
to.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The <classname>Random</classname> object we're using offers us
|
||||||
|
a "sample" method that takes a sequence and a number (we'll
|
||||||
|
call it <varname>N</varname>) and returns a list of
|
||||||
|
<varname>N</varname> items taken randomly from the sequence.
|
||||||
|
So I'll show you an example that takes advantage of multiple
|
||||||
|
arguments but doesn't use
|
||||||
|
<function>privmsgs.getArgs</function> (and thus has to handle
|
||||||
|
its own errors if the number of arguments isn't right).
|
||||||
|
Here's the code:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def sample(self, irc, msg, args):
|
||||||
|
"""<number of items> [<text> ...]
|
||||||
|
|
||||||
|
Returns a sample of the <number of items> taken from the remaining
|
||||||
|
arguments. Obviously <number of items> must be less than the number
|
||||||
|
of arguments given.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
n = int(args.pop(0))
|
||||||
|
except IndexError: # raised by .pop(0)
|
||||||
|
raise callbacks.ArgumentError
|
||||||
|
except ValueError:
|
||||||
|
irc.error('<number of items> must be an integer.')
|
||||||
|
return
|
||||||
|
if n > len(args):
|
||||||
|
irc.error('<number of items> must be less than the number '
|
||||||
|
'of arguments.')
|
||||||
|
return
|
||||||
|
sample = self.rng.sample(args, n)
|
||||||
|
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
Most everything here is familiar. The difference between this
|
||||||
|
and the previous examples is that we're dealing with
|
||||||
|
<varname>args</varname> directly, rather than through
|
||||||
|
<function>getArgs</function>. Since we already have the
|
||||||
|
arguments in a list, it doesn't make any sense to have
|
||||||
|
<function>privmsgs.getArgs</function> smush them all together
|
||||||
|
into a big long string that we'll just have to re-split. But
|
||||||
|
we still want the nice error handling of
|
||||||
|
<function>privmsgs.getArgs</function>. So what do we do? We
|
||||||
|
raise <classname>callbacks.ArgumentError</classname>! That's
|
||||||
|
the secret juju that <function>privmsgs.getArgs</function> is
|
||||||
|
doing; now we're just doing it ourself. Someone up our
|
||||||
|
callchain knows how to handle it so a neat error message is
|
||||||
|
returned. So in this function, if
|
||||||
|
<function>.pop(0)</function> fails, we weren't given enough
|
||||||
|
arguments and thus need to tell the user how to call us.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So we have the args, we have the number, we do a simple call
|
||||||
|
to <function>random.sample</function> and then we do this
|
||||||
|
funky <function>utils.commaAndify</function> to it. Yeah, so
|
||||||
|
I was running low on useful names :) Anyway, what it does is
|
||||||
|
take a list of strings and return a string with them joined by
|
||||||
|
a comma, the last one being joined with a comma and "and". So
|
||||||
|
the list ['foo', 'bar', 'baz'] becomes "foo, bar, and baz".
|
||||||
|
It's pretty useful for showing the user lists in a useful
|
||||||
|
form. We map the strings with <function>repr()</function>
|
||||||
|
first just to surround them with quotes.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So we have one more example. Yes, I hear your groans, but
|
||||||
|
it's pedagogically useful :) This time we're going to write a
|
||||||
|
command that makes the bot roll a die. It'll take one
|
||||||
|
argument (the number of sides on the die) and will respond
|
||||||
|
with the equivalent of "/me rolls a __" where __ is the number
|
||||||
|
the bot rolled. So here's the code:
|
||||||
|
</para>
|
||||||
|
<programlisting>
|
||||||
|
def diceroll(self, irc, msg, args):
|
||||||
|
"""[<number of sides>]
|
||||||
|
|
||||||
|
Rolls a die with <number of sides> sides. The default number
|
||||||
|
of sides is 6.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
n = privmsgs.getArgs(args, required=0, optional=1)
|
||||||
|
if not n:
|
||||||
|
n = 6
|
||||||
|
n = int(n)
|
||||||
|
except ValueError:
|
||||||
|
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
||||||
|
return
|
||||||
|
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||||
|
irc.reply(s, action=True)
|
||||||
|
</programlisting>
|
||||||
|
<para>
|
||||||
|
There's a lot of stuff you haven't seen before in there. The
|
||||||
|
most important, though, is the first thing you'll notice
|
||||||
|
that's different: the <function>privmsg.getArgs</function>
|
||||||
|
call. Here we're offering a default argument in case the user
|
||||||
|
is too lazy to supply one (or just wants a nice, standard
|
||||||
|
six-sided die :)) <function>privmsgs.getArgs</function>
|
||||||
|
supports that; we'll just tell it that we don't
|
||||||
|
<emphasis>need</emphasis> any arguments (via
|
||||||
|
<varname>required=0</varname>) and that we <emphasis>might
|
||||||
|
like</emphasis> one argument (<varname>optional=1</varname>).
|
||||||
|
If the user provides an argument, we'll get it -- if they
|
||||||
|
don't, we'll just get an empty string. Hence the "if not n: n
|
||||||
|
= 6", where we provide the default.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You'll also note that <function>irc.reply</function> was given
|
||||||
|
a keyword argument here, <varname>action</varname>. This
|
||||||
|
means that the reply is to be made as an action rather than a
|
||||||
|
normal reply.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
So that's our plugin. 5 commands, each building in
|
||||||
|
complexity. You should now be able to write most anything you
|
||||||
|
want to do in Supybot. Except regexp-based plugins, but
|
||||||
|
that's a story for another day (and those aren't nearly as
|
||||||
|
cool as these command-based callbacks anyway :)). Now we need
|
||||||
|
to flesh it out to make it a full-fledged plugin.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
<sect2>
|
||||||
|
<title>Using the registry in your plugin</title>
|
||||||
|
<para>
|
||||||
|
TODO: Describe the registry and how to write a proper plugin
|
||||||
|
configure function.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
<para>
|
||||||
|
We've written our own plugin from scratch (well, from the
|
||||||
|
boilerplate that we got from
|
||||||
|
<script>scripts/newplugin.py</script> :)) and
|
||||||
|
survived! Now go write more plugins for supybot, and send
|
||||||
|
them to me so I can use them too :)
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
</article>
|
46
docs/DocBook/supybot-html.dsl
Normal file
46
docs/DocBook/supybot-html.dsl
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
(define %stylesheet% "../stylesheets/supybot.css")
|
||||||
|
|
||||||
|
(element botcommand
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "botcommand"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element plugin
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "plugin"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element flag
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "flag"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element nick
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "nick"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element capability
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "capability"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element registrygroup
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "registrygroup"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element ircsession
|
||||||
|
(make element gi: "pre"
|
||||||
|
attributes: '(("class" "ircsession"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element script
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "script"))
|
||||||
|
(process-children)))
|
||||||
|
|
||||||
|
(element channel
|
||||||
|
(make element gi: "span"
|
||||||
|
attributes: '(("class" "channel"))
|
||||||
|
(process-children)))
|
43
docs/DocBook/supybot-print.dsl
Normal file
43
docs/DocBook/supybot-print.dsl
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
(define %mono-font-family% "Courier New")
|
||||||
|
|
||||||
|
(element botcommand
|
||||||
|
(make sequence
|
||||||
|
font-family-name: %mono-font-family%))
|
||||||
|
|
||||||
|
(element plugin
|
||||||
|
(make sequence
|
||||||
|
font-weight: 'bold))
|
||||||
|
|
||||||
|
(element flag
|
||||||
|
(make sequence
|
||||||
|
font-posture: 'italic))
|
||||||
|
|
||||||
|
(element nick
|
||||||
|
(make sequence
|
||||||
|
font-family-name: %mono-font-family%))
|
||||||
|
|
||||||
|
(element capability
|
||||||
|
(make sequence
|
||||||
|
font-weight: 'bold))
|
||||||
|
|
||||||
|
(element registrygroup
|
||||||
|
(make sequence
|
||||||
|
font-weight: 'bold))
|
||||||
|
|
||||||
|
(element ircsession
|
||||||
|
(make paragraph
|
||||||
|
font-family-name: %mono-font-family%
|
||||||
|
space-before: 12pt
|
||||||
|
space-after: 12pt
|
||||||
|
start-indent: 6pt
|
||||||
|
lines: 'asis
|
||||||
|
input-whitespace-treatment: 'preserve))
|
||||||
|
|
||||||
|
(element script
|
||||||
|
(make sequence
|
||||||
|
font-family-name: %mono-font-family%))
|
||||||
|
|
||||||
|
(element channel
|
||||||
|
(make sequence
|
||||||
|
font-weight: 'bold))
|
||||||
|
|
41
docs/DocBook/supybot.css
Normal file
41
docs/DocBook/supybot.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.channel {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.botcommand {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nick {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capability {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registrygroup {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ircsession {
|
||||||
|
font-family: monospace;
|
||||||
|
display: block;
|
||||||
|
background-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
23
docs/DocBook/supybot.dsl
Normal file
23
docs/DocBook/supybot.dsl
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
|
||||||
|
<!ENTITY print-ss PUBLIC
|
||||||
|
"-//Norman Walsh//DOCUMENT DocBook Print Stylesheet//EN" CDATA DSSSL>
|
||||||
|
<!ENTITY html-ss PUBLIC
|
||||||
|
"-//Norman Walsh//DOCUMENT DocBook HTML Stylesheet//EN" CDATA DSSSL>
|
||||||
|
<!ENTITY supybot-print SYSTEM "supybot-print.dsl">
|
||||||
|
<!ENTITY supybot-html SYSTEM "supybot-html.dsl">
|
||||||
|
]>
|
||||||
|
|
||||||
|
<style-sheet>
|
||||||
|
<style-specification id="print" use="print-stylesheet">
|
||||||
|
<style-specification-body>
|
||||||
|
&supybot-print;
|
||||||
|
</style-specification-body>
|
||||||
|
</style-specification>
|
||||||
|
<style-specification id="html" use="html-stylesheet">
|
||||||
|
<style-specification-body>
|
||||||
|
&supybot-html;
|
||||||
|
</style-specification-body>
|
||||||
|
</style-specification>
|
||||||
|
<external-specification id="print-stylesheet" document="print-ss">
|
||||||
|
<external-specification id="html-stylesheet" document="html-ss">
|
||||||
|
</style-sheet>
|
139
docs/DocBook/supybot.dtd
Normal file
139
docs/DocBook/supybot.dtd
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<!-- Segregate all of our stuff into its own class for possible extension
|
||||||
|
later and just because I wanted to write my own class :) -->
|
||||||
|
<!ENTITY % local.supybot.tech.char.class "">
|
||||||
|
<!ENTITY % supybot.tech.char.class "BotCommand|Plugin|Flag|Nick|Capability
|
||||||
|
|RegistryGroup|Registry|Script
|
||||||
|
|Channel %local.supybot.tech.char.class;">
|
||||||
|
|
||||||
|
<!-- Stuff that isn't supybot-specific, but it's python-related and no
|
||||||
|
suitable element exists in the DocBook DTD -->
|
||||||
|
<!ENTITY % local.python.tech.char.class "">
|
||||||
|
<!ENTITY % python.tech.char.class "Module|Keyword
|
||||||
|
%local.python.tech.char.class;">
|
||||||
|
|
||||||
|
<!-- Pretty much all of our stuff fits where stuff in the tech.char class
|
||||||
|
goes, so we simply add our stuff using the local extension -->
|
||||||
|
<!ENTITY % local.tech.char.class "|%supybot.tech.char.class;
|
||||||
|
|%python.tech.char.class;">
|
||||||
|
|
||||||
|
<!-- linespecific is the same class as things like screen and programlisting,
|
||||||
|
so it's added here to fit with the DocBook stuff (i.e., so putting an
|
||||||
|
ircsession in where one of those previous two elements would be is a
|
||||||
|
valid operation -->
|
||||||
|
<!ENTITY % local.linespecific.class "|IrcSession">
|
||||||
|
|
||||||
|
<!-- Source the original DocBook DTD -->
|
||||||
|
<!ENTITY % DocBookDTD PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
|
||||||
|
%DocBookDTD;
|
||||||
|
|
||||||
|
<!ELEMENT BotCommand - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.botcommand.attrib "">
|
||||||
|
<!ENTITY % botcommand.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST BotCommand
|
||||||
|
%common.attrib;
|
||||||
|
%local.botcommand.attrib;
|
||||||
|
%botcommand.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Plugin - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.plugin.attrib "">
|
||||||
|
<!ENTITY % plugin.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Plugin
|
||||||
|
%common.attrib;
|
||||||
|
%local.plugin.attrib;
|
||||||
|
%plugin.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Flag - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.flag.attrib "">
|
||||||
|
<!ENTITY % flag.role.attrib
|
||||||
|
"
|
||||||
|
flagtype (arg|noarg) #IMPLIED
|
||||||
|
%role.attrib;"
|
||||||
|
>
|
||||||
|
<!ATTLIST Flag
|
||||||
|
%common.attrib;
|
||||||
|
%local.flag.attrib;
|
||||||
|
%flag.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Nick - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.nick.attrib "">
|
||||||
|
<!ENTITY % nick.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Nick
|
||||||
|
%common.attrib;
|
||||||
|
%local.nick.attrib;
|
||||||
|
%nick.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Capability - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.capability.attrib "">
|
||||||
|
<!ENTITY % capability.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Capability
|
||||||
|
%common.attrib;
|
||||||
|
%local.capability.attrib;
|
||||||
|
%capability.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Comment - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.comment.attrib "">
|
||||||
|
<!ENTITY % comment.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Comment
|
||||||
|
%common.attrib;
|
||||||
|
%local.comment.attrib;
|
||||||
|
%comment.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT RegistryGroup - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.registrygroup.attrib "">
|
||||||
|
<!ENTITY % registrygroup.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST RegistryGroup
|
||||||
|
%common.attrib;
|
||||||
|
%local.registrygroup.attrib;
|
||||||
|
%registrygroup.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Registry - - ((RegistryGroup|Comment)+)>
|
||||||
|
<!ENTITY % local.registry.attrib "">
|
||||||
|
<!ENTITY % registry.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Registry
|
||||||
|
%common.attrib;
|
||||||
|
%local.registry.attrib;
|
||||||
|
%registry.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT IrcSession - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.ircsession.attrib "">
|
||||||
|
<!ENTITY % ircsession.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST IrcSession
|
||||||
|
%common.attrib;
|
||||||
|
%local.ircsession.attrib;
|
||||||
|
%ircsession.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Script - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.script.attrib "">
|
||||||
|
<!ENTITY % script.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Script
|
||||||
|
%common.attrib;
|
||||||
|
%local.script.attrib;
|
||||||
|
%script.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Channel - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.channel.attrib "">
|
||||||
|
<!ENTITY % channel.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Channel
|
||||||
|
%common.attrib;
|
||||||
|
%local.channel.attrib;
|
||||||
|
%channel.role.attrib;
|
||||||
|
>
|
||||||
|
|
||||||
|
<!ELEMENT Module - - ((%smallcptr.char.mix)+)>
|
||||||
|
<!ENTITY % local.module.attrib "">
|
||||||
|
<!ENTITY % module.role.attrib "%role.attrib;">
|
||||||
|
<!ATTLIST Module
|
||||||
|
%common.attrib;
|
||||||
|
%local.module.attrib;
|
||||||
|
%module.role.attrib;
|
||||||
|
>
|
240
docs/FAQ
Normal file
240
docs/FAQ
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
Q: Why does my bot not recognize me or tell me that I don't have the
|
||||||
|
"owner" capability?
|
||||||
|
|
||||||
|
A: Because you've not given it anything to recognize you from!
|
||||||
|
You'll need to identify with the bot ("help identify" to see how
|
||||||
|
that works) or add your hostmask to your user record ("help
|
||||||
|
addhostmask" to see how that works) for it to know that you're you.
|
||||||
|
You may wish to note that addhostmask can accept a password; rather
|
||||||
|
than identify, you can send the command "addhostmask myOwnerUser
|
||||||
|
[hostmask] myOwnerUserPassword" and the bot will add your current
|
||||||
|
hostmask to your owner user (of course, you should change
|
||||||
|
myOwnerUser and myOwnerUserPassword appropriately for your bot).
|
||||||
|
|
||||||
|
|
||||||
|
Q: What's a hostmask?
|
||||||
|
|
||||||
|
A: Each user on IRC is uniquely identified by a string which we call
|
||||||
|
a "hostmask." The IRC RFC refers to it as a prefix. Either way,
|
||||||
|
it consists of a nick, a user, and a host, in the form
|
||||||
|
nick!user@host. If your Supybot complains that something you've
|
||||||
|
given to it isn't a hostmask, make sure that you have those three
|
||||||
|
components and that they're joined in the appropriate manner.
|
||||||
|
|
||||||
|
|
||||||
|
Q: How do I make my Supybot op my users?
|
||||||
|
|
||||||
|
A: First, you'll have to make sure that your users register with the
|
||||||
|
bot. They can do this with the "register" command. After they do
|
||||||
|
so, you'll want to add the #channel,op capability to their user.
|
||||||
|
Use the "channel addcapability" command to do this. After that,
|
||||||
|
your users should be able to use the "op" command to get ops.
|
||||||
|
|
||||||
|
If you want your users to be auto-opped when they join the channel,
|
||||||
|
you'll need to load the Enforcer plugin and turn its autoOp
|
||||||
|
configuration variable on. Use the "config" command to do so.
|
||||||
|
Here's an example of how to do these steps:
|
||||||
|
|
||||||
|
<jemfinch> I'm going to make an example session for giving
|
||||||
|
you auto-ops, for our FAQ.
|
||||||
|
<dunk1> ah ok ;]
|
||||||
|
<jemfinch> First, I need you to register with supybot, using
|
||||||
|
the "register" command (remember to send it in
|
||||||
|
private).
|
||||||
|
<dunk1> done
|
||||||
|
<jemfinch> what name are you registered under?
|
||||||
|
<dunk1> dunk1
|
||||||
|
<jemfinch> ok, cool.
|
||||||
|
<jemfinch> @channel addcapability dunk1 op
|
||||||
|
<supybot> jemfinch: The operation succeeded.
|
||||||
|
<jemfinch> now use the "op" command to get ops.
|
||||||
|
<dunk1> @op
|
||||||
|
--- supybot gives channel operator status to dunk1
|
||||||
|
<dunk1> works!
|
||||||
|
<dunk1> ;]
|
||||||
|
<jemfinch> @load Enforcer
|
||||||
|
<supybot> jemfinch: The operation succeeded.
|
||||||
|
<jemfinch> @config channel supybot.plugins.Enforcer.autoOp On
|
||||||
|
<supybot> jemfinch: The operation succeeded.
|
||||||
|
<jemfinch> ok, now cycle the channel (part and then rejoin)
|
||||||
|
<-- dunk1 (dunker@freebsd.nl) has left #supybot
|
||||||
|
--> dunk1 (dunker@freebsd.nl) has joined #supybot
|
||||||
|
--- supybot gives channel operator status to dunk1
|
||||||
|
<jemfinch> cool, thanks :)
|
||||||
|
|
||||||
|
|
||||||
|
Q: Can users with the "admin" capability change configuration
|
||||||
|
variables?
|
||||||
|
|
||||||
|
A: Currently, no. Feel free to make your case to us as to why a
|
||||||
|
certain configuration variable should only require the "admin"
|
||||||
|
capability instead of the "owner" capability, and if we agree
|
||||||
|
with you, we'll change it for the next release.
|
||||||
|
|
||||||
|
|
||||||
|
Q: Can Supybot do factoids?
|
||||||
|
|
||||||
|
A: Supybot most certainly can! In fact, we offer three full-fledged
|
||||||
|
factoids-related plugins!
|
||||||
|
|
||||||
|
Factoids (written by jemfinch) is Supybot's original
|
||||||
|
factoids-related plugin. It offers full integration with Supybot's
|
||||||
|
nested commands as well as a complete 1:n key to factoid ratio,
|
||||||
|
with lookup by individual number. Factoids also uses a
|
||||||
|
channel-specific database instead of a global database, though
|
||||||
|
that's configurable with the
|
||||||
|
supybot.databases.plugins.channelSpecific configuration variable.
|
||||||
|
|
||||||
|
MoobotFactoids (written by Strike) is much more full-featured,
|
||||||
|
offering users the ability to define factoids in a slightly more
|
||||||
|
user-friendly way, as well as parsing factoids to handle <reply>,
|
||||||
|
<action>, and alternations (defining a factoid "test" as
|
||||||
|
"<reply>(foo|bar|baz)" will make the bot send "foo" or "bar" or
|
||||||
|
"baz" to the channel (without the normal "test is " at the
|
||||||
|
beginning)). If you're accustomed to Moobot's factoids or
|
||||||
|
Blootbot's factoids, then this is the Factoids plugin for you.
|
||||||
|
Unfortunately, due to the more natural definition syntax (required
|
||||||
|
to be compatible with Moobot) you can't define Factoids with nested
|
||||||
|
commands; you'll have to evaluate the command first and then copy
|
||||||
|
the result into your factoid definition.
|
||||||
|
|
||||||
|
Infobot (written by jamessan) is used for Infobot compatibility;
|
||||||
|
if you still want the basic functionality of Infobot, this is the
|
||||||
|
plugin to use.
|
||||||
|
|
||||||
|
|
||||||
|
Q: Can I import my Infobot/Blootbot/Moobot factoids into Supybot?
|
||||||
|
|
||||||
|
A: As of present, we have no automated way to do so. Strike has
|
||||||
|
written a few scripts for importing a Moobot database into
|
||||||
|
MoobotFactoids, however, so you'll want to talk to him about
|
||||||
|
helping you with that. We're certainly happy to help you convert
|
||||||
|
such databases; if you can provide us with such a database exported
|
||||||
|
to a flat file, we can probably do the rest of the work to write a
|
||||||
|
script that imports it into a database for one of our
|
||||||
|
factoids-related plugins.
|
||||||
|
|
||||||
|
|
||||||
|
Q: Do I really have to use separate databases for each channel?
|
||||||
|
|
||||||
|
A: Of course not! We default to separate databases for each channel
|
||||||
|
because, well, that's what jemfinch always thought was
|
||||||
|
reasonable. Anyway, if you change the configuration variable
|
||||||
|
supybot.databases.plugins.channelSpecific to False instead of
|
||||||
|
True, for *most* databases, each channel will share the same
|
||||||
|
database (the exceptions are ChannelStats, Herald, Seen, and
|
||||||
|
WordStats, which are inherently rather channel-based).
|
||||||
|
|
||||||
|
|
||||||
|
Q: Karma doesn't seem to work for me.
|
||||||
|
|
||||||
|
A: Karma by default doesn't acknowledge karma updates. If you check
|
||||||
|
the karma of whatever you increased/decreased, you'll note that
|
||||||
|
your increment or decrement still took place. If you'd rather
|
||||||
|
Karma acknowledge karma updates, change the
|
||||||
|
supybot.plugins.Karma.response configuration variable to On.
|
||||||
|
|
||||||
|
|
||||||
|
Q: I added an alias, but it doesn't work!
|
||||||
|
|
||||||
|
A: Take a look at "help <alias you added>". If the alias the bot has
|
||||||
|
listed doesn't match what you're giving it, chances are you need
|
||||||
|
to quote your alias in order for the brackets not to be
|
||||||
|
evaluated. For instance, if you're adding an alias to give you a
|
||||||
|
link to your homepage, you need to say:
|
||||||
|
|
||||||
|
alias add mylink "format concat http://my.host.com/ [urlquote $1]"
|
||||||
|
|
||||||
|
and not:
|
||||||
|
|
||||||
|
alias add mylink format concat http://my.host.com/ [urlquote $1]
|
||||||
|
|
||||||
|
The first version works; the second version will always return the
|
||||||
|
same url.
|
||||||
|
|
||||||
|
Q: Is there a command that can tell me what capability another
|
||||||
|
command requires?
|
||||||
|
|
||||||
|
A: No, there isn't, and there probably never will be. Commands have
|
||||||
|
the flexibility to check any capabilities they wish to check;
|
||||||
|
while this flexibility is useful, it also makes it hard to guess
|
||||||
|
what capability a certain command requires. We could make a
|
||||||
|
solution that would work in a large majority of cases, but it
|
||||||
|
wouldn't (and couldn't!) be absolutely correct in all
|
||||||
|
circumstances, and since we're anal and we hate doing things
|
||||||
|
halfway, we probably won't ever add this partial solution.
|
||||||
|
|
||||||
|
Besides, is the error message so bad? If we did have such a
|
||||||
|
command, many users would call the command, see that they could
|
||||||
|
perform it, and then run the command, thus doubling the activity
|
||||||
|
in the channel. Is that something you want?
|
||||||
|
|
||||||
|
|
||||||
|
Q: How do I make my Supybot connect to multiple servers?
|
||||||
|
|
||||||
|
A: Just use the "connect" command in the Network plugin. Easy as pie!
|
||||||
|
|
||||||
|
|
||||||
|
Q: My bot can't handle nicks with brackets in them! It always
|
||||||
|
complains about something not being a valid command, or about
|
||||||
|
spurious or missing right brackets, etc.
|
||||||
|
|
||||||
|
A: You should quote arguments (using double quotes, like this:
|
||||||
|
"foo[bar]") that have brackets in them that you don't wish to be
|
||||||
|
evaluated as nested commands. Otherwise, you can turn off nested
|
||||||
|
commands by setting supybot.commands.nested to False, or change
|
||||||
|
the brackets that nest commands, by setting
|
||||||
|
supybot.commands.nested.brackets to some other value (like <>,
|
||||||
|
which can't occur in IRC nicks).
|
||||||
|
|
||||||
|
|
||||||
|
Q: I've edited my configuration file, but my Supybot doesn't notice
|
||||||
|
the changes! Even if I restart it, it doesn't see them. What's
|
||||||
|
the deal?
|
||||||
|
|
||||||
|
A: Supybot won't reload its configuration files unless you tell it
|
||||||
|
to. In addition, when Supybot exits (and periodically while it
|
||||||
|
runs) it flushes its configuration file to disk. The safest way
|
||||||
|
to avoid problems with configuration file edits is simply to exit
|
||||||
|
the bot before editing the configuration file(s). If you don't
|
||||||
|
wish to do that, however, you can edit the file, save the changes,
|
||||||
|
and tell the bot to reload its configuration, either via the
|
||||||
|
reload command in the Config plugin, or by sending the bot a
|
||||||
|
SIGHUP. There is a brief period in this whole sequence where the
|
||||||
|
bot can flush its configuration to disk after you write your
|
||||||
|
changes, but we even have something to fix that: set the
|
||||||
|
configuration variable supybot.flush to False, and then reload the
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
|
||||||
|
Q: I found a bug, what do I do?
|
||||||
|
|
||||||
|
A: Submit it on Sourceforge through our Sourceforge project page:
|
||||||
|
<http://sourceforge.net/tracker/?group_id=58965&atid=489447>. If
|
||||||
|
Sourceforge happens to be down when you try to submit your bug,
|
||||||
|
then post it in the "Supybot Developer Discussion" forum at our
|
||||||
|
forums at <http://forums.supybot.org/>. If that doesn't work,
|
||||||
|
email supybot-bugs@lists.sourceforge.net. If that doesn't work,
|
||||||
|
email jemfinch@supybot.org. If that doesn't work, find yourself
|
||||||
|
some carrier pigeons and ... hah! You thought I was serious!
|
||||||
|
|
||||||
|
Anyway, when you submit your bug, we'll need several things. If
|
||||||
|
the bug involved an uncaught exception, we need the traceback
|
||||||
|
(basically the stuff from "Uncaught exception in ..." to the next
|
||||||
|
log entry). We'd also like to see the commands that caused the
|
||||||
|
bug, or happened around the time you saw the bug. If the bug
|
||||||
|
involved a database, we'd love to see the database. Remember, it's
|
||||||
|
always worse to send us too little information in a bug report than
|
||||||
|
too much.
|
||||||
|
|
||||||
|
|
||||||
|
Q: Is there a way just to load *all* the plugins Supybot has?
|
||||||
|
|
||||||
|
A: No, there isn't. Even if there were, some plugins conflict with
|
||||||
|
other plugins, so it wouldn't make much sense to load them. For
|
||||||
|
instance, what would a bot do with Factoids, MoobotFactoids, and
|
||||||
|
Infobot all loaded? Probably just annoy people :)
|
||||||
|
|
||||||
|
If you want to know more about the plugins that are available,
|
||||||
|
check out our "Plugin index" at our website.
|
||||||
|
|
71
docs/FIXING-BUGS
Normal file
71
docs/FIXING-BUGS
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
These are a list of recurrent bugs in Supybot, and ways to notice
|
||||||
|
them. It just might come in useful for people maintaining code.
|
||||||
|
|
||||||
|
1. Using == or != when you mean ircutils.strEqual. Nicks, prefixes,
|
||||||
|
and channels should be compared for equality not by == or !=, but
|
||||||
|
by ircutils.strEqual. This does a case-normalized check. Don't
|
||||||
|
just use lower() because that doesn't use the rfc1459 casemapping.
|
||||||
|
|
||||||
|
To find:
|
||||||
|
grep == plugins/*.py | egrep "nick|channel"
|
||||||
|
grep "!=" plugins/*.py | egrep "nick|channel"
|
||||||
|
|
||||||
|
2. Using a warning log when it really should be an info log. Users
|
||||||
|
don't like to see warnings. If we have warning logs, they'll
|
||||||
|
complain. So let's try to make them complain as little as
|
||||||
|
possible, and only use warnings when you've encountered a *very*
|
||||||
|
odd situation, or when you need more information before
|
||||||
|
determining if something is correct.
|
||||||
|
|
||||||
|
An example: The Services plugin has two methods, doNickservNotice
|
||||||
|
and doChanservNotice, which doNotice dispatches to appropriately.
|
||||||
|
Now, we can't possibly predict all the possible messages that
|
||||||
|
ChanServ or NickServ might send to us. So I put a default clause
|
||||||
|
in there that just says, "Hey, this is an unexpected message from
|
||||||
|
ChanServ/NickServ: ..." I log this at warning level because I
|
||||||
|
want to know when there's a NickServ/ChanServ message I haven't
|
||||||
|
seen before. This works: the warning makes users report it.
|
||||||
|
|
||||||
|
Another example: We used to log failures in snarfers at the
|
||||||
|
warning level. But do the users really want to be warned when a
|
||||||
|
given "url" isn't valid? No! So now we just make it an info log,
|
||||||
|
so that users who care can still see why a snarfer didn't snarf,
|
||||||
|
but no one complains to us about it.
|
||||||
|
|
||||||
|
To find:
|
||||||
|
grep log.warning plugins/*.py
|
||||||
|
|
||||||
|
3. Spelling errors. They plague almost every large piece of
|
||||||
|
software. Supybot is no different, but we have a weapon against
|
||||||
|
them: find-strings.py. Give find-strings.py a set of files, and
|
||||||
|
it'll extract all the non-raw literal strings from the source code
|
||||||
|
and output them to a file, with the originating file, line number,
|
||||||
|
and string. Spell-checking Supybot is just a matter of taking
|
||||||
|
this 10,000 line file and an hour and running aspell over it,
|
||||||
|
correcting the errors in the original file as you find them.
|
||||||
|
|
||||||
|
To find:
|
||||||
|
find-strings.py src/*.py plugins/*.py scripts/supybot*
|
||||||
|
|
||||||
|
4. Pegging the CPU. It has happened in the past that bugs in Supybot
|
||||||
|
have caused 100% CPU usage. These are insidious, and generally
|
||||||
|
hard to track down. Here are our tools against them; we assuming
|
||||||
|
that the bug is reproducible.
|
||||||
|
|
||||||
|
First, I load the Debug plugin and settrace to a file, then I
|
||||||
|
quickly reproduce the bug, and let the CPU spin for awhile. Then
|
||||||
|
I kill the bot and check the trace file (which should be large).
|
||||||
|
I look for patterns that would indicate an infinite loop of some
|
||||||
|
sort.
|
||||||
|
|
||||||
|
Second, I strace the bot when it's looping. If I see output, it's
|
||||||
|
making syscalls; if I don't see any output, it's not. If it's
|
||||||
|
making syscalls, that might mean it's looping in the network
|
||||||
|
drivers somehow; if not, it's somewhere else.
|
||||||
|
|
||||||
|
Third, I check that no regexps could be causing it. They're
|
||||||
|
notorious for appearing safe, but actually hiding exponential
|
||||||
|
complexity code (the kind of code that strong cryptography is
|
||||||
|
based on).
|
||||||
|
|
||||||
|
After that, I pray harder :)
|
143
docs/GETTING_STARTED
Normal file
143
docs/GETTING_STARTED
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
Ok, so you've decided to try out Supybot. That's great! The more
|
||||||
|
people who use Supybot, the more people can submit bugs and help us to
|
||||||
|
make it the best IRC bot in the world :)
|
||||||
|
|
||||||
|
First things first. You should have already read through INSTALL
|
||||||
|
before reading any further.
|
||||||
|
|
||||||
|
Ok, so let's assume your bot connected to the server fine and joined
|
||||||
|
the channels you told it to join. For now we'll assume you named your
|
||||||
|
bot "supybot" (you probably didn't, but it'll make it much clearer in
|
||||||
|
the examples that follow to assume that you did). We'll also assume
|
||||||
|
that you told it to join #channel (a nice generic name for a channel,
|
||||||
|
isn't it? :)) So what do you do with this bot that you just made to
|
||||||
|
join your channel? Try this in the channel:
|
||||||
|
|
||||||
|
supybot: list
|
||||||
|
|
||||||
|
Replacing "supybot" with the actual name you picked for your bot, of
|
||||||
|
course. Your bot should reply with a list of the plugins he currently
|
||||||
|
has loaded. At least Admin, Channel, Config, Misc, Owner, and User
|
||||||
|
should be there; if you used supybot-wizard to create your
|
||||||
|
configuration file you may have many more plugins loaded. The list
|
||||||
|
command can also be used to list the commands in a given plugin:
|
||||||
|
|
||||||
|
supybot: list Misc
|
||||||
|
|
||||||
|
Will list all the commands in the Misc plugin. If you want to see the
|
||||||
|
help for any command, just use the help command:
|
||||||
|
|
||||||
|
supybot: help help
|
||||||
|
supybot: help list
|
||||||
|
supybot: help load
|
||||||
|
|
||||||
|
Sometimes more than one plugin will have a given command; for
|
||||||
|
instance, the "list" command exists in both the Misc and Config
|
||||||
|
plugins (both loaded by default). List, in this case, defaults to the
|
||||||
|
Misc plugin, but you may want to get the help for the list command in
|
||||||
|
the Config plugin. In that case, you'll want to give your command
|
||||||
|
like this:
|
||||||
|
|
||||||
|
supybot: help config list
|
||||||
|
|
||||||
|
Anytime your bot tells you that a given command is defined in several
|
||||||
|
plugins, you'll want to use this syntax ("plugin command") to
|
||||||
|
disambiguate which plugin's command you wish to call. For instance,
|
||||||
|
if you wanted to call the Config plugin's list command, then you'd
|
||||||
|
need to say:
|
||||||
|
|
||||||
|
supybot: config list
|
||||||
|
|
||||||
|
Rather than just "list".
|
||||||
|
|
||||||
|
Now that you know how to deal with plugins having commands with the
|
||||||
|
same name, let's take a look at loading other plugins. If you didn't
|
||||||
|
use supybot-wizard, though, you might do well to try it before playing
|
||||||
|
around with loading plugins yourself: each plugin has its own
|
||||||
|
configure function that the wizard uses to setup the appropriate
|
||||||
|
registry entries if the plugin requires any.
|
||||||
|
|
||||||
|
Now, if you do want to play around with loading plugins, you're going
|
||||||
|
to need to have the owner capability. If you ran the wizard, then
|
||||||
|
chances are you already added an owner user for yourself. If not,
|
||||||
|
however, you can add one via the handy-dandy supybot-adduser script.
|
||||||
|
You'll want to run it while the bot is not running (otherwise it could
|
||||||
|
overwrite supybot-adduser's changes to your user database before you
|
||||||
|
get a chance to reload them). Just follow the prompts, and when it
|
||||||
|
asks if you want to give the user any capabilities, say yes and then
|
||||||
|
give yourself the "owner" capability (without the quotes), restart the
|
||||||
|
bot and you'll be ready to load some plugins!
|
||||||
|
|
||||||
|
Now, in order for the bot to recognize you as your owner user, you'll
|
||||||
|
have to identify with the bot. Open up a query window in your irc
|
||||||
|
client (/query should do it; if not, just know that you can't identify
|
||||||
|
in a channel because it requires sending your password to the bot).
|
||||||
|
Then type this:
|
||||||
|
|
||||||
|
help identify
|
||||||
|
|
||||||
|
And follow the instructions; the command you send will probably look
|
||||||
|
like this, with your owner user and password replaced:
|
||||||
|
|
||||||
|
identify myowneruser myuserpassword
|
||||||
|
|
||||||
|
The bot will tell you that "The operation succeeded" if you got the
|
||||||
|
right name and password. Now that you're identified, you can do
|
||||||
|
anything that requires any privilege: that includes all the commands
|
||||||
|
in the Owner and Admin plugins, which you may want to take a look at
|
||||||
|
(using the list and help commands, of course). One command in
|
||||||
|
particular that you might want to use (it's from the User plugin) is
|
||||||
|
the addhostmask command: it lets you add a hostmask to your user
|
||||||
|
record so the bot recognizes you by your hostmask instead of requiring
|
||||||
|
you to always identify with it before it recognizes you. Use the help
|
||||||
|
command to see how this command works. Here's how I often use it:
|
||||||
|
|
||||||
|
addhostmask myuser [hostmask] mypassword
|
||||||
|
|
||||||
|
You may not have seen that "[hostmask]" syntax before. Supybot allows
|
||||||
|
nested commands, which means that any command's output can be nested
|
||||||
|
as an argument to another command. The hostmask command from the Misc
|
||||||
|
plugin returns the hostmask of a given nick, but if given no
|
||||||
|
arguments, it returns the hostmask of the person giving the command.
|
||||||
|
So the command above adds the hostmask I'm currently using to my
|
||||||
|
user's list of recognized hostmasks. I'm only required to give
|
||||||
|
mypassword if I'm not already identified with the bot.
|
||||||
|
|
||||||
|
Now, with all that out of the way, we can continue telling you how to
|
||||||
|
load plugins :) Remember earlier when I told you to try "help
|
||||||
|
load"? That's the very command you'll be using. Basically, if you
|
||||||
|
want to load, say, the Fun plugin, then "load Fun". Simple, right?
|
||||||
|
If you need a list of the plugins you can load, you'll have to either
|
||||||
|
list the directory the plugins are in, or you'll have to check our
|
||||||
|
website -- look for the "Plugin index."
|
||||||
|
|
||||||
|
Another command you might find yourself needing somewhat often is the
|
||||||
|
"more" command. The IRC protocol limits messages to 512 bytes, 60 or
|
||||||
|
so of which must be devoted to some bookkeeping. Sometimes, however,
|
||||||
|
Supybot wants to send a message that's longer than that. What it
|
||||||
|
does, then, is break it into "chunks" and send the first one,
|
||||||
|
following it with "(X more messages)" where X is how many more chunks
|
||||||
|
there are. To get to these chunks, use the more command. One way to
|
||||||
|
try is to look at the default value of
|
||||||
|
supybot.replies.genericNoCapability -- it's so long that it'll
|
||||||
|
stretch across two messages.
|
||||||
|
|
||||||
|
<jemfinch|lambda> $config default
|
||||||
|
supybot.replies.genericNoCapability
|
||||||
|
<lambdaman> jemfinch|lambda: You're missing some capability
|
||||||
|
you need. This could be because you actually
|
||||||
|
possess the anti-capability for the capability
|
||||||
|
that's required of you, or because the channel
|
||||||
|
provides that anti-capability by default, or
|
||||||
|
because the global capabilities include that
|
||||||
|
anti-capability. Or, it could be because the
|
||||||
|
channel or the global defaultAllow is set to
|
||||||
|
False, meaning (1 more message)
|
||||||
|
<jemfinch|lambda> $more
|
||||||
|
<lambdaman> jemfinch|lambda: that no commands are allowed
|
||||||
|
unless explicitly in your capabilities. Either
|
||||||
|
way, you can't do what you want to do.
|
||||||
|
|
||||||
|
You should now have a solid foundation for using Supybot. Be sure to
|
||||||
|
check the help that is built-in to the bot itself if you have any
|
||||||
|
questions, and enjoy using Supybot!
|
48
docs/HACKING
Normal file
48
docs/HACKING
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
So, you want to hack on Supybot? Cool! I'm glad -- more developers
|
||||||
|
means more users, and more users means better software (although I
|
||||||
|
suppose more developers means better software even without the
|
||||||
|
addition of more users :))
|
||||||
|
|
||||||
|
Anyway, there are a few things you should know before you submit your
|
||||||
|
code to be accepted into Supybot. The first, and most important
|
||||||
|
thing is that we really do value your contribution. We may say that
|
||||||
|
it's not appropriate for the core distribution and any number of
|
||||||
|
varying reasons, but regardless, we're happy that you're hacking on
|
||||||
|
Supybot and bending it to your will, and we'll be happy to post your
|
||||||
|
patch as long as it applies cleanly.
|
||||||
|
|
||||||
|
The second thing you should know is that, despite the fact that we're
|
||||||
|
happy you want to contribute to Supybot, we're not afraid to piss you
|
||||||
|
off by turning down your code. We won't hesitate to reject code
|
||||||
|
because it's "bad" or because it doesn't fit our style guidelines
|
||||||
|
(read docs/STYLE). We don't really care if it makes you angry or
|
||||||
|
makes you use another IRC bot; we're in the practice of writing good
|
||||||
|
software, not placating whiners. Despite this, we're not entirely
|
||||||
|
heartless, and if you've done something we're interested in, we're
|
||||||
|
willing to work with you and your code until such a time as it's
|
||||||
|
ready to be accepted into the core. But if, at some point, we say,
|
||||||
|
"This needs fixed" and you say, "I refuse to fix it," you can go put
|
||||||
|
your code on the patch tracker; our time together is done. Supybot
|
||||||
|
is #1 here -- we don't care about your feelings, we don't care about
|
||||||
|
jamessan's feelings, we don't care about jemfinch's feelings if it
|
||||||
|
means that the code quality and user experience of Supybot is to
|
||||||
|
suffer.
|
||||||
|
|
||||||
|
Anyway, the normal process is that you'll submit a few patches,
|
||||||
|
jemfinch will review them and tell you what needs to happen for them
|
||||||
|
to be accepted into the core, you'll fix those problems, jemfinch
|
||||||
|
will review them again, that cycle will repeat a few times. When
|
||||||
|
your code is to jemfinch's satisfaction, it'll be integrated into the
|
||||||
|
core. For many people, this is the end of the line. For some
|
||||||
|
others (perhaps you!), you'll continue to write patches for Supybot,
|
||||||
|
and your coding ability and commitment will be obvious through
|
||||||
|
those. If your code quality is consistently high enough that
|
||||||
|
jemfinch (or other Supybot developers) don't have to spend a
|
||||||
|
significant amount of time reviewing your code, you'll be added as a
|
||||||
|
developer on the SF.net project and given commit access to our CVS
|
||||||
|
repository. From then on, you can do what you want, but be aware
|
||||||
|
that the other developers are watching what you do -- if you have a
|
||||||
|
big architecture change, you should probably talk to them before you
|
||||||
|
commit.
|
||||||
|
|
||||||
|
So welcome aboard, and have fun hacking on Supybot!
|
271
docs/INTERFACES
Normal file
271
docs/INTERFACES
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
These are the interfaces for some of the objects you'll deal with if
|
||||||
|
you code for Supybot.
|
||||||
|
|
||||||
|
ircmsgs.IrcMsg:
|
||||||
|
This is the object that represents an IRC message. It has
|
||||||
|
several methods and attributes. The most important thing
|
||||||
|
about this class, however, is that it *is* hashable, and thus
|
||||||
|
*cannot* be modified. Do not change any attributes; any code
|
||||||
|
that modifies an IRC message is *broken* and should not
|
||||||
|
exist.
|
||||||
|
|
||||||
|
Interesting Methods:
|
||||||
|
__init__: One of the more complex initializers in
|
||||||
|
a class. It can be used in three different ways:
|
||||||
|
|
||||||
|
1) It can be given a string, as one received from
|
||||||
|
the server, which it will then parse into its
|
||||||
|
separate components and instantiate the class
|
||||||
|
with those components as attributes.
|
||||||
|
|
||||||
|
2) It can be given a command, some (optional)
|
||||||
|
arguments, and a (optional) prefix, and will
|
||||||
|
instantiate the class with those components as
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
3) It can be given, in addition to any of the
|
||||||
|
above arguments, a 'msg' keyword argument that
|
||||||
|
will use the attributes of msg as defaults.
|
||||||
|
This exists to make it easier to copy
|
||||||
|
messages, since the class is immutable.
|
||||||
|
|
||||||
|
__str__: This returns the message in a string form
|
||||||
|
suitable for sending to a server.
|
||||||
|
|
||||||
|
__repr__: This returns the message in a form
|
||||||
|
suitable for eval(), assuming the name "IrcMsg" is
|
||||||
|
in your namespace and is bound to this class.
|
||||||
|
|
||||||
|
Interesting Attributes:
|
||||||
|
This is the meat of this class. These are
|
||||||
|
generally what you'll be looking at with IrcMsgs.
|
||||||
|
|
||||||
|
command: This is the command of the IrcMsg --
|
||||||
|
PRIVMSG, NOTICE, WHOIS, etc.
|
||||||
|
|
||||||
|
args: This is a tuple of the arguments to the
|
||||||
|
IrcMsg. Some messages have arguments, some don't,
|
||||||
|
depending on what command they are. You are, of
|
||||||
|
course, always assured that args exists and is a
|
||||||
|
tuple, though it might be empty.
|
||||||
|
|
||||||
|
prefix: This is the hostmask of the person/server
|
||||||
|
the message is from. In general, you won't be
|
||||||
|
setting this on your outgoing messages, but
|
||||||
|
incoming messages will always have one. This is
|
||||||
|
the whole hostmask; if the message was received
|
||||||
|
from a server, it'll be the server's hostmask; if
|
||||||
|
the message was received from a user, it'll be the
|
||||||
|
whole user hostmask. In that case, however, it's
|
||||||
|
also parsed out into the nick/user/host
|
||||||
|
attributes, which are probably more useful to
|
||||||
|
check for many purposes.
|
||||||
|
|
||||||
|
nick: If the message was sent by a user, this will
|
||||||
|
be the nick of the user. If it was sent by a
|
||||||
|
server, this will be the server's name (something
|
||||||
|
like calvino.freenode.net or similar).
|
||||||
|
|
||||||
|
user: If the message was sent by a user, this will
|
||||||
|
be the user string of the user -- what they put
|
||||||
|
into their IRC client for their "full name." If
|
||||||
|
it was sent by a server, it'll be the server's
|
||||||
|
name, again.
|
||||||
|
|
||||||
|
host: If the message was sent by a user, this will
|
||||||
|
be the host portion of their hostmask. If it was
|
||||||
|
sent by a server, it'll be the server's name (yet
|
||||||
|
again :))
|
||||||
|
|
||||||
|
|
||||||
|
irclib.Irc:
|
||||||
|
This is the object to handle everything about IRC except the
|
||||||
|
actual connection to the server itself. (*NOTE* that the
|
||||||
|
object actually received by commands in subclasses of
|
||||||
|
callbacks.Privmsg is an IrcObjectProxy, which is described
|
||||||
|
later. It augments the following interface with several
|
||||||
|
methods of its own to help plugin authors.)
|
||||||
|
|
||||||
|
Interesting Methods:
|
||||||
|
The two following messages (queueMsg and
|
||||||
|
sendMsg) are the methods by far most commonly
|
||||||
|
called by plugin authors. They're generally
|
||||||
|
the only methods you need to pay attention to
|
||||||
|
if you're writing plugins.
|
||||||
|
|
||||||
|
queueMsg: Queues a message for sending to the
|
||||||
|
server. The queue is generally FIFO, but it
|
||||||
|
does prioritize messages based on their command.
|
||||||
|
|
||||||
|
sendMsg: Queues a message for sending to the
|
||||||
|
server prior to any messages in the normal
|
||||||
|
queue. This is exactly a FIFO queue, no
|
||||||
|
reordering is done at all.
|
||||||
|
|
||||||
|
The following two methods are the most important
|
||||||
|
for people writing new IrcDrivers. Otherwise,
|
||||||
|
you really don't need to pay attention to them.
|
||||||
|
|
||||||
|
feedMsg: Feeds the Irc object a message for it
|
||||||
|
handle appropriately, as well as passing it on
|
||||||
|
to callbacks.
|
||||||
|
|
||||||
|
takeMsg: If the Irc object has a message it's
|
||||||
|
ready to send to the server, this will return
|
||||||
|
it. Otherwise, it will return None.
|
||||||
|
|
||||||
|
The next several methods are of far more marginal
|
||||||
|
utility. But someone may need them, so they're
|
||||||
|
documented here.
|
||||||
|
|
||||||
|
addCallback: Takes a callback to add to the list
|
||||||
|
of callbacks in the Irc object. See the
|
||||||
|
interface for IrcCallback for more information.
|
||||||
|
|
||||||
|
getCallback: Gets a callback by name, if it is
|
||||||
|
in the Irc object's list of callbacks. If it
|
||||||
|
it isn't, returns None.
|
||||||
|
|
||||||
|
removeCallback: Removes a callback by name.
|
||||||
|
Returns a list of the callbacks removed (since
|
||||||
|
it is technically possible to have multiple
|
||||||
|
callbacks with the same name. This list may
|
||||||
|
be empty.
|
||||||
|
|
||||||
|
__init__: Requires a nick. Optional arguments
|
||||||
|
include user and ident, which default to the
|
||||||
|
nick given, password, which defaults to the empty
|
||||||
|
password, and callbacks, a list of callbacks
|
||||||
|
(which defaults to nothing, an empty list).
|
||||||
|
|
||||||
|
reset: Resets the Irc object to its original
|
||||||
|
state, as well as sends a reset() to every
|
||||||
|
callbacks.
|
||||||
|
|
||||||
|
die: Kills the IRC object and all its callbacks.
|
||||||
|
|
||||||
|
Interesting attributes:
|
||||||
|
nick: The current nick of the bot.
|
||||||
|
|
||||||
|
prefix: The current prefix of the bot.
|
||||||
|
|
||||||
|
server: The current server the bot is connected to.
|
||||||
|
|
||||||
|
network: The current network name the bot is connected to.
|
||||||
|
|
||||||
|
afterConnect: False until the bot has received a
|
||||||
|
command sent after the connection is finished --
|
||||||
|
376, 377, or 422.
|
||||||
|
|
||||||
|
state: An IrcState object for this particular
|
||||||
|
connection. See the interface for the IrcState
|
||||||
|
object for more information.
|
||||||
|
|
||||||
|
|
||||||
|
irclib.IrcCallback:
|
||||||
|
Interesting Methods:
|
||||||
|
name: Returns the name of the callback. The
|
||||||
|
default implementation simply returns the name
|
||||||
|
of the class.
|
||||||
|
|
||||||
|
__call__: Called by the Irc object with itself
|
||||||
|
and the message whenever a message is fed to
|
||||||
|
the Irc object. Nothing is done with the return
|
||||||
|
value.
|
||||||
|
|
||||||
|
inFilter: Called by the Irc object with itself
|
||||||
|
and the message whenever a message is fed to
|
||||||
|
the Irc object. The return value should be an
|
||||||
|
IrcMsg object to be passed to the next callback
|
||||||
|
in the Irc's list of callbacks. If None is
|
||||||
|
returned, all processing stops. This gives
|
||||||
|
callbacks an oppurtunity to "filter" incoming
|
||||||
|
messages before general callbacks are given
|
||||||
|
them.
|
||||||
|
|
||||||
|
outFilter: Basically equivalent to inFilter,
|
||||||
|
except instead of being called on messages
|
||||||
|
as they enter the Irc object, it's called on
|
||||||
|
messages as they leave the Irc object.
|
||||||
|
|
||||||
|
die: Called when the parent Irc is told to
|
||||||
|
die. This gives callbacks an oppurtunity to
|
||||||
|
close open files, network connections, or
|
||||||
|
databases before they're deleted.
|
||||||
|
|
||||||
|
reset: Called when the parent Irc is told to
|
||||||
|
reset (which is generally when reconnecting
|
||||||
|
to the server). Most callbacks don't need
|
||||||
|
to define this.
|
||||||
|
|
||||||
|
Interesting attributes:
|
||||||
|
priority: Determines the priority of the
|
||||||
|
callback in the Irc object's list of
|
||||||
|
callbacks. Defaults to 99; the valid range
|
||||||
|
includes 0 through sys.maxint-1 (don't use
|
||||||
|
sys.maxint itself, that's reserved for the
|
||||||
|
Misc plugin). The lower the number, the
|
||||||
|
higher the priority. High priority
|
||||||
|
callbacks are called earlier in the
|
||||||
|
inFilter cycle, earlier in the __call__
|
||||||
|
cycle, and later in the outFilter cycle --
|
||||||
|
basically, they're given the first chances
|
||||||
|
on the way in and the last chances on the
|
||||||
|
way out.
|
||||||
|
|
||||||
|
|
||||||
|
callbacks.IrcObjectProxy:
|
||||||
|
IrcObjectProxy is a proxy for an irclib.Irc instance that
|
||||||
|
serves to provide a much fuller interface for handling
|
||||||
|
replies and errors as well as to handle the nesting of
|
||||||
|
commands. This is what you'll be dealing with almost all the
|
||||||
|
time when writing commands; when writing doCommand methods
|
||||||
|
(the kind you read about in the interface description of
|
||||||
|
irclib.IrcCallback) you'll be dealing with plain old
|
||||||
|
irclib.Irc objects.
|
||||||
|
|
||||||
|
Interesting methods:
|
||||||
|
reply: Called to reply to the current message
|
||||||
|
with a string that is to be the reply.
|
||||||
|
|
||||||
|
replySuccess, replyError: These reply with the
|
||||||
|
configured responses for success and generic
|
||||||
|
error, respectively. If an additional argument
|
||||||
|
is given, it's (intelligently) appended to the
|
||||||
|
generic message to be more specific.
|
||||||
|
|
||||||
|
error: Called to send an error reply to the
|
||||||
|
current message; not only does the response
|
||||||
|
indicate an error, but commands that error out
|
||||||
|
break the nested-command chain, which is
|
||||||
|
generally useful for not confusing the user :)
|
||||||
|
|
||||||
|
errorNoCapability: Like error, except it accepts
|
||||||
|
the capability that's missing and integrates it
|
||||||
|
into the configured error message for such
|
||||||
|
things. Also accepts an additional string for a
|
||||||
|
more descriptive message, if that's what you
|
||||||
|
want.
|
||||||
|
|
||||||
|
errorPossibleBug, errorNotRegistered,
|
||||||
|
errorNoUser, errorRequiresPrivacy: These methods
|
||||||
|
reply with the appropriate configured error
|
||||||
|
message for the conditions in their names; they
|
||||||
|
all take an additional arguments to be more
|
||||||
|
specific about the conditions they indicate, but
|
||||||
|
this argument is very rarely necessary.
|
||||||
|
|
||||||
|
getRealIrc: Returns the actual Irc object being
|
||||||
|
proxied for.
|
||||||
|
|
||||||
|
replies: Sends a collection of messages to a given
|
||||||
|
target, much like reply; except in this case, the user
|
||||||
|
can configure whether the messages will be sent
|
||||||
|
one-by-one or combined into a single message. Thus, the
|
||||||
|
method accepts a "prefixer" argument, which prefixes the
|
||||||
|
messages with a given string (or according to a given
|
||||||
|
function), a "joiner" string (or function) used to join
|
||||||
|
the messages into a single message if necessary, and an
|
||||||
|
onlyPrefixFirst argument which determines whether only
|
||||||
|
the first message will be prefixed when the messages are
|
||||||
|
sent separately (it defaults to False).
|
61
docs/OVERVIEW
Normal file
61
docs/OVERVIEW
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
So here's a general *programming* introduction to what the different
|
||||||
|
modules do and what services they provide. It is, however, only an
|
||||||
|
introduction. Read the modules themselves for a much more detailed
|
||||||
|
explanation :)
|
||||||
|
|
||||||
|
fix.py: Stuff that Python should (but doesn't) include by default.
|
||||||
|
|
||||||
|
cdb.py: A constant database library, translated from C (and my O'Caml
|
||||||
|
version) More information available at
|
||||||
|
http://cr.yp.to/cdb.html. Not currently used since we
|
||||||
|
switched to PySQLite.
|
||||||
|
|
||||||
|
ansi.py: Contains different ANSI color sequences.
|
||||||
|
Mostly used by the debug module.
|
||||||
|
|
||||||
|
conf.py: The configuration file for the bot -- it sets a lot of
|
||||||
|
variables that other modules check for when they have
|
||||||
|
questions about what they need to do.
|
||||||
|
|
||||||
|
world.py: Just a dropping off place for some globals that need to be
|
||||||
|
shared among all the modules. It's obviously not used *a
|
||||||
|
lot*, but some things seem to fit better here than anywhere
|
||||||
|
else.
|
||||||
|
|
||||||
|
privmsgs.py: Basic stuff relating to callbacks.Privmsg, the base class
|
||||||
|
for most plugins.
|
||||||
|
|
||||||
|
callbacks.py: A few basic callbacks providing significant
|
||||||
|
functionality. You'll likely be inheriting from Privmsg
|
||||||
|
quite a bit.
|
||||||
|
|
||||||
|
ircdb.py: The users and channels databases are here, as well as the
|
||||||
|
IrcUser and IrcChannel classes. Look here when you want to
|
||||||
|
restrict a command to only certain users.
|
||||||
|
|
||||||
|
irclib.py: Provides the most important class in the irclib, Irc. It
|
||||||
|
represents a connection to an IRC server, but it's entirely
|
||||||
|
separate from the network implementation. It's connected
|
||||||
|
to the network (or whatever else drives it) by a "driver"
|
||||||
|
module which uses its feedMsg/takeMsg functions.
|
||||||
|
|
||||||
|
drivers.py: The baseclass (IrcDriver) for various drivers to drive
|
||||||
|
irclib.Irc classes. Also, the driving mechanism.
|
||||||
|
|
||||||
|
asyncoreDrivers.py: The asyncore-based drivers for use with the bot.
|
||||||
|
|
||||||
|
socketDrivers.py: The plain old socket-based drivers for use with the
|
||||||
|
bot.
|
||||||
|
|
||||||
|
twistedDrivers.py: The Twisted <http://www.twistedmatrix.com/> drivers
|
||||||
|
for use with the bot.
|
||||||
|
|
||||||
|
ircmsgs.py: The IrcMsg class (get to know it :)) and various functions
|
||||||
|
for making the creation of IrcMsgs easier.
|
||||||
|
|
||||||
|
ircutils.py: Various utility functions for Irc -- read the module to
|
||||||
|
see what goodies are there :)
|
||||||
|
|
||||||
|
schedule.py: A schedule driver (which is automatically registered with
|
||||||
|
the drivers module) to run things at a particular time,
|
||||||
|
or at specified periods of time.
|
449
docs/PLUGIN-EXAMPLE
Normal file
449
docs/PLUGIN-EXAMPLE
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
*******************************************************************************
|
||||||
|
The plugins/Random.py included with the distribution has been
|
||||||
|
updated to use an argument-parsing module recently added to Supybot
|
||||||
|
but not yet documented. Still, all of the functions used in this
|
||||||
|
tutorial are available, so the code (as shown in this tutorial*
|
||||||
|
should still work.
|
||||||
|
*******************************************************************************
|
||||||
|
|
||||||
|
Ok, so you want to write a callback for Supybot. Good, then this is
|
||||||
|
the place to be. We're going to start from the top (the highest
|
||||||
|
level, where Supybot code does the most work for you) and move lower
|
||||||
|
after that.
|
||||||
|
|
||||||
|
So have you used Supybot? If not, you need to go use it, get a feel
|
||||||
|
for it, see how the various commands work and such.
|
||||||
|
|
||||||
|
So now that we know you've used Supybot, we'll start getting into
|
||||||
|
details.
|
||||||
|
|
||||||
|
First, the easiest way to start writing a module is to use the wizard
|
||||||
|
provided, scripts/newplugin.py. Here's an example session:
|
||||||
|
|
||||||
|
-----
|
||||||
|
functor% supybot-newplugin
|
||||||
|
What should the name of the plugin be? Random
|
||||||
|
Supybot offers two major types of plugins: command-based and regexp-
|
||||||
|
based. Command-based plugins are the kind of plugins you've seen most
|
||||||
|
when you've used supybot. They're also the most featureful and
|
||||||
|
easiest to write. Commands can be nested, for instance, whereas
|
||||||
|
regexp-based callbacks can't do nesting. That doesn't mean that
|
||||||
|
you'll never want regexp-based callbacks. They offer a flexibility
|
||||||
|
that command-based callbacks don't offer; however, they don't tie into
|
||||||
|
the whole system as well. If you need to combine a command-based
|
||||||
|
callback with some regexp-based methods, you can do so by subclassing
|
||||||
|
callbacks.PrivmsgCommandAndRegexp and then adding a class-level
|
||||||
|
attribute "regexps" that is a sets.Set of methods that are regexp-
|
||||||
|
based. But you'll have to do that yourself after this wizard is
|
||||||
|
finished :)
|
||||||
|
Do you want a command-based plugin or a regexp-based plugin? [command/
|
||||||
|
regexp] command
|
||||||
|
Sometimes you'll want a callback to be threaded. If its methods
|
||||||
|
(command or regexp-based, either one) will take a signficant amount
|
||||||
|
of time to run, you'll want to thread them so they don't block
|
||||||
|
the entire bot.
|
||||||
|
|
||||||
|
Does your plugin need to be threaded? [y/n] n
|
||||||
|
Your new plugin template is Random.py
|
||||||
|
functor%
|
||||||
|
-----
|
||||||
|
|
||||||
|
So that's what it looks like. Now let's look at the source code (if
|
||||||
|
you'd like to look at it in your programming editor, the whole plugin
|
||||||
|
is available as examples/Random.py):
|
||||||
|
|
||||||
|
-----
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
###
|
||||||
|
# Copyright (c) 2004, Jeremiah Fincher
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
"""
|
||||||
|
Add the module docstring here. This will be used by the setup.py script.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__revision__ = "$Id$"
|
||||||
|
__author__ = ''
|
||||||
|
|
||||||
|
import supybot.plugins as plugins
|
||||||
|
|
||||||
|
import supybot.conf as conf
|
||||||
|
import supybot.utils as utils
|
||||||
|
import supybot.privmsgs as privmsgs
|
||||||
|
import supybot.callbacks as callbacks
|
||||||
|
|
||||||
|
|
||||||
|
def configure(advanced):
|
||||||
|
# This will be called by setup.py to configure this module. Advanced is
|
||||||
|
# a bool that specifies whether the user identified himself as an advanced
|
||||||
|
# user or not. You should effect your configuration by manipulating the
|
||||||
|
# registry as appropriate.
|
||||||
|
from questions import expect, anything, something, yn
|
||||||
|
conf.registerPlugin('Random', True)
|
||||||
|
|
||||||
|
class Random(callbacks.Privmsg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
Class = Random
|
||||||
|
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||||
|
-----
|
||||||
|
|
||||||
|
So a few notes, before we customize it.
|
||||||
|
|
||||||
|
You'll probably want to change the copyright notice to be your name.
|
||||||
|
It wouldn't stick even if you kept my name, so you might as well :)
|
||||||
|
|
||||||
|
Describe what you want the plugin to do in the docstring. This is
|
||||||
|
used in supybot-wizard in order to explain to the user the purpose of
|
||||||
|
the module. It's also returned when someone asks the bot for help for
|
||||||
|
a given module (instead of help for a certain command). We'll change
|
||||||
|
this one to "Lots of stuff relating to random numbers."
|
||||||
|
|
||||||
|
Then there are the imports. The callbacks module is used (the class
|
||||||
|
you're given subclasses callbacks.Privmsg) but the privmsgs module
|
||||||
|
isn't used. That's alright; we can almost guarantee you'll use it, so
|
||||||
|
we go ahead and add the import to the template.
|
||||||
|
|
||||||
|
Then you see a "configure" function. This is the function that's
|
||||||
|
called when users decide to add your module in supybot-wizard. You'll
|
||||||
|
note that by default it simply registers the plugin to be
|
||||||
|
automatically loaded on startup. For many plugins this is all you
|
||||||
|
need; for more complex plugins, you might need to ask questions and
|
||||||
|
add commands based on the answers.
|
||||||
|
|
||||||
|
Now comes the meat of the plugin: the plugin class.
|
||||||
|
|
||||||
|
What you're given is a skeleton: a simple subclass of
|
||||||
|
callbacks.Privmsg for you to start with. Now let's add a command.
|
||||||
|
|
||||||
|
I don't know what you know about random number generators, but the
|
||||||
|
short of it is that they start at a certain number (a seed) and they
|
||||||
|
continue (via some somewhat complicated/unpredictable algorithm) from
|
||||||
|
there. This seed (and the rest of the sequence, really) is all nice
|
||||||
|
and packaged up in Python's random module, the Random object. So the
|
||||||
|
first thing we're going to have to do is give our plugin a Random
|
||||||
|
object.
|
||||||
|
|
||||||
|
Normally, when we want to give instances of a class an object, we'll
|
||||||
|
do so in the __init__ method. And that works great for plugins, too.
|
||||||
|
The one thing you have to be careful of is that you call the
|
||||||
|
superclass __init__ method at the end of your own __init__. So to add
|
||||||
|
this random.Random object to our plugin, we can replace the "pass"
|
||||||
|
statement with this:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.rng = random.Random()
|
||||||
|
callbacks.Privmsg.__init__(self)
|
||||||
|
|
||||||
|
(rng is an abbreviation for "random number generator," in case you
|
||||||
|
were curious)
|
||||||
|
|
||||||
|
Do be careful not to give your __init__ any arguments (other than
|
||||||
|
self, of course). There's no way anything will ever get to them! If
|
||||||
|
you have some sort of initial values you need to get to your plugin
|
||||||
|
before it can do anything interesting, you should get those values
|
||||||
|
from the registry.
|
||||||
|
|
||||||
|
There's an easier way to get our plugin to have its own rng than to
|
||||||
|
define an __init__. Plugins are unique among classes because we're
|
||||||
|
always certain that there will only be one instance -- supybot doesn't
|
||||||
|
allow us to load multiple instances of a single plugin. So instead of
|
||||||
|
adding the rng in __init__, we can just add it as a attribute to the
|
||||||
|
class itself. Like so (replacing the "pass" statement again):
|
||||||
|
|
||||||
|
rng = random.Random()
|
||||||
|
|
||||||
|
And we save two lines of code and make our code a little more clear :)
|
||||||
|
|
||||||
|
Now that we have an RNG, we need some way to get random numbers. So
|
||||||
|
first, we'll add a command that simply gets the next random number and
|
||||||
|
gives it back to the user. It takes no arguments, of course (what
|
||||||
|
would you give it?). Here's the command, and I'll follow that with
|
||||||
|
the explanation of what each part means.
|
||||||
|
|
||||||
|
def random(self, irc, msg, args):
|
||||||
|
"""takes no arguments
|
||||||
|
|
||||||
|
Returns the next random number generated by the random number
|
||||||
|
generator.
|
||||||
|
"""
|
||||||
|
irc.reply(str(self.rng.random()))
|
||||||
|
|
||||||
|
And that's it! Pretty simple, huh? Anyway, you're probably wondering
|
||||||
|
what all that *means*. We'll start with the def statement:
|
||||||
|
|
||||||
|
def random(self, irc, msg, args):
|
||||||
|
|
||||||
|
What that does is define a command "random". You can call it by
|
||||||
|
saying "@random" (or whatever prefix character your specific bot
|
||||||
|
uses). The arguments are a bit less obvious. Self is self-evident
|
||||||
|
(hah!). irc is the Irc object passed to the command; msg is the
|
||||||
|
original IrcMsg object. But you're really not going to have to deal
|
||||||
|
with either of these too much (with the exception of calling irc.reply
|
||||||
|
or irc.error). What you're *really* interested in is the args arg.
|
||||||
|
That is a list of all the arguments passed to your command, pre-parsed
|
||||||
|
and already evaluated (i.e., you never have to worry about nested
|
||||||
|
commands, or handling double quoted strings, or splitting on
|
||||||
|
whitespace -- the work has already been done for you). You can read
|
||||||
|
about the Irc object in irclib.py (you won't find .reply or .error
|
||||||
|
there, though, because you're actually getting an IrcObjectProxy, but
|
||||||
|
that's beyond the level we want to describe here :)). You can read
|
||||||
|
about the msg object in ircmsgs.py. But again, you'll very rarely be
|
||||||
|
using these objects.
|
||||||
|
|
||||||
|
(In case you're curious, the answer is yes, you *must* name your
|
||||||
|
arguments (self, irc, msg, args). The names of those arguments is one
|
||||||
|
of the ways that supybot uses to determine which methods in a plugin
|
||||||
|
class are commands and which aren't. And while we're talking about
|
||||||
|
naming restrictions, all your commands should be named in
|
||||||
|
all-lowercase with no underscores. Before calling a command, supybot
|
||||||
|
always converts the command name to lowercase and removes all dashes
|
||||||
|
and underscores. On the other hand, you now know an easy way to make
|
||||||
|
sure a method is never called (even if its arguments are (self, irc,
|
||||||
|
msg, args), however unlikely that may be). Just name it with an
|
||||||
|
underscore or an uppercase letter in it :))
|
||||||
|
|
||||||
|
You'll also note that the docstring is odd. The wonderful thing about
|
||||||
|
the supybot framework is that it's easy to write complete commands
|
||||||
|
with help and everything: the docstring *IS* the help! Given the
|
||||||
|
above docstring, this is what a supybot does:
|
||||||
|
|
||||||
|
<jemfinch> @help random
|
||||||
|
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||||
|
next random number from the random number generator.
|
||||||
|
|
||||||
|
Now on to the actual body of the function:
|
||||||
|
|
||||||
|
irc.reply(str(self.rng.random()))
|
||||||
|
|
||||||
|
irc.reply takes one simple argument: a string. The string is the
|
||||||
|
reply to be sent. Don't worry about length restrictions or anything
|
||||||
|
-- if the string you want to send is too big for an IRC message (and
|
||||||
|
oftentimes that turns out to be the case :)) the Supybot framework
|
||||||
|
handles that entirely transparently to you. Do make sure, however,
|
||||||
|
that you give irc.reply a string. It doesn't take anything else
|
||||||
|
(sometimes even unicode fails!). That's why we have
|
||||||
|
"str(self.rng.random())" instead of simply "self.rng.random()" -- we
|
||||||
|
had to give irc.reply a string.
|
||||||
|
|
||||||
|
Anyway, now that we have an RNG, we have a need for seed! Of course,
|
||||||
|
Python gives us a good seed already (it uses the current time as a
|
||||||
|
seed if we don't give it one) but users might want to be able to
|
||||||
|
repeat "random" sequences, so letting them set the seed is a good
|
||||||
|
thing. So we'll add a seed command to give the RNG a specific seed:
|
||||||
|
|
||||||
|
def seed(self, irc, msg, args):
|
||||||
|
"""<seed>
|
||||||
|
|
||||||
|
Sets the seed of the random number generator. <seed> must be
|
||||||
|
an int or a long.
|
||||||
|
"""
|
||||||
|
seed = privmsgs.getArgs(args)
|
||||||
|
try:
|
||||||
|
seed = long(seed)
|
||||||
|
except ValueError:
|
||||||
|
# It wasn't a valid long!
|
||||||
|
irc.error('<seed> must be a valid int or long.')
|
||||||
|
return
|
||||||
|
self.rng.seed(seed)
|
||||||
|
irc.replySuccess()
|
||||||
|
|
||||||
|
So this one's a bit more complicated. But it's still pretty simple.
|
||||||
|
The method name is "seed" so that'll be the command name. The
|
||||||
|
arguments are the same, the docstring is of the same form, so we don't
|
||||||
|
need to go over that again. The body of the function, however, is
|
||||||
|
significantly different.
|
||||||
|
|
||||||
|
privmsgs.getArgs is a function you're going to be seeing a lot of when
|
||||||
|
you write plugins for Supybot. What it does is basically give you the
|
||||||
|
right number of arguments for your comamnd. In this case, we want one
|
||||||
|
argument. But we might have been given any number of arguments by the
|
||||||
|
user. So privmsgs.getArgs joins them appropriately, leaving us with
|
||||||
|
one single "seed" argument (by default, it returns one argument as a
|
||||||
|
single value; more arguments are returned in a tuple/list). Yes, we
|
||||||
|
could've just said "seed = args[0]" and gotten the first argument, but
|
||||||
|
what if the user didn't pass us an argument at all? Then we've got to
|
||||||
|
catch the IndexError from args[0] and complain to the user about it.
|
||||||
|
privmsgs.getArgs, on the other hand, handles all that for us. If the
|
||||||
|
user didn't give us enough arguments, it'll reply with the help string
|
||||||
|
for the command, thus saving us the effort.
|
||||||
|
|
||||||
|
So we have the seed from privmsgs.getArgs. But it's a string. The
|
||||||
|
next three lines are pretty darn obvious: we're just converting the
|
||||||
|
string to a int of some sort. But if it's not, that's when we're
|
||||||
|
going to call irc.error. It has the same interface as we saw before
|
||||||
|
in irc.reply, but it makes sure to remind the user that an error has
|
||||||
|
been encountered (currently, that means it puts "Error: " at the
|
||||||
|
beginning of the message). After erroring, we return. It's important
|
||||||
|
to remember this return here; otherwise, we'll just keep going down
|
||||||
|
through the function and try to use this "seed" variable that never
|
||||||
|
got assigned. A good general rule of thumb is that any time you use
|
||||||
|
irc.error, you'll want to return immediately afterwards.
|
||||||
|
|
||||||
|
Then we set the seed -- that's a simple function on our rng object.
|
||||||
|
Assuming that succeeds (and doesn't raise an exception, which it
|
||||||
|
shouldn't, because we already read the documentation and know that it
|
||||||
|
should work) we reply to say that everything worked fine. That's what
|
||||||
|
irc.replySuccess says. By default, it has the very dry (and
|
||||||
|
appropriately robot-like) "The operation succeeded." but you're
|
||||||
|
perfectly welcome to customize it yourself -- the registry was written to
|
||||||
|
be modified!
|
||||||
|
|
||||||
|
So that's a bit more complicated command. But we still haven't dealt
|
||||||
|
with multiple arguments. Let's do that next.
|
||||||
|
|
||||||
|
So these random numbers are useful, but they're not the kind of random
|
||||||
|
numbers we usually want in Real Life. In Real Life, we like to tell
|
||||||
|
someone to "pick a number between 1 and 10." So let's write a
|
||||||
|
function that does that. Of course, we won't hardcode the 1 or the 10
|
||||||
|
into the function, but we'll take them as arguments. First the
|
||||||
|
function:
|
||||||
|
|
||||||
|
def range(self, irc, msg, args):
|
||||||
|
"""<start> <end>
|
||||||
|
|
||||||
|
Returns a number between <start> and <end>, inclusive (i.e., the number
|
||||||
|
can be either of the endpoints.
|
||||||
|
"""
|
||||||
|
(start, end) = privmsgs.getArgs(args, required=2)
|
||||||
|
try:
|
||||||
|
end = int(end)
|
||||||
|
start = int(start)
|
||||||
|
except ValueError:
|
||||||
|
irc.error('<start> and <end> must both be integers.')
|
||||||
|
return
|
||||||
|
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||||
|
irc.reply(str(self.rng.randrange(start, end+1)))
|
||||||
|
|
||||||
|
Pretty simple. This is becoming old hat by now. The only new thing
|
||||||
|
here is the call to privmsgs.getArgs. We have to make sure, since we
|
||||||
|
want two values, to pass a keyword parameter "required" into
|
||||||
|
privmsgs.getArgs. Of course, privmsgs.getArgs handles all the
|
||||||
|
checking for missing arguments and whatnot so we don't have to.
|
||||||
|
|
||||||
|
The Random object we're using offers us a "sample" method that takes a
|
||||||
|
sequence and a number (we'll call it N) and returns a list of N items
|
||||||
|
taken randomly from the sequence. So I'll show you an example that
|
||||||
|
takes advantage of multiple arguments but doesn't use privmsgs.getArgs
|
||||||
|
(and thus has to handle its own errors if the number of arguments
|
||||||
|
isn't right). Here's the code:
|
||||||
|
|
||||||
|
def sample(self, irc, msg, args):
|
||||||
|
"""<number of items> [<text> ...]
|
||||||
|
|
||||||
|
Returns a sample of the <number of items> taken from the remaining
|
||||||
|
arguments. Obviously <number of items> must be less than the number
|
||||||
|
of arguments given.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
n = int(args.pop(0))
|
||||||
|
except IndexError: # raised by .pop(0)
|
||||||
|
raise callbacks.ArgumentError
|
||||||
|
except ValueError:
|
||||||
|
irc.error('<number of items> must be an integer.')
|
||||||
|
return
|
||||||
|
if n > len(args):
|
||||||
|
irc.error('<number of items> must be less than the number '
|
||||||
|
'of arguments.')
|
||||||
|
return
|
||||||
|
sample = self.rng.sample(args, n)
|
||||||
|
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||||
|
|
||||||
|
Most everything here is familiar. The difference between this and the
|
||||||
|
previous examples is that we're dealing with args directly, rather
|
||||||
|
than through getArgs. Since we already have the arguments in a list,
|
||||||
|
it doesn't make any sense to have privmsgs.getArgs smush them all
|
||||||
|
together into a big long string that we'll just have to re-split. But
|
||||||
|
we still want the nice error handling of privmsgs.getArgs. So what do
|
||||||
|
we do? We raise callbacks.ArgumentError! That's the secret juju that
|
||||||
|
privmsgs.getArgs is doing; now we're just doing it ourself. Someone
|
||||||
|
up our callchain knows how to handle it so a neat error message is
|
||||||
|
returned. So in this function, if .pop(0) fails, we weren't given
|
||||||
|
enough arguments and thus need to tell the user how to call us.
|
||||||
|
|
||||||
|
So we have the args, we have the number, we do a simple call to
|
||||||
|
random.sample and then we do this funky utils.commaAndify to it.
|
||||||
|
Yeah, so I was running low on useful names :) Anyway, what it does is
|
||||||
|
take a list of strings and return a string with them joined by a
|
||||||
|
comma, the last one being joined with a comma and "and". So the list
|
||||||
|
['foo', 'bar', 'baz'] becomes "foo, bar, and baz". It's pretty useful
|
||||||
|
for showing the user lists in a useful form. We map the strings with
|
||||||
|
repr() first just to surround them with quotes.
|
||||||
|
|
||||||
|
So we have one more example. Yes, I hear your groans, but it's
|
||||||
|
pedagogically useful :) This time we're going to write a command that
|
||||||
|
makes the bot roll a die. It'll take one argument (the number of
|
||||||
|
sides on the die) and will respond with the equivalent of "/me rolls a
|
||||||
|
__" where __ is the number the bot rolled. So here's the code:
|
||||||
|
|
||||||
|
def diceroll(self, irc, msg, args):
|
||||||
|
"""[<number of sides>]
|
||||||
|
|
||||||
|
Rolls a die with <number of sides> sides. The default number
|
||||||
|
of sides is 6.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
n = privmsgs.getArgs(args, required=0, optional=1)
|
||||||
|
if not n:
|
||||||
|
n = 6
|
||||||
|
n = int(n)
|
||||||
|
except ValueError:
|
||||||
|
irc.error('Dice have integer numbers of sides. Use one.')
|
||||||
|
return
|
||||||
|
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||||
|
irc.reply(s, action=True)
|
||||||
|
|
||||||
|
There's a lot of stuff you haven't seen before in there. The most
|
||||||
|
important, though, is the first thing you'll notice that's different:
|
||||||
|
the privmsg.getArgs call. Here we're offering a default argument in
|
||||||
|
case the user is too lazy to supply one (or just wants a nice,
|
||||||
|
standard six-sided die :)) privmsgs.getArgs supports that; we'll just
|
||||||
|
tell it that we don't *need* any arguments (via required=0) and that
|
||||||
|
we *might like* one argument (optional=1). If the user provides an
|
||||||
|
argument, we'll get it -- if they don't, we'll just get an empty
|
||||||
|
string. Hence the "if not n: n = 6", where we provide the default.
|
||||||
|
|
||||||
|
You'll also note that irc.reply was given a keyword argument here,
|
||||||
|
"action". This means that the reply is to be made as an action rather
|
||||||
|
than a normal reply.
|
||||||
|
|
||||||
|
So that's our plugin. 5 commands, each building in complexity. You
|
||||||
|
should now be able to write most anything you want to do in Supybot.
|
||||||
|
Except regexp-based plugins, but that's a story for another day (and
|
||||||
|
those aren't nearly as cool as these command-based callbacks anyway
|
||||||
|
:)). Now we need to flesh it out to make it a full-fledged plugin.
|
||||||
|
|
||||||
|
TODO: Describe the registry and how to write a proper plugin configure
|
||||||
|
function.
|
||||||
|
|
||||||
|
We've written our own plugin from scratch (well, from the boilerplate
|
||||||
|
that we got from scripts/newplugin.py :)) and survived! Now go write
|
||||||
|
more plugins for supybot, and send them to me so I can use them too :)
|
188
docs/STYLE
Normal file
188
docs/STYLE
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
====================================================================
|
||||||
|
Code not following these style guidelines fastidiously is likely
|
||||||
|
(*very* likely) not to be accepted into the Supybot core.
|
||||||
|
====================================================================
|
||||||
|
|
||||||
|
Read PEP 8 (Guido's Style Guide) and know that we use almost all the
|
||||||
|
same style guidelines.
|
||||||
|
|
||||||
|
Maximum line length is 79 characters. 78 is a safer bet, though.
|
||||||
|
This is **NON-NEGOTIABLE**. Your code will not be accepted while you
|
||||||
|
are violating this guidline.
|
||||||
|
|
||||||
|
Identation is 4 spaces per level. No tabs. This also is
|
||||||
|
**NON-NEGOTIABLE**. Your code, again, will *never* be accepted while
|
||||||
|
you have literal tabs in it.
|
||||||
|
|
||||||
|
Single quotes are used for all string literals that aren't docstrings.
|
||||||
|
They're just easier to type.
|
||||||
|
|
||||||
|
Triple double quotes (""") are always used for docstrings.
|
||||||
|
|
||||||
|
Raw strings (r'' or r"") should be used for regular expressions.
|
||||||
|
|
||||||
|
Spaces go around all operators (except around '=' in default
|
||||||
|
arguments to functions) and after all commas (unless doing so keeps a
|
||||||
|
line within the 79 character limit).
|
||||||
|
|
||||||
|
Functions calls should look like this: "foo(bar(baz(x), y))". They
|
||||||
|
should not look like "foo (bar (baz (x), y))", or like
|
||||||
|
"foo(bar(baz(x), y) )" or like anything else. I hate extraneous
|
||||||
|
spaces.
|
||||||
|
|
||||||
|
Class names are StudlyCaps. Method and function names are camelCaps
|
||||||
|
(StudlyCaps with an initial lowercase letter). If variable and
|
||||||
|
attribute names can maintain readability without being camelCaps,
|
||||||
|
then they should be entirely in lowercase, otherwise they should also
|
||||||
|
use camelCaps. Plugin names are StudlyCaps.
|
||||||
|
|
||||||
|
Imports should always happen at the top of the module, one import per
|
||||||
|
line (so if imports need to be added or removed later, it can be done
|
||||||
|
easily).
|
||||||
|
|
||||||
|
Unless absolutely required by some external force, imports should be
|
||||||
|
ordered by the string length of the module imported. I just think it
|
||||||
|
looks prettier.
|
||||||
|
|
||||||
|
A blank line should be between all consecutive method declarations in
|
||||||
|
a class definition. Two blank lines should be between all
|
||||||
|
consecutive class definitions in a file. Comments are even better
|
||||||
|
than blank lines for separating classes.
|
||||||
|
|
||||||
|
Database filenames should generally begin with the name of the plugin
|
||||||
|
and the extension should be 'db'. plugins.DBHandler does this
|
||||||
|
already.
|
||||||
|
|
||||||
|
Whenever creating a file descriptor or socket, keep a reference
|
||||||
|
around and be sure to close it. There should be no code like this:
|
||||||
|
s = urllib2.urlopen('url').read()
|
||||||
|
Instead, do this:
|
||||||
|
fd = urllib2.urlopen('url')
|
||||||
|
try:
|
||||||
|
s = fd.read()
|
||||||
|
finally:
|
||||||
|
fd.close()
|
||||||
|
This is to be sure the bot doesn't leak file descriptors.
|
||||||
|
|
||||||
|
All plugin files should include a docstring decsribing what the
|
||||||
|
plugin does. This docstring will be returned when the user is
|
||||||
|
configuring the plugin. All plugin classes should also include a
|
||||||
|
docstring describing how to do things with the plugin; this docstring
|
||||||
|
will be returned when the user requests help on a plugin name.
|
||||||
|
|
||||||
|
Method docstrings in classes deriving from callbacks.Privmsg should
|
||||||
|
include an argument list as their first line, and after that a blank
|
||||||
|
line followed by a longer description of what the command does. The
|
||||||
|
argument list is used by the 'syntax' command, and the longer
|
||||||
|
description is used by the 'help' command.
|
||||||
|
|
||||||
|
Whenever joining more than two strings, use string interpolation, not
|
||||||
|
addition:
|
||||||
|
s = x + y + z # Bad.
|
||||||
|
s = '%s%s%s' % (x, y, z) # Good.
|
||||||
|
s = ''.join([x, y, z]) # Best, but not as general.
|
||||||
|
This has to do with efficiency; the intermediate string x+y is made
|
||||||
|
(and thus copied) before x+y+z is made, so it's less efficient.
|
||||||
|
People who use string concatenation in a for loop will be swiftly
|
||||||
|
kicked in the head.
|
||||||
|
|
||||||
|
When writing strings that have formatting characters in them, don't
|
||||||
|
use anything but %s unless you absolutely must. In particular, %d
|
||||||
|
should never be used, it's less general than %s and serves no useful
|
||||||
|
purpose. If you got the %d wrong, you'll get an exception that says,
|
||||||
|
"foo instance can't be converted to an integer." But if you use %s,
|
||||||
|
you'll get to see your nice little foo instance, if it doesn't
|
||||||
|
convert to a string cleanly, and if it does convert cleanly, you'll
|
||||||
|
get to see what you expect to see. Basically, %d just sucks.
|
||||||
|
|
||||||
|
As a corrolary to the above, note that sometimes %f is used, but on
|
||||||
|
when floats need to be formatted, e.g., %.2f.
|
||||||
|
|
||||||
|
Use the log module to its fullest; when you need to print some values
|
||||||
|
to debug, use self.log.debug to do so, and leave those statements in
|
||||||
|
the code (commented out) so they can later be re-enabled. Remember
|
||||||
|
that once code is buggy, it tends to have more bugs, and you'll
|
||||||
|
probably need those print statements again.
|
||||||
|
|
||||||
|
While on the topic of logs, note that we do not use % (i.e.,
|
||||||
|
str.__mod__) with logged strings; we simple pass the format
|
||||||
|
parameters as additional arguments. The reason is simple: the
|
||||||
|
logging module supports it, and it's cleaner (fewer tokens/glyphs) to
|
||||||
|
read.
|
||||||
|
|
||||||
|
While still on the topic of logs, it's also important to pick the
|
||||||
|
appropriate log level for given information.
|
||||||
|
DEBUG: Appropriate to tell a programmer *how* we're doing
|
||||||
|
something (i.e., debugging printfs, basically). If
|
||||||
|
you're trying to figure out why your code doesn't work,
|
||||||
|
DEBUG is the new printf -- use that, and leave the
|
||||||
|
statements in your code.
|
||||||
|
INFO: Appropriate to tell a user *what* we're doing, when what
|
||||||
|
we're doing isn't important for the user to pay attention
|
||||||
|
to. A user who likes to keep up with things should enjoy
|
||||||
|
watching our logging at the INFO level; it shouldn't be
|
||||||
|
too low-level, but it should give enough information that
|
||||||
|
it keeps him relatively interested at peak times.
|
||||||
|
WARNING: Appropriate to tell a user when we're doing something
|
||||||
|
that he really ought to pay attention to. Users should
|
||||||
|
see WARNING and think, "Hmm, should I tell the Supybot
|
||||||
|
developers about this?" Later, he should decide not to,
|
||||||
|
but it should give the user a moment to pause and think
|
||||||
|
about what's actually happening with his bot.
|
||||||
|
ERROR: Appropriate to tell a user when something has gone wrong.
|
||||||
|
Uncaught exceptions are ERRORs. Conditions that we
|
||||||
|
absolutely want to hear about should be errors. Things
|
||||||
|
that should *scare* the user should be errors.
|
||||||
|
CRITICAL: Not really appropriate. I can think of no absolutely
|
||||||
|
critical issue yet encountered in Supybot; the only
|
||||||
|
possible thing I can imagine is to notify the user that
|
||||||
|
the partition on which Supybot is running has filled up.
|
||||||
|
That would be a CRITICAL condition, but it would also be
|
||||||
|
hard to log :)
|
||||||
|
|
||||||
|
All plugins should have test cases written for them. Even if it
|
||||||
|
doesn't actually test anything but just exists, it's good to have the
|
||||||
|
test there so there's a place to add more tests later (and so we can
|
||||||
|
be sure that all plugins are adequately documented; PluginTestCase
|
||||||
|
checks that every command has documentation)
|
||||||
|
|
||||||
|
All uses of eval() that expect to get integrated in Supybot must be
|
||||||
|
approved by jemfinch, no exceptions. Chances are, it won't be
|
||||||
|
accepted. Have you looked at utils.safeEval?
|
||||||
|
|
||||||
|
SQL table names should be all-lowercase and include underscores to
|
||||||
|
separate words. This is because SQL itself is case-insensitive.
|
||||||
|
This doesn't change, however the fact that variable/member names
|
||||||
|
should be camel case.
|
||||||
|
|
||||||
|
SQL statements in code should put SQL words in ALL CAPS:
|
||||||
|
"""SELECT quote FROM quotes ORDER BY random() LIMIT 1""". This makes
|
||||||
|
SQL significantly easier to read.
|
||||||
|
|
||||||
|
Common variable names:
|
||||||
|
L => an arbitrary list.
|
||||||
|
t => an arbitrary tuple.
|
||||||
|
x => an arbitrary float.
|
||||||
|
s => an arbitrary string.
|
||||||
|
f => an arbitrary function.
|
||||||
|
p => an arbitrary predicate.
|
||||||
|
i,n => an arbitrary integer.
|
||||||
|
cb => an arbitrary callback.
|
||||||
|
db => a database handle.
|
||||||
|
fd => a file-like object.
|
||||||
|
msg => an ircmsgs.IrcMsg object.
|
||||||
|
irc => an irclib.Irc object (or proxy)
|
||||||
|
nick => a string that is an IRC nick.
|
||||||
|
channel => a string that is an IRC channel.
|
||||||
|
hostmask => a string that is a user's IRC prefix.
|
||||||
|
When the semantic functionality (that is, the "meaning" of a variable
|
||||||
|
is obvious from context, one of these names should be used. This
|
||||||
|
just makes it easier for people reading our code to know what a
|
||||||
|
variable represents without scouring the surrounding code.
|
||||||
|
|
||||||
|
Multiple variable assignments should always be surrounded with
|
||||||
|
parentheses -- i.e., if you're using the partition function, then
|
||||||
|
your assignment statement should look like
|
||||||
|
(good, bad) = partition(p, L). The parentheses make it obvious that
|
||||||
|
you're doing a multiple assignment, and that's important because I
|
||||||
|
hate reading code and wondering where a variable came from.
|
43
docs/man/supybot-adduser.1
Normal file
43
docs/man/supybot-adduser.1
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.\" Process this file with
|
||||||
|
.\" groff -man -Tascii supybot-adduser.1
|
||||||
|
.\"
|
||||||
|
.TH SUPYBOT\-ADDUSER 1 "SEPTEMBER 2004"
|
||||||
|
.SH NAME
|
||||||
|
supybot\-adduser \- Adds a user to a Supybot users.conf file
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B supybot\-adduser
|
||||||
|
.RI [ options ] " users.conf
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B
|
||||||
|
supybot\-adduser
|
||||||
|
adds a user to the specified users.conf file.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-\^\-version
|
||||||
|
Show version of program.
|
||||||
|
.TP
|
||||||
|
.BR \-h ", " \-\^\-help
|
||||||
|
Show summary of options.
|
||||||
|
.TP
|
||||||
|
.BR \-u " NAME" "\fR,\fP \-\^\-username=" NAME
|
||||||
|
Specifies the username to use for the new user.
|
||||||
|
.TP
|
||||||
|
.BR \-x ", " \-\^\-hashed
|
||||||
|
Hash encrypt the password.
|
||||||
|
.TP
|
||||||
|
.BR \-n ", " \-\^\-plain
|
||||||
|
Store the password in plain text.
|
||||||
|
.TP
|
||||||
|
.BR \-c " CAPABILITY" "\fR,\fP \-\^\-capability=" CAPABILITY
|
||||||
|
Capability the user should have; this option may be given
|
||||||
|
multiple times.
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
.IR python (1),
|
||||||
|
.IR supybot (1),
|
||||||
|
.IR supybot\-wizard (1),
|
||||||
|
.IR supybot\-newplugin (1)
|
||||||
|
.SH AUTHOR
|
||||||
|
This manual page was originally written by James Vega
|
||||||
|
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||||
|
distribute and/or modify this document under the terms of the Supybot
|
||||||
|
license, a BSD\-style license.
|
41
docs/man/supybot-newplugin.1
Normal file
41
docs/man/supybot-newplugin.1
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.\" Process this file with
|
||||||
|
.\" groff -man -Tascii supybot-newplugin.1
|
||||||
|
.\"
|
||||||
|
.TH SUPYBOT\-NEWPLUGIN 1 "SEPTEMBER 2004"
|
||||||
|
.SH NAME
|
||||||
|
supybot\-newplugin \- A wizard for creating Supybot plugins
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B supybot\-newplugin
|
||||||
|
.RI [ options ]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B
|
||||||
|
supybot\-newplugin
|
||||||
|
is a wizard that creates a template python source file for a new
|
||||||
|
.IR supybot (1)
|
||||||
|
plugin.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-\^\-version
|
||||||
|
Show version of program.
|
||||||
|
.TP
|
||||||
|
.BR \-h ", " \-\^\-help
|
||||||
|
Show summary of options.
|
||||||
|
.TP
|
||||||
|
.BR \-r ", " \-\^\-regexp
|
||||||
|
Uses a regexp\-based callback.
|
||||||
|
.TP
|
||||||
|
.BI \-n " NAME" "\fR,\fP \-\^\-name=" NAME
|
||||||
|
Sets the name for the plugin.
|
||||||
|
.TP
|
||||||
|
.BR \-t ", " \-\^\-thread
|
||||||
|
Makes the plugin threaded.
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
.IR python (1),
|
||||||
|
.IR supybot (1),
|
||||||
|
.IR supybot\-wizard (1),
|
||||||
|
.IR supybot\-adduser (1),
|
||||||
|
.SH AUTHOR
|
||||||
|
This manual page was originally written by James Vega
|
||||||
|
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||||
|
distribute and/or modify this document under the terms of the Supybot
|
||||||
|
license, a BSD\-style license.
|
40
docs/man/supybot-wizard.1
Normal file
40
docs/man/supybot-wizard.1
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.\" Process this file with
|
||||||
|
.\" groff -man -Tascii supybot-wizard.1
|
||||||
|
.\"
|
||||||
|
.TH SUPYBOT\-WIZARD 1 "SEPTEMBER 2004"
|
||||||
|
.SH NAME
|
||||||
|
supybot\-wizard \- A wizard for creating Supybot configuration files
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B supybot\-wizard
|
||||||
|
.RI [ options ]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B
|
||||||
|
supybot\-wizard
|
||||||
|
is an in\-depth wizard that provides a nice user interface for creating
|
||||||
|
configuration files for
|
||||||
|
.IR supybot (1).
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-\^\-version
|
||||||
|
Show version of program.
|
||||||
|
.TP
|
||||||
|
.BR \-h ", " \-\^\-help
|
||||||
|
Show summary of options.
|
||||||
|
.TP
|
||||||
|
.B \-\^\-allow\-root
|
||||||
|
Determines whether the wizard will be allowed to run as root. You do not
|
||||||
|
want this. Do not do it. Even if you think you want it, you do not.
|
||||||
|
.TP
|
||||||
|
.B \-\^\-no\-network
|
||||||
|
Determines whether the wizard will be allowed to run without a network
|
||||||
|
connection.
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
.IR python (1),
|
||||||
|
.IR supybot (1),
|
||||||
|
.IR supybot\-adduser (1),
|
||||||
|
.IR supybot\-newplugin (1)
|
||||||
|
.SH AUTHOR
|
||||||
|
This manual page was originally written by James Vega
|
||||||
|
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||||
|
distribute and/or modify this document under the terms of the Supybot
|
||||||
|
license, a BSD\-style license.
|
68
docs/man/supybot.1
Normal file
68
docs/man/supybot.1
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
.\" Process this file with
|
||||||
|
.\" groff -man -Tascii supybot.1
|
||||||
|
.\"
|
||||||
|
.TH SUPYBOT 1 "SEPTEMBER 2004"
|
||||||
|
.SH NAME
|
||||||
|
supybot \- A robust and user friendly Python IRC bot
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B supybot
|
||||||
|
.RI [ options ] " configFile
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B
|
||||||
|
Supybot
|
||||||
|
is a robust, user\-friendly, and programmer\-friendly Python IRC bot.
|
||||||
|
It aims to be an adequate replacement for most existing IRC bots. It
|
||||||
|
includes a very flexible and powerful ACL system for controlling access
|
||||||
|
to commands, as well as more than 50 builtin plugins providing around
|
||||||
|
400 actual commands.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-\^\-version
|
||||||
|
Show version of program.
|
||||||
|
.TP
|
||||||
|
.BR \-h ", " \-\^\-help
|
||||||
|
Show summary of options.
|
||||||
|
.TP
|
||||||
|
.BR \-P ", " \-\^\-profile
|
||||||
|
Enable profiling.
|
||||||
|
.TP
|
||||||
|
.B \-O
|
||||||
|
Optimizes asserts out of the code; \-O0 optimizes asserts and uses
|
||||||
|
.IR psyco .
|
||||||
|
.TP
|
||||||
|
.BI \-n " NICK" "\fR,\fP \-\^\-nick=" NICK
|
||||||
|
Nick the bot should use.
|
||||||
|
.TP
|
||||||
|
.BI \-u " USER" "\fR,\fP \-\^\-user=" USER
|
||||||
|
Full username the bot should use.
|
||||||
|
.TP
|
||||||
|
.BI \-i " IDENT" "\fR,\fP \-\^\-ident=" IDENT
|
||||||
|
Ident the bot should use.
|
||||||
|
.TP
|
||||||
|
.BR \-d ", " \-\^\-daemon
|
||||||
|
Determines whether the bot will daemonize. This is a no\-op on
|
||||||
|
non\-POSIX systems.
|
||||||
|
.TP
|
||||||
|
.B \-\^\-allow\-default\-owner
|
||||||
|
Determines whether the bot will allow its defaultCapabilities not to
|
||||||
|
include "\-owner", thus giving all users the owner capability by
|
||||||
|
default. This is dumb, hence we require a command\-line option to
|
||||||
|
enable it.
|
||||||
|
.TP
|
||||||
|
.B \-\^\-allow\-root
|
||||||
|
Determines whether the bot will be allowed to run as root. You do not
|
||||||
|
want this. Do not do it. Even if you think you want it, you do not.
|
||||||
|
.TP
|
||||||
|
.B \-\^\-debug
|
||||||
|
Determines whether some extra debugging stuff will be logged by this
|
||||||
|
script.
|
||||||
|
.SH "SEE ALSO"
|
||||||
|
.IR python (1),
|
||||||
|
.IR supybot\-wizard (1),
|
||||||
|
.IR supybot\-adduser (1),
|
||||||
|
.IR supybot\-newplugin (1)
|
||||||
|
.SH AUTHOR
|
||||||
|
This manual page was originally written by James Vega
|
||||||
|
<vega dot james at gmail dot com>. Permission is granted to copy,
|
||||||
|
distribute and/or modify this document under the terms of the Supybot
|
||||||
|
license, a BSD\-style license.
|
30
feed.xml
30
feed.xml
@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
layout: null
|
|
||||||
---
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
||||||
<channel>
|
|
||||||
<title>{{ site.title | xml_escape }}</title>
|
|
||||||
<description>{{ site.description | xml_escape }}</description>
|
|
||||||
<link>{{ site.url }}{{ site.baseurl }}/</link>
|
|
||||||
<atom:link href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" rel="self" type="application/rss+xml"/>
|
|
||||||
<pubDate>{{ site.time | date_to_rfc822 }}</pubDate>
|
|
||||||
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
|
|
||||||
<generator>Jekyll v{{ jekyll.version }}</generator>
|
|
||||||
{% for post in site.posts limit:10 %}
|
|
||||||
<item>
|
|
||||||
<title>{{ post.title | xml_escape }}</title>
|
|
||||||
<description>{{ post.content | xml_escape }}</description>
|
|
||||||
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
|
|
||||||
<link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>
|
|
||||||
<guid isPermaLink="true">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>
|
|
||||||
{% for tag in post.tags %}
|
|
||||||
<category>{{ tag | xml_escape }}</category>
|
|
||||||
{% endfor %}
|
|
||||||
{% for cat in post.categories %}
|
|
||||||
<category>{{ cat | xml_escape }}</category>
|
|
||||||
{% endfor %}
|
|
||||||
</item>
|
|
||||||
{% endfor %}
|
|
||||||
</channel>
|
|
||||||
</rss>
|
|
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
layout: default
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- @format -->
|
|
||||||
|
|
||||||
**_WARNING: most of the content on this site originates from 2014!_**
|
|
||||||
|
|
||||||
Welcome to Mikaela's Supybot pages.
|
|
||||||
|
|
||||||
This site isn't official and won't help with most of issues. In case you are
|
|
||||||
looking for the official sites, they are here:
|
|
||||||
|
|
||||||
- [Limnoria's website](https://limnoria.net/)
|
|
||||||
- [Limnoria official documentation](https://docs.limnoria.net/)
|
|
||||||
- [Supybook](https://hoxu.github.io/supybook/devel/)
|
|
||||||
- [Gribble Wiki](https://sourceforge.net/p/gribble/wiki/Main_Page/)
|
|
||||||
|
|
||||||
I also have
|
|
||||||
[something in my gist repo](https://gitea.blesmrt.net/mikaela/gist/src/branch/master/irc/limnoria/)
|
|
||||||
at 2021-06-11 16:07 UTC
|
|
||||||
[opinionated titlefetching instructions](https://gitea.blesmrt.net/mikaela/gist/src/branch/master/irc/limnoria/titlefetching.md).
|
|
||||||
|
|
||||||
If you cannot find what you are looking for from them, please come to IRC and
|
|
||||||
ask. The Support channels are
|
|
||||||
[#supybot,#limnoria on irc.libera.chat](ircs://irc.libera.chat:6697/%23supybot%2c%23limnoria)
|
|
450
others/BeautifulSoup.py
Normal file
450
others/BeautifulSoup.py
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
"""Beautiful Soup
|
||||||
|
Elixir and Tonic
|
||||||
|
"The Screen-Scraper's Friend"
|
||||||
|
|
||||||
|
The BeautifulSoup class turns arbitrarily bad HTML into a tree-like
|
||||||
|
nested tag-soup list of Tag objects and text snippets. A Tag object
|
||||||
|
corresponds to an HTML tag. It knows about the HTML tag's attributes,
|
||||||
|
and contains a representation of everything contained between the
|
||||||
|
original tag and its closing tag (if any). It's easy to extract Tags
|
||||||
|
that meet certain criteria.
|
||||||
|
|
||||||
|
A well-formed HTML document will yield a well-formed data
|
||||||
|
structure. An ill-formed HTML document will yield a correspondingly
|
||||||
|
ill-formed data structure. If your document is only locally
|
||||||
|
well-formed, you can use this to process the well-formed part of it.
|
||||||
|
|
||||||
|
#Example:
|
||||||
|
#--------
|
||||||
|
from BeautifulSoup import BeautifulSoup
|
||||||
|
text = '''<html>
|
||||||
|
<head><title>The Title</title></head>
|
||||||
|
<body>
|
||||||
|
<a class="foo" href="http://www.crummy.com/">Link <i>text (italicized)</i></a>
|
||||||
|
<a href="http://www.foo.com/">Link text 2</a>
|
||||||
|
</body>
|
||||||
|
</html>'''
|
||||||
|
soup = BeautifulSoup()
|
||||||
|
soup.feed(text)
|
||||||
|
print soup("a") #Returns a list of 2 Tag objects, one for each link in
|
||||||
|
#the source
|
||||||
|
print soup.first("a", {'class':'foo'})['href'] #Returns http://www.crummy.com/
|
||||||
|
print soup.first("title").contents[0] #Returns "The title"
|
||||||
|
print soup.first("a", {'href':'http://www.crummy.com/'}).first("i").contents[0]
|
||||||
|
#Returns "text (italicized)"
|
||||||
|
|
||||||
|
#Example of SQL-style attribute wildcards -- all four 'find' calls will
|
||||||
|
#find the link.
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
soup = BeautifulSoup()
|
||||||
|
soup.feed('''<a href="http://foo.com/">bla</a>''')
|
||||||
|
print soup.fetch('a', {'href': 'http://foo.com/'})
|
||||||
|
print soup.fetch('a', {'href': 'http://%'})
|
||||||
|
print soup.fetch('a', {'href': '%.com/'})
|
||||||
|
print soup.fetch('a', {'href': '%o.c%'})
|
||||||
|
|
||||||
|
#Example with horrible HTML:
|
||||||
|
#---------------------------
|
||||||
|
soup = BeautifulSoup()
|
||||||
|
soup.feed('''<body>
|
||||||
|
Go <a class="that" href="here.html"><i>here</i></a>
|
||||||
|
or <i>go <b><a href="index.html">Home</a>
|
||||||
|
</html>''')
|
||||||
|
print soup.fetch('a') #Returns a list of 2 Tag objects.
|
||||||
|
print soup.first(attrs={'href': 'here.html'})['class'] #Returns "that"
|
||||||
|
print soup.first(attrs={'class': 'that'}).first('i').contents[0] #returns "here"
|
||||||
|
|
||||||
|
This library has no external dependencies. It works with Python 1.5.2
|
||||||
|
and up. If you can install a Python extension, you might want to use
|
||||||
|
the ElementTree Tidy HTML Tree Builder instead:
|
||||||
|
http://www.effbot.org/zone/element-tidylib.htm
|
||||||
|
|
||||||
|
You can use BeautifulSoup on any SGML-like substance, such as XML or a
|
||||||
|
domain-specific language that looks like HTML but has different tag
|
||||||
|
names. For such purposes you may want to use the BeautifulStoneSoup
|
||||||
|
class, which knows nothing at all about HTML per se. I also reserve
|
||||||
|
the right to make the BeautifulSoup parser smarter between releases,
|
||||||
|
so if you want forwards-compatibility without having to think about
|
||||||
|
it, you might want to go with BeautifulStoneSoup.
|
||||||
|
|
||||||
|
Release status:
|
||||||
|
|
||||||
|
(I do a new release whenever I make a change that breaks backwards
|
||||||
|
compatibility.)
|
||||||
|
|
||||||
|
Current release:
|
||||||
|
|
||||||
|
Applied patch from Richie Hindle (richie at entrian dot com) that
|
||||||
|
makes tag.string a shorthand for tag.contents[0].string when the tag
|
||||||
|
has only one string-owning child.
|
||||||
|
|
||||||
|
1.2 "Who for such dainties would not stoop?" (2004/07/08): Applied
|
||||||
|
patch from Ben Last (ben at benlast dot com) that made
|
||||||
|
Tag.renderContents() correctly handle Unicode.
|
||||||
|
|
||||||
|
Made BeautifulStoneSoup even dumber by making it not implicitly
|
||||||
|
close a tag when another tag of the same type is encountered; only
|
||||||
|
when an actual closing tag is encountered. This change courtesy of
|
||||||
|
Fuzzy (mike at pcblokes dot com). BeautifulSoup still works as
|
||||||
|
before.
|
||||||
|
|
||||||
|
1.1 "Swimming in a hot tureen": Added more 'nestable' tags. Changed
|
||||||
|
popping semantics so that when a nestable tag is encountered, tags are
|
||||||
|
popped up to the previously encountered nestable tag (of whatever kind).
|
||||||
|
I will revert this if enough people complain, but it should make
|
||||||
|
more people's lives easier than harder.
|
||||||
|
|
||||||
|
This enhancement was suggested by Anthony Baxter (anthony at
|
||||||
|
interlink dot com dot au).
|
||||||
|
|
||||||
|
1.0 "So rich and green": Initial release.
|
||||||
|
|
||||||
|
Retreived from: http://www.crummy.com/software/BeautifulSoup/
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Leonard Richardson (leonardr@segfault.org)"
|
||||||
|
__version__ = "1.1 $Revision$"
|
||||||
|
__date__ = "$Date$"
|
||||||
|
__copyright__ = "Copyright (c) 2004 Leonard Richardson"
|
||||||
|
__license__ = "Python"
|
||||||
|
|
||||||
|
from sgmllib import SGMLParser
|
||||||
|
import string
|
||||||
|
import types
|
||||||
|
|
||||||
|
class PageElement:
|
||||||
|
"""Contains the navigational information for some part of the page
|
||||||
|
(either a tag or a piece of text)"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None, previous=None):
|
||||||
|
self.parent = parent
|
||||||
|
self.previous = previous
|
||||||
|
self.next = None
|
||||||
|
|
||||||
|
class NavigableText(PageElement):
|
||||||
|
|
||||||
|
"""A simple wrapper around a string that keeps track of where in
|
||||||
|
the document the string was found. Doesn't implement all the
|
||||||
|
string methods because I'm lazy. You could have this extend
|
||||||
|
UserString if you were using 2.2."""
|
||||||
|
|
||||||
|
def __init__(self, string, parent=None, previous=None):
|
||||||
|
PageElement.__init__(self, parent, previous)
|
||||||
|
self.string = string
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.string == str(other)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.string
|
||||||
|
|
||||||
|
def strip(self):
|
||||||
|
return self.string.strip()
|
||||||
|
|
||||||
|
class Tag(PageElement):
|
||||||
|
|
||||||
|
"""Represents a found HTML tag with its attributes and contents."""
|
||||||
|
|
||||||
|
def __init__(self, name, attrs={}, parent=None, previous=None):
|
||||||
|
PageElement.__init__(self, parent, previous)
|
||||||
|
self.name = name
|
||||||
|
self.attrs = attrs
|
||||||
|
self.contents = []
|
||||||
|
self.foundClose = 0
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self._getAttrMap().get(key, default)
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return apply(self.fetch, args)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._getAttrMap()[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._getAttrMap()
|
||||||
|
self.attrMap[key] = value
|
||||||
|
for i in range(0, len(self.attrs)):
|
||||||
|
if self.attrs[i][0] == key:
|
||||||
|
self.attrs[i] = (key, value)
|
||||||
|
|
||||||
|
def _getAttrMap(self):
|
||||||
|
if not hasattr(self, 'attrMap'):
|
||||||
|
self.attrMap = {}
|
||||||
|
for (key, value) in self.attrs:
|
||||||
|
self.attrMap[key] = value
|
||||||
|
return self.attrMap
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Tag) or self.name != other.name or self.attrs != other.attrs or len(self.contents) != len(other.contents):
|
||||||
|
return 0
|
||||||
|
for i in range(0, len(self.contents)):
|
||||||
|
if self.contents[i] != other.contents[i]:
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
attrs = ''
|
||||||
|
if self.attrs:
|
||||||
|
for key, val in self.attrs:
|
||||||
|
attrs = attrs + ' %s="%s"' % (key, val)
|
||||||
|
close = ''
|
||||||
|
closeTag = ''
|
||||||
|
if self.isSelfClosing():
|
||||||
|
close = ' /'
|
||||||
|
elif self.foundClose:
|
||||||
|
closeTag = '</%s>' % self.name
|
||||||
|
s = self.renderContents()
|
||||||
|
if not hasattr(self, 'hideTag'):
|
||||||
|
s = '<%s%s%s>' % (self.name, attrs, close) + s + closeTag
|
||||||
|
return s
|
||||||
|
|
||||||
|
def renderContents(self):
|
||||||
|
s='' #non-Unicode
|
||||||
|
for c in self.contents:
|
||||||
|
try:
|
||||||
|
s = s + str(c)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
if type(s) <> types.UnicodeType:
|
||||||
|
s = s.decode('utf8') #convert ascii to Unicode
|
||||||
|
#str() should, strictly speaking, not return a Unicode
|
||||||
|
#string, but NavigableText never checks and will return
|
||||||
|
#Unicode data if it was initialised with it.
|
||||||
|
s = s + str(c)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def isSelfClosing(self):
|
||||||
|
return self.name in BeautifulSoup.SELF_CLOSING_TAGS
|
||||||
|
|
||||||
|
def append(self, tag):
|
||||||
|
self.contents.append(tag)
|
||||||
|
|
||||||
|
def first(self, name=None, attrs={}, contents=None, recursive=1):
|
||||||
|
r = None
|
||||||
|
l = self.fetch(name, attrs, contents, recursive)
|
||||||
|
if l:
|
||||||
|
r = l[0]
|
||||||
|
return r
|
||||||
|
|
||||||
|
def fetch(self, name=None, attrs={}, contents=None, recursive=1):
|
||||||
|
"""Extracts Tag objects that match the given criteria. You
|
||||||
|
can specify the name of the Tag, any attributes you want the
|
||||||
|
Tag to have, and what text and Tags you want to see inside the
|
||||||
|
Tag."""
|
||||||
|
if contents and type(contents) != type([]):
|
||||||
|
contents = [contents]
|
||||||
|
results = []
|
||||||
|
for i in self.contents:
|
||||||
|
if isinstance(i, Tag):
|
||||||
|
if not name or i.name == name:
|
||||||
|
match = 1
|
||||||
|
for attr, value in attrs.items():
|
||||||
|
check = i.get(attr)
|
||||||
|
#By default, find the specific value called for.
|
||||||
|
#Use SQL-style wildcards to find substrings, prefix,
|
||||||
|
#suffix, etc.
|
||||||
|
result = (check == value)
|
||||||
|
if check and value:
|
||||||
|
if len(value) > 1 and value[0] == '%' and value[-1] == '%' and value[-2] != '\\':
|
||||||
|
result = (check.find(value[1:-1]) != -1)
|
||||||
|
elif value[0] == '%':
|
||||||
|
print "blah"
|
||||||
|
result = check.rfind(value[1:]) == len(check)-len(value)+1
|
||||||
|
elif value[-1] == '%':
|
||||||
|
result = check.find(value[:-1]) == 0
|
||||||
|
if not result:
|
||||||
|
match = 0
|
||||||
|
break
|
||||||
|
match = match and (not contents or i.contents == contents)
|
||||||
|
if match:
|
||||||
|
results.append(i)
|
||||||
|
if recursive:
|
||||||
|
results.extend(i.fetch(name, attrs, contents, recursive))
|
||||||
|
return results
|
||||||
|
|
||||||
|
class BeautifulSoup(SGMLParser, Tag):
|
||||||
|
|
||||||
|
"""The actual parser. It knows the following facts about HTML, and
|
||||||
|
not much else:
|
||||||
|
|
||||||
|
* Some tags have no closing tag and should be interpreted as being
|
||||||
|
closed as soon as they are encountered.
|
||||||
|
|
||||||
|
* Most tags can't be nested; encountering an open tag when there's
|
||||||
|
already an open tag of that type in the stack means that the
|
||||||
|
previous tag of that type should be implicitly closed. However,
|
||||||
|
some tags can be nested. When a nestable tag is encountered,
|
||||||
|
it's okay to close all unclosed tags up to the last nestable
|
||||||
|
tag. It might not be safe to close any more, so that's all it
|
||||||
|
closes.
|
||||||
|
|
||||||
|
* The text inside some tags (ie. 'script') may contain tags which
|
||||||
|
are not really part of the document and which should be parsed
|
||||||
|
as text, not tags. If you want to parse the text as tags, you can
|
||||||
|
always get it and parse it explicitly."""
|
||||||
|
|
||||||
|
SELF_CLOSING_TAGS = ['br', 'hr', 'input', 'img', 'meta', 'spacer',
|
||||||
|
'link', 'frame']
|
||||||
|
NESTABLE_TAGS = ['font', 'table', 'tr', 'td', 'th', 'tbody', 'p',
|
||||||
|
'div']
|
||||||
|
QUOTE_TAGS = ['script']
|
||||||
|
|
||||||
|
IMPLICITLY_CLOSE_TAGS = 1
|
||||||
|
|
||||||
|
def __init__(self, text=None):
|
||||||
|
Tag.__init__(self, '[document]')
|
||||||
|
SGMLParser.__init__(self)
|
||||||
|
self.quoteStack = []
|
||||||
|
self.hideTag = 1
|
||||||
|
self.reset()
|
||||||
|
if text:
|
||||||
|
self.feed(text)
|
||||||
|
|
||||||
|
def feed(self, text):
|
||||||
|
SGMLParser.feed(self, text)
|
||||||
|
self.endData()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
SGMLParser.reset(self)
|
||||||
|
self.currentData = ''
|
||||||
|
self.currentTag = None
|
||||||
|
self.tagStack = []
|
||||||
|
self.pushTag(self)
|
||||||
|
|
||||||
|
def popTag(self, closedTagName=None):
|
||||||
|
tag = self.tagStack.pop()
|
||||||
|
if closedTagName == tag.name:
|
||||||
|
tag.foundClose = 1
|
||||||
|
|
||||||
|
# Tags with just one string-owning child get the same string
|
||||||
|
# property as the child, so that soup.tag.string is shorthand
|
||||||
|
# for soup.tag.contents[0].string
|
||||||
|
if len(self.currentTag.contents) == 1 and \
|
||||||
|
hasattr(self.currentTag.contents[0], 'string'):
|
||||||
|
self.currentTag.string = self.currentTag.contents[0].string
|
||||||
|
|
||||||
|
#print "Pop", tag.name
|
||||||
|
self.currentTag = self.tagStack[-1]
|
||||||
|
return self.currentTag
|
||||||
|
|
||||||
|
def pushTag(self, tag):
|
||||||
|
#print "Push", tag.name
|
||||||
|
if self.currentTag:
|
||||||
|
self.currentTag.append(tag)
|
||||||
|
self.tagStack.append(tag)
|
||||||
|
self.currentTag = self.tagStack[-1]
|
||||||
|
|
||||||
|
def endData(self):
|
||||||
|
if self.currentData:
|
||||||
|
if not string.strip(self.currentData):
|
||||||
|
if '\n' in self.currentData:
|
||||||
|
self.currentData = '\n'
|
||||||
|
else:
|
||||||
|
self.currentData = ' '
|
||||||
|
o = NavigableText(self.currentData, self.currentTag, self.previous)
|
||||||
|
if self.previous:
|
||||||
|
self.previous.next = o
|
||||||
|
self.previous = o
|
||||||
|
self.currentTag.contents.append(o)
|
||||||
|
self.currentData = ''
|
||||||
|
|
||||||
|
def _popToTag(self, name, closedTag=0):
|
||||||
|
"""Pops the tag stack up to and including the most recent
|
||||||
|
instance of the given tag. If a list of tags is given, will
|
||||||
|
accept any of those tags as an excuse to stop popping, and will
|
||||||
|
*not* pop the tag that caused it to stop popping."""
|
||||||
|
if self.IMPLICITLY_CLOSE_TAGS:
|
||||||
|
closedTag = 1
|
||||||
|
numPops = 0
|
||||||
|
mostRecentTag = None
|
||||||
|
oneTag = (type(name) == types.StringType)
|
||||||
|
for i in range(len(self.tagStack)-1, 0, -1):
|
||||||
|
thisTag = self.tagStack[i].name
|
||||||
|
if (oneTag and thisTag == name) \
|
||||||
|
or (not oneTag and thisTag in name):
|
||||||
|
numPops = len(self.tagStack)-i
|
||||||
|
break
|
||||||
|
if not oneTag:
|
||||||
|
numPops = numPops - 1
|
||||||
|
|
||||||
|
closedTagName = None
|
||||||
|
if closedTag:
|
||||||
|
closedTagName = name
|
||||||
|
|
||||||
|
for i in range(0, numPops):
|
||||||
|
mostRecentTag = self.popTag(closedTagName)
|
||||||
|
return mostRecentTag
|
||||||
|
|
||||||
|
def unknown_starttag(self, name, attrs):
|
||||||
|
if self.quoteStack:
|
||||||
|
#This is not a real tag.
|
||||||
|
#print "<%s> is not real!" % name
|
||||||
|
attrs = map(lambda(x, y): '%s="%s"' % (x, y), attrs)
|
||||||
|
self.handle_data('<%s %s>' % (name, attrs))
|
||||||
|
return
|
||||||
|
self.endData()
|
||||||
|
tag = Tag(name, attrs, self.currentTag, self.previous)
|
||||||
|
if self.previous:
|
||||||
|
self.previous.next = tag
|
||||||
|
self.previous = tag
|
||||||
|
if not name in self.SELF_CLOSING_TAGS:
|
||||||
|
if name in self.NESTABLE_TAGS:
|
||||||
|
self._popToTag(self.NESTABLE_TAGS)
|
||||||
|
else:
|
||||||
|
self._popToTag(name)
|
||||||
|
self.pushTag(tag)
|
||||||
|
if name in self.SELF_CLOSING_TAGS:
|
||||||
|
self.popTag()
|
||||||
|
if name in self.QUOTE_TAGS:
|
||||||
|
#print "Beginning quote (%s)" % name
|
||||||
|
self.quoteStack.append(name)
|
||||||
|
|
||||||
|
def unknown_endtag(self, name):
|
||||||
|
if self.quoteStack and self.quoteStack[-1] != name:
|
||||||
|
#This is not a real end tag.
|
||||||
|
#print "</%s> is not real!" % name
|
||||||
|
self.handle_data('</%s>' % name)
|
||||||
|
return
|
||||||
|
self.endData()
|
||||||
|
self._popToTag(name, 1)
|
||||||
|
if self.quoteStack and self.quoteStack[-1] == name:
|
||||||
|
#print "That's the end of %s!" % self.quoteStack[-1]
|
||||||
|
self.quoteStack.pop()
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
self.currentData = self.currentData + data
|
||||||
|
|
||||||
|
def handle_comment(self, text):
|
||||||
|
"Propagate comments right through."
|
||||||
|
self.handle_data("<!--%s-->" % text)
|
||||||
|
|
||||||
|
def handle_charref(self, ref):
|
||||||
|
"Propagate char refs right through."
|
||||||
|
self.handle_data('&#%s;' % ref)
|
||||||
|
|
||||||
|
def handle_entityref(self, ref):
|
||||||
|
"Propagate entity refs right through."
|
||||||
|
self.handle_data('&%s;' % ref)
|
||||||
|
|
||||||
|
def handle_decl(self, data):
|
||||||
|
"Propagate DOCTYPEs right through."
|
||||||
|
self.handle_data('<!%s>' % data)
|
||||||
|
|
||||||
|
class BeautifulStoneSoup(BeautifulSoup):
|
||||||
|
|
||||||
|
"""A version of BeautifulSoup that doesn't know anything at all
|
||||||
|
about what HTML tags have special behavior. Useful for parsing
|
||||||
|
things that aren't HTML, or when BeautifulSoup makes an assumption
|
||||||
|
counter to what you were expecting."""
|
||||||
|
|
||||||
|
IMPLICITLY_CLOSE_TAGS = 0
|
||||||
|
|
||||||
|
SELF_CLOSING_TAGS = []
|
||||||
|
NESTABLE_TAGS = []
|
||||||
|
QUOTE_TAGS = []
|
85
others/GoogleSOAPFacade.py
Normal file
85
others/GoogleSOAPFacade.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
Facade that hides the differences between the SOAPpy and SOAP.py
|
||||||
|
libraries, so that google.py doesn't have to deal with them.
|
||||||
|
|
||||||
|
@author: Brian Landers <brian@bluecoat93.org>
|
||||||
|
@license: Python
|
||||||
|
@version: 0.5.4
|
||||||
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
|
__author__ = "Brian Landers <brian@bluecoat93.org>"
|
||||||
|
__version__ = "0.6"
|
||||||
|
__license__ = "Python"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Wrapper around the python 'warnings' facility
|
||||||
|
#
|
||||||
|
def warn( message, level=RuntimeWarning ):
|
||||||
|
warnings.warn( message, level, stacklevel=3 )
|
||||||
|
|
||||||
|
# We can't use older version of SOAPpy, due to bugs that break the Google API
|
||||||
|
minSOAPpyVersion = "0.11.3"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Try loading SOAPpy first. If that fails, fall back to the old SOAP.py
|
||||||
|
#
|
||||||
|
SOAPpy = None
|
||||||
|
try:
|
||||||
|
import SOAPpy
|
||||||
|
from SOAPpy import SOAPProxy, Types
|
||||||
|
|
||||||
|
if LooseVersion( minSOAPpyVersion ) > \
|
||||||
|
LooseVersion( SOAPpy.version.__version__ ):
|
||||||
|
|
||||||
|
warn( "Versions of SOAPpy before %s have known bugs that prevent " +
|
||||||
|
"PyGoogle from functioning." % minSOAPpyVersion )
|
||||||
|
raise ImportError
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
warn( "SOAPpy not imported. Trying legacy SOAP.py.",
|
||||||
|
DeprecationWarning )
|
||||||
|
try:
|
||||||
|
import SOAP
|
||||||
|
except ImportError:
|
||||||
|
raise RuntimeError( "Unable to find SOAPpy or SOAP. Can't continue.\n" )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constants that differ between the modules
|
||||||
|
#
|
||||||
|
if SOAPpy:
|
||||||
|
false = Types.booleanType(0)
|
||||||
|
true = Types.booleanType(1)
|
||||||
|
structType = Types.structType
|
||||||
|
faultType = Types.faultType
|
||||||
|
else:
|
||||||
|
false = SOAP.booleanType(0)
|
||||||
|
true = SOAP.booleanType(1)
|
||||||
|
structType = SOAP.structType
|
||||||
|
faultType = SOAP.faultType
|
||||||
|
|
||||||
|
#
|
||||||
|
# Get a SOAP Proxy object in the correct way for the module we're using
|
||||||
|
#
|
||||||
|
def getProxy( url, namespace, http_proxy ):
|
||||||
|
if SOAPpy:
|
||||||
|
return SOAPProxy( url,
|
||||||
|
namespace = namespace,
|
||||||
|
http_proxy = http_proxy )
|
||||||
|
|
||||||
|
else:
|
||||||
|
return SOAP.SOAPProxy( url,
|
||||||
|
namespace = namespace,
|
||||||
|
http_proxy = http_proxy )
|
||||||
|
|
||||||
|
#
|
||||||
|
# Convert an object to a dictionary in the proper way for the module
|
||||||
|
# we're using for SOAP
|
||||||
|
#
|
||||||
|
def toDict( obj ):
|
||||||
|
if SOAPpy:
|
||||||
|
return obj._asdict()
|
||||||
|
else:
|
||||||
|
return obj._asdict
|
478
others/SOAPpy/Client.py
Normal file
478
others/SOAPpy/Client.py
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||||
|
# Brian Matthews (blm@actzero.com)
|
||||||
|
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||||
|
# Christopher Blunck (blunck@gst.com)
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
from __future__ import nested_scopes
|
||||||
|
|
||||||
|
#import xml.sax
|
||||||
|
import urllib
|
||||||
|
from types import *
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# SOAPpy modules
|
||||||
|
from Errors import *
|
||||||
|
from Config import Config
|
||||||
|
from Parser import parseSOAPRPC
|
||||||
|
from SOAPBuilder import buildSOAP
|
||||||
|
from Utilities import *
|
||||||
|
from Types import faultType, simplify
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Client
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def SOAPUserAgent():
|
||||||
|
return "SOAPpy " + __version__ + " (pywebsvcs.sf.net)"
|
||||||
|
|
||||||
|
|
||||||
|
class SOAPAddress:
|
||||||
|
def __init__(self, url, config = Config):
|
||||||
|
proto, uri = urllib.splittype(url)
|
||||||
|
|
||||||
|
# apply some defaults
|
||||||
|
if uri[0:2] != '//':
|
||||||
|
if proto != None:
|
||||||
|
uri = proto + ':' + uri
|
||||||
|
|
||||||
|
uri = '//' + uri
|
||||||
|
proto = 'http'
|
||||||
|
|
||||||
|
host, path = urllib.splithost(uri)
|
||||||
|
|
||||||
|
try:
|
||||||
|
int(host)
|
||||||
|
host = 'localhost:' + host
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not path:
|
||||||
|
path = '/'
|
||||||
|
|
||||||
|
if proto not in ('http', 'https', 'httpg'):
|
||||||
|
raise IOError, "unsupported SOAP protocol"
|
||||||
|
if proto == 'httpg' and not config.GSIclient:
|
||||||
|
raise AttributeError, \
|
||||||
|
"GSI client not supported by this Python installation"
|
||||||
|
if proto == 'https' and not config.SSLclient:
|
||||||
|
raise AttributeError, \
|
||||||
|
"SSL client not supported by this Python installation"
|
||||||
|
|
||||||
|
self.user,host = urllib.splituser(host)
|
||||||
|
self.proto = proto
|
||||||
|
self.host = host
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%(proto)s://%(host)s%(path)s" % self.__dict__
|
||||||
|
|
||||||
|
__repr__ = __str__
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPTransport:
|
||||||
|
def getNS(self, original_namespace, data):
|
||||||
|
"""Extract the (possibly extended) namespace from the returned
|
||||||
|
SOAP message."""
|
||||||
|
|
||||||
|
if type(original_namespace) == StringType:
|
||||||
|
pattern="xmlns:\w+=['\"](" + original_namespace + "[^'\"]*)['\"]"
|
||||||
|
match = re.search(pattern, data)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
else:
|
||||||
|
return original_namespace
|
||||||
|
else:
|
||||||
|
return original_namespace
|
||||||
|
|
||||||
|
# Need a Timeout someday?
|
||||||
|
def call(self, addr, data, namespace, soapaction = None, encoding = None,
|
||||||
|
http_proxy = None, config = Config):
|
||||||
|
|
||||||
|
import httplib
|
||||||
|
|
||||||
|
if not isinstance(addr, SOAPAddress):
|
||||||
|
addr = SOAPAddress(addr, config)
|
||||||
|
|
||||||
|
# Build a request
|
||||||
|
if http_proxy:
|
||||||
|
real_addr = http_proxy
|
||||||
|
real_path = addr.proto + "://" + addr.host + addr.path
|
||||||
|
else:
|
||||||
|
real_addr = addr.host
|
||||||
|
real_path = addr.path
|
||||||
|
|
||||||
|
if addr.proto == 'httpg':
|
||||||
|
from pyGlobus.io import GSIHTTP
|
||||||
|
r = GSIHTTP(real_addr, tcpAttr = config.tcpAttr)
|
||||||
|
elif addr.proto == 'https':
|
||||||
|
r = httplib.HTTPS(real_addr)
|
||||||
|
else:
|
||||||
|
r = httplib.HTTP(real_addr)
|
||||||
|
|
||||||
|
r.putrequest("POST", real_path)
|
||||||
|
|
||||||
|
r.putheader("Host", addr.host)
|
||||||
|
r.putheader("User-agent", SOAPUserAgent())
|
||||||
|
t = 'text/xml';
|
||||||
|
if encoding != None:
|
||||||
|
t += '; charset="%s"' % encoding
|
||||||
|
r.putheader("Content-type", t)
|
||||||
|
r.putheader("Content-length", str(len(data)))
|
||||||
|
|
||||||
|
# if user is not a user:passwd format
|
||||||
|
# we'll receive a failure from the server. . .I guess (??)
|
||||||
|
if addr.user != None:
|
||||||
|
val = base64.encodestring(addr.user)
|
||||||
|
r.putheader('Authorization','Basic ' + val.replace('\012',''))
|
||||||
|
|
||||||
|
# This fixes sending either "" or "None"
|
||||||
|
if soapaction == None or len(soapaction) == 0:
|
||||||
|
r.putheader("SOAPAction", "")
|
||||||
|
else:
|
||||||
|
r.putheader("SOAPAction", '"%s"' % soapaction)
|
||||||
|
|
||||||
|
if config.dumpHeadersOut:
|
||||||
|
s = 'Outgoing HTTP headers'
|
||||||
|
debugHeader(s)
|
||||||
|
print "POST %s %s" % (real_path, r._http_vsn_str)
|
||||||
|
print "Host:", addr.host
|
||||||
|
print "User-agent: SOAPpy " + __version__ + " (http://pywebsvcs.sf.net)"
|
||||||
|
print "Content-type:", t
|
||||||
|
print "Content-length:", len(data)
|
||||||
|
print 'SOAPAction: "%s"' % soapaction
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
r.endheaders()
|
||||||
|
|
||||||
|
if config.dumpSOAPOut:
|
||||||
|
s = 'Outgoing SOAP'
|
||||||
|
debugHeader(s)
|
||||||
|
print data,
|
||||||
|
if data[-1] != '\n':
|
||||||
|
print
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
# send the payload
|
||||||
|
r.send(data)
|
||||||
|
|
||||||
|
# read response line
|
||||||
|
code, msg, headers = r.getreply()
|
||||||
|
|
||||||
|
content_type = headers.get("content-type","text/xml")
|
||||||
|
content_length = headers.get("Content-length")
|
||||||
|
if content_length == None:
|
||||||
|
# No Content-Length provided; just read the whole socket
|
||||||
|
# This won't work with HTTP/1.1 chunked encoding
|
||||||
|
data = r.getfile().read()
|
||||||
|
message_len = len(data)
|
||||||
|
else:
|
||||||
|
message_len = int(content_length)
|
||||||
|
data = r.getfile().read(message_len)
|
||||||
|
|
||||||
|
if(config.debug):
|
||||||
|
print "code=",code
|
||||||
|
print "msg=", msg
|
||||||
|
print "headers=", headers
|
||||||
|
print "content-type=", content_type
|
||||||
|
print "data=", data
|
||||||
|
|
||||||
|
if config.dumpHeadersIn:
|
||||||
|
s = 'Incoming HTTP headers'
|
||||||
|
debugHeader(s)
|
||||||
|
if headers.headers:
|
||||||
|
print "HTTP/1.? %d %s" % (code, msg)
|
||||||
|
print "\n".join(map (lambda x: x.strip(), headers.headers))
|
||||||
|
else:
|
||||||
|
print "HTTP/0.9 %d %s" % (code, msg)
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
def startswith(string, val):
|
||||||
|
return string[0:len(val)] == val
|
||||||
|
|
||||||
|
if code == 500 and not \
|
||||||
|
( startswith(content_type, "text/xml") and message_len > 0 ):
|
||||||
|
raise HTTPError(code, msg)
|
||||||
|
|
||||||
|
if config.dumpSOAPIn:
|
||||||
|
s = 'Incoming SOAP'
|
||||||
|
debugHeader(s)
|
||||||
|
print data,
|
||||||
|
if (len(data)>0) and (data[-1] != '\n'):
|
||||||
|
print
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
if code not in (200, 500):
|
||||||
|
raise HTTPError(code, msg)
|
||||||
|
|
||||||
|
|
||||||
|
# get the new namespace
|
||||||
|
if namespace is None:
|
||||||
|
new_ns = None
|
||||||
|
else:
|
||||||
|
new_ns = self.getNS(namespace, data)
|
||||||
|
|
||||||
|
# return response payload
|
||||||
|
return data, new_ns
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# SOAP Proxy
|
||||||
|
################################################################################
|
||||||
|
class SOAPProxy:
|
||||||
|
def __init__(self, proxy, namespace = None, soapaction = None,
|
||||||
|
header = None, methodattrs = None, transport = HTTPTransport,
|
||||||
|
encoding = 'UTF-8', throw_faults = 1, unwrap_results = None,
|
||||||
|
http_proxy=None, config = Config, noroot = 0,
|
||||||
|
simplify_objects=None):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
# get default values for unwrap_results and simplify_objects
|
||||||
|
# from config
|
||||||
|
if unwrap_results is None:
|
||||||
|
self.unwrap_results=config.unwrap_results
|
||||||
|
else:
|
||||||
|
self.unwrap_results=unwrap_results
|
||||||
|
|
||||||
|
if simplify_objects is None:
|
||||||
|
self.simplify_objects=config.simplify_objects
|
||||||
|
else:
|
||||||
|
self.simplify_objects=simplify_objects
|
||||||
|
|
||||||
|
self.proxy = SOAPAddress(proxy, config)
|
||||||
|
self.namespace = namespace
|
||||||
|
self.soapaction = soapaction
|
||||||
|
self.header = header
|
||||||
|
self.methodattrs = methodattrs
|
||||||
|
self.transport = transport()
|
||||||
|
self.encoding = encoding
|
||||||
|
self.throw_faults = throw_faults
|
||||||
|
self.http_proxy = http_proxy
|
||||||
|
self.config = config
|
||||||
|
self.noroot = noroot
|
||||||
|
|
||||||
|
# GSI Additions
|
||||||
|
if hasattr(config, "channel_mode") and \
|
||||||
|
hasattr(config, "delegation_mode"):
|
||||||
|
self.channel_mode = config.channel_mode
|
||||||
|
self.delegation_mode = config.delegation_mode
|
||||||
|
#end GSI Additions
|
||||||
|
|
||||||
|
def invoke(self, method, args):
|
||||||
|
return self.__call(method, args, {})
|
||||||
|
|
||||||
|
def __call(self, name, args, kw, ns = None, sa = None, hd = None,
|
||||||
|
ma = None):
|
||||||
|
|
||||||
|
ns = ns or self.namespace
|
||||||
|
ma = ma or self.methodattrs
|
||||||
|
|
||||||
|
if sa: # Get soapaction
|
||||||
|
if type(sa) == TupleType:
|
||||||
|
sa = sa[0]
|
||||||
|
else:
|
||||||
|
if self.soapaction:
|
||||||
|
sa = self.soapaction
|
||||||
|
else:
|
||||||
|
sa = name
|
||||||
|
|
||||||
|
if hd: # Get header
|
||||||
|
if type(hd) == TupleType:
|
||||||
|
hd = hd[0]
|
||||||
|
else:
|
||||||
|
hd = self.header
|
||||||
|
|
||||||
|
hd = hd or self.header
|
||||||
|
|
||||||
|
if ma: # Get methodattrs
|
||||||
|
if type(ma) == TupleType: ma = ma[0]
|
||||||
|
else:
|
||||||
|
ma = self.methodattrs
|
||||||
|
ma = ma or self.methodattrs
|
||||||
|
|
||||||
|
m = buildSOAP(args = args, kw = kw, method = name, namespace = ns,
|
||||||
|
header = hd, methodattrs = ma, encoding = self.encoding,
|
||||||
|
config = self.config, noroot = self.noroot)
|
||||||
|
|
||||||
|
|
||||||
|
call_retry = 0
|
||||||
|
try:
|
||||||
|
|
||||||
|
r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
|
||||||
|
encoding = self.encoding,
|
||||||
|
http_proxy = self.http_proxy,
|
||||||
|
config = self.config)
|
||||||
|
|
||||||
|
except Exception, ex:
|
||||||
|
#
|
||||||
|
# Call failed.
|
||||||
|
#
|
||||||
|
# See if we have a fault handling vector installed in our
|
||||||
|
# config. If we do, invoke it. If it returns a true value,
|
||||||
|
# retry the call.
|
||||||
|
#
|
||||||
|
# In any circumstance other than the fault handler returning
|
||||||
|
# true, reraise the exception. This keeps the semantics of this
|
||||||
|
# code the same as without the faultHandler code.
|
||||||
|
#
|
||||||
|
|
||||||
|
if hasattr(self.config, "faultHandler"):
|
||||||
|
if callable(self.config.faultHandler):
|
||||||
|
call_retry = self.config.faultHandler(self.proxy, ex)
|
||||||
|
if not call_retry:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
if call_retry:
|
||||||
|
r, self.namespace = self.transport.call(self.proxy, m, ns, sa,
|
||||||
|
encoding = self.encoding,
|
||||||
|
http_proxy = self.http_proxy,
|
||||||
|
config = self.config)
|
||||||
|
|
||||||
|
|
||||||
|
p, attrs = parseSOAPRPC(r, attrs = 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
throw_struct = self.throw_faults and \
|
||||||
|
isinstance (p, faultType)
|
||||||
|
except:
|
||||||
|
throw_struct = 0
|
||||||
|
|
||||||
|
if throw_struct:
|
||||||
|
#print p
|
||||||
|
raise p
|
||||||
|
|
||||||
|
# If unwrap_results=1 and there is only element in the struct,
|
||||||
|
# SOAPProxy will assume that this element is the result
|
||||||
|
# and return it rather than the struct containing it.
|
||||||
|
# Otherwise SOAPproxy will return the struct with all the
|
||||||
|
# elements as attributes.
|
||||||
|
if self.unwrap_results:
|
||||||
|
try:
|
||||||
|
count = 0
|
||||||
|
for i in p.__dict__.keys():
|
||||||
|
if i[0] != "_": # don't count the private stuff
|
||||||
|
count += 1
|
||||||
|
t = getattr(p, i)
|
||||||
|
if count == 1: # Only one piece of data, bubble it up
|
||||||
|
p = t
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Automatically simplfy SOAP complex types into the
|
||||||
|
# corresponding python types. (structType --> dict,
|
||||||
|
# arrayType --> array, etc.)
|
||||||
|
if self.simplify_objects:
|
||||||
|
p = simplify(p)
|
||||||
|
|
||||||
|
if self.config.returnAllAttrs:
|
||||||
|
return p, attrs
|
||||||
|
return p
|
||||||
|
|
||||||
|
def _callWithBody(self, body):
|
||||||
|
return self.__call(None, body, {})
|
||||||
|
|
||||||
|
def __getattr__(self, name): # hook to catch method calls
|
||||||
|
if name == '__del__':
|
||||||
|
raise AttributeError, name
|
||||||
|
return self.__Method(self.__call, name, config = self.config)
|
||||||
|
|
||||||
|
# To handle attribute wierdness
|
||||||
|
class __Method:
|
||||||
|
# Some magic to bind a SOAP method to an RPC server.
|
||||||
|
# Supports "nested" methods (e.g. examples.getStateName) -- concept
|
||||||
|
# borrowed from xmlrpc/soaplib -- www.pythonware.com
|
||||||
|
# Altered (improved?) to let you inline namespaces on a per call
|
||||||
|
# basis ala SOAP::LITE -- www.soaplite.com
|
||||||
|
|
||||||
|
def __init__(self, call, name, ns = None, sa = None, hd = None,
|
||||||
|
ma = None, config = Config):
|
||||||
|
|
||||||
|
self.__call = call
|
||||||
|
self.__name = name
|
||||||
|
self.__ns = ns
|
||||||
|
self.__sa = sa
|
||||||
|
self.__hd = hd
|
||||||
|
self.__ma = ma
|
||||||
|
self.__config = config
|
||||||
|
return
|
||||||
|
|
||||||
|
def __call__(self, *args, **kw):
|
||||||
|
if self.__name[0] == "_":
|
||||||
|
if self.__name in ["__repr__","__str__"]:
|
||||||
|
return self.__repr__()
|
||||||
|
else:
|
||||||
|
return self.__f_call(*args, **kw)
|
||||||
|
else:
|
||||||
|
return self.__r_call(*args, **kw)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name == '__del__':
|
||||||
|
raise AttributeError, name
|
||||||
|
if self.__name[0] == "_":
|
||||||
|
# Don't nest method if it is a directive
|
||||||
|
return self.__class__(self.__call, name, self.__ns,
|
||||||
|
self.__sa, self.__hd, self.__ma)
|
||||||
|
|
||||||
|
return self.__class__(self.__call, "%s.%s" % (self.__name, name),
|
||||||
|
self.__ns, self.__sa, self.__hd, self.__ma)
|
||||||
|
|
||||||
|
def __f_call(self, *args, **kw):
|
||||||
|
if self.__name == "_ns": self.__ns = args
|
||||||
|
elif self.__name == "_sa": self.__sa = args
|
||||||
|
elif self.__name == "_hd": self.__hd = args
|
||||||
|
elif self.__name == "_ma": self.__ma = args
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __r_call(self, *args, **kw):
|
||||||
|
return self.__call(self.__name, args, kw, self.__ns, self.__sa,
|
||||||
|
self.__hd, self.__ma)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s at %d>" % (self.__class__, id(self))
|
202
others/SOAPpy/Config.py
Normal file
202
others/SOAPpy/Config.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
import copy, socket
|
||||||
|
from types import *
|
||||||
|
|
||||||
|
from NS import NS
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Configuration class
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
class SOAPConfig:
|
||||||
|
__readonly = ('SSLserver', 'SSLclient', 'GSIserver', 'GSIclient')
|
||||||
|
|
||||||
|
def __init__(self, config = None, **kw):
|
||||||
|
d = self.__dict__
|
||||||
|
|
||||||
|
if config:
|
||||||
|
if not isinstance(config, SOAPConfig):
|
||||||
|
raise AttributeError, \
|
||||||
|
"initializer must be SOAPConfig instance"
|
||||||
|
|
||||||
|
s = config.__dict__
|
||||||
|
|
||||||
|
for k, v in s.items():
|
||||||
|
if k[0] != '_':
|
||||||
|
d[k] = v
|
||||||
|
else:
|
||||||
|
# Setting debug also sets returnFaultInfo,
|
||||||
|
# dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut
|
||||||
|
self.debug = 0
|
||||||
|
self.dumpFaultInfo = 0
|
||||||
|
# Setting namespaceStyle sets typesNamespace, typesNamespaceURI,
|
||||||
|
# schemaNamespace, and schemaNamespaceURI
|
||||||
|
self.namespaceStyle = '1999'
|
||||||
|
self.strictNamespaces = 0
|
||||||
|
self.typed = 1
|
||||||
|
self.buildWithNamespacePrefix = 1
|
||||||
|
self.returnAllAttrs = 0
|
||||||
|
|
||||||
|
# Strict checking of range for floats and doubles
|
||||||
|
self.strict_range = 0
|
||||||
|
|
||||||
|
# Default encoding for dictionary keys
|
||||||
|
self.dict_encoding = 'ascii'
|
||||||
|
|
||||||
|
# New argument name handling mechanism. See
|
||||||
|
# README.MethodParameterNaming for details
|
||||||
|
self.specialArgs = 1
|
||||||
|
|
||||||
|
# If unwrap_results=1 and there is only element in the struct,
|
||||||
|
# SOAPProxy will assume that this element is the result
|
||||||
|
# and return it rather than the struct containing it.
|
||||||
|
# Otherwise SOAPproxy will return the struct with all the
|
||||||
|
# elements as attributes.
|
||||||
|
self.unwrap_results = 1
|
||||||
|
|
||||||
|
# Automatically convert SOAP complex types, and
|
||||||
|
# (recursively) public contents into the corresponding
|
||||||
|
# python types. (Private subobjects have names that start
|
||||||
|
# with '_'.)
|
||||||
|
#
|
||||||
|
# Conversions:
|
||||||
|
# - faultType --> raise python exception
|
||||||
|
# - arrayType --> array
|
||||||
|
# - compoundType --> dictionary
|
||||||
|
#
|
||||||
|
self.simplify_objects = 0
|
||||||
|
|
||||||
|
# Per-class authorization method. If this is set, before
|
||||||
|
# calling a any class method, the specified authorization
|
||||||
|
# method will be called. If it returns 1, the method call
|
||||||
|
# will proceed, otherwise the call will throw with an
|
||||||
|
# authorization error.
|
||||||
|
self.authMethod = None
|
||||||
|
|
||||||
|
# Globus Support if pyGlobus.io available
|
||||||
|
try:
|
||||||
|
from pyGlobus import io;
|
||||||
|
d['GSIserver'] = 1
|
||||||
|
d['GSIclient'] = 1
|
||||||
|
except:
|
||||||
|
d['GSIserver'] = 0
|
||||||
|
d['GSIclient'] = 0
|
||||||
|
|
||||||
|
|
||||||
|
# Server SSL support if M2Crypto.SSL available
|
||||||
|
try:
|
||||||
|
from M2Crypto import SSL
|
||||||
|
d['SSLserver'] = 1
|
||||||
|
except:
|
||||||
|
d['SSLserver'] = 0
|
||||||
|
|
||||||
|
# Client SSL support if socket.ssl available
|
||||||
|
try:
|
||||||
|
from socket import ssl
|
||||||
|
d['SSLclient'] = 1
|
||||||
|
except:
|
||||||
|
d['SSLclient'] = 0
|
||||||
|
|
||||||
|
for k, v in kw.items():
|
||||||
|
if k[0] != '_':
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name in self.__readonly:
|
||||||
|
raise AttributeError, "readonly configuration setting"
|
||||||
|
|
||||||
|
d = self.__dict__
|
||||||
|
|
||||||
|
if name in ('typesNamespace', 'typesNamespaceURI',
|
||||||
|
'schemaNamespace', 'schemaNamespaceURI'):
|
||||||
|
|
||||||
|
if name[-3:] == 'URI':
|
||||||
|
base, uri = name[:-3], 1
|
||||||
|
else:
|
||||||
|
base, uri = name, 0
|
||||||
|
|
||||||
|
if type(value) == StringType:
|
||||||
|
if NS.NSMAP.has_key(value):
|
||||||
|
n = (value, NS.NSMAP[value])
|
||||||
|
elif NS.NSMAP_R.has_key(value):
|
||||||
|
n = (NS.NSMAP_R[value], value)
|
||||||
|
else:
|
||||||
|
raise AttributeError, "unknown namespace"
|
||||||
|
elif type(value) in (ListType, TupleType):
|
||||||
|
if uri:
|
||||||
|
n = (value[1], value[0])
|
||||||
|
else:
|
||||||
|
n = (value[0], value[1])
|
||||||
|
else:
|
||||||
|
raise AttributeError, "unknown namespace type"
|
||||||
|
|
||||||
|
d[base], d[base + 'URI'] = n
|
||||||
|
|
||||||
|
try:
|
||||||
|
d['namespaceStyle'] = \
|
||||||
|
NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])]
|
||||||
|
except:
|
||||||
|
d['namespaceStyle'] = ''
|
||||||
|
|
||||||
|
elif name == 'namespaceStyle':
|
||||||
|
value = str(value)
|
||||||
|
|
||||||
|
if not NS.STMAP.has_key(value):
|
||||||
|
raise AttributeError, "unknown namespace style"
|
||||||
|
|
||||||
|
d[name] = value
|
||||||
|
n = d['typesNamespace'] = NS.STMAP[value][0]
|
||||||
|
d['typesNamespaceURI'] = NS.NSMAP[n]
|
||||||
|
n = d['schemaNamespace'] = NS.STMAP[value][1]
|
||||||
|
d['schemaNamespaceURI'] = NS.NSMAP[n]
|
||||||
|
|
||||||
|
elif name == 'debug':
|
||||||
|
d[name] = \
|
||||||
|
d['returnFaultInfo'] = \
|
||||||
|
d['dumpHeadersIn'] = \
|
||||||
|
d['dumpHeadersOut'] = \
|
||||||
|
d['dumpSOAPIn'] = \
|
||||||
|
d['dumpSOAPOut'] = value
|
||||||
|
|
||||||
|
else:
|
||||||
|
d[name] = value
|
||||||
|
|
||||||
|
|
||||||
|
Config = SOAPConfig()
|
79
others/SOAPpy/Errors.py
Normal file
79
others/SOAPpy/Errors.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||||
|
# Brian Matthews (blm@actzero.com)
|
||||||
|
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||||
|
# Christopher Blunck (blunck@gst.com)
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
import exceptions
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Exceptions
|
||||||
|
################################################################################
|
||||||
|
class Error(exceptions.Exception):
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.msg = msg
|
||||||
|
def __str__(self):
|
||||||
|
return "<Error : %s>" % self.msg
|
||||||
|
__repr__ = __str__
|
||||||
|
def __call__(self):
|
||||||
|
return (msg,)
|
||||||
|
|
||||||
|
class RecursionError(Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UnknownTypeError(Error):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class HTTPError(Error):
|
||||||
|
# indicates an HTTP protocol error
|
||||||
|
def __init__(self, code, msg):
|
||||||
|
self.code = code
|
||||||
|
self.msg = msg
|
||||||
|
def __str__(self):
|
||||||
|
return "<HTTPError %s %s>" % (self.code, self.msg)
|
||||||
|
__repr__ = __str__
|
||||||
|
def __call___(self):
|
||||||
|
return (self.code, self.msg, )
|
||||||
|
|
||||||
|
class UnderflowError(exceptions.ArithmeticError):
|
||||||
|
pass
|
||||||
|
|
143
others/SOAPpy/GSIServer.py
Normal file
143
others/SOAPpy/GSIServer.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
GSIServer - Contributed by Ivan R. Judson <judson@mcs.anl.gov>
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||||
|
# Brian Matthews (blm@actzero.com)
|
||||||
|
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||||
|
# Christopher Blunck (blunck@gst.com)
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
from __future__ import nested_scopes
|
||||||
|
|
||||||
|
#import xml.sax
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import SocketServer
|
||||||
|
from types import *
|
||||||
|
import BaseHTTPServer
|
||||||
|
|
||||||
|
# SOAPpy modules
|
||||||
|
from Parser import parseSOAPRPC
|
||||||
|
from Config import SOAPConfig
|
||||||
|
from Types import faultType, voidType, simplify
|
||||||
|
from NS import NS
|
||||||
|
from SOAPBuilder import buildSOAP
|
||||||
|
from Utilities import debugHeader, debugFooter
|
||||||
|
|
||||||
|
try: from M2Crypto import SSL
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
from Server import *
|
||||||
|
|
||||||
|
from pyGlobus.io import GSITCPSocketServer, ThreadingGSITCPSocketServer
|
||||||
|
from pyGlobus import ioc
|
||||||
|
|
||||||
|
def GSIConfig():
|
||||||
|
config = SOAPConfig()
|
||||||
|
config.channel_mode = ioc.GLOBUS_IO_SECURE_CHANNEL_MODE_GSI_WRAP
|
||||||
|
config.delegation_mode = ioc.GLOBUS_IO_SECURE_DELEGATION_MODE_FULL_PROXY
|
||||||
|
config.tcpAttr = None
|
||||||
|
config.authMethod = "_authorize"
|
||||||
|
return config
|
||||||
|
|
||||||
|
Config = GSIConfig()
|
||||||
|
|
||||||
|
class GSISOAPServer(GSITCPSocketServer, SOAPServerBase):
|
||||||
|
def __init__(self, addr = ('localhost', 8000),
|
||||||
|
RequestHandler = SOAPRequestHandler, log = 0,
|
||||||
|
encoding = 'UTF-8', config = Config, namespace = None):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
self.namespace = namespace
|
||||||
|
self.objmap = {}
|
||||||
|
self.funcmap = {}
|
||||||
|
self.encoding = encoding
|
||||||
|
self.config = config
|
||||||
|
self.log = log
|
||||||
|
|
||||||
|
self.allow_reuse_address= 1
|
||||||
|
|
||||||
|
GSITCPSocketServer.__init__(self, addr, RequestHandler,
|
||||||
|
self.config.channel_mode,
|
||||||
|
self.config.delegation_mode,
|
||||||
|
tcpAttr = self.config.tcpAttr)
|
||||||
|
|
||||||
|
def get_request(self):
|
||||||
|
sock, addr = GSITCPSocketServer.get_request(self)
|
||||||
|
|
||||||
|
return sock, addr
|
||||||
|
|
||||||
|
class ThreadingGSISOAPServer(ThreadingGSITCPSocketServer, SOAPServerBase):
|
||||||
|
|
||||||
|
def __init__(self, addr = ('localhost', 8000),
|
||||||
|
RequestHandler = SOAPRequestHandler, log = 0,
|
||||||
|
encoding = 'UTF-8', config = Config, namespace = None):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
self.namespace = namespace
|
||||||
|
self.objmap = {}
|
||||||
|
self.funcmap = {}
|
||||||
|
self.encoding = encoding
|
||||||
|
self.config = config
|
||||||
|
self.log = log
|
||||||
|
|
||||||
|
self.allow_reuse_address= 1
|
||||||
|
|
||||||
|
ThreadingGSITCPSocketServer.__init__(self, addr, RequestHandler,
|
||||||
|
self.config.channel_mode,
|
||||||
|
self.config.delegation_mode,
|
||||||
|
tcpAttr = self.config.tcpAttr)
|
||||||
|
|
||||||
|
def get_request(self):
|
||||||
|
sock, addr = ThreadingGSITCPSocketServer.get_request(self)
|
||||||
|
|
||||||
|
return sock, addr
|
||||||
|
|
104
others/SOAPpy/NS.py
Normal file
104
others/SOAPpy/NS.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||||
|
# Brian Matthews (blm@actzero.com)
|
||||||
|
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||||
|
# Christopher Blunck (blunck@gst.com)
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import nested_scopes
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Namespace Class
|
||||||
|
################################################################################
|
||||||
|
def invertDict(dict):
|
||||||
|
d = {}
|
||||||
|
|
||||||
|
for k, v in dict.items():
|
||||||
|
d[v] = k
|
||||||
|
|
||||||
|
return d
|
||||||
|
|
||||||
|
class NS:
|
||||||
|
XML = "http://www.w3.org/XML/1998/namespace"
|
||||||
|
|
||||||
|
ENV = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
|
ENC = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||||
|
|
||||||
|
XSD = "http://www.w3.org/1999/XMLSchema"
|
||||||
|
XSD2 = "http://www.w3.org/2000/10/XMLSchema"
|
||||||
|
XSD3 = "http://www.w3.org/2001/XMLSchema"
|
||||||
|
|
||||||
|
XSD_L = [XSD, XSD2, XSD3]
|
||||||
|
EXSD_L= [ENC, XSD, XSD2, XSD3]
|
||||||
|
|
||||||
|
XSI = "http://www.w3.org/1999/XMLSchema-instance"
|
||||||
|
XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance"
|
||||||
|
XSI3 = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
XSI_L = [XSI, XSI2, XSI3]
|
||||||
|
|
||||||
|
URN = "http://soapinterop.org/xsd"
|
||||||
|
|
||||||
|
# For generated messages
|
||||||
|
XML_T = "xml"
|
||||||
|
ENV_T = "SOAP-ENV"
|
||||||
|
ENC_T = "SOAP-ENC"
|
||||||
|
XSD_T = "xsd"
|
||||||
|
XSD2_T= "xsd2"
|
||||||
|
XSD3_T= "xsd3"
|
||||||
|
XSI_T = "xsi"
|
||||||
|
XSI2_T= "xsi2"
|
||||||
|
XSI3_T= "xsi3"
|
||||||
|
URN_T = "urn"
|
||||||
|
|
||||||
|
NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2,
|
||||||
|
XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3,
|
||||||
|
URN_T: URN}
|
||||||
|
NSMAP_R = invertDict(NSMAP)
|
||||||
|
|
||||||
|
STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T),
|
||||||
|
'2001': (XSD3_T, XSI3_T)}
|
||||||
|
STMAP_R = invertDict(STMAP)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
raise Error, "Don't instantiate this"
|
||||||
|
|
||||||
|
|
||||||
|
|
1024
others/SOAPpy/Parser.py
Normal file
1024
others/SOAPpy/Parser.py
Normal file
File diff suppressed because it is too large
Load Diff
40
others/SOAPpy/SOAP.py
Normal file
40
others/SOAPpy/SOAP.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""This file is here for backward compatibility with versions <= 0.9.9
|
||||||
|
|
||||||
|
Delete when 1.0.0 is released!
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
from Client import *
|
||||||
|
from Config import *
|
||||||
|
from Errors import *
|
||||||
|
from NS import *
|
||||||
|
from Parser import *
|
||||||
|
from SOAPBuilder import *
|
||||||
|
from Server import *
|
||||||
|
from Types import *
|
||||||
|
from Utilities import *
|
||||||
|
import wstools
|
||||||
|
import WSDL
|
||||||
|
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
warn("""
|
||||||
|
|
||||||
|
The sub-module SOAPpy.SOAP is deprecated and is only
|
||||||
|
provided for short-term backward compatibility. Objects are now
|
||||||
|
available directly within the SOAPpy module. Thus, instead of
|
||||||
|
|
||||||
|
from SOAPpy import SOAP
|
||||||
|
...
|
||||||
|
SOAP.SOAPProxy(...)
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
from SOAPpy import SOAPProxy
|
||||||
|
...
|
||||||
|
SOAPProxy(...)
|
||||||
|
|
||||||
|
instead.
|
||||||
|
""", DeprecationWarning)
|
620
others/SOAPpy/SOAPBuilder.py
Normal file
620
others/SOAPpy/SOAPBuilder.py
Normal file
@ -0,0 +1,620 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
import copy
|
||||||
|
from wstools.XMLname import toXMLname, fromXMLname
|
||||||
|
import fpconst
|
||||||
|
|
||||||
|
# SOAPpy modules
|
||||||
|
from Config import Config
|
||||||
|
from NS import NS
|
||||||
|
from Types import *
|
||||||
|
|
||||||
|
# Test whether this Python version has Types.BooleanType
|
||||||
|
# If it doesn't have it, then False and True are serialized as integers
|
||||||
|
try:
|
||||||
|
BooleanType
|
||||||
|
pythonHasBooleanType = 1
|
||||||
|
except NameError:
|
||||||
|
pythonHasBooleanType = 0
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# SOAP Builder
|
||||||
|
################################################################################
|
||||||
|
class SOAPBuilder:
|
||||||
|
_xml_top = '<?xml version="1.0"?>\n'
|
||||||
|
_xml_enc_top = '<?xml version="1.0" encoding="%s"?>\n'
|
||||||
|
_env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \
|
||||||
|
NS.__dict__
|
||||||
|
_env_bot = '</%(ENV_T)s:Envelope>\n' % NS.__dict__
|
||||||
|
|
||||||
|
# Namespaces potentially defined in the Envelope tag.
|
||||||
|
|
||||||
|
_env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T,
|
||||||
|
NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T,
|
||||||
|
NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T}
|
||||||
|
|
||||||
|
def __init__(self, args = (), kw = {}, method = None, namespace = None,
|
||||||
|
header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8',
|
||||||
|
use_refs = 0, config = Config, noroot = 0):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
self.args = args
|
||||||
|
self.kw = kw
|
||||||
|
self.envelope = envelope
|
||||||
|
self.encoding = encoding
|
||||||
|
self.method = method
|
||||||
|
self.namespace = namespace
|
||||||
|
self.header = header
|
||||||
|
self.methodattrs= methodattrs
|
||||||
|
self.use_refs = use_refs
|
||||||
|
self.config = config
|
||||||
|
self.out = []
|
||||||
|
self.tcounter = 0
|
||||||
|
self.ncounter = 1
|
||||||
|
self.icounter = 1
|
||||||
|
self.envns = {}
|
||||||
|
self.ids = {}
|
||||||
|
self.depth = 0
|
||||||
|
self.multirefs = []
|
||||||
|
self.multis = 0
|
||||||
|
self.body = not isinstance(args, bodyType)
|
||||||
|
self.noroot = noroot
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
if Config.debug: print "In build."
|
||||||
|
ns_map = {}
|
||||||
|
|
||||||
|
# Cache whether typing is on or not
|
||||||
|
typed = self.config.typed
|
||||||
|
|
||||||
|
if self.header:
|
||||||
|
# Create a header.
|
||||||
|
self.dump(self.header, "Header", typed = typed)
|
||||||
|
self.header = None # Wipe it out so no one is using it.
|
||||||
|
|
||||||
|
if self.body:
|
||||||
|
# Call genns to record that we've used SOAP-ENV.
|
||||||
|
self.depth += 1
|
||||||
|
body_ns = self.genns(ns_map, NS.ENV)[0]
|
||||||
|
self.out.append("<%sBody>\n" % body_ns)
|
||||||
|
|
||||||
|
if self.method:
|
||||||
|
self.depth += 1
|
||||||
|
a = ''
|
||||||
|
if self.methodattrs:
|
||||||
|
for (k, v) in self.methodattrs.items():
|
||||||
|
a += ' %s="%s"' % (k, v)
|
||||||
|
|
||||||
|
if self.namespace: # Use the namespace info handed to us
|
||||||
|
methodns, n = self.genns(ns_map, self.namespace)
|
||||||
|
else:
|
||||||
|
methodns, n = '', ''
|
||||||
|
|
||||||
|
self.out.append('<%s%s%s%s%s>\n' % (
|
||||||
|
methodns, self.method, n, a, self.genroot(ns_map)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if type(self.args) != TupleType:
|
||||||
|
args = (self.args,)
|
||||||
|
else:
|
||||||
|
args = self.args
|
||||||
|
|
||||||
|
for i in args:
|
||||||
|
self.dump(i, typed = typed, ns_map = ns_map)
|
||||||
|
|
||||||
|
if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method):
|
||||||
|
for k in self.config.argsOrdering.get(self.method):
|
||||||
|
self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map)
|
||||||
|
else:
|
||||||
|
for (k, v) in self.kw.items():
|
||||||
|
self.dump(v, k, typed = typed, ns_map = ns_map)
|
||||||
|
|
||||||
|
except RecursionError:
|
||||||
|
if self.use_refs == 0:
|
||||||
|
# restart
|
||||||
|
b = SOAPBuilder(args = self.args, kw = self.kw,
|
||||||
|
method = self.method, namespace = self.namespace,
|
||||||
|
header = self.header, methodattrs = self.methodattrs,
|
||||||
|
envelope = self.envelope, encoding = self.encoding,
|
||||||
|
use_refs = 1, config = self.config)
|
||||||
|
return b.build()
|
||||||
|
raise
|
||||||
|
|
||||||
|
if self.method:
|
||||||
|
self.out.append("</%s%s>\n" % (methodns, self.method))
|
||||||
|
self.depth -= 1
|
||||||
|
|
||||||
|
if self.body:
|
||||||
|
# dump may add to self.multirefs, but the for loop will keep
|
||||||
|
# going until it has used all of self.multirefs, even those
|
||||||
|
# entries added while in the loop.
|
||||||
|
|
||||||
|
self.multis = 1
|
||||||
|
|
||||||
|
for obj, tag in self.multirefs:
|
||||||
|
self.dump(obj, tag, typed = typed, ns_map = ns_map)
|
||||||
|
|
||||||
|
self.out.append("</%sBody>\n" % body_ns)
|
||||||
|
self.depth -= 1
|
||||||
|
|
||||||
|
if self.envelope:
|
||||||
|
e = map (lambda ns: ' xmlns:%s="%s"' % (ns[1], ns[0]),
|
||||||
|
self.envns.items())
|
||||||
|
|
||||||
|
self.out = ['<', self._env_top] + e + ['>\n'] + \
|
||||||
|
self.out + \
|
||||||
|
[self._env_bot]
|
||||||
|
|
||||||
|
if self.encoding != None:
|
||||||
|
self.out.insert(0, self._xml_enc_top % self.encoding)
|
||||||
|
return ''.join(self.out).encode(self.encoding)
|
||||||
|
|
||||||
|
self.out.insert(0, self._xml_top)
|
||||||
|
return ''.join(self.out)
|
||||||
|
|
||||||
|
def gentag(self):
|
||||||
|
if Config.debug: print "In gentag."
|
||||||
|
self.tcounter += 1
|
||||||
|
return "v%d" % self.tcounter
|
||||||
|
|
||||||
|
def genns(self, ns_map, nsURI):
|
||||||
|
if nsURI == None:
|
||||||
|
return ('', '')
|
||||||
|
|
||||||
|
if type(nsURI) == TupleType: # already a tuple
|
||||||
|
if len(nsURI) == 2:
|
||||||
|
ns, nsURI = nsURI
|
||||||
|
else:
|
||||||
|
ns, nsURI = None, nsURI[0]
|
||||||
|
else:
|
||||||
|
ns = None
|
||||||
|
|
||||||
|
if ns_map.has_key(nsURI):
|
||||||
|
return (ns_map[nsURI] + ':', '')
|
||||||
|
|
||||||
|
if self._env_ns.has_key(nsURI):
|
||||||
|
ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI]
|
||||||
|
return (ns + ':', '')
|
||||||
|
|
||||||
|
if not ns:
|
||||||
|
ns = "ns%d" % self.ncounter
|
||||||
|
self.ncounter += 1
|
||||||
|
ns_map[nsURI] = ns
|
||||||
|
if self.config.buildWithNamespacePrefix:
|
||||||
|
return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI))
|
||||||
|
else:
|
||||||
|
return ('', ' xmlns="%s"' % (nsURI))
|
||||||
|
|
||||||
|
def genroot(self, ns_map):
|
||||||
|
if self.noroot:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if self.depth != 2:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
ns, n = self.genns(ns_map, NS.ENC)
|
||||||
|
return ' %sroot="%d"%s' % (ns, not self.multis, n)
|
||||||
|
|
||||||
|
# checkref checks an element to see if it needs to be encoded as a
|
||||||
|
# multi-reference element or not. If it returns None, the element has
|
||||||
|
# been handled and the caller can continue with subsequent elements.
|
||||||
|
# If it returns a string, the string should be included in the opening
|
||||||
|
# tag of the marshaled element.
|
||||||
|
|
||||||
|
def checkref(self, obj, tag, ns_map):
|
||||||
|
if self.depth < 2:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if not self.ids.has_key(id(obj)):
|
||||||
|
n = self.ids[id(obj)] = self.icounter
|
||||||
|
self.icounter = n + 1
|
||||||
|
|
||||||
|
if self.use_refs == 0:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if self.depth == 2:
|
||||||
|
return ' id="i%d"' % n
|
||||||
|
|
||||||
|
self.multirefs.append((obj, tag))
|
||||||
|
else:
|
||||||
|
if self.use_refs == 0:
|
||||||
|
raise RecursionError, "Cannot serialize recursive object"
|
||||||
|
|
||||||
|
n = self.ids[id(obj)]
|
||||||
|
|
||||||
|
if self.multis and self.depth == 2:
|
||||||
|
return ' id="i%d"' % n
|
||||||
|
|
||||||
|
self.out.append('<%s href="#i%d"%s/>\n' %
|
||||||
|
(tag, n, self.genroot(ns_map)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
# dumpers
|
||||||
|
|
||||||
|
def dump(self, obj, tag = None, typed = 1, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump.", "obj=", obj
|
||||||
|
ns_map = ns_map.copy()
|
||||||
|
self.depth += 1
|
||||||
|
|
||||||
|
if type(tag) not in (NoneType, StringType, UnicodeType):
|
||||||
|
raise KeyError, "tag must be a string or None"
|
||||||
|
|
||||||
|
try:
|
||||||
|
meth = getattr(self, "dump_" + type(obj).__name__)
|
||||||
|
except AttributeError:
|
||||||
|
if type(obj) == LongType:
|
||||||
|
obj_type = "integer"
|
||||||
|
elif pythonHasBooleanType and type(obj) == BooleanType:
|
||||||
|
obj_type = "boolean"
|
||||||
|
else:
|
||||||
|
obj_type = type(obj).__name__
|
||||||
|
|
||||||
|
self.out.append(self.dumper(None, obj_type, obj, tag, typed,
|
||||||
|
ns_map, self.genroot(ns_map)))
|
||||||
|
else:
|
||||||
|
meth(obj, tag, typed, ns_map)
|
||||||
|
|
||||||
|
|
||||||
|
self.depth -= 1
|
||||||
|
|
||||||
|
# generic dumper
|
||||||
|
def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {},
|
||||||
|
rootattr = '', id = '',
|
||||||
|
xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s</%(tag)s>\n'):
|
||||||
|
if Config.debug: print "In dumper."
|
||||||
|
|
||||||
|
if nsURI == None:
|
||||||
|
nsURI = self.config.typesNamespaceURI
|
||||||
|
|
||||||
|
tag = tag or self.gentag()
|
||||||
|
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
a = n = t = ''
|
||||||
|
if typed and obj_type:
|
||||||
|
ns, n = self.genns(ns_map, nsURI)
|
||||||
|
ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
|
||||||
|
t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n)
|
||||||
|
|
||||||
|
try: a = obj._marshalAttrs(ns_map, self)
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: data = obj._marshalData()
|
||||||
|
except:
|
||||||
|
if (obj_type != "string"): # strings are already encoded
|
||||||
|
data = cgi.escape(str(obj))
|
||||||
|
else:
|
||||||
|
data = obj
|
||||||
|
|
||||||
|
|
||||||
|
return xml % {"tag": tag, "type": t, "data": data, "root": rootattr,
|
||||||
|
"id": id, "attrs": a}
|
||||||
|
|
||||||
|
def dump_float(self, obj, tag, typed = 1, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump_float."
|
||||||
|
tag = tag or self.gentag()
|
||||||
|
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
if Config.strict_range:
|
||||||
|
doubleType(obj)
|
||||||
|
|
||||||
|
if fpconst.isPosInf(obj):
|
||||||
|
obj = "INF"
|
||||||
|
elif fpconst.isNegInf(obj):
|
||||||
|
obj = "-INF"
|
||||||
|
elif fpconst.isNaN(obj):
|
||||||
|
obj = "NaN"
|
||||||
|
else:
|
||||||
|
obj = str(obj)
|
||||||
|
|
||||||
|
# Note: python 'float' is actually a SOAP 'double'.
|
||||||
|
self.out.append(self.dumper(None, "double", obj, tag, typed, ns_map,
|
||||||
|
self.genroot(ns_map)))
|
||||||
|
|
||||||
|
def dump_string(self, obj, tag, typed = 0, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump_string."
|
||||||
|
tag = tag or self.gentag()
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
id = self.checkref(obj, tag, ns_map)
|
||||||
|
if id == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try: data = obj._marshalData()
|
||||||
|
except: data = obj
|
||||||
|
|
||||||
|
self.out.append(self.dumper(None, "string", cgi.escape(data), tag,
|
||||||
|
typed, ns_map, self.genroot(ns_map), id))
|
||||||
|
|
||||||
|
dump_str = dump_string # For Python 2.2+
|
||||||
|
dump_unicode = dump_string
|
||||||
|
|
||||||
|
def dump_None(self, obj, tag, typed = 0, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump_None."
|
||||||
|
tag = tag or self.gentag()
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
|
||||||
|
|
||||||
|
self.out.append('<%s %snull="1"%s/>\n' %
|
||||||
|
(tag, ns, self.genroot(ns_map)))
|
||||||
|
|
||||||
|
dump_NoneType = dump_None # For Python 2.2+
|
||||||
|
|
||||||
|
def dump_list(self, obj, tag, typed = 1, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump_list.", "obj=", obj
|
||||||
|
tag = tag or self.gentag()
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
if type(obj) == InstanceType:
|
||||||
|
data = obj.data
|
||||||
|
else:
|
||||||
|
data = obj
|
||||||
|
|
||||||
|
id = self.checkref(obj, tag, ns_map)
|
||||||
|
if id == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
sample = data[0]
|
||||||
|
empty = 0
|
||||||
|
except:
|
||||||
|
# preserve type if present
|
||||||
|
if getattr(obj,"_typed",None) and getattr(obj,"_type",None):
|
||||||
|
if getattr(obj, "_complexType", None):
|
||||||
|
sample = typedArrayType(typed=obj._type,
|
||||||
|
complexType = obj._complexType)
|
||||||
|
sample._typename = obj._type
|
||||||
|
obj._ns = NS.URN
|
||||||
|
else:
|
||||||
|
sample = typedArrayType(typed=obj._type)
|
||||||
|
else:
|
||||||
|
sample = structType()
|
||||||
|
empty = 1
|
||||||
|
|
||||||
|
# First scan list to see if all are the same type
|
||||||
|
same_type = 1
|
||||||
|
|
||||||
|
if not empty:
|
||||||
|
for i in data[1:]:
|
||||||
|
if type(sample) != type(i) or \
|
||||||
|
(type(sample) == InstanceType and \
|
||||||
|
sample.__class__ != i.__class__):
|
||||||
|
same_type = 0
|
||||||
|
break
|
||||||
|
|
||||||
|
ndecl = ''
|
||||||
|
if same_type:
|
||||||
|
if (isinstance(sample, structType)) or \
|
||||||
|
type(sample) == DictType or \
|
||||||
|
(isinstance(sample, anyType) and \
|
||||||
|
(getattr(sample, "_complexType", None) and \
|
||||||
|
sample._complexType)): # force to urn struct
|
||||||
|
try:
|
||||||
|
tns = obj._ns or NS.URN
|
||||||
|
except:
|
||||||
|
tns = NS.URN
|
||||||
|
|
||||||
|
ns, ndecl = self.genns(ns_map, tns)
|
||||||
|
|
||||||
|
try:
|
||||||
|
typename = sample._typename
|
||||||
|
except:
|
||||||
|
typename = "SOAPStruct"
|
||||||
|
|
||||||
|
t = ns + typename
|
||||||
|
|
||||||
|
elif isinstance(sample, anyType):
|
||||||
|
ns = sample._validNamespaceURI(self.config.typesNamespaceURI,
|
||||||
|
self.config.strictNamespaces)
|
||||||
|
if ns:
|
||||||
|
ns, ndecl = self.genns(ns_map, ns)
|
||||||
|
t = ns + sample._type
|
||||||
|
else:
|
||||||
|
t = 'ur-type'
|
||||||
|
else:
|
||||||
|
typename = type(sample).__name__
|
||||||
|
|
||||||
|
# For Python 2.2+
|
||||||
|
if type(sample) == StringType: typename = 'string'
|
||||||
|
|
||||||
|
# HACK: python 'float' is actually a SOAP 'double'.
|
||||||
|
if typename=="float": typename="double"
|
||||||
|
t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
|
||||||
|
typename
|
||||||
|
|
||||||
|
else:
|
||||||
|
t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
|
||||||
|
"ur-type"
|
||||||
|
|
||||||
|
try: a = obj._marshalAttrs(ns_map, self)
|
||||||
|
except: a = ''
|
||||||
|
|
||||||
|
ens, edecl = self.genns(ns_map, NS.ENC)
|
||||||
|
ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI)
|
||||||
|
|
||||||
|
self.out.append(
|
||||||
|
'<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
|
||||||
|
(tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
|
||||||
|
self.genroot(ns_map), id, a))
|
||||||
|
|
||||||
|
typed = not same_type
|
||||||
|
|
||||||
|
try: elemsname = obj._elemsname
|
||||||
|
except: elemsname = "item"
|
||||||
|
|
||||||
|
for i in data:
|
||||||
|
self.dump(i, elemsname, typed, ns_map)
|
||||||
|
|
||||||
|
self.out.append('</%s>\n' % tag)
|
||||||
|
|
||||||
|
dump_tuple = dump_list
|
||||||
|
|
||||||
|
def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump_dictionary."
|
||||||
|
tag = tag or self.gentag()
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
id = self.checkref(obj, tag, ns_map)
|
||||||
|
if id == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try: a = obj._marshalAttrs(ns_map, self)
|
||||||
|
except: a = ''
|
||||||
|
|
||||||
|
self.out.append('<%s%s%s%s>\n' %
|
||||||
|
(tag, id, a, self.genroot(ns_map)))
|
||||||
|
|
||||||
|
for (k, v) in obj.items():
|
||||||
|
if k[0] != "_":
|
||||||
|
self.dump(v, k, 1, ns_map)
|
||||||
|
|
||||||
|
self.out.append('</%s>\n' % tag)
|
||||||
|
|
||||||
|
dump_dict = dump_dictionary # For Python 2.2+
|
||||||
|
|
||||||
|
def dump_instance(self, obj, tag, typed = 1, ns_map = {}):
|
||||||
|
if Config.debug: print "In dump_instance.", "obj=", obj, "tag=", tag
|
||||||
|
if not tag:
|
||||||
|
# If it has a name use it.
|
||||||
|
if isinstance(obj, anyType) and obj._name:
|
||||||
|
tag = obj._name
|
||||||
|
else:
|
||||||
|
tag = self.gentag()
|
||||||
|
tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
if isinstance(obj, arrayType): # Array
|
||||||
|
self.dump_list(obj, tag, typed, ns_map)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, faultType): # Fault
|
||||||
|
cns, cdecl = self.genns(ns_map, NS.ENC)
|
||||||
|
vns, vdecl = self.genns(ns_map, NS.ENV)
|
||||||
|
self.out.append('''<%sFault %sroot="1"%s%s>
|
||||||
|
<faultcode>%s</faultcode>
|
||||||
|
<faultstring>%s</faultstring>
|
||||||
|
''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring))
|
||||||
|
if hasattr(obj, "detail"):
|
||||||
|
self.dump(obj.detail, "detail", typed, ns_map)
|
||||||
|
self.out.append("</%sFault>\n" % vns)
|
||||||
|
return
|
||||||
|
|
||||||
|
r = self.genroot(ns_map)
|
||||||
|
|
||||||
|
try: a = obj._marshalAttrs(ns_map, self)
|
||||||
|
except: a = ''
|
||||||
|
|
||||||
|
if isinstance(obj, voidType): # void
|
||||||
|
self.out.append("<%s%s%s></%s>\n" % (tag, a, r, tag))
|
||||||
|
return
|
||||||
|
|
||||||
|
id = self.checkref(obj, tag, ns_map)
|
||||||
|
if id == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, structType):
|
||||||
|
# Check for namespace
|
||||||
|
ndecl = ''
|
||||||
|
ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
|
||||||
|
self.config.strictNamespaces)
|
||||||
|
if ns:
|
||||||
|
ns, ndecl = self.genns(ns_map, ns)
|
||||||
|
tag = ns + tag
|
||||||
|
self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r))
|
||||||
|
|
||||||
|
keylist = obj.__dict__.keys()
|
||||||
|
|
||||||
|
# first write out items with order information
|
||||||
|
if hasattr(obj, '_keyord'):
|
||||||
|
for i in range(len(obj._keyord)):
|
||||||
|
self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map)
|
||||||
|
keylist.remove(obj._keyord[i])
|
||||||
|
|
||||||
|
# now write out the rest
|
||||||
|
for k in keylist:
|
||||||
|
if (k[0] != "_"):
|
||||||
|
self.dump(getattr(obj,k), k, 1, ns_map)
|
||||||
|
|
||||||
|
if isinstance(obj, bodyType):
|
||||||
|
self.multis = 1
|
||||||
|
|
||||||
|
for v, k in self.multirefs:
|
||||||
|
self.dump(v, k, typed = typed, ns_map = ns_map)
|
||||||
|
|
||||||
|
self.out.append('</%s>\n' % tag)
|
||||||
|
|
||||||
|
elif isinstance(obj, anyType):
|
||||||
|
t = ''
|
||||||
|
|
||||||
|
if typed:
|
||||||
|
ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
|
||||||
|
self.config.strictNamespaces)
|
||||||
|
if ns:
|
||||||
|
ons, ondecl = self.genns(ns_map, ns)
|
||||||
|
ins, indecl = self.genns(ns_map,
|
||||||
|
self.config.schemaNamespaceURI)
|
||||||
|
t = ' %stype="%s%s"%s%s' % \
|
||||||
|
(ins, ons, obj._type, ondecl, indecl)
|
||||||
|
|
||||||
|
self.out.append('<%s%s%s%s%s>%s</%s>\n' %
|
||||||
|
(tag, t, id, a, r, obj._marshalData(), tag))
|
||||||
|
|
||||||
|
else: # Some Class
|
||||||
|
self.out.append('<%s%s%s>\n' % (tag, id, r))
|
||||||
|
|
||||||
|
for (k, v) in obj.__dict__.items():
|
||||||
|
if k[0] != "_":
|
||||||
|
self.dump(v, k, 1, ns_map)
|
||||||
|
|
||||||
|
self.out.append('</%s>\n' % tag)
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# SOAPBuilder's more public interface
|
||||||
|
################################################################################
|
||||||
|
def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None,
|
||||||
|
methodattrs=None,envelope=1,encoding='UTF-8',config=Config,noroot = 0):
|
||||||
|
t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace,
|
||||||
|
header=header, methodattrs=methodattrs,envelope=envelope,
|
||||||
|
encoding=encoding, config=config,noroot=noroot)
|
||||||
|
return t.build()
|
706
others/SOAPpy/Server.py
Normal file
706
others/SOAPpy/Server.py
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# SOAPpy - Cayce Ullman (cayce@actzero.com)
|
||||||
|
# Brian Matthews (blm@actzero.com)
|
||||||
|
# Gregory Warnes (gregory_r_warnes@groton.pfizer.com)
|
||||||
|
# Christopher Blunck (blunck@gst.com)
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
from __future__ import nested_scopes
|
||||||
|
|
||||||
|
#import xml.sax
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import SocketServer
|
||||||
|
from types import *
|
||||||
|
import BaseHTTPServer
|
||||||
|
import thread
|
||||||
|
|
||||||
|
# SOAPpy modules
|
||||||
|
from Parser import parseSOAPRPC
|
||||||
|
from Config import Config
|
||||||
|
from Types import faultType, voidType, simplify
|
||||||
|
from NS import NS
|
||||||
|
from SOAPBuilder import buildSOAP
|
||||||
|
from Utilities import debugHeader, debugFooter
|
||||||
|
|
||||||
|
try: from M2Crypto import SSL
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Call context dictionary
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
_contexts = dict()
|
||||||
|
|
||||||
|
def GetSOAPContext():
|
||||||
|
global _contexts
|
||||||
|
return _contexts[thread.get_ident()]
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Server
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Method Signature class for adding extra info to registered funcs, right now
|
||||||
|
# used just to indicate it should be called with keywords, instead of ordered
|
||||||
|
# params.
|
||||||
|
class MethodSig:
|
||||||
|
def __init__(self, func, keywords=0, context=0):
|
||||||
|
self.func = func
|
||||||
|
self.keywords = keywords
|
||||||
|
self.context = context
|
||||||
|
self.__name__ = func.__name__
|
||||||
|
|
||||||
|
def __call__(self, *args, **kw):
|
||||||
|
return apply(self.func,args,kw)
|
||||||
|
|
||||||
|
class SOAPContext:
|
||||||
|
def __init__(self, header, body, attrs, xmldata, connection, httpheaders,
|
||||||
|
soapaction):
|
||||||
|
|
||||||
|
self.header = header
|
||||||
|
self.body = body
|
||||||
|
self.attrs = attrs
|
||||||
|
self.xmldata = xmldata
|
||||||
|
self.connection = connection
|
||||||
|
self.httpheaders= httpheaders
|
||||||
|
self.soapaction = soapaction
|
||||||
|
|
||||||
|
# A class to describe how header messages are handled
|
||||||
|
class HeaderHandler:
|
||||||
|
# Initially fail out if there are any problems.
|
||||||
|
def __init__(self, header, attrs):
|
||||||
|
for i in header.__dict__.keys():
|
||||||
|
if i[0] == "_":
|
||||||
|
continue
|
||||||
|
|
||||||
|
d = getattr(header, i)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')])
|
||||||
|
except:
|
||||||
|
fault = 0
|
||||||
|
|
||||||
|
if fault:
|
||||||
|
raise faultType, ("%s:MustUnderstand" % NS.ENV_T,
|
||||||
|
"Required Header Misunderstood",
|
||||||
|
"%s" % i)
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# SOAP Server
|
||||||
|
################################################################################
|
||||||
|
class SOAPServerBase:
|
||||||
|
|
||||||
|
def get_request(self):
|
||||||
|
sock, addr = SocketServer.TCPServer.get_request(self)
|
||||||
|
|
||||||
|
if self.ssl_context:
|
||||||
|
sock = SSL.Connection(self.ssl_context, sock)
|
||||||
|
sock._setup_ssl(addr)
|
||||||
|
if sock.accept_ssl() != 1:
|
||||||
|
raise socket.error, "Couldn't accept SSL connection"
|
||||||
|
|
||||||
|
return sock, addr
|
||||||
|
|
||||||
|
def registerObject(self, object, namespace = '', path = ''):
|
||||||
|
if namespace == '' and path == '': namespace = self.namespace
|
||||||
|
if namespace == '' and path != '':
|
||||||
|
namespace = path.replace("/", ":")
|
||||||
|
if namespace[0] == ":": namespace = namespace[1:]
|
||||||
|
self.objmap[namespace] = object
|
||||||
|
|
||||||
|
def registerFunction(self, function, namespace = '', funcName = None,
|
||||||
|
path = ''):
|
||||||
|
if not funcName : funcName = function.__name__
|
||||||
|
if namespace == '' and path == '': namespace = self.namespace
|
||||||
|
if namespace == '' and path != '':
|
||||||
|
namespace = path.replace("/", ":")
|
||||||
|
if namespace[0] == ":": namespace = namespace[1:]
|
||||||
|
if self.funcmap.has_key(namespace):
|
||||||
|
self.funcmap[namespace][funcName] = function
|
||||||
|
else:
|
||||||
|
self.funcmap[namespace] = {funcName : function}
|
||||||
|
|
||||||
|
def registerKWObject(self, object, namespace = '', path = ''):
|
||||||
|
if namespace == '' and path == '': namespace = self.namespace
|
||||||
|
if namespace == '' and path != '':
|
||||||
|
namespace = path.replace("/", ":")
|
||||||
|
if namespace[0] == ":": namespace = namespace[1:]
|
||||||
|
for i in dir(object.__class__):
|
||||||
|
if i[0] != "_" and callable(getattr(object, i)):
|
||||||
|
self.registerKWFunction(getattr(object,i), namespace)
|
||||||
|
|
||||||
|
# convenience - wraps your func for you.
|
||||||
|
def registerKWFunction(self, function, namespace = '', funcName = None,
|
||||||
|
path = ''):
|
||||||
|
if namespace == '' and path == '': namespace = self.namespace
|
||||||
|
if namespace == '' and path != '':
|
||||||
|
namespace = path.replace("/", ":")
|
||||||
|
if namespace[0] == ":": namespace = namespace[1:]
|
||||||
|
self.registerFunction(MethodSig(function,keywords=1), namespace,
|
||||||
|
funcName)
|
||||||
|
|
||||||
|
def unregisterObject(self, object, namespace = '', path = ''):
|
||||||
|
if namespace == '' and path == '': namespace = self.namespace
|
||||||
|
if namespace == '' and path != '':
|
||||||
|
namespace = path.replace("/", ":")
|
||||||
|
if namespace[0] == ":": namespace = namespace[1:]
|
||||||
|
|
||||||
|
del self.objmap[namespace]
|
||||||
|
|
||||||
|
class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
|
def version_string(self):
|
||||||
|
return '<a href="http://pywebsvcs.sf.net">' + \
|
||||||
|
'SOAPpy ' + __version__ + '</a> (Python ' + \
|
||||||
|
sys.version.split()[0] + ')'
|
||||||
|
|
||||||
|
def date_time_string(self):
|
||||||
|
self.__last_date_time_string = \
|
||||||
|
BaseHTTPServer.BaseHTTPRequestHandler.\
|
||||||
|
date_time_string(self)
|
||||||
|
|
||||||
|
return self.__last_date_time_string
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
global _contexts
|
||||||
|
|
||||||
|
status = 500
|
||||||
|
try:
|
||||||
|
if self.server.config.dumpHeadersIn:
|
||||||
|
s = 'Incoming HTTP headers'
|
||||||
|
debugHeader(s)
|
||||||
|
print self.raw_requestline.strip()
|
||||||
|
print "\n".join(map (lambda x: x.strip(),
|
||||||
|
self.headers.headers))
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
data = self.rfile.read(int(self.headers["Content-length"]))
|
||||||
|
|
||||||
|
if self.server.config.dumpSOAPIn:
|
||||||
|
s = 'Incoming SOAP'
|
||||||
|
debugHeader(s)
|
||||||
|
print data,
|
||||||
|
if data[-1] != '\n':
|
||||||
|
print
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
(r, header, body, attrs) = \
|
||||||
|
parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
|
||||||
|
|
||||||
|
method = r._name
|
||||||
|
args = r._aslist()
|
||||||
|
kw = r._asdict()
|
||||||
|
|
||||||
|
if Config.simplify_objects:
|
||||||
|
args = simplify(args)
|
||||||
|
kw = simplify(kw)
|
||||||
|
|
||||||
|
# Handle mixed named and unnamed arguments by assuming
|
||||||
|
# that all arguments with names of the form "v[0-9]+"
|
||||||
|
# are unnamed and should be passed in numeric order,
|
||||||
|
# other arguments are named and should be passed using
|
||||||
|
# this name.
|
||||||
|
|
||||||
|
# This is a non-standard exension to the SOAP protocol,
|
||||||
|
# but is supported by Apache AXIS.
|
||||||
|
|
||||||
|
# It is enabled by default. To disable, set
|
||||||
|
# Config.specialArgs to False.
|
||||||
|
|
||||||
|
if Config.specialArgs:
|
||||||
|
|
||||||
|
ordered_args = {}
|
||||||
|
named_args = {}
|
||||||
|
|
||||||
|
for (k,v) in kw.items():
|
||||||
|
|
||||||
|
if k[0]=="v":
|
||||||
|
try:
|
||||||
|
i = int(k[1:])
|
||||||
|
ordered_args[i] = v
|
||||||
|
except ValueError:
|
||||||
|
named_args[str(k)] = v
|
||||||
|
|
||||||
|
else:
|
||||||
|
named_args[str(k)] = v
|
||||||
|
|
||||||
|
# We have to decide namespace precedence
|
||||||
|
# I'm happy with the following scenario
|
||||||
|
# if r._ns is specified use it, if not check for
|
||||||
|
# a path, if it's specified convert it and use it as the
|
||||||
|
# namespace. If both are specified, use r._ns.
|
||||||
|
|
||||||
|
ns = r._ns
|
||||||
|
|
||||||
|
if len(self.path) > 1 and not ns:
|
||||||
|
ns = self.path.replace("/", ":")
|
||||||
|
if ns[0] == ":": ns = ns[1:]
|
||||||
|
|
||||||
|
# authorization method
|
||||||
|
a = None
|
||||||
|
|
||||||
|
keylist = ordered_args.keys()
|
||||||
|
keylist.sort()
|
||||||
|
|
||||||
|
# create list in proper order w/o names
|
||||||
|
tmp = map( lambda x: ordered_args[x], keylist)
|
||||||
|
ordered_args = tmp
|
||||||
|
|
||||||
|
#print '<-> Argument Matching Yielded:'
|
||||||
|
#print '<-> Ordered Arguments:' + str(ordered_args)
|
||||||
|
#print '<-> Named Arguments :' + str(named_args)
|
||||||
|
|
||||||
|
resp = ""
|
||||||
|
|
||||||
|
# For fault messages
|
||||||
|
if ns:
|
||||||
|
nsmethod = "%s:%s" % (ns, method)
|
||||||
|
else:
|
||||||
|
nsmethod = method
|
||||||
|
|
||||||
|
try:
|
||||||
|
# First look for registered functions
|
||||||
|
if self.server.funcmap.has_key(ns) and \
|
||||||
|
self.server.funcmap[ns].has_key(method):
|
||||||
|
f = self.server.funcmap[ns][method]
|
||||||
|
|
||||||
|
# look for the authorization method
|
||||||
|
if self.server.config.authMethod != None:
|
||||||
|
authmethod = self.server.config.authMethod
|
||||||
|
if self.server.funcmap.has_key(ns) and \
|
||||||
|
self.server.funcmap[ns].has_key(authmethod):
|
||||||
|
a = self.server.funcmap[ns][authmethod]
|
||||||
|
else:
|
||||||
|
# Now look at registered objects
|
||||||
|
# Check for nested attributes. This works even if
|
||||||
|
# there are none, because the split will return
|
||||||
|
# [method]
|
||||||
|
f = self.server.objmap[ns]
|
||||||
|
|
||||||
|
# Look for the authorization method
|
||||||
|
if self.server.config.authMethod != None:
|
||||||
|
authmethod = self.server.config.authMethod
|
||||||
|
if hasattr(f, authmethod):
|
||||||
|
a = getattr(f, authmethod)
|
||||||
|
|
||||||
|
# then continue looking for the method
|
||||||
|
l = method.split(".")
|
||||||
|
for i in l:
|
||||||
|
f = getattr(f, i)
|
||||||
|
except:
|
||||||
|
info = sys.exc_info()
|
||||||
|
try:
|
||||||
|
resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
|
||||||
|
"Method Not Found",
|
||||||
|
"%s : %s %s %s" % (nsmethod,
|
||||||
|
info[0],
|
||||||
|
info[1],
|
||||||
|
info[2])),
|
||||||
|
encoding = self.server.encoding,
|
||||||
|
config = self.server.config)
|
||||||
|
finally:
|
||||||
|
del info
|
||||||
|
status = 500
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if header:
|
||||||
|
x = HeaderHandler(header, attrs)
|
||||||
|
|
||||||
|
fr = 1
|
||||||
|
|
||||||
|
# call context book keeping
|
||||||
|
# We're stuffing the method into the soapaction if there
|
||||||
|
# isn't one, someday, we'll set that on the client
|
||||||
|
# and it won't be necessary here
|
||||||
|
# for now we're doing both
|
||||||
|
|
||||||
|
if "SOAPAction".lower() not in self.headers.keys() or \
|
||||||
|
self.headers["SOAPAction"] == "\"\"":
|
||||||
|
self.headers["SOAPAction"] = method
|
||||||
|
|
||||||
|
thread_id = thread.get_ident()
|
||||||
|
_contexts[thread_id] = SOAPContext(header, body,
|
||||||
|
attrs, data,
|
||||||
|
self.connection,
|
||||||
|
self.headers,
|
||||||
|
self.headers["SOAPAction"])
|
||||||
|
|
||||||
|
# Do an authorization check
|
||||||
|
if a != None:
|
||||||
|
if not apply(a, (), {"_SOAPContext" :
|
||||||
|
_contexts[thread_id] }):
|
||||||
|
raise faultType("%s:Server" % NS.ENV_T,
|
||||||
|
"Authorization failed.",
|
||||||
|
"%s" % nsmethod)
|
||||||
|
|
||||||
|
# If it's wrapped, some special action may be needed
|
||||||
|
if isinstance(f, MethodSig):
|
||||||
|
c = None
|
||||||
|
|
||||||
|
if f.context: # retrieve context object
|
||||||
|
c = _contexts[thread_id]
|
||||||
|
|
||||||
|
if Config.specialArgs:
|
||||||
|
if c:
|
||||||
|
named_args["_SOAPContext"] = c
|
||||||
|
fr = apply(f, ordered_args, named_args)
|
||||||
|
elif f.keywords:
|
||||||
|
# This is lame, but have to de-unicode
|
||||||
|
# keywords
|
||||||
|
|
||||||
|
strkw = {}
|
||||||
|
|
||||||
|
for (k, v) in kw.items():
|
||||||
|
strkw[str(k)] = v
|
||||||
|
if c:
|
||||||
|
strkw["_SOAPContext"] = c
|
||||||
|
fr = apply(f, (), strkw)
|
||||||
|
elif c:
|
||||||
|
fr = apply(f, args, {'_SOAPContext':c})
|
||||||
|
else:
|
||||||
|
fr = apply(f, args, {})
|
||||||
|
|
||||||
|
else:
|
||||||
|
if Config.specialArgs:
|
||||||
|
fr = apply(f, ordered_args, named_args)
|
||||||
|
else:
|
||||||
|
fr = apply(f, args, {})
|
||||||
|
|
||||||
|
|
||||||
|
if type(fr) == type(self) and \
|
||||||
|
isinstance(fr, voidType):
|
||||||
|
resp = buildSOAP(kw = {'%sResponse' % method: fr},
|
||||||
|
encoding = self.server.encoding,
|
||||||
|
config = self.server.config)
|
||||||
|
else:
|
||||||
|
resp = buildSOAP(kw =
|
||||||
|
{'%sResponse' % method: {'Result': fr}},
|
||||||
|
encoding = self.server.encoding,
|
||||||
|
config = self.server.config)
|
||||||
|
|
||||||
|
# Clean up _contexts
|
||||||
|
if _contexts.has_key(thread_id):
|
||||||
|
del _contexts[thread_id]
|
||||||
|
|
||||||
|
except Exception, e:
|
||||||
|
import traceback
|
||||||
|
info = sys.exc_info()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.server.config.dumpFaultInfo:
|
||||||
|
s = 'Method %s exception' % nsmethod
|
||||||
|
debugHeader(s)
|
||||||
|
traceback.print_exception(info[0], info[1],
|
||||||
|
info[2])
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
if isinstance(e, faultType):
|
||||||
|
f = e
|
||||||
|
else:
|
||||||
|
f = faultType("%s:Server" % NS.ENV_T,
|
||||||
|
"Method Failed",
|
||||||
|
"%s" % nsmethod)
|
||||||
|
|
||||||
|
if self.server.config.returnFaultInfo:
|
||||||
|
f._setDetail("".join(traceback.format_exception(
|
||||||
|
info[0], info[1], info[2])))
|
||||||
|
elif not hasattr(f, 'detail'):
|
||||||
|
f._setDetail("%s %s" % (info[0], info[1]))
|
||||||
|
finally:
|
||||||
|
del info
|
||||||
|
|
||||||
|
resp = buildSOAP(f, encoding = self.server.encoding,
|
||||||
|
config = self.server.config)
|
||||||
|
status = 500
|
||||||
|
else:
|
||||||
|
status = 200
|
||||||
|
except faultType, e:
|
||||||
|
import traceback
|
||||||
|
info = sys.exc_info()
|
||||||
|
try:
|
||||||
|
if self.server.config.dumpFaultInfo:
|
||||||
|
s = 'Received fault exception'
|
||||||
|
debugHeader(s)
|
||||||
|
traceback.print_exception(info[0], info[1],
|
||||||
|
info[2])
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
if self.server.config.returnFaultInfo:
|
||||||
|
e._setDetail("".join(traceback.format_exception(
|
||||||
|
info[0], info[1], info[2])))
|
||||||
|
elif not hasattr(e, 'detail'):
|
||||||
|
e._setDetail("%s %s" % (info[0], info[1]))
|
||||||
|
finally:
|
||||||
|
del info
|
||||||
|
|
||||||
|
resp = buildSOAP(e, encoding = self.server.encoding,
|
||||||
|
config = self.server.config)
|
||||||
|
status = 500
|
||||||
|
except Exception, e:
|
||||||
|
# internal error, report as HTTP server error
|
||||||
|
|
||||||
|
if self.server.config.dumpFaultInfo:
|
||||||
|
s = 'Internal exception %s' % e
|
||||||
|
import traceback
|
||||||
|
debugHeader(s)
|
||||||
|
info = sys.exc_info()
|
||||||
|
try:
|
||||||
|
traceback.print_exception(info[0], info[1], info[2])
|
||||||
|
finally:
|
||||||
|
del info
|
||||||
|
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
self.send_response(500)
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
if self.server.config.dumpHeadersOut and \
|
||||||
|
self.request_version != 'HTTP/0.9':
|
||||||
|
s = 'Outgoing HTTP headers'
|
||||||
|
debugHeader(s)
|
||||||
|
if self.responses.has_key(status):
|
||||||
|
s = ' ' + self.responses[status][0]
|
||||||
|
else:
|
||||||
|
s = ''
|
||||||
|
print "%s %d%s" % (self.protocol_version, 500, s)
|
||||||
|
print "Server:", self.version_string()
|
||||||
|
print "Date:", self.__last_date_time_string
|
||||||
|
debugFooter(s)
|
||||||
|
else:
|
||||||
|
# got a valid SOAP response
|
||||||
|
self.send_response(status)
|
||||||
|
|
||||||
|
t = 'text/xml';
|
||||||
|
if self.server.encoding != None:
|
||||||
|
t += '; charset="%s"' % self.server.encoding
|
||||||
|
self.send_header("Content-type", t)
|
||||||
|
self.send_header("Content-length", str(len(resp)))
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
if self.server.config.dumpHeadersOut and \
|
||||||
|
self.request_version != 'HTTP/0.9':
|
||||||
|
s = 'Outgoing HTTP headers'
|
||||||
|
debugHeader(s)
|
||||||
|
if self.responses.has_key(status):
|
||||||
|
s = ' ' + self.responses[status][0]
|
||||||
|
else:
|
||||||
|
s = ''
|
||||||
|
print "%s %d%s" % (self.protocol_version, status, s)
|
||||||
|
print "Server:", self.version_string()
|
||||||
|
print "Date:", self.__last_date_time_string
|
||||||
|
print "Content-type:", t
|
||||||
|
print "Content-length:", len(resp)
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
if self.server.config.dumpSOAPOut:
|
||||||
|
s = 'Outgoing SOAP'
|
||||||
|
debugHeader(s)
|
||||||
|
print resp,
|
||||||
|
if resp[-1] != '\n':
|
||||||
|
print
|
||||||
|
debugFooter(s)
|
||||||
|
|
||||||
|
self.wfile.write(resp)
|
||||||
|
self.wfile.flush()
|
||||||
|
|
||||||
|
# We should be able to shut down both a regular and an SSL
|
||||||
|
# connection, but under Python 2.1, calling shutdown on an
|
||||||
|
# SSL connections drops the output, so this work-around.
|
||||||
|
# This should be investigated more someday.
|
||||||
|
|
||||||
|
if self.server.config.SSLserver and \
|
||||||
|
isinstance(self.connection, SSL.Connection):
|
||||||
|
self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN |
|
||||||
|
SSL.SSL_RECEIVED_SHUTDOWN)
|
||||||
|
else:
|
||||||
|
self.connection.shutdown(1)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
|
||||||
|
#print 'command ', self.command
|
||||||
|
#print 'path ', self.path
|
||||||
|
#print 'request_version', self.request_version
|
||||||
|
#print 'headers'
|
||||||
|
#print ' type ', self.headers.type
|
||||||
|
#print ' maintype', self.headers.maintype
|
||||||
|
#print ' subtype ', self.headers.subtype
|
||||||
|
#print ' params ', self.headers.plist
|
||||||
|
|
||||||
|
path = self.path.lower()
|
||||||
|
if path.endswith('wsdl'):
|
||||||
|
method = 'wsdl'
|
||||||
|
function = namespace = None
|
||||||
|
if self.server.funcmap.has_key(namespace) \
|
||||||
|
and self.server.funcmap[namespace].has_key(method):
|
||||||
|
function = self.server.funcmap[namespace][method]
|
||||||
|
else:
|
||||||
|
if namespace in self.server.objmap.keys():
|
||||||
|
function = self.server.objmap[namespace]
|
||||||
|
l = method.split(".")
|
||||||
|
for i in l:
|
||||||
|
function = getattr(function, i)
|
||||||
|
|
||||||
|
if function:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
response = apply(function, ())
|
||||||
|
self.wfile.write(str(response))
|
||||||
|
return
|
||||||
|
|
||||||
|
# return error
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write('''\
|
||||||
|
<title>
|
||||||
|
<head>Error!</head>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Oops!</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This server supports HTTP GET requests only for the the purpose of
|
||||||
|
obtaining Web Services Description Language (WSDL) for a specific
|
||||||
|
service.
|
||||||
|
|
||||||
|
Either you requested an URL that does not end in "wsdl" or this
|
||||||
|
server does not implement a wsdl method.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</body>''')
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
if self.server.log:
|
||||||
|
BaseHTTPServer.BaseHTTPRequestHandler.\
|
||||||
|
log_message (self, format, *args)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SOAPServer(SOAPServerBase, SocketServer.TCPServer):
|
||||||
|
|
||||||
|
def __init__(self, addr = ('localhost', 8000),
|
||||||
|
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||||
|
config = Config, namespace = None, ssl_context = None):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
if ssl_context != None and not config.SSLserver:
|
||||||
|
raise AttributeError, \
|
||||||
|
"SSL server not supported by this Python installation"
|
||||||
|
|
||||||
|
self.namespace = namespace
|
||||||
|
self.objmap = {}
|
||||||
|
self.funcmap = {}
|
||||||
|
self.ssl_context = ssl_context
|
||||||
|
self.encoding = encoding
|
||||||
|
self.config = config
|
||||||
|
self.log = log
|
||||||
|
|
||||||
|
self.allow_reuse_address= 1
|
||||||
|
|
||||||
|
SocketServer.TCPServer.__init__(self, addr, RequestHandler)
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadingSOAPServer(SOAPServerBase, SocketServer.ThreadingTCPServer):
|
||||||
|
|
||||||
|
def __init__(self, addr = ('localhost', 8000),
|
||||||
|
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||||
|
config = Config, namespace = None, ssl_context = None):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
if ssl_context != None and not config.SSLserver:
|
||||||
|
raise AttributeError, \
|
||||||
|
"SSL server not supported by this Python installation"
|
||||||
|
|
||||||
|
self.namespace = namespace
|
||||||
|
self.objmap = {}
|
||||||
|
self.funcmap = {}
|
||||||
|
self.ssl_context = ssl_context
|
||||||
|
self.encoding = encoding
|
||||||
|
self.config = config
|
||||||
|
self.log = log
|
||||||
|
|
||||||
|
self.allow_reuse_address= 1
|
||||||
|
|
||||||
|
SocketServer.ThreadingTCPServer.__init__(self, addr, RequestHandler)
|
||||||
|
|
||||||
|
# only define class if Unix domain sockets are available
|
||||||
|
if hasattr(socket, "AF_UNIX"):
|
||||||
|
|
||||||
|
class SOAPUnixSocketServer(SOAPServerBase, SocketServer.UnixStreamServer):
|
||||||
|
|
||||||
|
def __init__(self, addr = 8000,
|
||||||
|
RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
|
||||||
|
config = Config, namespace = None, ssl_context = None):
|
||||||
|
|
||||||
|
# Test the encoding, raising an exception if it's not known
|
||||||
|
if encoding != None:
|
||||||
|
''.encode(encoding)
|
||||||
|
|
||||||
|
if ssl_context != None and not config.SSLserver:
|
||||||
|
raise AttributeError, \
|
||||||
|
"SSL server not supported by this Python installation"
|
||||||
|
|
||||||
|
self.namespace = namespace
|
||||||
|
self.objmap = {}
|
||||||
|
self.funcmap = {}
|
||||||
|
self.ssl_context = ssl_context
|
||||||
|
self.encoding = encoding
|
||||||
|
self.config = config
|
||||||
|
self.log = log
|
||||||
|
|
||||||
|
self.allow_reuse_address= 1
|
||||||
|
|
||||||
|
SocketServer.UnixStreamServer.__init__(self, str(addr), RequestHandler)
|
||||||
|
|
1734
others/SOAPpy/Types.py
Normal file
1734
others/SOAPpy/Types.py
Normal file
File diff suppressed because it is too large
Load Diff
23
others/SOAPpy/URLopener.py
Normal file
23
others/SOAPpy/URLopener.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""Provide a class for loading data from URL's that handles basic
|
||||||
|
authentication"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
from Config import Config
|
||||||
|
from urllib import FancyURLopener
|
||||||
|
|
||||||
|
class URLopener(FancyURLopener):
|
||||||
|
|
||||||
|
username = None
|
||||||
|
passwd = None
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, username=None, passwd=None, *args, **kw):
|
||||||
|
FancyURLopener.__init__( self, *args, **kw)
|
||||||
|
self.username = username
|
||||||
|
self.passwd = passwd
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_user_passwd(self, host, realm):
|
||||||
|
return self.username, self.passwd
|
178
others/SOAPpy/Utilities.py
Normal file
178
others/SOAPpy/Utilities.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
"""
|
||||||
|
################################################################################
|
||||||
|
# Copyright (c) 2003, Pfizer
|
||||||
|
# Copyright (c) 2001, Cayce Ullman.
|
||||||
|
# Copyright (c) 2001, Brian Matthews.
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
# Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# Neither the name of actzero, inc. nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
import exceptions
|
||||||
|
import copy
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
from types import *
|
||||||
|
|
||||||
|
# SOAPpy modules
|
||||||
|
from Errors import *
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Utility infielders
|
||||||
|
################################################################################
|
||||||
|
def collapseWhiteSpace(s):
|
||||||
|
return re.sub('\s+', ' ', s).strip()
|
||||||
|
|
||||||
|
def decodeHexString(data):
|
||||||
|
conv = {
|
||||||
|
'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4,
|
||||||
|
'5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9,
|
||||||
|
|
||||||
|
'a': 0xa, 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe,
|
||||||
|
'f': 0xf,
|
||||||
|
|
||||||
|
'A': 0xa, 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe,
|
||||||
|
'F': 0xf,
|
||||||
|
}
|
||||||
|
|
||||||
|
ws = string.whitespace
|
||||||
|
|
||||||
|
bin = ''
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while i < len(data):
|
||||||
|
if data[i] not in ws:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
low = 0
|
||||||
|
|
||||||
|
while i < len(data):
|
||||||
|
c = data[i]
|
||||||
|
|
||||||
|
if c in string.whitespace:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
c = conv[c]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError, \
|
||||||
|
"invalid hex string character `%s'" % c
|
||||||
|
|
||||||
|
if low:
|
||||||
|
bin += chr(high * 16 + c)
|
||||||
|
low = 0
|
||||||
|
else:
|
||||||
|
high = c
|
||||||
|
low = 1
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if low:
|
||||||
|
raise ValueError, "invalid hex string length"
|
||||||
|
|
||||||
|
while i < len(data):
|
||||||
|
if data[i] not in string.whitespace:
|
||||||
|
raise ValueError, \
|
||||||
|
"invalid hex string character `%s'" % c
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return bin
|
||||||
|
|
||||||
|
def encodeHexString(data):
|
||||||
|
h = ''
|
||||||
|
|
||||||
|
for i in data:
|
||||||
|
h += "%02X" % ord(i)
|
||||||
|
|
||||||
|
return h
|
||||||
|
|
||||||
|
def leapMonth(year, month):
|
||||||
|
return month == 2 and \
|
||||||
|
year % 4 == 0 and \
|
||||||
|
(year % 100 != 0 or year % 400 == 0)
|
||||||
|
|
||||||
|
def cleanDate(d, first = 0):
|
||||||
|
ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61))
|
||||||
|
months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
||||||
|
names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds')
|
||||||
|
|
||||||
|
if len(d) != 6:
|
||||||
|
raise ValueError, "date must have 6 elements"
|
||||||
|
|
||||||
|
for i in range(first, 6):
|
||||||
|
s = d[i]
|
||||||
|
|
||||||
|
if type(s) == FloatType:
|
||||||
|
if i < 5:
|
||||||
|
try:
|
||||||
|
s = int(s)
|
||||||
|
except OverflowError:
|
||||||
|
if i > 0:
|
||||||
|
raise
|
||||||
|
s = long(s)
|
||||||
|
|
||||||
|
if s != d[i]:
|
||||||
|
raise ValueError, "%s must be integral" % names[i]
|
||||||
|
|
||||||
|
d[i] = s
|
||||||
|
elif type(s) == LongType:
|
||||||
|
try: s = int(s)
|
||||||
|
except: pass
|
||||||
|
elif type(s) != IntType:
|
||||||
|
raise TypeError, "%s isn't a valid type" % names[i]
|
||||||
|
|
||||||
|
if i == first and s < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ranges[i] != None and \
|
||||||
|
(s < ranges[i][0] or ranges[i][1] < s):
|
||||||
|
raise ValueError, "%s out of range" % names[i]
|
||||||
|
|
||||||
|
if first < 6 and d[5] >= 61:
|
||||||
|
raise ValueError, "seconds out of range"
|
||||||
|
|
||||||
|
if first < 2:
|
||||||
|
leap = first < 1 and leapMonth(d[0], d[1])
|
||||||
|
|
||||||
|
if d[2] > months[d[1]] + leap:
|
||||||
|
raise ValueError, "day out of range"
|
||||||
|
|
||||||
|
def debugHeader(title):
|
||||||
|
s = '*** ' + title + ' '
|
||||||
|
print s + ('*' * (72 - len(s)))
|
||||||
|
|
||||||
|
def debugFooter(title):
|
||||||
|
print '*' * 72
|
||||||
|
sys.stdout.flush()
|
102
others/SOAPpy/WSDL.py
Normal file
102
others/SOAPpy/WSDL.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
"""Parse web services description language to get SOAP methods.
|
||||||
|
|
||||||
|
Rudimentary support."""
|
||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
import wstools
|
||||||
|
from Client import SOAPProxy, SOAPAddress
|
||||||
|
from Config import Config
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
class Proxy:
|
||||||
|
"""WSDL Proxy.
|
||||||
|
|
||||||
|
SOAPProxy wrapper that parses method names, namespaces, soap actions from
|
||||||
|
the web service description language (WSDL) file passed into the
|
||||||
|
constructor. The WSDL reference can be passed in as a stream, an url, a
|
||||||
|
file name, or a string.
|
||||||
|
|
||||||
|
Loads info into self.methods, a dictionary with methodname keys and values
|
||||||
|
of WSDLTools.SOAPCallinfo.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
url = 'http://www.xmethods.org/sd/2001/TemperatureService.wsdl'
|
||||||
|
wsdl = WSDL.Proxy(url)
|
||||||
|
print len(wsdl.methods) # 1
|
||||||
|
print wsdl.methods.keys() # getTemp
|
||||||
|
|
||||||
|
|
||||||
|
See WSDLTools.SOAPCallinfo for more info on each method's attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wsdlsource, config=Config, **kw ):
|
||||||
|
|
||||||
|
reader = wstools.WSDLTools.WSDLReader()
|
||||||
|
self.wsdl = None
|
||||||
|
|
||||||
|
# From Mark Pilgrim's "Dive Into Python" toolkit.py--open anything.
|
||||||
|
if self.wsdl is None and hasattr(wsdlsource, "read"):
|
||||||
|
#print 'stream'
|
||||||
|
self.wsdl = reader.loadFromStream(wsdlsource)
|
||||||
|
|
||||||
|
# NOT TESTED (as of April 17, 2003)
|
||||||
|
#if self.wsdl is None and wsdlsource == '-':
|
||||||
|
# import sys
|
||||||
|
# self.wsdl = reader.loadFromStream(sys.stdin)
|
||||||
|
# print 'stdin'
|
||||||
|
|
||||||
|
if self.wsdl is None:
|
||||||
|
try:
|
||||||
|
file(wsdlsource)
|
||||||
|
self.wsdl = reader.loadFromFile(wsdlsource)
|
||||||
|
#print 'file'
|
||||||
|
except (IOError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.wsdl is None:
|
||||||
|
try:
|
||||||
|
stream = urllib.urlopen(wsdlsource)
|
||||||
|
self.wsdl = reader.loadFromStream(stream, wsdlsource)
|
||||||
|
except (IOError, OSError): pass
|
||||||
|
|
||||||
|
if self.wsdl is None:
|
||||||
|
import StringIO
|
||||||
|
self.wsdl = reader.loadFromString(str(wsdlsource))
|
||||||
|
#print 'string'
|
||||||
|
|
||||||
|
# Package wsdl info as a dictionary of remote methods, with method name
|
||||||
|
# as key (based on ServiceProxy.__init__ in ZSI library).
|
||||||
|
self.methods = {}
|
||||||
|
service = self.wsdl.services[0]
|
||||||
|
port = service.ports[0]
|
||||||
|
name = service.name
|
||||||
|
binding = port.getBinding()
|
||||||
|
portType = binding.getPortType()
|
||||||
|
for operation in portType.operations:
|
||||||
|
callinfo = wstools.WSDLTools.callInfoFromWSDL(port, operation.name)
|
||||||
|
self.methods[callinfo.methodName] = callinfo
|
||||||
|
|
||||||
|
self.soapproxy = SOAPProxy('http://localhost/dummy.webservice',
|
||||||
|
config=config, **kw)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
s = ''
|
||||||
|
for method in self.methods.values():
|
||||||
|
s += str(method)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""Set up environment then let parent class handle call.
|
||||||
|
|
||||||
|
Raises AttributeError is method name is not found."""
|
||||||
|
|
||||||
|
if not self.methods.has_key(name): raise AttributeError, name
|
||||||
|
|
||||||
|
callinfo = self.methods[name]
|
||||||
|
self.soapproxy.proxy = SOAPAddress(callinfo.location)
|
||||||
|
self.soapproxy.namespace = callinfo.namespace
|
||||||
|
self.soapproxy.soapaction = callinfo.soapAction
|
||||||
|
return self.soapproxy.__getattr__(name)
|
15
others/SOAPpy/__init__.py
Normal file
15
others/SOAPpy/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
ident = '$Id$'
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
from Client import *
|
||||||
|
from Config import *
|
||||||
|
from Errors import *
|
||||||
|
from NS import *
|
||||||
|
from Parser import *
|
||||||
|
from SOAPBuilder import *
|
||||||
|
from Server import *
|
||||||
|
from Types import *
|
||||||
|
from Utilities import *
|
||||||
|
import wstools
|
||||||
|
import WSDL
|
2
others/SOAPpy/version.py
Normal file
2
others/SOAPpy/version.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
__version__="0.11.6"
|
||||||
|
|
91
others/SOAPpy/wstools/Namespaces.py
Executable file
91
others/SOAPpy/wstools/Namespaces.py
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
"""Namespace module, so you don't need PyXML
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from xml.ns import SOAP, SCHEMA, WSDL, XMLNS, DSIG, ENCRYPTION
|
||||||
|
except:
|
||||||
|
class SOAP:
|
||||||
|
ENV = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
|
ENC = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||||
|
ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next"
|
||||||
|
|
||||||
|
class SCHEMA:
|
||||||
|
XSD1 = "http://www.w3.org/1999/XMLSchema"
|
||||||
|
XSD2 = "http://www.w3.org/2000/10/XMLSchema"
|
||||||
|
XSD3 = "http://www.w3.org/2001/XMLSchema"
|
||||||
|
XSD_LIST = [ XSD1, XSD2, XSD3 ]
|
||||||
|
XSI1 = "http://www.w3.org/1999/XMLSchema-instance"
|
||||||
|
XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance"
|
||||||
|
XSI3 = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
XSI_LIST = [ XSI1, XSI2, XSI3 ]
|
||||||
|
BASE = XSD3
|
||||||
|
|
||||||
|
class WSDL:
|
||||||
|
BASE = "http://schemas.xmlsoap.org/wsdl/"
|
||||||
|
BIND_HTTP = "http://schemas.xmlsoap.org/wsdl/http/"
|
||||||
|
BIND_MIME = "http://schemas.xmlsoap.org/wsdl/mime/"
|
||||||
|
BIND_SOAP = "http://schemas.xmlsoap.org/wsdl/soap/"
|
||||||
|
|
||||||
|
class XMLNS:
|
||||||
|
BASE = "http://www.w3.org/2000/xmlns/"
|
||||||
|
XML = "http://www.w3.org/XML/1998/namespace"
|
||||||
|
HTML = "http://www.w3.org/TR/REC-html40"
|
||||||
|
|
||||||
|
class DSIG:
|
||||||
|
BASE = "http://www.w3.org/2000/09/xmldsig#"
|
||||||
|
C14N = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315"
|
||||||
|
C14N_COMM = "http://www.w3.org/TR/2000/CR-xml-c14n-20010315#WithComments"
|
||||||
|
C14N_EXCL = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||||
|
DIGEST_MD2 = "http://www.w3.org/2000/09/xmldsig#md2"
|
||||||
|
DIGEST_MD5 = "http://www.w3.org/2000/09/xmldsig#md5"
|
||||||
|
DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||||
|
ENC_BASE64 = "http://www.w3.org/2000/09/xmldsig#base64"
|
||||||
|
ENVELOPED = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
||||||
|
HMAC_SHA1 = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
|
||||||
|
SIG_DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||||
|
SIG_RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||||
|
XPATH = "http://www.w3.org/TR/1999/REC-xpath-19991116"
|
||||||
|
XSLT = "http://www.w3.org/TR/1999/REC-xslt-19991116"
|
||||||
|
|
||||||
|
class ENCRYPTION:
|
||||||
|
BASE = "http://www.w3.org/2001/04/xmlenc#"
|
||||||
|
BLOCK_3DES = "http://www.w3.org/2001/04/xmlenc#des-cbc"
|
||||||
|
BLOCK_AES128 = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
|
||||||
|
BLOCK_AES192 = "http://www.w3.org/2001/04/xmlenc#aes192-cbc"
|
||||||
|
BLOCK_AES256 = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
||||||
|
DIGEST_RIPEMD160 = "http://www.w3.org/2001/04/xmlenc#ripemd160"
|
||||||
|
DIGEST_SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
|
||||||
|
DIGEST_SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
|
||||||
|
KA_DH = "http://www.w3.org/2001/04/xmlenc#dh"
|
||||||
|
KT_RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
|
||||||
|
KT_RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
|
||||||
|
STREAM_ARCFOUR = "http://www.w3.org/2001/04/xmlenc#arcfour"
|
||||||
|
WRAP_3DES = "http://www.w3.org/2001/04/xmlenc#kw-3des"
|
||||||
|
WRAP_AES128 = "http://www.w3.org/2001/04/xmlenc#kw-aes128"
|
||||||
|
WRAP_AES192 = "http://www.w3.org/2001/04/xmlenc#kw-aes192"
|
||||||
|
WRAP_AES256 = "http://www.w3.org/2001/04/xmlenc#kw-aes256"
|
||||||
|
|
||||||
|
|
||||||
|
class WSSE:
|
||||||
|
BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext"
|
||||||
|
|
||||||
|
class WSU:
|
||||||
|
BASE = "http://schemas.xmlsoap.org/ws/2002/04/utility"
|
||||||
|
UTILITY = "http://schemas.xmlsoap.org/ws/2002/07/utility"
|
||||||
|
|
||||||
|
class WSR:
|
||||||
|
PROPERTIES = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceProperties"
|
||||||
|
LIFETIME = "http://www.ibm.com/xmlns/stdwip/web-services/WS-ResourceLifetime"
|
||||||
|
|
||||||
|
class WSA:
|
||||||
|
ADDRESS = "http://schemas.xmlsoap.org/ws/2003/03/addressing"
|
||||||
|
ADDRESS2004 = "http://schemas.xmlsoap.org/ws/2004/03/addressing"
|
||||||
|
ANONYMOUS = "%s/role/anonymous" %ADDRESS
|
||||||
|
ANONYMOUS2004 = "%s/role/anonymous" %ADDRESS2004
|
||||||
|
FAULT = "http://schemas.xmlsoap.org/ws/2004/03/addressing/fault"
|
||||||
|
|
||||||
|
class WSP:
|
||||||
|
POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy"
|
||||||
|
|
||||||
|
|
||||||
|
|
179
others/SOAPpy/wstools/TimeoutSocket.py
Executable file
179
others/SOAPpy/wstools/TimeoutSocket.py
Executable file
@ -0,0 +1,179 @@
|
|||||||
|
"""Based on code from timeout_socket.py, with some tweaks for compatibility.
|
||||||
|
These tweaks should really be rolled back into timeout_socket, but it's
|
||||||
|
not totally clear who is maintaining it at this point. In the meantime,
|
||||||
|
we'll use a different module name for our tweaked version to avoid any
|
||||||
|
confusion.
|
||||||
|
|
||||||
|
The original timeout_socket is by:
|
||||||
|
|
||||||
|
Scott Cotton <scott@chronis.pobox.com>
|
||||||
|
Lloyd Zusman <ljz@asfast.com>
|
||||||
|
Phil Mayes <pmayes@olivebr.com>
|
||||||
|
Piers Lauder <piers@cs.su.oz.au>
|
||||||
|
Radovan Garabik <garabik@melkor.dnp.fmph.uniba.sk>
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = "$Id$"
|
||||||
|
|
||||||
|
import string, socket, select, errno
|
||||||
|
|
||||||
|
WSAEINVAL = getattr(errno, 'WSAEINVAL', 10022)
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutSocket:
|
||||||
|
"""A socket imposter that supports timeout limits."""
|
||||||
|
|
||||||
|
def __init__(self, timeout=20, sock=None):
|
||||||
|
self.timeout = float(timeout)
|
||||||
|
self.inbuf = ''
|
||||||
|
if sock is None:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.sock = sock
|
||||||
|
self.sock.setblocking(0)
|
||||||
|
self._rbuf = ''
|
||||||
|
self._wbuf = ''
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
# Delegate to real socket attributes.
|
||||||
|
return getattr(self.sock, name)
|
||||||
|
|
||||||
|
def connect(self, *addr):
|
||||||
|
timeout = self.timeout
|
||||||
|
sock = self.sock
|
||||||
|
try:
|
||||||
|
# Non-blocking mode
|
||||||
|
sock.setblocking(0)
|
||||||
|
apply(sock.connect, addr)
|
||||||
|
sock.setblocking(timeout != 0)
|
||||||
|
return 1
|
||||||
|
except socket.error,why:
|
||||||
|
if not timeout:
|
||||||
|
raise
|
||||||
|
sock.setblocking(1)
|
||||||
|
if len(why.args) == 1:
|
||||||
|
code = 0
|
||||||
|
else:
|
||||||
|
code, why = why
|
||||||
|
if code not in (
|
||||||
|
errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK
|
||||||
|
):
|
||||||
|
raise
|
||||||
|
r,w,e = select.select([],[sock],[],timeout)
|
||||||
|
if w:
|
||||||
|
try:
|
||||||
|
apply(sock.connect, addr)
|
||||||
|
return 1
|
||||||
|
except socket.error,why:
|
||||||
|
if len(why.args) == 1:
|
||||||
|
code = 0
|
||||||
|
else:
|
||||||
|
code, why = why
|
||||||
|
if code in (errno.EISCONN, WSAEINVAL):
|
||||||
|
return 1
|
||||||
|
raise
|
||||||
|
raise TimeoutError('socket connect() timeout.')
|
||||||
|
|
||||||
|
def send(self, data, flags=0):
|
||||||
|
total = len(data)
|
||||||
|
next = 0
|
||||||
|
while 1:
|
||||||
|
r, w, e = select.select([],[self.sock], [], self.timeout)
|
||||||
|
if w:
|
||||||
|
buff = data[next:next + 8192]
|
||||||
|
sent = self.sock.send(buff, flags)
|
||||||
|
next = next + sent
|
||||||
|
if next == total:
|
||||||
|
return total
|
||||||
|
continue
|
||||||
|
raise TimeoutError('socket send() timeout.')
|
||||||
|
|
||||||
|
def recv(self, amt, flags=0):
|
||||||
|
if select.select([self.sock], [], [], self.timeout)[0]:
|
||||||
|
return self.sock.recv(amt, flags)
|
||||||
|
raise TimeoutError('socket recv() timeout.')
|
||||||
|
|
||||||
|
buffsize = 4096
|
||||||
|
handles = 1
|
||||||
|
|
||||||
|
def makefile(self, mode="r", buffsize=-1):
|
||||||
|
self.handles = self.handles + 1
|
||||||
|
self.mode = mode
|
||||||
|
return self
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.handles = self.handles - 1
|
||||||
|
if self.handles == 0 and self.sock.fileno() >= 0:
|
||||||
|
self.sock.close()
|
||||||
|
|
||||||
|
def read(self, n=-1):
|
||||||
|
if not isinstance(n, type(1)):
|
||||||
|
n = -1
|
||||||
|
if n >= 0:
|
||||||
|
k = len(self._rbuf)
|
||||||
|
if n <= k:
|
||||||
|
data = self._rbuf[:n]
|
||||||
|
self._rbuf = self._rbuf[n:]
|
||||||
|
return data
|
||||||
|
n = n - k
|
||||||
|
L = [self._rbuf]
|
||||||
|
self._rbuf = ""
|
||||||
|
while n > 0:
|
||||||
|
new = self.recv(max(n, self.buffsize))
|
||||||
|
if not new: break
|
||||||
|
k = len(new)
|
||||||
|
if k > n:
|
||||||
|
L.append(new[:n])
|
||||||
|
self._rbuf = new[n:]
|
||||||
|
break
|
||||||
|
L.append(new)
|
||||||
|
n = n - k
|
||||||
|
return "".join(L)
|
||||||
|
k = max(4096, self.buffsize)
|
||||||
|
L = [self._rbuf]
|
||||||
|
self._rbuf = ""
|
||||||
|
while 1:
|
||||||
|
new = self.recv(k)
|
||||||
|
if not new: break
|
||||||
|
L.append(new)
|
||||||
|
k = min(k*2, 1024**2)
|
||||||
|
return "".join(L)
|
||||||
|
|
||||||
|
def readline(self, limit=-1):
|
||||||
|
data = ""
|
||||||
|
i = self._rbuf.find('\n')
|
||||||
|
while i < 0 and not (0 < limit <= len(self._rbuf)):
|
||||||
|
new = self.recv(self.buffsize)
|
||||||
|
if not new: break
|
||||||
|
i = new.find('\n')
|
||||||
|
if i >= 0: i = i + len(self._rbuf)
|
||||||
|
self._rbuf = self._rbuf + new
|
||||||
|
if i < 0: i = len(self._rbuf)
|
||||||
|
else: i = i+1
|
||||||
|
if 0 <= limit < len(self._rbuf): i = limit
|
||||||
|
data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def readlines(self, sizehint = 0):
|
||||||
|
total = 0
|
||||||
|
list = []
|
||||||
|
while 1:
|
||||||
|
line = self.readline()
|
||||||
|
if not line: break
|
||||||
|
list.append(line)
|
||||||
|
total += len(line)
|
||||||
|
if sizehint and total >= sizehint:
|
||||||
|
break
|
||||||
|
return list
|
||||||
|
|
||||||
|
def writelines(self, list):
|
||||||
|
self.send(''.join(list))
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
self.send(data)
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutError(Exception):
|
||||||
|
pass
|
99
others/SOAPpy/wstools/UserTuple.py
Executable file
99
others/SOAPpy/wstools/UserTuple.py
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
"""
|
||||||
|
A more or less complete user-defined wrapper around tuple objects.
|
||||||
|
Adapted version of the standard library's UserList.
|
||||||
|
|
||||||
|
Taken from Stefan Schwarzer's ftputil library, available at
|
||||||
|
<http://www.ndh.net/home/sschwarzer/python/python_software.html>, and used under this license:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (C) 1999, Stefan Schwarzer
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
- Neither the name of the above author nor the names of the
|
||||||
|
contributors to the software may be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# $Id$
|
||||||
|
|
||||||
|
#XXX tuple instances (in Python 2.2) contain also:
|
||||||
|
# __class__, __delattr__, __getattribute__, __hash__, __new__,
|
||||||
|
# __reduce__, __setattr__, __str__
|
||||||
|
# What about these?
|
||||||
|
|
||||||
|
class UserTuple:
|
||||||
|
def __init__(self, inittuple=None):
|
||||||
|
self.data = ()
|
||||||
|
if inittuple is not None:
|
||||||
|
# XXX should this accept an arbitrary sequence?
|
||||||
|
if type(inittuple) == type(self.data):
|
||||||
|
self.data = inittuple
|
||||||
|
elif isinstance(inittuple, UserTuple):
|
||||||
|
# this results in
|
||||||
|
# self.data is inittuple.data
|
||||||
|
# but that's ok for tuples because they are
|
||||||
|
# immutable. (Builtin tuples behave the same.)
|
||||||
|
self.data = inittuple.data[:]
|
||||||
|
else:
|
||||||
|
# the same applies here; (t is tuple(t)) == 1
|
||||||
|
self.data = tuple(inittuple)
|
||||||
|
def __repr__(self): return repr(self.data)
|
||||||
|
def __lt__(self, other): return self.data < self.__cast(other)
|
||||||
|
def __le__(self, other): return self.data <= self.__cast(other)
|
||||||
|
def __eq__(self, other): return self.data == self.__cast(other)
|
||||||
|
def __ne__(self, other): return self.data != self.__cast(other)
|
||||||
|
def __gt__(self, other): return self.data > self.__cast(other)
|
||||||
|
def __ge__(self, other): return self.data >= self.__cast(other)
|
||||||
|
def __cast(self, other):
|
||||||
|
if isinstance(other, UserTuple): return other.data
|
||||||
|
else: return other
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(self.data, self.__cast(other))
|
||||||
|
def __contains__(self, item): return item in self.data
|
||||||
|
def __len__(self): return len(self.data)
|
||||||
|
def __getitem__(self, i): return self.data[i]
|
||||||
|
def __getslice__(self, i, j):
|
||||||
|
i = max(i, 0); j = max(j, 0)
|
||||||
|
return self.__class__(self.data[i:j])
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, UserTuple):
|
||||||
|
return self.__class__(self.data + other.data)
|
||||||
|
elif isinstance(other, type(self.data)):
|
||||||
|
return self.__class__(self.data + other)
|
||||||
|
else:
|
||||||
|
return self.__class__(self.data + tuple(other))
|
||||||
|
# dir( () ) contains no __radd__ (at least in Python 2.2)
|
||||||
|
def __mul__(self, n):
|
||||||
|
return self.__class__(self.data*n)
|
||||||
|
__rmul__ = __mul__
|
||||||
|
|
839
others/SOAPpy/wstools/Utility.py
Executable file
839
others/SOAPpy/wstools/Utility.py
Executable file
@ -0,0 +1,839 @@
|
|||||||
|
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# This software is subject to the provisions of the Zope Public License,
|
||||||
|
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
|
||||||
|
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
|
||||||
|
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
ident = "$Id$"
|
||||||
|
|
||||||
|
import types
|
||||||
|
import string, httplib, smtplib, urllib, socket, weakref
|
||||||
|
import xml.dom.minidom
|
||||||
|
from string import join, strip, split
|
||||||
|
from UserDict import UserDict
|
||||||
|
from StringIO import StringIO
|
||||||
|
from TimeoutSocket import TimeoutSocket, TimeoutError
|
||||||
|
from urlparse import urlparse
|
||||||
|
from httplib import HTTPConnection, HTTPSConnection
|
||||||
|
from exceptions import Exception
|
||||||
|
|
||||||
|
try:
|
||||||
|
from xml.dom.ext import SplitQName
|
||||||
|
except:
|
||||||
|
def SplitQName(qname):
|
||||||
|
'''SplitQName(qname) -> (string, string)
|
||||||
|
|
||||||
|
Split Qualified Name into a tuple of len 2, consisting
|
||||||
|
of the prefix and the local name.
|
||||||
|
|
||||||
|
(prefix, localName)
|
||||||
|
|
||||||
|
Special Cases:
|
||||||
|
xmlns -- (localName, 'xmlns')
|
||||||
|
None -- (None, localName)
|
||||||
|
'''
|
||||||
|
|
||||||
|
l = qname.split(':')
|
||||||
|
if len(l) == 1:
|
||||||
|
l.insert(0, None)
|
||||||
|
elif len(l) == 2:
|
||||||
|
if l[0] == 'xmlns':
|
||||||
|
l.reverse()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
return tuple(l)
|
||||||
|
|
||||||
|
class RecursionError(Exception):
|
||||||
|
"""Used to indicate a HTTP redirect recursion."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class HTTPResponse:
|
||||||
|
"""Captures the information in an HTTP response message."""
|
||||||
|
|
||||||
|
def __init__(self, response):
|
||||||
|
self.status = response.status
|
||||||
|
self.reason = response.reason
|
||||||
|
self.headers = response.msg
|
||||||
|
self.body = response.read() or None
|
||||||
|
response.close()
|
||||||
|
|
||||||
|
class TimeoutHTTP(HTTPConnection):
|
||||||
|
"""A custom http connection object that supports socket timeout."""
|
||||||
|
def __init__(self, host, port=None, timeout=20):
|
||||||
|
HTTPConnection.__init__(self, host, port)
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.sock = TimeoutSocket(self.timeout)
|
||||||
|
self.sock.connect((self.host, self.port))
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutHTTPS(HTTPSConnection):
|
||||||
|
"""A custom https object that supports socket timeout. Note that this
|
||||||
|
is not really complete. The builtin SSL support in the Python socket
|
||||||
|
module requires a real socket (type) to be passed in to be hooked to
|
||||||
|
SSL. That means our fake socket won't work and our timeout hacks are
|
||||||
|
bypassed for send and recv calls. Since our hack _is_ in place at
|
||||||
|
connect() time, it should at least provide some timeout protection."""
|
||||||
|
def __init__(self, host, port=None, timeout=20, **kwargs):
|
||||||
|
HTTPSConnection.__init__(self, str(host), port, **kwargs)
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
sock = TimeoutSocket(self.timeout)
|
||||||
|
sock.connect((self.host, self.port))
|
||||||
|
realsock = getattr(sock.sock, '_sock', sock.sock)
|
||||||
|
ssl = socket.ssl(realsock, self.key_file, self.cert_file)
|
||||||
|
self.sock = httplib.FakeSocket(sock, ssl)
|
||||||
|
|
||||||
|
def urlopen(url, timeout=20, redirects=None):
|
||||||
|
"""A minimal urlopen replacement hack that supports timeouts for http.
|
||||||
|
Note that this supports GET only."""
|
||||||
|
scheme, host, path, params, query, frag = urlparse(url)
|
||||||
|
if not scheme in ('http', 'https'):
|
||||||
|
return urllib.urlopen(url)
|
||||||
|
if params: path = '%s;%s' % (path, params)
|
||||||
|
if query: path = '%s?%s' % (path, query)
|
||||||
|
if frag: path = '%s#%s' % (path, frag)
|
||||||
|
|
||||||
|
if scheme == 'https':
|
||||||
|
# If ssl is not compiled into Python, you will not get an exception
|
||||||
|
# until a conn.endheaders() call. We need to know sooner, so use
|
||||||
|
# getattr.
|
||||||
|
if hasattr(socket, 'ssl'):
|
||||||
|
conn = TimeoutHTTPS(host, None, timeout)
|
||||||
|
else:
|
||||||
|
import M2Crypto
|
||||||
|
ctx = M2Crypto.SSL.Context()
|
||||||
|
ctx.set_session_timeout(timeout)
|
||||||
|
conn = M2Crypto.httpslib.HTTPSConnection(host, ssl_context=ctx)
|
||||||
|
#conn.set_debuglevel(1)
|
||||||
|
else:
|
||||||
|
conn = TimeoutHTTP(host, None, timeout)
|
||||||
|
|
||||||
|
conn.putrequest('GET', path)
|
||||||
|
conn.putheader('Connection', 'close')
|
||||||
|
conn.endheaders()
|
||||||
|
response = None
|
||||||
|
while 1:
|
||||||
|
response = conn.getresponse()
|
||||||
|
if response.status != 100:
|
||||||
|
break
|
||||||
|
conn._HTTPConnection__state = httplib._CS_REQ_SENT
|
||||||
|
conn._HTTPConnection__response = None
|
||||||
|
|
||||||
|
status = response.status
|
||||||
|
|
||||||
|
# If we get an HTTP redirect, we will follow it automatically.
|
||||||
|
if status >= 300 and status < 400:
|
||||||
|
location = response.msg.getheader('location')
|
||||||
|
if location is not None:
|
||||||
|
response.close()
|
||||||
|
if redirects is not None and redirects.has_key(location):
|
||||||
|
raise RecursionError(
|
||||||
|
'Circular HTTP redirection detected.'
|
||||||
|
)
|
||||||
|
if redirects is None:
|
||||||
|
redirects = {}
|
||||||
|
redirects[location] = 1
|
||||||
|
return urlopen(location, timeout, redirects)
|
||||||
|
raise HTTPResponse(response)
|
||||||
|
|
||||||
|
if not (status >= 200 and status < 300):
|
||||||
|
raise HTTPResponse(response)
|
||||||
|
|
||||||
|
body = StringIO(response.read())
|
||||||
|
response.close()
|
||||||
|
return body
|
||||||
|
|
||||||
|
class DOM:
|
||||||
|
"""The DOM singleton defines a number of XML related constants and
|
||||||
|
provides a number of utility methods for DOM related tasks. It
|
||||||
|
also provides some basic abstractions so that the rest of the
|
||||||
|
package need not care about actual DOM implementation in use."""
|
||||||
|
|
||||||
|
# Namespace stuff related to the SOAP specification.
|
||||||
|
|
||||||
|
NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
|
||||||
|
NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
|
||||||
|
|
||||||
|
NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope'
|
||||||
|
NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding'
|
||||||
|
|
||||||
|
NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2)
|
||||||
|
NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2)
|
||||||
|
|
||||||
|
NS_SOAP_ENV = NS_SOAP_ENV_1_1
|
||||||
|
NS_SOAP_ENC = NS_SOAP_ENC_1_1
|
||||||
|
|
||||||
|
_soap_uri_mapping = {
|
||||||
|
NS_SOAP_ENV_1_1 : '1.1',
|
||||||
|
NS_SOAP_ENV_1_2 : '1.2',
|
||||||
|
}
|
||||||
|
|
||||||
|
SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next'
|
||||||
|
SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next'
|
||||||
|
SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2)
|
||||||
|
|
||||||
|
def SOAPUriToVersion(self, uri):
|
||||||
|
"""Return the SOAP version related to an envelope uri."""
|
||||||
|
value = self._soap_uri_mapping.get(uri)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported SOAP envelope uri: %s' % uri
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetSOAPEnvUri(self, version):
|
||||||
|
"""Return the appropriate SOAP envelope uri for a given
|
||||||
|
human-friendly SOAP version string (e.g. '1.1')."""
|
||||||
|
attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attrname, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported SOAP version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetSOAPEncUri(self, version):
|
||||||
|
"""Return the appropriate SOAP encoding uri for a given
|
||||||
|
human-friendly SOAP version string (e.g. '1.1')."""
|
||||||
|
attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attrname, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported SOAP version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetSOAPActorNextUri(self, version):
|
||||||
|
"""Return the right special next-actor uri for a given
|
||||||
|
human-friendly SOAP version string (e.g. '1.1')."""
|
||||||
|
attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attrname, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported SOAP version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Namespace stuff related to XML Schema.
|
||||||
|
|
||||||
|
NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
|
||||||
|
NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'
|
||||||
|
|
||||||
|
NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
|
||||||
|
NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'
|
||||||
|
|
||||||
|
NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
|
||||||
|
NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||||
|
|
||||||
|
NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01)
|
||||||
|
NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01)
|
||||||
|
|
||||||
|
NS_XSD = NS_XSD_01
|
||||||
|
NS_XSI = NS_XSI_01
|
||||||
|
|
||||||
|
_xsd_uri_mapping = {
|
||||||
|
NS_XSD_99 : NS_XSI_99,
|
||||||
|
NS_XSD_00 : NS_XSI_00,
|
||||||
|
NS_XSD_01 : NS_XSI_01,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in _xsd_uri_mapping.items():
|
||||||
|
_xsd_uri_mapping[value] = key
|
||||||
|
|
||||||
|
|
||||||
|
def InstanceUriForSchemaUri(self, uri):
|
||||||
|
"""Return the appropriate matching XML Schema instance uri for
|
||||||
|
the given XML Schema namespace uri."""
|
||||||
|
return self._xsd_uri_mapping.get(uri)
|
||||||
|
|
||||||
|
def SchemaUriForInstanceUri(self, uri):
|
||||||
|
"""Return the appropriate matching XML Schema namespace uri for
|
||||||
|
the given XML Schema instance namespace uri."""
|
||||||
|
return self._xsd_uri_mapping.get(uri)
|
||||||
|
|
||||||
|
|
||||||
|
# Namespace stuff related to WSDL.
|
||||||
|
|
||||||
|
NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/'
|
||||||
|
NS_WSDL_ALL = (NS_WSDL_1_1,)
|
||||||
|
NS_WSDL = NS_WSDL_1_1
|
||||||
|
|
||||||
|
NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
|
||||||
|
NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/'
|
||||||
|
NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/'
|
||||||
|
|
||||||
|
NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,)
|
||||||
|
NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,)
|
||||||
|
NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,)
|
||||||
|
|
||||||
|
NS_SOAP_BINDING = NS_SOAP_BINDING_1_1
|
||||||
|
NS_HTTP_BINDING = NS_HTTP_BINDING_1_1
|
||||||
|
NS_MIME_BINDING = NS_MIME_BINDING_1_1
|
||||||
|
|
||||||
|
NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http'
|
||||||
|
NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,)
|
||||||
|
NS_SOAP_HTTP = NS_SOAP_HTTP_1_1
|
||||||
|
|
||||||
|
|
||||||
|
_wsdl_uri_mapping = {
|
||||||
|
NS_WSDL_1_1 : '1.1',
|
||||||
|
}
|
||||||
|
|
||||||
|
def WSDLUriToVersion(self, uri):
|
||||||
|
"""Return the WSDL version related to a WSDL namespace uri."""
|
||||||
|
value = self._wsdl_uri_mapping.get(uri)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported SOAP envelope uri: %s' % uri
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetWSDLUri(self, version):
|
||||||
|
attr = 'NS_WSDL_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attr, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported WSDL version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetWSDLSoapBindingUri(self, version):
|
||||||
|
attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attr, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported WSDL version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetWSDLHttpBindingUri(self, version):
|
||||||
|
attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attr, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported WSDL version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetWSDLMimeBindingUri(self, version):
|
||||||
|
attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attr, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported WSDL version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
def GetWSDLHttpTransportUri(self, version):
|
||||||
|
attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_')
|
||||||
|
value = getattr(self, attr, None)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise ValueError(
|
||||||
|
'Unsupported WSDL version: %s' % version
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Other xml namespace constants.
|
||||||
|
NS_XMLNS = 'http://www.w3.org/2000/xmlns/'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def isElement(self, node, name, nsuri=None):
|
||||||
|
"""Return true if the given node is an element with the given
|
||||||
|
name and optional namespace uri."""
|
||||||
|
if node.nodeType != node.ELEMENT_NODE:
|
||||||
|
return 0
|
||||||
|
return node.localName == name and \
|
||||||
|
(nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
|
||||||
|
|
||||||
|
def getElement(self, node, name, nsuri=None, default=join):
|
||||||
|
"""Return the first child of node with a matching name and
|
||||||
|
namespace uri, or the default if one is provided."""
|
||||||
|
nsmatch = self.nsUriMatch
|
||||||
|
ELEMENT_NODE = node.ELEMENT_NODE
|
||||||
|
for child in node.childNodes:
|
||||||
|
if child.nodeType == ELEMENT_NODE:
|
||||||
|
if ((child.localName == name or name is None) and
|
||||||
|
(nsuri is None or nsmatch(child.namespaceURI, nsuri))
|
||||||
|
):
|
||||||
|
return child
|
||||||
|
if default is not join:
|
||||||
|
return default
|
||||||
|
raise KeyError, name
|
||||||
|
|
||||||
|
def getElementById(self, node, id, default=join):
|
||||||
|
"""Return the first child of node matching an id reference."""
|
||||||
|
attrget = self.getAttr
|
||||||
|
ELEMENT_NODE = node.ELEMENT_NODE
|
||||||
|
for child in node.childNodes:
|
||||||
|
if child.nodeType == ELEMENT_NODE:
|
||||||
|
if attrget(child, 'id') == id:
|
||||||
|
return child
|
||||||
|
if default is not join:
|
||||||
|
return default
|
||||||
|
raise KeyError, name
|
||||||
|
|
||||||
|
def getMappingById(self, document, depth=None, element=None,
|
||||||
|
mapping=None, level=1):
|
||||||
|
"""Create an id -> element mapping of those elements within a
|
||||||
|
document that define an id attribute. The depth of the search
|
||||||
|
may be controlled by using the (1-based) depth argument."""
|
||||||
|
if document is not None:
|
||||||
|
element = document.documentElement
|
||||||
|
mapping = {}
|
||||||
|
attr = element._attrs.get('id', None)
|
||||||
|
if attr is not None:
|
||||||
|
mapping[attr.value] = element
|
||||||
|
if depth is None or depth > level:
|
||||||
|
level = level + 1
|
||||||
|
ELEMENT_NODE = element.ELEMENT_NODE
|
||||||
|
for child in element.childNodes:
|
||||||
|
if child.nodeType == ELEMENT_NODE:
|
||||||
|
self.getMappingById(None, depth, child, mapping, level)
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
def getElements(self, node, name, nsuri=None):
|
||||||
|
"""Return a sequence of the child elements of the given node that
|
||||||
|
match the given name and optional namespace uri."""
|
||||||
|
nsmatch = self.nsUriMatch
|
||||||
|
result = []
|
||||||
|
ELEMENT_NODE = node.ELEMENT_NODE
|
||||||
|
for child in node.childNodes:
|
||||||
|
if child.nodeType == ELEMENT_NODE:
|
||||||
|
if ((child.localName == name or name is None) and (
|
||||||
|
(nsuri is None) or nsmatch(child.namespaceURI, nsuri))):
|
||||||
|
result.append(child)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def hasAttr(self, node, name, nsuri=None):
|
||||||
|
"""Return true if element has attribute with the given name and
|
||||||
|
optional nsuri. If nsuri is not specified, returns true if an
|
||||||
|
attribute exists with the given name with any namespace."""
|
||||||
|
if nsuri is None:
|
||||||
|
if node.hasAttribute(name):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
return node.hasAttributeNS(nsuri, name)
|
||||||
|
|
||||||
|
def getAttr(self, node, name, nsuri=None, default=join):
|
||||||
|
"""Return the value of the attribute named 'name' with the
|
||||||
|
optional nsuri, or the default if one is specified. If
|
||||||
|
nsuri is not specified, an attribute that matches the
|
||||||
|
given name will be returned regardless of namespace."""
|
||||||
|
if nsuri is None:
|
||||||
|
result = node._attrs.get(name, None)
|
||||||
|
if result is None:
|
||||||
|
for item in node._attrsNS.keys():
|
||||||
|
if item[1] == name:
|
||||||
|
result = node._attrsNS[item]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
result = node._attrsNS.get((nsuri, name), None)
|
||||||
|
if result is not None:
|
||||||
|
return result.value
|
||||||
|
if default is not join:
|
||||||
|
return default
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getAttrs(self, node):
|
||||||
|
"""Return a Collection of all attributes
|
||||||
|
"""
|
||||||
|
attrs = {}
|
||||||
|
for k,v in node._attrs.items():
|
||||||
|
attrs[k] = v.value
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def getElementText(self, node, preserve_ws=None):
|
||||||
|
"""Return the text value of an xml element node. Leading and trailing
|
||||||
|
whitespace is stripped from the value unless the preserve_ws flag
|
||||||
|
is passed with a true value."""
|
||||||
|
result = []
|
||||||
|
for child in node.childNodes:
|
||||||
|
nodetype = child.nodeType
|
||||||
|
if nodetype == child.TEXT_NODE or \
|
||||||
|
nodetype == child.CDATA_SECTION_NODE:
|
||||||
|
result.append(child.nodeValue)
|
||||||
|
value = join(result, '')
|
||||||
|
if preserve_ws is None:
|
||||||
|
value = strip(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def findNamespaceURI(self, prefix, node):
|
||||||
|
"""Find a namespace uri given a prefix and a context node."""
|
||||||
|
attrkey = (self.NS_XMLNS, prefix)
|
||||||
|
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||||
|
ELEMENT_NODE = node.ELEMENT_NODE
|
||||||
|
while 1:
|
||||||
|
if node.nodeType != ELEMENT_NODE:
|
||||||
|
node = node.parentNode
|
||||||
|
continue
|
||||||
|
result = node._attrsNS.get(attrkey, None)
|
||||||
|
if result is not None:
|
||||||
|
return result.value
|
||||||
|
if hasattr(node, '__imported__'):
|
||||||
|
raise DOMException('Value for prefix %s not found.' % prefix)
|
||||||
|
node = node.parentNode
|
||||||
|
if node.nodeType == DOCUMENT_NODE:
|
||||||
|
raise DOMException('Value for prefix %s not found.' % prefix)
|
||||||
|
|
||||||
|
def findDefaultNS(self, node):
|
||||||
|
"""Return the current default namespace uri for the given node."""
|
||||||
|
attrkey = (self.NS_XMLNS, 'xmlns')
|
||||||
|
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||||
|
ELEMENT_NODE = node.ELEMENT_NODE
|
||||||
|
while 1:
|
||||||
|
if node.nodeType != ELEMENT_NODE:
|
||||||
|
node = node.parentNode
|
||||||
|
continue
|
||||||
|
result = node._attrsNS.get(attrkey, None)
|
||||||
|
if result is not None:
|
||||||
|
return result.value
|
||||||
|
if hasattr(node, '__imported__'):
|
||||||
|
raise DOMException('Cannot determine default namespace.')
|
||||||
|
node = node.parentNode
|
||||||
|
if node.nodeType == DOCUMENT_NODE:
|
||||||
|
raise DOMException('Cannot determine default namespace.')
|
||||||
|
|
||||||
|
def findTargetNS(self, node):
|
||||||
|
"""Return the defined target namespace uri for the given node."""
|
||||||
|
attrget = self.getAttr
|
||||||
|
attrkey = (self.NS_XMLNS, 'xmlns')
|
||||||
|
DOCUMENT_NODE = node.DOCUMENT_NODE
|
||||||
|
ELEMENT_NODE = node.ELEMENT_NODE
|
||||||
|
while 1:
|
||||||
|
if node.nodeType != ELEMENT_NODE:
|
||||||
|
node = node.parentNode
|
||||||
|
continue
|
||||||
|
result = attrget(node, 'targetNamespace', default=None)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
node = node.parentNode
|
||||||
|
if node.nodeType == DOCUMENT_NODE:
|
||||||
|
raise DOMException('Cannot determine target namespace.')
|
||||||
|
|
||||||
|
def getTypeRef(self, element):
|
||||||
|
"""Return (namespaceURI, name) for a type attribue of the given
|
||||||
|
element, or None if the element does not have a type attribute."""
|
||||||
|
typeattr = self.getAttr(element, 'type', default=None)
|
||||||
|
if typeattr is None:
|
||||||
|
return None
|
||||||
|
parts = typeattr.split(':', 1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
nsuri = self.findNamespaceURI(parts[0], element)
|
||||||
|
else:
|
||||||
|
nsuri = self.findDefaultNS(element)
|
||||||
|
return (nsuri, parts[1])
|
||||||
|
|
||||||
|
def importNode(self, document, node, deep=0):
|
||||||
|
"""Implements (well enough for our purposes) DOM node import."""
|
||||||
|
nodetype = node.nodeType
|
||||||
|
if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE):
|
||||||
|
raise DOMException('Illegal node type for importNode')
|
||||||
|
if nodetype == node.ENTITY_REFERENCE_NODE:
|
||||||
|
deep = 0
|
||||||
|
clone = node.cloneNode(deep)
|
||||||
|
self._setOwnerDoc(document, clone)
|
||||||
|
clone.__imported__ = 1
|
||||||
|
return clone
|
||||||
|
|
||||||
|
def _setOwnerDoc(self, document, node):
|
||||||
|
node.ownerDocument = document
|
||||||
|
for child in node.childNodes:
|
||||||
|
self._setOwnerDoc(document, child)
|
||||||
|
|
||||||
|
def nsUriMatch(self, value, wanted, strict=0, tt=type(())):
|
||||||
|
"""Return a true value if two namespace uri values match."""
|
||||||
|
if value == wanted or (type(wanted) is tt) and value in wanted:
|
||||||
|
return 1
|
||||||
|
if not strict:
|
||||||
|
wanted = type(wanted) is tt and wanted or (wanted,)
|
||||||
|
value = value[-1:] != '/' and value or value[:-1]
|
||||||
|
for item in wanted:
|
||||||
|
if item == value or item[:-1] == value:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def createDocument(self, nsuri, qname, doctype=None):
|
||||||
|
"""Create a new writable DOM document object."""
|
||||||
|
impl = xml.dom.minidom.getDOMImplementation()
|
||||||
|
return impl.createDocument(nsuri, qname, doctype)
|
||||||
|
|
||||||
|
def loadDocument(self, data):
|
||||||
|
"""Load an xml file from a file-like object and return a DOM
|
||||||
|
document instance."""
|
||||||
|
return xml.dom.minidom.parse(data)
|
||||||
|
|
||||||
|
def loadFromURL(self, url):
|
||||||
|
"""Load an xml file from a URL and return a DOM document."""
|
||||||
|
file = urlopen(url)
|
||||||
|
try: result = self.loadDocument(file)
|
||||||
|
finally: file.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DOMException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
DOM = DOM()
|
||||||
|
|
||||||
|
|
||||||
|
class Collection(UserDict):
|
||||||
|
"""Helper class for maintaining ordered named collections."""
|
||||||
|
default = lambda self,k: k.name
|
||||||
|
def __init__(self, parent, key=None):
|
||||||
|
UserDict.__init__(self)
|
||||||
|
self.parent = weakref.ref(parent)
|
||||||
|
self.list = []
|
||||||
|
self._func = key or self.default
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if type(key) is type(1):
|
||||||
|
return self.list[key]
|
||||||
|
return self.data[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, item):
|
||||||
|
item.parent = weakref.ref(self)
|
||||||
|
self.list.append(item)
|
||||||
|
self.data[key] = item
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return map(lambda i: self._func(i), self.list)
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return map(lambda i: (self._func(i), i), self.list)
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self.list
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionNS(UserDict):
|
||||||
|
"""Helper class for maintaining ordered named collections."""
|
||||||
|
default = lambda self,k: k.name
|
||||||
|
def __init__(self, parent, key=None):
|
||||||
|
UserDict.__init__(self)
|
||||||
|
self.parent = weakref.ref(parent)
|
||||||
|
self.targetNamespace = None
|
||||||
|
self.list = []
|
||||||
|
self._func = key or self.default
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
self.targetNamespace = self.parent().targetNamespace
|
||||||
|
if type(key) is types.IntType:
|
||||||
|
return self.list[key]
|
||||||
|
elif self.__isSequence(key):
|
||||||
|
nsuri,name = key
|
||||||
|
return self.data[nsuri][name]
|
||||||
|
return self.data[self.parent().targetNamespace][key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, item):
|
||||||
|
item.parent = weakref.ref(self)
|
||||||
|
self.list.append(item)
|
||||||
|
targetNamespace = getattr(item, 'targetNamespace', self.parent().targetNamespace)
|
||||||
|
if not self.data.has_key(targetNamespace):
|
||||||
|
self.data[targetNamespace] = {}
|
||||||
|
self.data[targetNamespace][key] = item
|
||||||
|
|
||||||
|
def __isSequence(self, key):
|
||||||
|
return (type(key) in (types.TupleType,types.ListType) and len(key) == 2)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
keys = []
|
||||||
|
for tns in self.data.keys():
|
||||||
|
keys.append(map(lambda i: (tns,self._func(i)), self.data[tns].values()))
|
||||||
|
return keys
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return map(lambda i: (self._func(i), i), self.list)
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self.list
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# This is a runtime guerilla patch for pulldom (used by minidom) so
|
||||||
|
# that xml namespace declaration attributes are not lost in parsing.
|
||||||
|
# We need them to do correct QName linking for XML Schema and WSDL.
|
||||||
|
# The patch has been submitted to SF for the next Python version.
|
||||||
|
|
||||||
|
from xml.dom.pulldom import PullDOM, START_ELEMENT
|
||||||
|
if 1:
|
||||||
|
def startPrefixMapping(self, prefix, uri):
|
||||||
|
if not hasattr(self, '_xmlns_attrs'):
|
||||||
|
self._xmlns_attrs = []
|
||||||
|
self._xmlns_attrs.append((prefix or 'xmlns', uri))
|
||||||
|
self._ns_contexts.append(self._current_context.copy())
|
||||||
|
self._current_context[uri] = prefix or ''
|
||||||
|
|
||||||
|
PullDOM.startPrefixMapping = startPrefixMapping
|
||||||
|
|
||||||
|
def startElementNS(self, name, tagName , attrs):
|
||||||
|
# Retrieve xml namespace declaration attributes.
|
||||||
|
xmlns_uri = 'http://www.w3.org/2000/xmlns/'
|
||||||
|
xmlns_attrs = getattr(self, '_xmlns_attrs', None)
|
||||||
|
if xmlns_attrs is not None:
|
||||||
|
for aname, value in xmlns_attrs:
|
||||||
|
attrs._attrs[(xmlns_uri, aname)] = value
|
||||||
|
self._xmlns_attrs = []
|
||||||
|
uri, localname = name
|
||||||
|
if uri:
|
||||||
|
# When using namespaces, the reader may or may not
|
||||||
|
# provide us with the original name. If not, create
|
||||||
|
# *a* valid tagName from the current context.
|
||||||
|
if tagName is None:
|
||||||
|
prefix = self._current_context[uri]
|
||||||
|
if prefix:
|
||||||
|
tagName = prefix + ":" + localname
|
||||||
|
else:
|
||||||
|
tagName = localname
|
||||||
|
if self.document:
|
||||||
|
node = self.document.createElementNS(uri, tagName)
|
||||||
|
else:
|
||||||
|
node = self.buildDocument(uri, tagName)
|
||||||
|
else:
|
||||||
|
# When the tagname is not prefixed, it just appears as
|
||||||
|
# localname
|
||||||
|
if self.document:
|
||||||
|
node = self.document.createElement(localname)
|
||||||
|
else:
|
||||||
|
node = self.buildDocument(None, localname)
|
||||||
|
|
||||||
|
for aname,value in attrs.items():
|
||||||
|
a_uri, a_localname = aname
|
||||||
|
if a_uri == xmlns_uri:
|
||||||
|
if a_localname == 'xmlns':
|
||||||
|
qname = a_localname
|
||||||
|
else:
|
||||||
|
qname = 'xmlns:' + a_localname
|
||||||
|
attr = self.document.createAttributeNS(a_uri, qname)
|
||||||
|
node.setAttributeNodeNS(attr)
|
||||||
|
elif a_uri:
|
||||||
|
prefix = self._current_context[a_uri]
|
||||||
|
if prefix:
|
||||||
|
qname = prefix + ":" + a_localname
|
||||||
|
else:
|
||||||
|
qname = a_localname
|
||||||
|
attr = self.document.createAttributeNS(a_uri, qname)
|
||||||
|
node.setAttributeNodeNS(attr)
|
||||||
|
else:
|
||||||
|
attr = self.document.createAttribute(a_localname)
|
||||||
|
node.setAttributeNode(attr)
|
||||||
|
attr.value = value
|
||||||
|
|
||||||
|
self.lastEvent[1] = [(START_ELEMENT, node), None]
|
||||||
|
self.lastEvent = self.lastEvent[1]
|
||||||
|
self.push(node)
|
||||||
|
|
||||||
|
PullDOM.startElementNS = startElementNS
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is a runtime guerilla patch for minidom so
|
||||||
|
# that xmlns prefixed attributes dont raise AttributeErrors
|
||||||
|
# during cloning.
|
||||||
|
#
|
||||||
|
# Namespace declarations can appear in any start-tag, must look for xmlns
|
||||||
|
# prefixed attribute names during cloning.
|
||||||
|
#
|
||||||
|
# key (attr.namespaceURI, tag)
|
||||||
|
# ('http://www.w3.org/2000/xmlns/', u'xsd') <xml.dom.minidom.Attr instance at 0x82227c4>
|
||||||
|
# ('http://www.w3.org/2000/xmlns/', 'xmlns') <xml.dom.minidom.Attr instance at 0x8414b3c>
|
||||||
|
#
|
||||||
|
# xml.dom.minidom.Attr.nodeName = xmlns:xsd
|
||||||
|
# xml.dom.minidom.Attr.value = = http://www.w3.org/2001/XMLSchema
|
||||||
|
|
||||||
|
if 1:
|
||||||
|
def _clone_node(node, deep, newOwnerDocument):
|
||||||
|
"""
|
||||||
|
Clone a node and give it the new owner document.
|
||||||
|
Called by Node.cloneNode and Document.importNode
|
||||||
|
"""
|
||||||
|
if node.ownerDocument.isSameNode(newOwnerDocument):
|
||||||
|
operation = xml.dom.UserDataHandler.NODE_CLONED
|
||||||
|
else:
|
||||||
|
operation = xml.dom.UserDataHandler.NODE_IMPORTED
|
||||||
|
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
|
||||||
|
clone = newOwnerDocument.createElementNS(node.namespaceURI,
|
||||||
|
node.nodeName)
|
||||||
|
for attr in node.attributes.values():
|
||||||
|
clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
|
||||||
|
|
||||||
|
prefix, tag = xml.dom.minidom._nssplit(attr.nodeName)
|
||||||
|
if prefix == 'xmlns':
|
||||||
|
a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
|
||||||
|
elif prefix:
|
||||||
|
a = clone.getAttributeNodeNS(attr.namespaceURI, tag)
|
||||||
|
else:
|
||||||
|
a = clone.getAttributeNodeNS(attr.namespaceURI, attr.nodeName)
|
||||||
|
a.specified = attr.specified
|
||||||
|
|
||||||
|
if deep:
|
||||||
|
for child in node.childNodes:
|
||||||
|
c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
|
||||||
|
clone.appendChild(c)
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_FRAGMENT_NODE:
|
||||||
|
clone = newOwnerDocument.createDocumentFragment()
|
||||||
|
if deep:
|
||||||
|
for child in node.childNodes:
|
||||||
|
c = xml.dom.minidom._clone_node(child, deep, newOwnerDocument)
|
||||||
|
clone.appendChild(c)
|
||||||
|
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
|
||||||
|
clone = newOwnerDocument.createTextNode(node.data)
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.CDATA_SECTION_NODE:
|
||||||
|
clone = newOwnerDocument.createCDATASection(node.data)
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.PROCESSING_INSTRUCTION_NODE:
|
||||||
|
clone = newOwnerDocument.createProcessingInstruction(node.target,
|
||||||
|
node.data)
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
|
||||||
|
clone = newOwnerDocument.createComment(node.data)
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.ATTRIBUTE_NODE:
|
||||||
|
clone = newOwnerDocument.createAttributeNS(node.namespaceURI,
|
||||||
|
node.nodeName)
|
||||||
|
clone.specified = True
|
||||||
|
clone.value = node.value
|
||||||
|
elif node.nodeType == xml.dom.minidom.Node.DOCUMENT_TYPE_NODE:
|
||||||
|
assert node.ownerDocument is not newOwnerDocument
|
||||||
|
operation = xml.dom.UserDataHandler.NODE_IMPORTED
|
||||||
|
clone = newOwnerDocument.implementation.createDocumentType(
|
||||||
|
node.name, node.publicId, node.systemId)
|
||||||
|
clone.ownerDocument = newOwnerDocument
|
||||||
|
if deep:
|
||||||
|
clone.entities._seq = []
|
||||||
|
clone.notations._seq = []
|
||||||
|
for n in node.notations._seq:
|
||||||
|
notation = xml.dom.minidom.Notation(n.nodeName, n.publicId, n.systemId)
|
||||||
|
notation.ownerDocument = newOwnerDocument
|
||||||
|
clone.notations._seq.append(notation)
|
||||||
|
if hasattr(n, '_call_user_data_handler'):
|
||||||
|
n._call_user_data_handler(operation, n, notation)
|
||||||
|
for e in node.entities._seq:
|
||||||
|
entity = xml.dom.minidom.Entity(e.nodeName, e.publicId, e.systemId,
|
||||||
|
e.notationName)
|
||||||
|
entity.actualEncoding = e.actualEncoding
|
||||||
|
entity.encoding = e.encoding
|
||||||
|
entity.version = e.version
|
||||||
|
entity.ownerDocument = newOwnerDocument
|
||||||
|
clone.entities._seq.append(entity)
|
||||||
|
if hasattr(e, '_call_user_data_handler'):
|
||||||
|
e._call_user_data_handler(operation, n, entity)
|
||||||
|
else:
|
||||||
|
# Note the cloning of Document and DocumentType nodes is
|
||||||
|
# implemenetation specific. minidom handles those cases
|
||||||
|
# directly in the cloneNode() methods.
|
||||||
|
raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node))
|
||||||
|
|
||||||
|
# Check for _call_user_data_handler() since this could conceivably
|
||||||
|
# used with other DOM implementations (one of the FourThought
|
||||||
|
# DOMs, perhaps?).
|
||||||
|
if hasattr(node, '_call_user_data_handler'):
|
||||||
|
node._call_user_data_handler(operation, node, clone)
|
||||||
|
return clone
|
||||||
|
|
||||||
|
xml.dom.minidom._clone_node = _clone_node
|
1336
others/SOAPpy/wstools/WSDLTools.py
Executable file
1336
others/SOAPpy/wstools/WSDLTools.py
Executable file
File diff suppressed because it is too large
Load Diff
2976
others/SOAPpy/wstools/XMLSchema.py
Executable file
2976
others/SOAPpy/wstools/XMLSchema.py
Executable file
File diff suppressed because it is too large
Load Diff
88
others/SOAPpy/wstools/XMLname.py
Executable file
88
others/SOAPpy/wstools/XMLname.py
Executable file
@ -0,0 +1,88 @@
|
|||||||
|
"""Translate strings to and from SOAP 1.2 XML name encoding
|
||||||
|
|
||||||
|
Implements rules for mapping application defined name to XML names
|
||||||
|
specified by the w3 SOAP working group for SOAP version 1.2 in
|
||||||
|
Appendix A of "SOAP Version 1.2 Part 2: Adjuncts", W3C Working Draft
|
||||||
|
17, December 2001, <http://www.w3.org/TR/soap12-part2/#namemap>
|
||||||
|
|
||||||
|
Also see <http://www.w3.org/2000/xp/Group/xmlp-issues>.
|
||||||
|
|
||||||
|
Author: Gregory R. Warnes <gregory_r_warnes@groton.pfizer.com>
|
||||||
|
Date:: 2002-04-25
|
||||||
|
Version 0.9.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
ident = "$Id$"
|
||||||
|
|
||||||
|
from re import *
|
||||||
|
|
||||||
|
|
||||||
|
def _NCNameChar(x):
|
||||||
|
return x.isalpha() or x.isdigit() or x=="." or x=='-' or x=="_"
|
||||||
|
|
||||||
|
|
||||||
|
def _NCNameStartChar(x):
|
||||||
|
return x.isalpha() or x=="_"
|
||||||
|
|
||||||
|
|
||||||
|
def _toUnicodeHex(x):
|
||||||
|
hexval = hex(ord(x[0]))[2:]
|
||||||
|
hexlen = len(hexval)
|
||||||
|
# Make hexval have either 4 or 8 digits by prepending 0's
|
||||||
|
if (hexlen==1): hexval = "000" + hexval
|
||||||
|
elif (hexlen==2): hexval = "00" + hexval
|
||||||
|
elif (hexlen==3): hexval = "0" + hexval
|
||||||
|
elif (hexlen==4): hexval = "" + hexval
|
||||||
|
elif (hexlen==5): hexval = "000" + hexval
|
||||||
|
elif (hexlen==6): hexval = "00" + hexval
|
||||||
|
elif (hexlen==7): hexval = "0" + hexval
|
||||||
|
elif (hexlen==8): hexval = "" + hexval
|
||||||
|
else: raise Exception, "Illegal Value returned from hex(ord(x))"
|
||||||
|
|
||||||
|
return "_x"+ hexval + "_"
|
||||||
|
|
||||||
|
|
||||||
|
def _fromUnicodeHex(x):
|
||||||
|
return eval( r'u"\u'+x[2:-1]+'"' )
|
||||||
|
|
||||||
|
|
||||||
|
def toXMLname(string):
|
||||||
|
"""Convert string to a XML name."""
|
||||||
|
if string.find(':') != -1 :
|
||||||
|
(prefix, localname) = string.split(':',1)
|
||||||
|
else:
|
||||||
|
prefix = None
|
||||||
|
localname = string
|
||||||
|
|
||||||
|
T = unicode(localname)
|
||||||
|
|
||||||
|
N = len(localname)
|
||||||
|
X = [];
|
||||||
|
for i in range(N) :
|
||||||
|
if i< N-1 and T[i]==u'_' and T[i+1]==u'x':
|
||||||
|
X.append(u'_x005F_')
|
||||||
|
elif i==0 and N >= 3 and \
|
||||||
|
( T[0]==u'x' or T[0]==u'X' ) and \
|
||||||
|
( T[1]==u'm' or T[1]==u'M' ) and \
|
||||||
|
( T[2]==u'l' or T[2]==u'L' ):
|
||||||
|
X.append(u'_xFFFF_' + T[0])
|
||||||
|
elif (not _NCNameChar(T[i])) or (i==0 and not _NCNameStartChar(T[i])):
|
||||||
|
X.append(_toUnicodeHex(T[i]))
|
||||||
|
else:
|
||||||
|
X.append(T[i])
|
||||||
|
|
||||||
|
return u''.join(X)
|
||||||
|
|
||||||
|
|
||||||
|
def fromXMLname(string):
|
||||||
|
"""Convert XML name to unicode string."""
|
||||||
|
|
||||||
|
retval = sub(r'_xFFFF_','', string )
|
||||||
|
|
||||||
|
def fun( matchobj ):
|
||||||
|
return _fromUnicodeHex( matchobj.group(0) )
|
||||||
|
|
||||||
|
retval = sub(r'_x[0-9A-Za-z]+_', fun, retval )
|
||||||
|
|
||||||
|
return retval
|
35
others/SOAPpy/wstools/__init__.py
Normal file
35
others/SOAPpy/wstools/__init__.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"""WSDL parsing services package for Web Services for Python."""
|
||||||
|
|
||||||
|
ident = "$Id$"
|
||||||
|
|
||||||
|
import WSDLTools
|
||||||
|
import XMLname
|
||||||
|
from logging import getLogger as _getLogger
|
||||||
|
import logging.config as _config
|
||||||
|
|
||||||
|
LOGGING = 'logging.txt'
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
#
|
||||||
|
# If LOGGING configuration file is not found, turn off logging
|
||||||
|
# and use _noLogger class because logging module's performance
|
||||||
|
# is terrible.
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
_config.fileConfig(LOGGING)
|
||||||
|
except:
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
|
||||||
|
class Base:
|
||||||
|
def __init__(self, module=__name__):
|
||||||
|
self.logger = _noLogger()
|
||||||
|
if DEBUG is True:
|
||||||
|
self.logger = _getLogger('%s-%s(%x)' %(module, self.__class__, id(self)))
|
||||||
|
|
||||||
|
class _noLogger:
|
||||||
|
def __init__(self, *args): pass
|
||||||
|
def warning(self, *args): pass
|
||||||
|
def debug(self, *args): pass
|
||||||
|
def error(self, *args): pass
|
0
others/__init__.py
Normal file
0
others/__init__.py
Normal file
364
others/amazon.py
Normal file
364
others/amazon.py
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
"""Python wrapper
|
||||||
|
|
||||||
|
|
||||||
|
for Amazon web APIs
|
||||||
|
|
||||||
|
This module allows you to access Amazon's web APIs,
|
||||||
|
to do things like search Amazon and get the results programmatically.
|
||||||
|
Described here:
|
||||||
|
http://www.amazon.com/webservices
|
||||||
|
|
||||||
|
You need a Amazon-provided license key to use these services.
|
||||||
|
Follow the link above to get one. These functions will look in
|
||||||
|
several places (in this order) for the license key:
|
||||||
|
- the "license_key" argument of each function
|
||||||
|
- the module-level LICENSE_KEY variable (call setLicense once to set it)
|
||||||
|
- an environment variable called AMAZON_LICENSE_KEY
|
||||||
|
- a file called ".amazonkey" in the current directory
|
||||||
|
- a file called "amazonkey.txt" in the current directory
|
||||||
|
- a file called ".amazonkey" in your home directory
|
||||||
|
- a file called "amazonkey.txt" in your home directory
|
||||||
|
- a file called ".amazonkey" in the same directory as amazon.py
|
||||||
|
- a file called "amazonkey.txt" in the same directory as amazon.py
|
||||||
|
|
||||||
|
Sample usage:
|
||||||
|
>>> import amazon
|
||||||
|
>>> amazon.setLicense('...') # must get your own key!
|
||||||
|
>>> pythonBooks = amazon.searchByKeyword('Python')
|
||||||
|
>>> pythonBooks[0].ProductName
|
||||||
|
u'Learning Python (Help for Programmers)'
|
||||||
|
>>> pythonBooks[0].URL
|
||||||
|
...
|
||||||
|
>>> pythonBooks[0].OurPrice
|
||||||
|
...
|
||||||
|
|
||||||
|
Other available functions:
|
||||||
|
- browseBestSellers
|
||||||
|
- searchByASIN
|
||||||
|
- searchByUPC
|
||||||
|
- searchByAuthor
|
||||||
|
- searchByArtist
|
||||||
|
- searchByActor
|
||||||
|
- searchByDirector
|
||||||
|
- searchByManufacturer
|
||||||
|
- searchByListMania
|
||||||
|
- searchSimilar
|
||||||
|
- searchByWishlist
|
||||||
|
|
||||||
|
Other usage notes:
|
||||||
|
- Most functions can take product_line as well, see source for possible values
|
||||||
|
- All functions can take type="lite" to get less detail in results
|
||||||
|
- All functions can take page=N to get second, third, fourth page of results
|
||||||
|
- All functions can take license_key="XYZ", instead of setting it globally
|
||||||
|
- All functions can take http_proxy="http://x/y/z" which overrides your system setting
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
|
||||||
|
__version__ = "0.64.1"
|
||||||
|
__cvsversion__ = "$Revision$"[11:-2]
|
||||||
|
__date__ = "$Date$"[7:-2]
|
||||||
|
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
|
||||||
|
__license__ = "Python"
|
||||||
|
# Powersearch and return object type fix by Joseph Reagle <geek@goatee.net>
|
||||||
|
|
||||||
|
# Locale support by Michael Josephson <mike@josephson.org>
|
||||||
|
|
||||||
|
# Modification to _contentsOf to strip trailing whitespace when loading Amazon key
|
||||||
|
# from a file submitted by Patrick Phalen.
|
||||||
|
|
||||||
|
# Support for specifying locale and associates ID as search parameters and
|
||||||
|
# internationalisation fix for the SalesRank integer conversion by
|
||||||
|
# Christian Theune <ct@gocept.com>, gocept gmbh & co. kg
|
||||||
|
|
||||||
|
# Support for BlendedSearch contributed by Alex Choo
|
||||||
|
|
||||||
|
from xml.dom import minidom
|
||||||
|
import os, sys, getopt, cgi, urllib, string
|
||||||
|
try:
|
||||||
|
import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py
|
||||||
|
timeoutsocket.setDefaultSocketTimeout(10)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
LICENSE_KEY = None
|
||||||
|
ASSOCIATE = "webservices-20"
|
||||||
|
HTTP_PROXY = None
|
||||||
|
LOCALE = "us"
|
||||||
|
|
||||||
|
# don't touch the rest of these constants
|
||||||
|
class AmazonError(Exception): pass
|
||||||
|
class NoLicenseKey(Exception): pass
|
||||||
|
_amazonfile1 = ".amazonkey"
|
||||||
|
_amazonfile2 = "amazonkey.txt"
|
||||||
|
_licenseLocations = (
|
||||||
|
(lambda key: key, 'passed to the function in license_key variable'),
|
||||||
|
(lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'),
|
||||||
|
(lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'),
|
||||||
|
(lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1),
|
||||||
|
(lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2),
|
||||||
|
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1),
|
||||||
|
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2),
|
||||||
|
(lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1),
|
||||||
|
(lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2)
|
||||||
|
)
|
||||||
|
_supportedLocales = {
|
||||||
|
"us" : (None, "xml.amazon.com"),
|
||||||
|
"uk" : ("uk", "xml-eu.amazon.com"),
|
||||||
|
"de" : ("de", "xml-eu.amazon.com"),
|
||||||
|
"jp" : ("jp", "xml.amazon.co.jp")
|
||||||
|
}
|
||||||
|
|
||||||
|
## administrative functions
|
||||||
|
def version():
|
||||||
|
print """PyAmazon %(__version__)s
|
||||||
|
%(__copyright__)s
|
||||||
|
released %(__date__)s
|
||||||
|
""" % globals()
|
||||||
|
|
||||||
|
def setAssociate(associate):
|
||||||
|
global ASSOCIATE
|
||||||
|
ASSOCIATE=associate
|
||||||
|
|
||||||
|
def getAssociate(override=None):
|
||||||
|
return override or ASSOCIATE
|
||||||
|
|
||||||
|
## utility functions
|
||||||
|
|
||||||
|
def _checkLocaleSupported(locale):
|
||||||
|
if not _supportedLocales.has_key(locale):
|
||||||
|
raise AmazonError, ("Unsupported locale. Locale must be one of: %s" %
|
||||||
|
string.join(_supportedLocales, ", "))
|
||||||
|
|
||||||
|
def setLocale(locale):
|
||||||
|
"""set locale"""
|
||||||
|
global LOCALE
|
||||||
|
_checkLocaleSupported(locale)
|
||||||
|
LOCALE = locale
|
||||||
|
|
||||||
|
def getLocale(locale=None):
|
||||||
|
"""get locale"""
|
||||||
|
return locale or LOCALE
|
||||||
|
|
||||||
|
def setLicense(license_key):
|
||||||
|
"""set license key"""
|
||||||
|
global LICENSE_KEY
|
||||||
|
LICENSE_KEY = license_key
|
||||||
|
|
||||||
|
def getLicense(license_key = None):
|
||||||
|
"""get license key
|
||||||
|
|
||||||
|
license key can come from any number of locations;
|
||||||
|
see module docs for search order"""
|
||||||
|
for get, location in _licenseLocations:
|
||||||
|
rc = get(license_key)
|
||||||
|
if rc: return rc
|
||||||
|
raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices'
|
||||||
|
|
||||||
|
def setProxy(http_proxy):
|
||||||
|
"""set HTTP proxy"""
|
||||||
|
global HTTP_PROXY
|
||||||
|
HTTP_PROXY = http_proxy
|
||||||
|
|
||||||
|
def getProxy(http_proxy = None):
|
||||||
|
"""get HTTP proxy"""
|
||||||
|
return http_proxy or HTTP_PROXY
|
||||||
|
|
||||||
|
def getProxies(http_proxy = None):
|
||||||
|
http_proxy = getProxy(http_proxy)
|
||||||
|
if http_proxy:
|
||||||
|
proxies = {"http": http_proxy}
|
||||||
|
else:
|
||||||
|
proxies = None
|
||||||
|
return proxies
|
||||||
|
|
||||||
|
def _contentsOf(dirname, filename):
|
||||||
|
filename = os.path.join(dirname, filename)
|
||||||
|
if not os.path.exists(filename): return None
|
||||||
|
fsock = open(filename)
|
||||||
|
contents = fsock.read().strip()
|
||||||
|
fsock.close()
|
||||||
|
return contents
|
||||||
|
|
||||||
|
def _getScriptDir():
|
||||||
|
if __name__ == '__main__':
|
||||||
|
return os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||||
|
else:
|
||||||
|
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
|
||||||
|
|
||||||
|
class Bag: pass
|
||||||
|
|
||||||
|
def unmarshal(element):
|
||||||
|
rc = Bag()
|
||||||
|
if isinstance(element, minidom.Element) and (element.tagName == 'Details'):
|
||||||
|
rc.URL = element.attributes["url"].value
|
||||||
|
childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)]
|
||||||
|
if childElements:
|
||||||
|
for child in childElements:
|
||||||
|
key = child.tagName
|
||||||
|
if hasattr(rc, key):
|
||||||
|
if type(getattr(rc, key)) <> type([]):
|
||||||
|
setattr(rc, key, [getattr(rc, key)])
|
||||||
|
setattr(rc, key, getattr(rc, key) + [unmarshal(child)])
|
||||||
|
elif isinstance(child, minidom.Element) and (child.tagName == 'Details'):
|
||||||
|
# make the first Details element a key
|
||||||
|
setattr(rc,key,[unmarshal(child)])
|
||||||
|
#dbg: because otherwise 'hasattr' only tests
|
||||||
|
#dbg: on the second occurence: if there's a
|
||||||
|
#dbg: single return to a query, it's not a
|
||||||
|
#dbg: list. This module should always
|
||||||
|
#dbg: return a list of Details objects.
|
||||||
|
else:
|
||||||
|
setattr(rc, key, unmarshal(child))
|
||||||
|
else:
|
||||||
|
rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)])
|
||||||
|
if element.tagName == 'SalesRank':
|
||||||
|
rc = rc.replace('.', '')
|
||||||
|
rc = rc.replace(',', '')
|
||||||
|
rc = int(rc)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def buildURL(search_type, keyword, product_line, type, page, license_key, locale, associate):
|
||||||
|
_checkLocaleSupported(locale)
|
||||||
|
url = "http://" + _supportedLocales[locale][1] + "/onca/xml3?f=xml"
|
||||||
|
url += "&t=%s" % associate
|
||||||
|
url += "&dev-t=%s" % license_key.strip()
|
||||||
|
url += "&type=%s" % type
|
||||||
|
if _supportedLocales[locale][0]:
|
||||||
|
url += "&locale=%s" % _supportedLocales[locale][0]
|
||||||
|
if page:
|
||||||
|
url += "&page=%s" % page
|
||||||
|
if product_line:
|
||||||
|
url += "&mode=%s" % product_line
|
||||||
|
url += "&%s=%s" % (search_type, urllib.quote(keyword))
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
## main functions
|
||||||
|
|
||||||
|
|
||||||
|
def search(search_type, keyword, product_line, type = "heavy", page = None,
|
||||||
|
license_key=None, http_proxy = None, locale = None, associate = None):
|
||||||
|
"""search Amazon
|
||||||
|
|
||||||
|
You need a license key to call this function; see
|
||||||
|
http://www.amazon.com/webservices
|
||||||
|
to get one. Then you can either pass it to
|
||||||
|
this function every time, or set it globally; see the module docs for details.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
keyword - keyword to search
|
||||||
|
search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch)
|
||||||
|
product_line - type of product to search for. restrictions based on search_type
|
||||||
|
UpcSearch - in (music, classical)
|
||||||
|
AuthorSearch - must be "books"
|
||||||
|
ArtistSearch - in (music, classical)
|
||||||
|
ActorSearch - in (dvd, vhs, video)
|
||||||
|
DirectorSearch - in (dvd, vhs, video)
|
||||||
|
ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware)
|
||||||
|
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
|
||||||
|
|
||||||
|
Returns: list of Bags, each Bag may contain the following attributes:
|
||||||
|
Asin - Amazon ID ("ASIN" number) of this item
|
||||||
|
Authors - list of authors
|
||||||
|
Availability - "available", etc.
|
||||||
|
BrowseList - list of related categories
|
||||||
|
Catalog - catalog type ("Book", etc)
|
||||||
|
CollectiblePrice - ?, format "$34.95"
|
||||||
|
ImageUrlLarge - URL of large image of this item
|
||||||
|
ImageUrlMedium - URL of medium image of this item
|
||||||
|
ImageUrlSmall - URL of small image of this item
|
||||||
|
Isbn - ISBN number
|
||||||
|
ListPrice - list price, format "$34.95"
|
||||||
|
Lists - list of ListMania lists that include this item
|
||||||
|
Manufacturer - manufacturer
|
||||||
|
Media - media ("Paperback", "Audio CD", etc)
|
||||||
|
NumMedia - number of different media types in which this item is available
|
||||||
|
OurPrice - Amazon price, format "$24.47"
|
||||||
|
ProductName - name of this item
|
||||||
|
ReleaseDate - release date, format "09 April, 1999"
|
||||||
|
Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content)
|
||||||
|
SalesRank - sales rank (integer)
|
||||||
|
SimilarProducts - list of Product, which is ASIN number
|
||||||
|
ThirdPartyNewPrice - ?, format "$34.95"
|
||||||
|
URL - URL of this item
|
||||||
|
"""
|
||||||
|
license_key = getLicense(license_key)
|
||||||
|
locale = getLocale(locale)
|
||||||
|
associate = getAssociate(associate)
|
||||||
|
url = buildURL(search_type, keyword, product_line, type, page,
|
||||||
|
license_key, locale, associate)
|
||||||
|
proxies = getProxies(http_proxy)
|
||||||
|
u = urllib.FancyURLopener(proxies)
|
||||||
|
usock = u.open(url)
|
||||||
|
xmldoc = minidom.parse(usock)
|
||||||
|
|
||||||
|
# from xml.dom.ext import PrettyPrint
|
||||||
|
# PrettyPrint(xmldoc)
|
||||||
|
|
||||||
|
usock.close()
|
||||||
|
if search_type == "BlendedSearch":
|
||||||
|
data = unmarshal(xmldoc).BlendedSearch
|
||||||
|
else:
|
||||||
|
data = unmarshal(xmldoc).ProductInfo
|
||||||
|
|
||||||
|
if hasattr(data, 'ErrorMsg'):
|
||||||
|
raise AmazonError, data.ErrorMsg
|
||||||
|
else:
|
||||||
|
if search_type == "BlendedSearch":
|
||||||
|
# a list of ProductLine containing a list of ProductInfo
|
||||||
|
# containing a list of Details.
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
return data.Details
|
||||||
|
|
||||||
|
def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("UpcSearch", UPC, None, type, None, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("AuthorSearch", author, "books", type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
if product_line not in ("music", "classical"):
|
||||||
|
raise AmazonError, "product_line must be in ('music', 'classical')"
|
||||||
|
return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
if product_line not in ("dvd", "vhs", "video"):
|
||||||
|
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
|
||||||
|
return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
if product_line not in ("dvd", "vhs", "video"):
|
||||||
|
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
|
||||||
|
return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"):
|
||||||
|
raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')"
|
||||||
|
return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByWishlist(wishlistID, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("WishlistSearch", wishlistID, None, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
|
||||||
|
def searchByPower(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("PowerSearch", keyword, product_line, type, page, license_key, http_proxy, locale, associate)
|
||||||
|
# >>> RecentKing = amazon.searchByPower('author:Stephen King and pubdate:2003')
|
||||||
|
# >>> SnowCrash = amazon.searchByPower('title:Snow Crash')
|
||||||
|
|
||||||
|
def searchByBlended(keyword, type="heavy", page=1, license_key=None, http_proxy=None, locale=None, associate=None):
|
||||||
|
return search("BlendedSearch", keyword, None, type, page, license_key, http_proxy, locale, associate)
|
294
others/asynchat.py
Normal file
294
others/asynchat.py
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
# -*- Mode: Python; tab-width: 4 -*-
|
||||||
|
# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
|
||||||
|
# Author: Sam Rushing <rushing@nightmare.com>
|
||||||
|
|
||||||
|
# ======================================================================
|
||||||
|
# Copyright 1996 by Sam Rushing
|
||||||
|
#
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this software and
|
||||||
|
# its documentation for any purpose and without fee is hereby
|
||||||
|
# granted, provided that the above copyright notice appear in all
|
||||||
|
# copies and that both that copyright notice and this permission
|
||||||
|
# notice appear in supporting documentation, and that the name of Sam
|
||||||
|
# Rushing not be used in advertising or publicity pertaining to
|
||||||
|
# distribution of the software without specific, written prior
|
||||||
|
# permission.
|
||||||
|
#
|
||||||
|
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||||
|
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||||
|
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||||
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
# ======================================================================
|
||||||
|
|
||||||
|
r"""A class supporting chat-style (command/response) protocols.
|
||||||
|
|
||||||
|
This class adds support for 'chat' style protocols - where one side
|
||||||
|
sends a 'command', and the other sends a response (examples would be
|
||||||
|
the common internet protocols - smtp, nntp, ftp, etc..).
|
||||||
|
|
||||||
|
The handle_read() method looks at the input stream for the current
|
||||||
|
'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
|
||||||
|
for multi-line output), calling self.found_terminator() on its
|
||||||
|
receipt.
|
||||||
|
|
||||||
|
for example:
|
||||||
|
Say you build an async nntp client using this class. At the start
|
||||||
|
of the connection, you'll have self.terminator set to '\r\n', in
|
||||||
|
order to process the single-line greeting. Just before issuing a
|
||||||
|
'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
|
||||||
|
command will be accumulated (using your own 'collect_incoming_data'
|
||||||
|
method) up to the terminator, and then control will be returned to
|
||||||
|
you - by calling your self.found_terminator() method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import asyncore
|
||||||
|
|
||||||
|
class async_chat (asyncore.dispatcher):
|
||||||
|
"""This is an abstract class. You must derive from this class, and add
|
||||||
|
the two methods collect_incoming_data() and found_terminator()"""
|
||||||
|
|
||||||
|
# these are overridable defaults
|
||||||
|
|
||||||
|
ac_in_buffer_size = 4096
|
||||||
|
ac_out_buffer_size = 4096
|
||||||
|
|
||||||
|
def __init__ (self, conn=None):
|
||||||
|
self.ac_in_buffer = ''
|
||||||
|
self.ac_out_buffer = ''
|
||||||
|
self.producer_fifo = fifo()
|
||||||
|
asyncore.dispatcher.__init__ (self, conn)
|
||||||
|
|
||||||
|
def set_terminator (self, term):
|
||||||
|
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
|
||||||
|
self.terminator = term
|
||||||
|
|
||||||
|
def get_terminator (self):
|
||||||
|
return self.terminator
|
||||||
|
|
||||||
|
# grab some more data from the socket,
|
||||||
|
# throw it to the collector method,
|
||||||
|
# check for the terminator,
|
||||||
|
# if found, transition to the next state.
|
||||||
|
|
||||||
|
def handle_read (self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.recv (self.ac_in_buffer_size)
|
||||||
|
except socket.error, _:
|
||||||
|
self.handle_error()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ac_in_buffer = self.ac_in_buffer + data
|
||||||
|
|
||||||
|
# Continue to search for self.terminator in self.ac_in_buffer,
|
||||||
|
# while calling self.collect_incoming_data. The while loop
|
||||||
|
# is necessary because we might read several data+terminator
|
||||||
|
# combos with a single recv(1024).
|
||||||
|
|
||||||
|
while self.ac_in_buffer:
|
||||||
|
lb = len(self.ac_in_buffer)
|
||||||
|
terminator = self.get_terminator()
|
||||||
|
if terminator is None:
|
||||||
|
# no terminator, collect it all
|
||||||
|
self.collect_incoming_data (self.ac_in_buffer)
|
||||||
|
self.ac_in_buffer = ''
|
||||||
|
elif type(terminator) == type(0):
|
||||||
|
# numeric terminator
|
||||||
|
n = terminator
|
||||||
|
if lb < n:
|
||||||
|
self.collect_incoming_data (self.ac_in_buffer)
|
||||||
|
self.ac_in_buffer = ''
|
||||||
|
self.terminator = self.terminator - lb
|
||||||
|
else:
|
||||||
|
self.collect_incoming_data (self.ac_in_buffer[:n])
|
||||||
|
self.ac_in_buffer = self.ac_in_buffer[n:]
|
||||||
|
self.terminator = 0
|
||||||
|
self.found_terminator()
|
||||||
|
else:
|
||||||
|
# 3 cases:
|
||||||
|
# 1) end of buffer matches terminator exactly:
|
||||||
|
# collect data, transition
|
||||||
|
# 2) end of buffer matches some prefix:
|
||||||
|
# collect data to the prefix
|
||||||
|
# 3) end of buffer does not match any prefix:
|
||||||
|
# collect data
|
||||||
|
terminator_len = len(terminator)
|
||||||
|
index = self.ac_in_buffer.find(terminator)
|
||||||
|
if index != -1:
|
||||||
|
# we found the terminator
|
||||||
|
if index > 0:
|
||||||
|
# don't bother reporting the empty string (source of subtle bugs)
|
||||||
|
self.collect_incoming_data (self.ac_in_buffer[:index])
|
||||||
|
self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
|
||||||
|
# This does the Right Thing if the terminator is changed here.
|
||||||
|
self.found_terminator()
|
||||||
|
else:
|
||||||
|
# check for a prefix of the terminator
|
||||||
|
index = find_prefix_at_end (self.ac_in_buffer, terminator)
|
||||||
|
if index:
|
||||||
|
if index != lb:
|
||||||
|
# we found a prefix, collect up to the prefix
|
||||||
|
self.collect_incoming_data (self.ac_in_buffer[:-index])
|
||||||
|
self.ac_in_buffer = self.ac_in_buffer[-index:]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# no prefix, collect it all
|
||||||
|
self.collect_incoming_data (self.ac_in_buffer)
|
||||||
|
self.ac_in_buffer = ''
|
||||||
|
|
||||||
|
def handle_write (self):
|
||||||
|
self.initiate_send ()
|
||||||
|
|
||||||
|
def handle_close (self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def push (self, data):
|
||||||
|
self.producer_fifo.push (simple_producer (data))
|
||||||
|
self.initiate_send()
|
||||||
|
|
||||||
|
def push_with_producer (self, producer):
|
||||||
|
self.producer_fifo.push (producer)
|
||||||
|
self.initiate_send()
|
||||||
|
|
||||||
|
def readable (self):
|
||||||
|
"predicate for inclusion in the readable for select()"
|
||||||
|
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
|
||||||
|
|
||||||
|
def writable (self):
|
||||||
|
"predicate for inclusion in the writable for select()"
|
||||||
|
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
|
||||||
|
# this is about twice as fast, though not as clear.
|
||||||
|
return not (
|
||||||
|
(self.ac_out_buffer == '') and
|
||||||
|
self.producer_fifo.is_empty() and
|
||||||
|
self.connected
|
||||||
|
)
|
||||||
|
|
||||||
|
def close_when_done (self):
|
||||||
|
"automatically close this channel once the outgoing queue is empty"
|
||||||
|
self.producer_fifo.push (None)
|
||||||
|
|
||||||
|
# refill the outgoing buffer by calling the more() method
|
||||||
|
# of the first producer in the queue
|
||||||
|
def refill_buffer (self):
|
||||||
|
_string_type = type('')
|
||||||
|
while 1:
|
||||||
|
if len(self.producer_fifo):
|
||||||
|
p = self.producer_fifo.first()
|
||||||
|
# a 'None' in the producer fifo is a sentinel,
|
||||||
|
# telling us to close the channel.
|
||||||
|
if p is None:
|
||||||
|
if not self.ac_out_buffer:
|
||||||
|
self.producer_fifo.pop()
|
||||||
|
self.close()
|
||||||
|
return
|
||||||
|
elif type(p) is _string_type:
|
||||||
|
self.producer_fifo.pop()
|
||||||
|
self.ac_out_buffer = self.ac_out_buffer + p
|
||||||
|
return
|
||||||
|
data = p.more()
|
||||||
|
if data:
|
||||||
|
self.ac_out_buffer = self.ac_out_buffer + data
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.producer_fifo.pop()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
def initiate_send (self):
|
||||||
|
obs = self.ac_out_buffer_size
|
||||||
|
# try to refill the buffer
|
||||||
|
if (len (self.ac_out_buffer) < obs):
|
||||||
|
self.refill_buffer()
|
||||||
|
|
||||||
|
if self.ac_out_buffer and self.connected:
|
||||||
|
# try to send the buffer
|
||||||
|
try:
|
||||||
|
num_sent = self.send (self.ac_out_buffer[:obs])
|
||||||
|
if num_sent:
|
||||||
|
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
|
||||||
|
|
||||||
|
except socket.error, _:
|
||||||
|
self.handle_error()
|
||||||
|
return
|
||||||
|
|
||||||
|
def discard_buffers (self):
|
||||||
|
# Emergencies only!
|
||||||
|
self.ac_in_buffer = ''
|
||||||
|
self.ac_out_buffer = ''
|
||||||
|
while self.producer_fifo:
|
||||||
|
self.producer_fifo.pop()
|
||||||
|
|
||||||
|
|
||||||
|
class simple_producer:
|
||||||
|
|
||||||
|
def __init__ (self, data, buffer_size=512):
|
||||||
|
self.data = data
|
||||||
|
self.buffer_size = buffer_size
|
||||||
|
|
||||||
|
def more (self):
|
||||||
|
if len (self.data) > self.buffer_size:
|
||||||
|
result = self.data[:self.buffer_size]
|
||||||
|
self.data = self.data[self.buffer_size:]
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
result = self.data
|
||||||
|
self.data = ''
|
||||||
|
return result
|
||||||
|
|
||||||
|
class fifo:
|
||||||
|
def __init__ (self, list=None):
|
||||||
|
if not list:
|
||||||
|
self.list = []
|
||||||
|
else:
|
||||||
|
self.list = list
|
||||||
|
|
||||||
|
def __len__ (self):
|
||||||
|
return len(self.list)
|
||||||
|
|
||||||
|
def is_empty (self):
|
||||||
|
return self.list == []
|
||||||
|
|
||||||
|
def first (self):
|
||||||
|
return self.list[0]
|
||||||
|
|
||||||
|
def push (self, data):
|
||||||
|
self.list.append (data)
|
||||||
|
|
||||||
|
def pop (self):
|
||||||
|
if self.list:
|
||||||
|
result = self.list[0]
|
||||||
|
del self.list[0]
|
||||||
|
return (1, result)
|
||||||
|
else:
|
||||||
|
return (0, None)
|
||||||
|
|
||||||
|
# Given 'haystack', see if any prefix of 'needle' is at its end. This
|
||||||
|
# assumes an exact match has already been checked. Return the number of
|
||||||
|
# characters matched.
|
||||||
|
# for example:
|
||||||
|
# f_p_a_e ("qwerty\r", "\r\n") => 1
|
||||||
|
# f_p_a_e ("qwerty\r\n", "\r\n") => 2
|
||||||
|
# f_p_a_e ("qwertydkjf", "\r\n") => 0
|
||||||
|
|
||||||
|
# this could maybe be made faster with a computed regex?
|
||||||
|
# [answer: no; circa Python-2.0, Jan 2001]
|
||||||
|
# python: 18307/s
|
||||||
|
# re: 12820/s
|
||||||
|
# regex: 14035/s
|
||||||
|
|
||||||
|
def find_prefix_at_end (haystack, needle):
|
||||||
|
nl = len(needle)
|
||||||
|
result = 0
|
||||||
|
for i in range (1,nl):
|
||||||
|
if haystack[-(nl-i):] == needle[:(nl-i)]:
|
||||||
|
result = nl-i
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
494
others/asyncore.py
Normal file
494
others/asyncore.py
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
# -*- Mode: Python -*-
|
||||||
|
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
|
||||||
|
# Author: Sam Rushing <rushing@nightmare.com>
|
||||||
|
|
||||||
|
# ======================================================================
|
||||||
|
# Copyright 1996 by Sam Rushing
|
||||||
|
#
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and distribute this software and
|
||||||
|
# its documentation for any purpose and without fee is hereby
|
||||||
|
# granted, provided that the above copyright notice appear in all
|
||||||
|
# copies and that both that copyright notice and this permission
|
||||||
|
# notice appear in supporting documentation, and that the name of Sam
|
||||||
|
# Rushing not be used in advertising or publicity pertaining to
|
||||||
|
# distribution of the software without specific, written prior
|
||||||
|
# permission.
|
||||||
|
#
|
||||||
|
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||||
|
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
|
||||||
|
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
||||||
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
# ======================================================================
|
||||||
|
|
||||||
|
"""Basic infrastructure for asynchronous socket service clients and servers.
|
||||||
|
|
||||||
|
There are only two ways to have a program on a single processor do "more
|
||||||
|
than one thing at a time". Multi-threaded programming is the simplest and
|
||||||
|
most popular way to do it, but there is another very different technique,
|
||||||
|
that lets you have nearly all the advantages of multi-threading, without
|
||||||
|
actually using multiple threads. it's really only practical if your program
|
||||||
|
is largely I/O bound. If your program is CPU bound, then pre-emptive
|
||||||
|
scheduled threads are probably what you really need. Network servers are
|
||||||
|
rarely CPU-bound, however.
|
||||||
|
|
||||||
|
If your operating system supports the select() system call in its I/O
|
||||||
|
library (and nearly all do), then you can use it to juggle multiple
|
||||||
|
communication channels at once; doing other work while your I/O is taking
|
||||||
|
place in the "background." Although this strategy can seem strange and
|
||||||
|
complex, especially at first, it is in many ways easier to understand and
|
||||||
|
control than multi-threaded programming. The module documented here solves
|
||||||
|
many of the difficult problems for you, making the task of building
|
||||||
|
sophisticated high-performance network servers and clients a snap.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import exceptions
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import os
|
||||||
|
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
|
||||||
|
ENOTCONN, ESHUTDOWN, EINTR, EISCONN
|
||||||
|
|
||||||
|
try:
|
||||||
|
socket_map
|
||||||
|
except NameError:
|
||||||
|
socket_map = {}
|
||||||
|
|
||||||
|
class ExitNow(exceptions.Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(obj):
|
||||||
|
try:
|
||||||
|
obj.handle_read_event()
|
||||||
|
except ExitNow:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
obj.handle_error()
|
||||||
|
|
||||||
|
def write(obj):
|
||||||
|
try:
|
||||||
|
obj.handle_write_event()
|
||||||
|
except ExitNow:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
obj.handle_error()
|
||||||
|
|
||||||
|
def readwrite(obj, flags):
|
||||||
|
try:
|
||||||
|
if flags & select.POLLIN:
|
||||||
|
obj.handle_read_event()
|
||||||
|
if flags & select.POLLOUT:
|
||||||
|
obj.handle_write_event()
|
||||||
|
except ExitNow:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
obj.handle_error()
|
||||||
|
|
||||||
|
def poll(timeout=0.0, map=None):
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
if map:
|
||||||
|
r = []; w = []; e = []
|
||||||
|
for fd, obj in map.items():
|
||||||
|
if obj.readable():
|
||||||
|
r.append(fd)
|
||||||
|
if obj.writable():
|
||||||
|
w.append(fd)
|
||||||
|
if [] == r == w == e:
|
||||||
|
time.sleep(timeout)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
r, w, e = select.select(r, w, e, timeout)
|
||||||
|
except select.error, err:
|
||||||
|
if err[0] != EINTR:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
for fd in r:
|
||||||
|
obj = map.get(fd)
|
||||||
|
if obj is None:
|
||||||
|
continue
|
||||||
|
read(obj)
|
||||||
|
|
||||||
|
for fd in w:
|
||||||
|
obj = map.get(fd)
|
||||||
|
if obj is None:
|
||||||
|
continue
|
||||||
|
write(obj)
|
||||||
|
|
||||||
|
def poll2(timeout=0.0, map=None):
|
||||||
|
import poll
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
if timeout is not None:
|
||||||
|
# timeout is in milliseconds
|
||||||
|
timeout = int(timeout*1000)
|
||||||
|
if map:
|
||||||
|
l = []
|
||||||
|
for fd, obj in map.items():
|
||||||
|
flags = 0
|
||||||
|
if obj.readable():
|
||||||
|
flags = poll.POLLIN
|
||||||
|
if obj.writable():
|
||||||
|
flags = flags | poll.POLLOUT
|
||||||
|
if flags:
|
||||||
|
l.append((fd, flags))
|
||||||
|
r = poll.poll(l, timeout)
|
||||||
|
for fd, flags in r:
|
||||||
|
obj = map.get(fd)
|
||||||
|
if obj is None:
|
||||||
|
continue
|
||||||
|
readwrite(obj, flags)
|
||||||
|
|
||||||
|
def poll3(timeout=0.0, map=None):
|
||||||
|
# Use the poll() support added to the select module in Python 2.0
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
if timeout is not None:
|
||||||
|
# timeout is in milliseconds
|
||||||
|
timeout = int(timeout*1000)
|
||||||
|
pollster = select.poll()
|
||||||
|
if map:
|
||||||
|
for fd, obj in map.items():
|
||||||
|
flags = 0
|
||||||
|
if obj.readable():
|
||||||
|
flags = select.POLLIN
|
||||||
|
if obj.writable():
|
||||||
|
flags = flags | select.POLLOUT
|
||||||
|
if flags:
|
||||||
|
pollster.register(fd, flags)
|
||||||
|
try:
|
||||||
|
r = pollster.poll(timeout)
|
||||||
|
except select.error, err:
|
||||||
|
if err[0] != EINTR:
|
||||||
|
raise
|
||||||
|
r = []
|
||||||
|
for fd, flags in r:
|
||||||
|
obj = map.get(fd)
|
||||||
|
if obj is None:
|
||||||
|
continue
|
||||||
|
readwrite(obj, flags)
|
||||||
|
|
||||||
|
def loop(timeout=30.0, use_poll=0, map=None):
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
|
||||||
|
if use_poll:
|
||||||
|
if hasattr(select, 'poll'):
|
||||||
|
poll_fun = poll3
|
||||||
|
else:
|
||||||
|
poll_fun = poll2
|
||||||
|
else:
|
||||||
|
poll_fun = poll
|
||||||
|
|
||||||
|
while map:
|
||||||
|
poll_fun(timeout, map)
|
||||||
|
|
||||||
|
class dispatcher:
|
||||||
|
|
||||||
|
debug = 0
|
||||||
|
connected = 0
|
||||||
|
accepting = 0
|
||||||
|
closing = 0
|
||||||
|
addr = None
|
||||||
|
|
||||||
|
def __init__(self, sock=None, map=None):
|
||||||
|
if sock:
|
||||||
|
self.set_socket(sock, map)
|
||||||
|
# I think it should inherit this anyway
|
||||||
|
self.socket.setblocking(0)
|
||||||
|
self.connected = 1
|
||||||
|
# XXX Does the constructor require that the socket passed
|
||||||
|
# be connected?
|
||||||
|
try:
|
||||||
|
self.addr = sock.getpeername()
|
||||||
|
except socket.error:
|
||||||
|
# The addr isn't crucial
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.socket = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
status = [self.__class__.__module__+"."+self.__class__.__name__]
|
||||||
|
if self.accepting and self.addr:
|
||||||
|
status.append('listening')
|
||||||
|
elif self.connected:
|
||||||
|
status.append('connected')
|
||||||
|
if self.addr is not None:
|
||||||
|
try:
|
||||||
|
status.append('%s:%d' % self.addr)
|
||||||
|
except TypeError:
|
||||||
|
status.append(repr(self.addr))
|
||||||
|
return '<%s at %#x>' % (' '.join(status), id(self))
|
||||||
|
|
||||||
|
def add_channel(self, map=None):
|
||||||
|
#self.log_info('adding channel %s' % self)
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
map[self._fileno] = self
|
||||||
|
|
||||||
|
def del_channel(self, map=None):
|
||||||
|
fd = self._fileno
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
if map.has_key(fd):
|
||||||
|
#self.log_info('closing channel %d:%s' % (fd, self))
|
||||||
|
del map[fd]
|
||||||
|
|
||||||
|
def create_socket(self, family, type):
|
||||||
|
self.family_and_type = family, type
|
||||||
|
self.socket = socket.socket(family, type)
|
||||||
|
self.socket.setblocking(0)
|
||||||
|
self._fileno = self.socket.fileno()
|
||||||
|
self.add_channel()
|
||||||
|
|
||||||
|
def set_socket(self, sock, map=None):
|
||||||
|
self.socket = sock
|
||||||
|
## self.__dict__['socket'] = sock
|
||||||
|
self._fileno = sock.fileno()
|
||||||
|
self.add_channel(map)
|
||||||
|
|
||||||
|
def set_reuse_addr(self):
|
||||||
|
# try to re-use a server port if possible
|
||||||
|
try:
|
||||||
|
self.socket.setsockopt(
|
||||||
|
socket.SOL_SOCKET, socket.SO_REUSEADDR,
|
||||||
|
self.socket.getsockopt(socket.SOL_SOCKET,
|
||||||
|
socket.SO_REUSEADDR) | 1
|
||||||
|
)
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ==================================================
|
||||||
|
# predicates for select()
|
||||||
|
# these are used as filters for the lists of sockets
|
||||||
|
# to pass to select().
|
||||||
|
# ==================================================
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if os.name == 'mac':
|
||||||
|
# The macintosh will select a listening socket for
|
||||||
|
# write if you let it. What might this mean?
|
||||||
|
def writable(self):
|
||||||
|
return not self.accepting
|
||||||
|
else:
|
||||||
|
def writable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ==================================================
|
||||||
|
# socket object methods.
|
||||||
|
# ==================================================
|
||||||
|
|
||||||
|
def listen(self, num):
|
||||||
|
self.accepting = 1
|
||||||
|
if os.name == 'nt' and num > 5:
|
||||||
|
num = 1
|
||||||
|
return self.socket.listen(num)
|
||||||
|
|
||||||
|
def bind(self, addr):
|
||||||
|
self.addr = addr
|
||||||
|
return self.socket.bind(addr)
|
||||||
|
|
||||||
|
def connect(self, address):
|
||||||
|
self.connected = 0
|
||||||
|
err = self.socket.connect_ex(address)
|
||||||
|
# XXX Should interpret Winsock return values
|
||||||
|
if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):
|
||||||
|
return
|
||||||
|
if err in (0, EISCONN):
|
||||||
|
self.addr = address
|
||||||
|
self.connected = 1
|
||||||
|
self.handle_connect()
|
||||||
|
else:
|
||||||
|
raise socket.error, err
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
# XXX can return either an address pair or None
|
||||||
|
try:
|
||||||
|
conn, addr = self.socket.accept()
|
||||||
|
return conn, addr
|
||||||
|
except socket.error, why:
|
||||||
|
if why[0] == EWOULDBLOCK:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise socket.error, why
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
try:
|
||||||
|
result = self.socket.send(data)
|
||||||
|
return result
|
||||||
|
except socket.error, why:
|
||||||
|
if why[0] == EWOULDBLOCK:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
raise socket.error, why
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def recv(self, buffer_size):
|
||||||
|
try:
|
||||||
|
data = self.socket.recv(buffer_size)
|
||||||
|
if not data:
|
||||||
|
# a closed connection is indicated by signaling
|
||||||
|
# a read condition, and having recv() return 0.
|
||||||
|
self.handle_close()
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
except socket.error, why:
|
||||||
|
# winsock sometimes throws ENOTCONN
|
||||||
|
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
|
||||||
|
self.handle_close()
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
raise socket.error, why
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.del_channel()
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
|
# cheap inheritance, used to pass all other attribute
|
||||||
|
# references to the underlying socket object.
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.socket, attr)
|
||||||
|
|
||||||
|
# log and log_info may be overridden to provide more sophisticated
|
||||||
|
# logging and warning methods. In general, log is for 'hit' logging
|
||||||
|
# and 'log_info' is for informational, warning and error logging.
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
sys.stderr.write('log: %s\n' % str(message))
|
||||||
|
|
||||||
|
def log_info(self, message, type='info'):
|
||||||
|
if __debug__ or type != 'info':
|
||||||
|
print '%s: %s' % (type, message)
|
||||||
|
|
||||||
|
def handle_read_event(self):
|
||||||
|
if self.accepting:
|
||||||
|
# for an accepting socket, getting a read implies
|
||||||
|
# that we are connected
|
||||||
|
if not self.connected:
|
||||||
|
self.connected = 1
|
||||||
|
self.handle_accept()
|
||||||
|
elif not self.connected:
|
||||||
|
self.handle_connect()
|
||||||
|
self.connected = 1
|
||||||
|
self.handle_read()
|
||||||
|
else:
|
||||||
|
self.handle_read()
|
||||||
|
|
||||||
|
def handle_write_event(self):
|
||||||
|
# getting a write implies that we are connected
|
||||||
|
if not self.connected:
|
||||||
|
self.handle_connect()
|
||||||
|
self.connected = 1
|
||||||
|
self.handle_write()
|
||||||
|
|
||||||
|
def handle_expt_event(self):
|
||||||
|
self.handle_expt()
|
||||||
|
|
||||||
|
def handle_error(self):
|
||||||
|
nil, t, v, tbinfo = compact_traceback()
|
||||||
|
|
||||||
|
# sometimes a user repr method will crash.
|
||||||
|
try:
|
||||||
|
self_repr = repr(self)
|
||||||
|
except:
|
||||||
|
self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
|
||||||
|
|
||||||
|
self.log_info(
|
||||||
|
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
|
||||||
|
self_repr,
|
||||||
|
t,
|
||||||
|
v,
|
||||||
|
tbinfo
|
||||||
|
),
|
||||||
|
'error'
|
||||||
|
)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def handle_expt(self):
|
||||||
|
self.log_info('unhandled exception', 'warning')
|
||||||
|
|
||||||
|
def handle_read(self):
|
||||||
|
self.log_info('unhandled read event', 'warning')
|
||||||
|
|
||||||
|
def handle_write(self):
|
||||||
|
self.log_info('unhandled write event', 'warning')
|
||||||
|
|
||||||
|
def handle_connect(self):
|
||||||
|
self.log_info('unhandled connect event', 'warning')
|
||||||
|
|
||||||
|
def handle_accept(self):
|
||||||
|
self.log_info('unhandled accept event', 'warning')
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
self.log_info('unhandled close event', 'warning')
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# adds simple buffered output capability, useful for simple clients.
|
||||||
|
# [for more sophisticated usage use asynchat.async_chat]
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class dispatcher_with_send(dispatcher):
|
||||||
|
|
||||||
|
def __init__(self, sock=None):
|
||||||
|
dispatcher.__init__(self, sock)
|
||||||
|
self.out_buffer = ''
|
||||||
|
|
||||||
|
def initiate_send(self):
|
||||||
|
num_sent = 0
|
||||||
|
num_sent = dispatcher.send(self, self.out_buffer[:512])
|
||||||
|
self.out_buffer = self.out_buffer[num_sent:]
|
||||||
|
|
||||||
|
def handle_write(self):
|
||||||
|
self.initiate_send()
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
return (not self.connected) or len(self.out_buffer)
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
if self.debug:
|
||||||
|
self.log_info('sending %s' % repr(data))
|
||||||
|
self.out_buffer = self.out_buffer + data
|
||||||
|
self.initiate_send()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# used for debugging.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def compact_traceback():
|
||||||
|
t, v, tb = sys.exc_info()
|
||||||
|
tbinfo = []
|
||||||
|
assert tb # Must have a traceback
|
||||||
|
while tb:
|
||||||
|
tbinfo.append((
|
||||||
|
tb.tb_frame.f_code.co_filename,
|
||||||
|
tb.tb_frame.f_code.co_name,
|
||||||
|
str(tb.tb_lineno)
|
||||||
|
))
|
||||||
|
tb = tb.tb_next
|
||||||
|
|
||||||
|
# just to be safe
|
||||||
|
del tb
|
||||||
|
|
||||||
|
file, function, line = tbinfo[-1]
|
||||||
|
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
|
||||||
|
return (file, function, line), t, v, info
|
||||||
|
|
||||||
|
def close_all(map=None):
|
||||||
|
if map is None:
|
||||||
|
map = socket_map
|
||||||
|
for x in map.values():
|
||||||
|
x.socket.close()
|
||||||
|
map.clear()
|
179
others/babelfish.py
Normal file
179
others/babelfish.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
# babelizer.py - API for simple access to babelfish.altavista.com.
|
||||||
|
# Requires python 2.0 or better.
|
||||||
|
#
|
||||||
|
# See it in use at http://babel.MrFeinberg.com/
|
||||||
|
|
||||||
|
"""API for simple access to babelfish.altavista.com.
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
import babelizer
|
||||||
|
|
||||||
|
print ' '.join(babelizer.available_languages)
|
||||||
|
|
||||||
|
print babelizer.translate( 'How much is that doggie in the window?',
|
||||||
|
'English', 'French' )
|
||||||
|
|
||||||
|
def babel_callback(phrase):
|
||||||
|
print phrase
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
babelizer.babelize( 'I love a reigning knight.',
|
||||||
|
'English', 'German',
|
||||||
|
callback = babel_callback )
|
||||||
|
|
||||||
|
available_languages
|
||||||
|
A list of languages available for use with babelfish.
|
||||||
|
|
||||||
|
translate( phrase, from_lang, to_lang )
|
||||||
|
Uses babelfish to translate phrase from from_lang to to_lang.
|
||||||
|
|
||||||
|
babelize(phrase, from_lang, through_lang, limit = 12, callback = None)
|
||||||
|
Uses babelfish to translate back and forth between from_lang and
|
||||||
|
through_lang until either no more changes occur in translation or
|
||||||
|
limit iterations have been reached, whichever comes first. Takes
|
||||||
|
an optional callback function which should receive a single
|
||||||
|
parameter, being the next translation. Without the callback
|
||||||
|
returns a list of successive translations.
|
||||||
|
|
||||||
|
It's only guaranteed to work if 'english' is one of the two languages
|
||||||
|
given to either of the translation methods.
|
||||||
|
|
||||||
|
Both translation methods throw exceptions which are all subclasses of
|
||||||
|
BabelizerError. They include
|
||||||
|
|
||||||
|
LanguageNotAvailableError
|
||||||
|
Thrown on an attempt to use an unknown language.
|
||||||
|
|
||||||
|
BabelfishChangedError
|
||||||
|
Thrown when babelfish.altavista.com changes some detail of their
|
||||||
|
layout, and babelizer can no longer parse the results or submit
|
||||||
|
the correct form (a not infrequent occurance).
|
||||||
|
|
||||||
|
BabelizerIOError
|
||||||
|
Thrown for various networking and IO errors.
|
||||||
|
|
||||||
|
Version: $Id$
|
||||||
|
Author: Jonathan Feinberg <jdf@pobox.com>
|
||||||
|
"""
|
||||||
|
import re, string, urllib
|
||||||
|
|
||||||
|
"""
|
||||||
|
Various patterns I have encountered in looking for the babelfish result.
|
||||||
|
We try each of them in turn, based on the relative number of times I've
|
||||||
|
seen each of these patterns. $1.00 to anyone who can provide a heuristic
|
||||||
|
for knowing which one to use. This includes AltaVista employees.
|
||||||
|
"""
|
||||||
|
__where = [ re.compile(r'lang=..>([^<]*)</div'),
|
||||||
|
re.compile(r'name=\"q\" value=\"([^\"]*)\">'),
|
||||||
|
re.compile(r'div style=padding:10px;>([^<]+)</div'),
|
||||||
|
]
|
||||||
|
|
||||||
|
__languages = { 'english' : 'en',
|
||||||
|
'chinese_simple' : 'zh',
|
||||||
|
'chinese_traditional' : 'zt',
|
||||||
|
'french' : 'fr',
|
||||||
|
'german' : 'de',
|
||||||
|
'italian' : 'it',
|
||||||
|
'japanese' : 'ja',
|
||||||
|
'korean' : 'ko',
|
||||||
|
'spanish' : 'es',
|
||||||
|
'portuguese' : 'pt',
|
||||||
|
'russian' : 'ru',
|
||||||
|
'greek' : 'el',
|
||||||
|
'dutch' : 'nl',
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
All of the available language names.
|
||||||
|
"""
|
||||||
|
available_languages = [ x.title() for x in __languages.keys() ]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Calling translate() or babelize() can raise a BabelizerError
|
||||||
|
"""
|
||||||
|
class BabelizerError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class LanguageNotAvailableError(BabelizerError):
|
||||||
|
pass
|
||||||
|
class BabelfishChangedError(BabelizerError):
|
||||||
|
pass
|
||||||
|
class BabelizerIOError(BabelizerError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clean(text):
|
||||||
|
return ' '.join(string.replace(text.strip(), "\n", ' ').split())
|
||||||
|
|
||||||
|
def translate(phrase, from_lang, to_lang):
|
||||||
|
phrase = clean(phrase)
|
||||||
|
try:
|
||||||
|
from_code = __languages[from_lang.lower()]
|
||||||
|
to_code = __languages[to_lang.lower()]
|
||||||
|
except KeyError, lang:
|
||||||
|
raise LanguageNotAvailableError(lang)
|
||||||
|
|
||||||
|
params = urllib.urlencode( { 'BabelFishFrontPage' : 'yes',
|
||||||
|
'doit' : 'done',
|
||||||
|
'tt' : 'urltext',
|
||||||
|
'intl' : '1',
|
||||||
|
'urltext' : phrase,
|
||||||
|
'lp' : from_code + '_' + to_code } )
|
||||||
|
try:
|
||||||
|
response = urllib.urlopen('http://babelfish.altavista.com/babelfish/tr', params)
|
||||||
|
except IOError, what:
|
||||||
|
raise BabelizerIOError("Couldn't talk to server: %s" % what)
|
||||||
|
except:
|
||||||
|
print "Unexpected error:", sys.exc_info()[0]
|
||||||
|
|
||||||
|
html = response.read()
|
||||||
|
try:
|
||||||
|
begin = html.index('<!-- Target text (content) -->')
|
||||||
|
end = html.index('<!-- end: Target text (content) -->')
|
||||||
|
html = html[begin:end]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
for regex in __where:
|
||||||
|
match = regex.search(html)
|
||||||
|
if match:
|
||||||
|
break
|
||||||
|
if not match:
|
||||||
|
raise BabelfishChangedError("Can't recognize translated string.")
|
||||||
|
return clean(match.group(1))
|
||||||
|
|
||||||
|
def babelize(phrase, from_language, through_language, limit = 12, callback = None):
|
||||||
|
phrase = clean(phrase)
|
||||||
|
seen = { phrase: 1 }
|
||||||
|
results = []
|
||||||
|
if callback:
|
||||||
|
def_callback = callback
|
||||||
|
else:
|
||||||
|
def_callback = results.append
|
||||||
|
def_callback(phrase)
|
||||||
|
flip = { from_language: through_language, through_language: from_language }
|
||||||
|
next = from_language
|
||||||
|
for i in range(limit):
|
||||||
|
phrase = translate(phrase, next, flip[next])
|
||||||
|
if seen.has_key(phrase):
|
||||||
|
break
|
||||||
|
seen[phrase] = 1
|
||||||
|
def_callback(phrase)
|
||||||
|
next = flip[next]
|
||||||
|
# next is set to the language of the last entry. this should be the same
|
||||||
|
# as the language we are translating to
|
||||||
|
if next != through_language:
|
||||||
|
phrase = translate(phrase, next, flip[next])
|
||||||
|
def_callback(phrase)
|
||||||
|
if not callback:
|
||||||
|
return results
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
def printer(x):
|
||||||
|
print x
|
||||||
|
sys.stdout.flush();
|
||||||
|
|
||||||
|
|
||||||
|
babelize("I won't take that sort of treatment from you, or from your doggie!",
|
||||||
|
'english', 'french', callback = printer)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user