Gisle Aune
7 years ago
7 changed files with 627 additions and 1 deletions
-
15Gopkg.lock
-
34Gopkg.toml
-
25vendor/github.com/sadbox/mediawiki/.gitignore
-
20vendor/github.com/sadbox/mediawiki/LICENSE
-
36vendor/github.com/sadbox/mediawiki/README.md
-
496vendor/github.com/sadbox/mediawiki/mediawiki.go
-
2wikiauth_test.go
@ -0,0 +1,15 @@ |
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. |
|||
|
|||
|
|||
[[projects]] |
|||
name = "github.com/sadbox/mediawiki" |
|||
packages = ["."] |
|||
revision = "39fea8a1336076a961a300d1d95765dcd17e8a3c" |
|||
version = "v0.1" |
|||
|
|||
[solve-meta] |
|||
analyzer-name = "dep" |
|||
analyzer-version = 1 |
|||
inputs-digest = "875ef87fe71f1595d9be3e43d2eba538fdb3c449361cf31b521971c297745867" |
|||
solver-name = "gps-cdcl" |
|||
solver-version = 1 |
@ -0,0 +1,34 @@ |
|||
# Gopkg.toml example |
|||
# |
|||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html |
|||
# for detailed Gopkg.toml documentation. |
|||
# |
|||
# required = ["github.com/user/thing/cmd/thing"] |
|||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] |
|||
# |
|||
# [[constraint]] |
|||
# name = "github.com/user/project" |
|||
# version = "1.0.0" |
|||
# |
|||
# [[constraint]] |
|||
# name = "github.com/user/project2" |
|||
# branch = "dev" |
|||
# source = "github.com/myfork/project2" |
|||
# |
|||
# [[override]] |
|||
# name = "github.com/x/y" |
|||
# version = "2.4.0" |
|||
# |
|||
# [prune] |
|||
# non-go = false |
|||
# go-tests = true |
|||
# unused-packages = true |
|||
|
|||
|
|||
[[constraint]] |
|||
name = "github.com/sadbox/mediawiki" |
|||
version = "0.1.0" |
|||
|
|||
[prune] |
|||
go-tests = true |
|||
unused-packages = true |
@ -0,0 +1,25 @@ |
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects) |
|||
*.o |
|||
*.a |
|||
*.so |
|||
|
|||
# Folders |
|||
_obj |
|||
_test |
|||
|
|||
# Architecture specific extensions/prefixes |
|||
*.[568vq] |
|||
[568vq].out |
|||
|
|||
*.cgo1.go |
|||
*.cgo2.c |
|||
_cgo_defun.c |
|||
_cgo_gotypes.go |
|||
_cgo_export.* |
|||
|
|||
_testmain.go |
|||
|
|||
*.exe |
|||
|
|||
/examples/examples |
|||
/examples/config.xml |
@ -0,0 +1,20 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2013 James McGuire |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|||
this software and associated documentation files (the "Software"), to deal in |
|||
the Software without restriction, including without limitation the rights to |
|||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
|||
the Software, and to permit persons to whom the Software is furnished to do so, |
|||
subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,36 @@ |
|||
go-mediawiki [![Build Status](https://drone.io/github.com/sadbox/go-mediawiki/status.png)](https://drone.io/github.com/sadbox/go-mediawiki/latest) |
|||
======== |
|||
A MediaWiki API wrapper for [Go](http://golang.org/) |
|||
|
|||
Documentation |
|||
------------- |
|||
Documentation specific to this implementaiton is [located on GoDoc](http://godoc.org/github.com/sadbox/go-mediawiki) |
|||
|
|||
Documentation specific to the Mediawiki API in general is located [at their own wiki](http://www.mediawiki.org/wiki/API:Main_page) |
|||
|
|||
Examples are located in the [examples subdirectory.](/examples) |
|||
|
|||
|
|||
TODO |
|||
---- |
|||
- ☑ Login/Logout |
|||
- ☑ Edit Pages |
|||
- ☑ Read Pages |
|||
- ☑ Upload |
|||
- ☑ Download |
|||
- ☑ Generic API Interface |
|||
- ☑ Unit tests |
|||
|
|||
Useful Links |
|||
------------ |
|||
For easy handling of items that you get back from client.API() |
|||
|
|||
https://github.com/jmoiron/jsonq |
|||
|
|||
For adding your own structs |
|||
|
|||
https://github.com/str1ngs/jflect |
|||
|
|||
License |
|||
------- |
|||
This software is licensed under the MIT license. Pull requests and issues are welcome. |
@ -0,0 +1,496 @@ |
|||
// Copyright 2013 James McGuire
|
|||
// This code is covered under the MIT License
|
|||
// Please refer to the LICENSE file in the root of this
|
|||
// repository for any information.
|
|||
|
|||
// go-mediawiki provides a wrapper for interacting with the Mediawiki API
|
|||
//
|
|||
// Please see http://www.mediawiki.org/wiki/API:Main_page
|
|||
// for any API specific information or refer to any of the
|
|||
// functions defined for the MWApi struct for information
|
|||
// regarding this specific implementation.
|
|||
//
|
|||
// The client subdirectory contains an example application
|
|||
// that uses this API.
|
|||
package mediawiki |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/json" |
|||
"errors" |
|||
"io" |
|||
"io/ioutil" |
|||
"mime/multipart" |
|||
"net/http" |
|||
"net/http/cookiejar" |
|||
"net/url" |
|||
"strings" |
|||
) |
|||
|
|||
// The main mediawiki API struct, this is generated via mwapi.New()
|
|||
type MWApi struct { |
|||
Username string |
|||
Password string |
|||
Domain string |
|||
userAgent string |
|||
debug bool |
|||
url *url.URL |
|||
client *http.Client |
|||
format string |
|||
edittoken string |
|||
} |
|||
|
|||
// This is used for passing data to the mediawiki API via key=value in a POST
|
|||
type Values map[string]string |
|||
|
|||
// Unmarshal login data...
|
|||
type outerLogin struct { |
|||
Login loginResponse |
|||
} |
|||
|
|||
type loginResponse struct { |
|||
Result string |
|||
Token string |
|||
} |
|||
|
|||
// Unmarshall response from page edits...
|
|||
type outerEdit struct { |
|||
Edit edit |
|||
} |
|||
|
|||
type edit struct { |
|||
Result string |
|||
PageId int |
|||
Title string |
|||
OldRevId int |
|||
NewRevId int |
|||
} |
|||
|
|||
// General query response from mediawiki
|
|||
type mwQuery struct { |
|||
Query query |
|||
} |
|||
|
|||
type query struct { |
|||
Pages map[string]page |
|||
} |
|||
|
|||
type page struct { |
|||
Pageid int |
|||
Ns float64 |
|||
Title string |
|||
Touched string |
|||
Lastrevid float64 |
|||
// This will appear as both a string and a number... and the JSON unmarshaler
|
|||
// will crap out if this isn't set to a string.
|
|||
//Counter string
|
|||
Length float64 |
|||
Edittoken string |
|||
Revisions []revision |
|||
Imageinfo []image |
|||
} |
|||
|
|||
type revision struct { |
|||
Body string `json:"*"` |
|||
User string |
|||
Timestamp string |
|||
comment string |
|||
} |
|||
|
|||
type image struct { |
|||
Url string |
|||
Descriptionurl string |
|||
} |
|||
|
|||
type mwError struct { |
|||
Error errorType |
|||
} |
|||
|
|||
type errorType struct { |
|||
Code string |
|||
Info string |
|||
} |
|||
|
|||
type uploadResponse struct { |
|||
Upload uploadResult |
|||
} |
|||
|
|||
type uploadResult struct { |
|||
Result string |
|||
} |
|||
|
|||
// Helper function for translating mediawiki errors in to golang errors.
|
|||
func CheckError(response []byte) error { |
|||
var mwerror mwError |
|||
err := json.Unmarshal(response, &mwerror) |
|||
if err != nil { |
|||
return nil |
|||
} else if mwerror.Error.Code != "" { |
|||
return errors.New(mwerror.Error.Code + ": " + mwerror.Error.Info) |
|||
} else { |
|||
return nil |
|||
} |
|||
} |
|||
|
|||
// Generate a new mediawiki API struct
|
|||
//
|
|||
// Example: mwapi.New("http://en.wikipedia.org/w/api.php", "My Mediawiki Bot")
|
|||
// Returns errors if the URL is invalid
|
|||
func New(wikiUrl, userAgent string) (*MWApi, error) { |
|||
cookiejar, err := cookiejar.New(nil) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
client := http.Client{ |
|||
Transport: nil, |
|||
CheckRedirect: nil, |
|||
Jar: cookiejar, |
|||
} |
|||
|
|||
clientUrl, err := url.Parse(wikiUrl) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &MWApi{ |
|||
url: clientUrl, |
|||
client: &client, |
|||
format: "json", |
|||
userAgent: "go-mediawiki https://github.com/sadbox/go-mediawiki " + userAgent, |
|||
debug: false, |
|||
}, nil |
|||
} |
|||
|
|||
// This will automatically add the user agent and encode the http request properly
|
|||
func (m *MWApi) postForm(query url.Values) ([]byte, error) { |
|||
request, err := http.NewRequest("POST", m.url.String(), strings.NewReader(query.Encode())) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
|||
request.Header.Set("user-agent", m.userAgent) |
|||
resp, err := m.client.Do(request) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
defer resp.Body.Close() |
|||
|
|||
body, err := ioutil.ReadAll(resp.Body) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if err = CheckError(body); err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return body, nil |
|||
} |
|||
|
|||
// Download a file.
|
|||
//
|
|||
// Returns a readcloser that must be closed manually. Refer to the
|
|||
// example app for additional usage.
|
|||
func (m *MWApi) Download(filename string) (io.ReadCloser, error) { |
|||
// First get the direct url of the file
|
|||
query := Values{ |
|||
"action": "query", |
|||
"prop": "imageinfo", |
|||
"iiprop": "url", |
|||
"titles": filename, |
|||
} |
|||
|
|||
body, _, err := m.API(query) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
var response mwQuery |
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
var fileurl string |
|||
for _, page := range response.Query.Pages { |
|||
if len(page.Imageinfo) < 1 { |
|||
return nil, errors.New("No file found") |
|||
} |
|||
fileurl = page.Imageinfo[0].Url |
|||
break |
|||
} |
|||
|
|||
// Then return the body of the response
|
|||
request, err := http.NewRequest("GET", fileurl, nil) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
request.Header.Set("user-agent", m.userAgent) |
|||
|
|||
resp, err := m.client.Do(request) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
return resp.Body, nil |
|||
} |
|||
|
|||
// Upload a file
|
|||
//
|
|||
// This does a simple, but more error-prone upload. Mediawiki
|
|||
// has a chunked upload version but it is only available in newer
|
|||
// versions of the API.
|
|||
//
|
|||
// Automatically retrieves an edit token if necessary.
|
|||
func (m *MWApi) Upload(dstFilename string, file io.Reader) error { |
|||
if m.edittoken == "" { |
|||
err := m.GetEditToken() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
} |
|||
|
|||
query := Values{ |
|||
"action": "upload", |
|||
"filename": dstFilename, |
|||
"token": m.edittoken, |
|||
"format": m.format, |
|||
} |
|||
|
|||
buffer := &bytes.Buffer{} |
|||
writer := multipart.NewWriter(buffer) |
|||
|
|||
for key, value := range query { |
|||
err := writer.WriteField(key, value) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
} |
|||
|
|||
part, err := writer.CreateFormFile("file", dstFilename) |
|||
_, err = io.Copy(part, file) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
err = writer.Close() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
request, err := http.NewRequest("POST", m.url.String(), buffer) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
request.Header.Set("Content-Type", "multipart/form-data; boundary="+writer.Boundary()) |
|||
request.Header.Set("user-agent", m.userAgent) |
|||
|
|||
resp, err := m.client.Do(request) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
defer resp.Body.Close() |
|||
body, err := ioutil.ReadAll(resp.Body) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
if err = CheckError(body); err != nil { |
|||
return err |
|||
} |
|||
|
|||
var response uploadResponse |
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
if response.Upload.Result == "Success" || response.Upload.Result == "Warning" { |
|||
return nil |
|||
} else { |
|||
return errors.New(response.Upload.Result) |
|||
} |
|||
} |
|||
|
|||
// Login to the Mediawiki Website
|
|||
//
|
|||
// This will throw an error if you didn't define a username
|
|||
// or password.
|
|||
func (m *MWApi) Login() error { |
|||
if m.Username == "" || m.Password == "" { |
|||
return errors.New("Username or password not set.") |
|||
} |
|||
|
|||
query := Values{ |
|||
"action": "login", |
|||
"lgname": m.Username, |
|||
"lgpassword": m.Password, |
|||
} |
|||
|
|||
if m.Domain != "" { |
|||
query["lgdomain"] = m.Domain |
|||
} |
|||
|
|||
body, _, err := m.API(query) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
var response outerLogin |
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
if response.Login.Result == "Success" { |
|||
return nil |
|||
} else if response.Login.Result != "NeedToken" { |
|||
return errors.New("Error logging in: " + response.Login.Result) |
|||
} |
|||
|
|||
// Need to use the login token
|
|||
query["lgtoken"] = response.Login.Token |
|||
|
|||
body, _, err = m.API(query) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
if response.Login.Result == "Success" { |
|||
return nil |
|||
} else { |
|||
return errors.New("Error logging in: " + response.Login.Result) |
|||
} |
|||
} |
|||
|
|||
// Get an edit token
|
|||
//
|
|||
// This is necessary for editing any page.
|
|||
//
|
|||
// The Edit() function will call this automatically
|
|||
// but it is available if you want to make direct
|
|||
// calls to API().
|
|||
func (m *MWApi) GetEditToken() error { |
|||
query := Values{ |
|||
"action": "query", |
|||
"prop": "info|revisions", |
|||
"intoken": "edit", |
|||
"titles": "Main Page", |
|||
} |
|||
|
|||
body, _, err := m.API(query) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
var response mwQuery |
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
for _, value := range response.Query.Pages { |
|||
m.edittoken = value.Edittoken |
|||
break |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
// Log out of the mediawiki website
|
|||
func (m *MWApi) Logout() { |
|||
m.API(Values{"action": "logout"}) |
|||
} |
|||
|
|||
// Edit a page
|
|||
//
|
|||
// This function will automatically grab an Edit Token if there
|
|||
// is not one currently stored.
|
|||
//
|
|||
// Example:
|
|||
//
|
|||
// editConfig := mediawiki.Values{
|
|||
// "title": "SOME PAGE",
|
|||
// "summary": "THIS IS WHAT SHOWS UP IN THE LOG",
|
|||
// "text": "THE ENTIRE TEXT OF THE PAGE",
|
|||
// }
|
|||
// err = client.Edit(editConfig)
|
|||
func (m *MWApi) Edit(values Values) error { |
|||
if m.edittoken == "" { |
|||
err := m.GetEditToken() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
} |
|||
query := Values{ |
|||
"action": "edit", |
|||
"token": m.edittoken, |
|||
} |
|||
body, _, err := m.API(query, values) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
var response outerEdit |
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
if response.Edit.Result == "Success" { |
|||
return nil |
|||
} else { |
|||
return errors.New(response.Edit.Result) |
|||
} |
|||
} |
|||
|
|||
// Request a wiki page and it's metadata.
|
|||
func (m *MWApi) Read(pageName string) (*revision, error) { |
|||
query := Values{ |
|||
"action": "query", |
|||
"prop": "revisions", |
|||
"titles": pageName, |
|||
"rvlimit": "1", |
|||
"rvprop": "content|timestamp|user|comment", |
|||
} |
|||
body, _, err := m.API(query) |
|||
|
|||
var response mwQuery |
|||
err = json.Unmarshal(body, &response) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
for _, page := range response.Query.Pages { |
|||
return &page.Revisions[0], nil |
|||
} |
|||
return nil, errors.New("No revisions found") |
|||
} |
|||
|
|||
// A generic interface to the Mediawiki API
|
|||
// Refer to the mediawiki API reference for any information regarding
|
|||
// what to pass to this function
|
|||
//
|
|||
// This is used by all internal functions to interact with the API
|
|||
//
|
|||
// The second return is simply the json data decoded in to an empty interface
|
|||
// that can be used by something like https://github.com/jmoiron/jsonq
|
|||
func (m *MWApi) API(values ...Values) ([]byte, interface{}, error) { |
|||
query := m.url.Query() |
|||
for _, valuemap := range values { |
|||
for key, value := range valuemap { |
|||
query.Set(key, value) |
|||
} |
|||
} |
|||
query.Set("format", m.format) |
|||
body, err := m.postForm(query) |
|||
if err != nil { |
|||
return nil, nil, err |
|||
} |
|||
var unmarshalto interface{} |
|||
err = json.Unmarshal(body, &unmarshalto) |
|||
if err != nil { |
|||
return nil, nil, err |
|||
} |
|||
return body, unmarshalto, nil |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue