Merge branch 'master' into macaroons
This commit is contained in:
commit
1abb82d6e0
53 changed files with 4261 additions and 112 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,8 @@
|
|||
scripts/gen
|
||||
scripts/continuserv/continuserv
|
||||
scripts/speculator/speculator
|
||||
templating/out
|
||||
*.pyc
|
||||
supporting-docs/_site
|
||||
supporting-docs/.sass-cache
|
||||
api/node_modules
|
||||
|
|
129
api/client-server/v1/membership.yaml
Normal file
129
api/client-server/v1/membership.yaml
Normal file
|
@ -0,0 +1,129 @@
|
|||
swagger: '2.0'
|
||||
info:
|
||||
title: "Matrix Client-Server v1 Room Membership API"
|
||||
version: "1.0.0"
|
||||
host: localhost:8008
|
||||
schemes:
|
||||
- https
|
||||
- http
|
||||
basePath: /_matrix/client/api/v1
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
securityDefinitions:
|
||||
accessToken:
|
||||
type: apiKey
|
||||
description: The user_id or application service access_token
|
||||
name: access_token
|
||||
in: query
|
||||
paths:
|
||||
"/rooms/{roomId}/join":
|
||||
post:
|
||||
summary: Start the requesting user participating in a particular room.
|
||||
description: |-
|
||||
This API starts a user participating in a particular room, if that user
|
||||
is allowed to participate in that room. After this call, the client is
|
||||
allowed to see all current state events in the room, and all subsequent
|
||||
events associated with the room until the user leaves the room.
|
||||
|
||||
After a user has joined a room, the room will appear as an entry in the
|
||||
response of the |initialSync| API.
|
||||
security:
|
||||
- accessToken: []
|
||||
parameters:
|
||||
- in: path
|
||||
type: string
|
||||
name: roomId
|
||||
description: The room identifier or room alias to join.
|
||||
required: true
|
||||
x-example: "#monkeys:matrix.org"
|
||||
responses:
|
||||
200:
|
||||
description: |-
|
||||
The room has been joined.
|
||||
|
||||
The joined room ID must be returned in the ``room_id`` field.
|
||||
examples:
|
||||
application/json: |-
|
||||
{"room_id": "!d41d8cd:matrix.org"}
|
||||
schema:
|
||||
type: object
|
||||
403:
|
||||
description: |-
|
||||
You do not have permission to join the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejection are:
|
||||
- The room is invite-only and the user was not invited.
|
||||
- The user has been banned from the room.
|
||||
examples:
|
||||
application/json: |-
|
||||
{"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."}
|
||||
429:
|
||||
description: This request was rate-limited.
|
||||
schema:
|
||||
"$ref": "definitions/error.yaml"
|
||||
x-alias:
|
||||
canonical-link: "post-matrix-client-api-v1-rooms-roomid-join"
|
||||
aliases:
|
||||
- /join/{roomId}
|
||||
|
||||
"/rooms/{roomId}/invite":
|
||||
post:
|
||||
summary: Invite a user to participate in a particular room.
|
||||
# It's a crying shame that I don't know how to force line breaks.
|
||||
description: |-
|
||||
This API invites a user to participate in a particular room.
|
||||
They do not start participating in the room until they actually join the
|
||||
room.
|
||||
|
||||
This serves two purposes; firstly, to notify the user that the room
|
||||
exists (and that their presence is requested). Secondly, some rooms can
|
||||
only be joined if a user is invited to join it; sending the invite gives
|
||||
that user permission to join the room.
|
||||
|
||||
Only users currently in a particular room can invite other users to
|
||||
join that room.
|
||||
security:
|
||||
- accessToken: []
|
||||
parameters:
|
||||
- in: path
|
||||
type: string
|
||||
name: roomId
|
||||
description: The room identifier (not alias) to which to invite the user.
|
||||
required: true
|
||||
x-example: "!d41d8cd:matrix.org"
|
||||
- in: body
|
||||
name: user_id
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
example: |-
|
||||
{
|
||||
"user_id": "@cheeky_monkey:matrix.org"
|
||||
}
|
||||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
description: The fully qualified user ID of the invitee.
|
||||
required: ["user_id"]
|
||||
responses:
|
||||
200:
|
||||
description: The user has been invited to join the room.
|
||||
examples:
|
||||
application/json: |-
|
||||
{}
|
||||
schema:
|
||||
type: object # empty json object
|
||||
403:
|
||||
description: |-
|
||||
You do not have permission to invite the user to the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are:
|
||||
- The invitee has been banned from the room.
|
||||
- The invitee is already a member of the room.
|
||||
- The inviter is not currently in the room.
|
||||
- The inviter's power level is insufficient to invite users to the room.
|
||||
examples:
|
||||
application/json: |-
|
||||
{"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"}
|
||||
429:
|
||||
description: This request was rate-limited.
|
||||
schema:
|
||||
"$ref": "definitions/error.yaml"
|
|
@ -206,4 +206,3 @@ paths:
|
|||
title: PresenceEvent
|
||||
allOf:
|
||||
- "$ref": "definitions/event.yaml"
|
||||
|
|
@ -285,8 +285,11 @@ paths:
|
|||
chunk:
|
||||
type: array
|
||||
description: |-
|
||||
A list of the most recent messages for this room. This
|
||||
array will consist of at most ``limit`` elements.
|
||||
If the user is a member of the room this will be a
|
||||
list of the most recent messages for this room. If
|
||||
the user has left the room this will be the
|
||||
messages that preceeded them leaving. This array
|
||||
will consist of at most ``limit`` elements.
|
||||
items:
|
||||
type: object
|
||||
title: RoomEvent
|
||||
|
@ -296,8 +299,10 @@ paths:
|
|||
state:
|
||||
type: array
|
||||
description: |-
|
||||
A list of state events representing the current state
|
||||
of the room.
|
||||
If the user is a member of the room this will be the
|
||||
current state of the room as a list of events. If the
|
||||
user has left the room this will be the state of the
|
||||
room when they left it.
|
||||
items:
|
||||
title: StateEvent
|
||||
type: object
|
||||
|
|
13
event-schemas/examples/v1/m.receipt
Normal file
13
event-schemas/examples/v1/m.receipt
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "m.receipt",
|
||||
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
|
||||
"content": {
|
||||
"$1435641916114394fHBLK:matrix.org": {
|
||||
"read": {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
event-schemas/examples/v1/m.room.canonical_alias
Normal file
12
event-schemas/examples/v1/m.room.canonical_alias
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"age": 242352,
|
||||
"content": {
|
||||
"alias": "#somewhere:localhost"
|
||||
},
|
||||
"state_key": "",
|
||||
"origin_server_ts": 1431961217939,
|
||||
"event_id": "$WLGTSEFSEF:localhost",
|
||||
"type": "m.room.canonical_alias",
|
||||
"room_id": "!Cuyf34gef24t:localhost",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
12
event-schemas/examples/v1/m.room.history_visibility
Normal file
12
event-schemas/examples/v1/m.room.history_visibility
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"age": 242352,
|
||||
"content": {
|
||||
"history_visibility": "shared"
|
||||
},
|
||||
"state_key": "",
|
||||
"origin_server_ts": 1431961217939,
|
||||
"event_id": "$WLGTSEFSEF:localhost",
|
||||
"type": "m.room.history_visibility",
|
||||
"room_id": "!Cuyf34gef24t:localhost",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
43
event-schemas/schema/v1/m.receipt
Normal file
43
event-schemas/schema/v1/m.receipt
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"title": "Receipt Event",
|
||||
"description": "Informs the client of new receipts.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"description": "The event ids which the receipts relate to.",
|
||||
"patternProperties": {
|
||||
"^\\$": {
|
||||
"type": "object",
|
||||
"description": "The types of the receipts.",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"description": "User ids of the receipts",
|
||||
"patternProperties": {
|
||||
"^@": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ts": {
|
||||
"type": "number",
|
||||
"description": "The timestamp the receipt was sent at"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["m.receipt"]
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["room_id", "type", "content"]
|
||||
}
|
30
event-schemas/schema/v1/m.room.canonical_alias
Normal file
30
event-schemas/schema/v1/m.room.canonical_alias
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"title": "Informs the room as to which alias is the canonical one.",
|
||||
"description": "This event is used to inform the room about which alias should be considered the canonical one. This could be for display purposes or as suggestion to users which alias to use to advertise the room.",
|
||||
"allOf": [{
|
||||
"$ref": "core#/definitions/state_event"
|
||||
}],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string",
|
||||
"description": "The canonical alias."
|
||||
}
|
||||
},
|
||||
"required": ["alias"]
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string",
|
||||
"description": "A zero-length string.",
|
||||
"pattern": "^$"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["m.room.canonical_alias"]
|
||||
}
|
||||
}
|
||||
}
|
31
event-schemas/schema/v1/m.room.history_visibility
Normal file
31
event-schemas/schema/v1/m.room.history_visibility
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"title": "Controls visibility of history.",
|
||||
"description": "This event controls whether a member of a room can see the events that happened in a room from before they joined.",
|
||||
"allOf": [{
|
||||
"$ref": "core#/definitions/state_event"
|
||||
}],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"history_visibility": {
|
||||
"type": "string",
|
||||
"description": "Who can see the room history.",
|
||||
"enum": ["invited","joined","shared"]
|
||||
}
|
||||
},
|
||||
"required": ["history_visibility"]
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string",
|
||||
"description": "A zero-length string.",
|
||||
"pattern": "^$"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["m.room.history_visibility"]
|
||||
}
|
||||
}
|
||||
}
|
6
scripts/continuserv/README
Normal file
6
scripts/continuserv/README
Normal file
|
@ -0,0 +1,6 @@
|
|||
continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
|
||||
|
||||
To run it, you must install the `go` tool. You will also need to install fsnotify by running:
|
||||
`go get gopkg.in/fsnotify.v1`
|
||||
You can then run continuserv by running:
|
||||
`go run main.go`
|
144
scripts/continuserv/main.go
Normal file
144
scripts/continuserv/main.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
// continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
|
||||
// It will always serve the most recent version of the spec, and may block an HTTP request until regeneration is finished.
|
||||
// It does not currently pre-empt stale generations, but will block until they are complete.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
fsnotify "gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 8000, "Port on which to serve HTTP")
|
||||
|
||||
toServe atomic.Value // Always contains valid []byte to serve. May be stale unless wg is zero.
|
||||
wg sync.WaitGroup // Indicates how many updates are pending.
|
||||
mu sync.Mutex // Prevent multiple updates in parallel.
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatalf("Error making watcher: %v", err)
|
||||
}
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting wd: %v", err)
|
||||
}
|
||||
for ; !exists(path.Join(dir, ".git")); dir = path.Dir(dir) {
|
||||
if dir == "/" {
|
||||
log.Fatalf("Could not find git root")
|
||||
}
|
||||
}
|
||||
|
||||
filepath.Walk(dir, makeWalker(w))
|
||||
|
||||
wg.Add(1)
|
||||
populateOnce(dir)
|
||||
|
||||
ch := make(chan struct{}, 100) // Buffered to ensure we can multiple-increment wg for pending writes
|
||||
go doPopulate(ch, dir)
|
||||
|
||||
go watchFS(ch, w)
|
||||
|
||||
http.HandleFunc("/", serve)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
||||
|
||||
}
|
||||
|
||||
func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
|
||||
for {
|
||||
select {
|
||||
case e := <-w.Events:
|
||||
if filter(e) {
|
||||
wg.Add(1)
|
||||
fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
|
||||
ch <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeWalker(w *fsnotify.Watcher) filepath.WalkFunc {
|
||||
return func(path string, _ os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Fatalf("Error walking: %v", err)
|
||||
}
|
||||
if err := w.Add(path); err != nil {
|
||||
log.Fatalf("Failed to add watch: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if event should trigger re-population
|
||||
func filter(e fsnotify.Event) bool {
|
||||
// vim is *really* noisy about how it writes files
|
||||
if e.Op != fsnotify.Write {
|
||||
return false
|
||||
}
|
||||
// Avoid some temp files that vim writes
|
||||
if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(e.Name, ".") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Avoid infinite cycles being caused by writing actual output
|
||||
if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func serve(w http.ResponseWriter, req *http.Request) {
|
||||
wg.Wait()
|
||||
b := toServe.Load().([]byte)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func populateOnce(dir string) {
|
||||
defer wg.Done()
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
cmd := exec.Command("python", "gendoc.py")
|
||||
cmd.Dir = path.Join(dir, "scripts")
|
||||
var b bytes.Buffer
|
||||
cmd.Stderr = &b
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
toServe.Store([]byte(fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()).Error()))
|
||||
return
|
||||
}
|
||||
specBytes, err := ioutil.ReadFile(path.Join(dir, "scripts", "gen", "specification.html"))
|
||||
if err != nil {
|
||||
toServe.Store([]byte(fmt.Errorf("error reading spec: %v", err).Error()))
|
||||
return
|
||||
}
|
||||
toServe.Store(specBytes)
|
||||
}
|
||||
|
||||
func doPopulate(ch chan struct{}, dir string) {
|
||||
for _ = range ch {
|
||||
populateOnce(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
|
@ -32,7 +32,9 @@ def rst2html(i, o):
|
|||
)
|
||||
|
||||
def run_through_template(input):
|
||||
null = open(os.devnull, 'w')
|
||||
tmpfile = './tmp/output'
|
||||
try:
|
||||
with open(tmpfile, 'w') as out:
|
||||
subprocess.check_output(
|
||||
[
|
||||
'python', 'build.py',
|
||||
|
@ -40,9 +42,13 @@ def run_through_template(input):
|
|||
"-o", "../scripts/tmp",
|
||||
"../scripts/"+input
|
||||
],
|
||||
stderr=null,
|
||||
stderr=out,
|
||||
cwd="../templating",
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
with open(tmpfile, 'r') as f:
|
||||
print f.read()
|
||||
raise
|
||||
|
||||
def prepare_env():
|
||||
try:
|
||||
|
@ -65,16 +71,19 @@ def main():
|
|||
run_through_template("tmp/howto.rst")
|
||||
rst2html("tmp/full_spec.rst", "gen/specification.html")
|
||||
rst2html("tmp/howto.rst", "gen/howtos.html")
|
||||
if "--nodelete" not in sys.argv:
|
||||
cleanup_env()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
# we accept no args, so they don't know what they're doing!
|
||||
if len(sys.argv) > 1 and sys.argv[1:] != ["--nodelete"]:
|
||||
# we accept almost no args, so they don't know what they're doing!
|
||||
print "gendoc.py - Generate the Matrix specification as HTML."
|
||||
print "Usage:"
|
||||
print " python gendoc.py"
|
||||
print " python gendoc.py [--nodelete]"
|
||||
print ""
|
||||
print "The specification can then be found in the gen/ folder."
|
||||
print ("If --nodelete was specified, intermediate files will be "
|
||||
"present in the tmp/ folder.")
|
||||
print ""
|
||||
print "Requirements:"
|
||||
print " - This script requires Jinja2 and rst2html (docutils)."
|
||||
|
|
9
scripts/speculator/README
Normal file
9
scripts/speculator/README
Normal file
|
@ -0,0 +1,9 @@
|
|||
speculator allows you to preview pull requests to the matrix.org specification.
|
||||
|
||||
It serves the following HTTP endpoints:
|
||||
- / lists open pull requests
|
||||
- /spec/123 which renders the spec as html at pull request 123.
|
||||
- /diff/rst/123 which gives a diff of the spec's rst at pull request 123.
|
||||
|
||||
To run it, you must install the `go` tool, and run:
|
||||
`go run main.go`
|
270
scripts/speculator/main.go
Normal file
270
scripts/speculator/main.go
Normal file
|
@ -0,0 +1,270 @@
|
|||
// speculator allows you to preview pull requests to the matrix.org specification.
|
||||
// It serves the following HTTP endpoints:
|
||||
// - / lists open pull requests
|
||||
// - /spec/123 which renders the spec as html at pull request 123.
|
||||
// - /diff/rst/123 which gives a diff of the spec's rst at pull request 123.
|
||||
// It is currently woefully inefficient, and there is a lot of low hanging fruit for improvement.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type PullRequest struct {
|
||||
Number int
|
||||
Base Commit
|
||||
Head Commit
|
||||
Title string
|
||||
User User
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
SHA string
|
||||
Repo RequestRepo
|
||||
}
|
||||
|
||||
type RequestRepo struct {
|
||||
CloneURL string `json:"clone_url"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Login string
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 9000, "Port on which to listen for HTTP")
|
||||
allowedMembers map[string]bool
|
||||
)
|
||||
|
||||
const pullsPrefix = "https://api.github.com/repos/matrix-org/matrix-doc/pulls"
|
||||
|
||||
func gitClone(url string) (string, error) {
|
||||
dst := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10))
|
||||
cmd := exec.Command("git", "clone", url, dst)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error cloning repo: %v", err)
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func gitCheckout(path, sha string) error {
|
||||
cmd := exec.Command("git", "checkout", sha)
|
||||
cmd.Dir = path
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking out repo: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupPullRequest(prNumber string) (*PullRequest, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/%s", pullsPrefix, prNumber))
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting pulls: %v", err)
|
||||
}
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
var pr PullRequest
|
||||
if err := dec.Decode(&pr); err != nil {
|
||||
return nil, fmt.Errorf("error decoding pulls: %v", err)
|
||||
}
|
||||
return &pr, nil
|
||||
}
|
||||
|
||||
func generate(dir string) error {
|
||||
cmd := exec.Command("python", "gendoc.py", "--nodelete")
|
||||
cmd.Dir = path.Join(dir, "scripts")
|
||||
var b bytes.Buffer
|
||||
cmd.Stderr = &b
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, err error) {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, fmt.Sprintf("%v\n", err))
|
||||
}
|
||||
|
||||
// generateAt generates spec from repo at sha.
|
||||
// Returns the path where the generation was done.
|
||||
func generateAt(repo, sha string) (dst string, err error) {
|
||||
dst, err = gitClone(repo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = gitCheckout(dst, sha); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = generate(dst)
|
||||
return
|
||||
}
|
||||
|
||||
func serveSpec(w http.ResponseWriter, req *http.Request) {
|
||||
parts := strings.Split(req.URL.Path, "/")
|
||||
if len(parts) != 3 {
|
||||
w.WriteHeader(400)
|
||||
io.WriteString(w, fmt.Sprintf("Invalid path passed: %v expect /pull/123", req.URL.Path))
|
||||
return
|
||||
}
|
||||
|
||||
pr, err := lookupPullRequest(parts[2])
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// We're going to run whatever Python is specified in the pull request, which
|
||||
// may do bad things, so only trust people we trust.
|
||||
if !allowedMembers[pr.User.Login] {
|
||||
w.WriteHeader(403)
|
||||
io.WriteString(w, fmt.Sprintf("%q is not a trusted pull requester", pr.User.Login))
|
||||
return
|
||||
}
|
||||
|
||||
dst, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
|
||||
defer os.RemoveAll(dst)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(path.Join(dst, "scripts/gen/specification.html"))
|
||||
if err != nil {
|
||||
writeError(w, fmt.Errorf("Error reading spec: %v", err))
|
||||
return
|
||||
}
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func serveRstDiff(w http.ResponseWriter, req *http.Request) {
|
||||
parts := strings.Split(req.URL.Path, "/")
|
||||
if len(parts) != 4 {
|
||||
w.WriteHeader(400)
|
||||
io.WriteString(w, fmt.Sprintf("Invalid path passed: %v expect /diff/rst/123", req.URL.Path))
|
||||
return
|
||||
}
|
||||
|
||||
pr, err := lookupPullRequest(parts[3])
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// We're going to run whatever Python is specified in the pull request, which
|
||||
// may do bad things, so only trust people we trust.
|
||||
if !allowedMembers[pr.User.Login] {
|
||||
w.WriteHeader(403)
|
||||
io.WriteString(w, fmt.Sprintf("%q is not a trusted pull requester", pr.User.Login))
|
||||
return
|
||||
}
|
||||
|
||||
base, err := generateAt(pr.Base.Repo.CloneURL, pr.Base.SHA)
|
||||
defer os.RemoveAll(base)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
head, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
|
||||
defer os.RemoveAll(head)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
diffCmd := exec.Command("diff", "-u", path.Join(base, "scripts", "tmp", "full_spec.rst"), path.Join(head, "scripts", "tmp", "full_spec.rst"))
|
||||
var diff bytes.Buffer
|
||||
diffCmd.Stdout = &diff
|
||||
if err := ignoreExitCodeOne(diffCmd.Run()); err != nil {
|
||||
writeError(w, fmt.Errorf("error running diff: %v", err))
|
||||
return
|
||||
}
|
||||
w.Write(diff.Bytes())
|
||||
}
|
||||
|
||||
func listPulls(w http.ResponseWriter, req *http.Request) {
|
||||
resp, err := http.Get(pullsPrefix)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
var pulls []PullRequest
|
||||
if err := dec.Decode(&pulls); err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
if len(pulls) == 0 {
|
||||
io.WriteString(w, "No pull requests found")
|
||||
return
|
||||
}
|
||||
s := "<body><ul>"
|
||||
for _, pull := range pulls {
|
||||
s += fmt.Sprintf(`<li>%d: <a href="%s">%s</a>: <a href="%s">%s</a>: <a href="spec/%d">spec</a> <a href="diff/rst/%d">rst diff</a></li>`,
|
||||
pull.Number, pull.User.HTMLURL, pull.User.Login, pull.HTMLURL, pull.Title, pull.Number, pull.Number)
|
||||
}
|
||||
s += "</ul></body>"
|
||||
io.WriteString(w, s)
|
||||
}
|
||||
|
||||
func ignoreExitCodeOne(err error) error {
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
if status.ExitStatus() == 1 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
// It would be great to read this from github, but there's no convenient way to do so.
|
||||
// Most of these memberships are "private", so would require some kind of auth.
|
||||
allowedMembers = map[string]bool{
|
||||
"dbkr": true,
|
||||
"erikjohnston": true,
|
||||
"illicitonion": true,
|
||||
"Kegsay": true,
|
||||
"NegativeMjark": true,
|
||||
}
|
||||
http.HandleFunc("/spec/", serveSpec)
|
||||
http.HandleFunc("/diff/rst/", serveRstDiff)
|
||||
http.HandleFunc("/healthz", serveText("ok"))
|
||||
http.HandleFunc("/", listPulls)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
||||
}
|
||||
|
||||
func serveText(s string) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
io.WriteString(w, s)
|
||||
}
|
||||
}
|
|
@ -879,54 +879,20 @@ The keys contained in ``m.room.power_levels`` determine the levels required for
|
|||
certain operations such as kicking, banning and sending state events. See
|
||||
`m.room.power_levels`_ for more information.
|
||||
|
||||
|
||||
Joining rooms
|
||||
~~~~~~~~~~~~~
|
||||
.. TODO-doc What does the home server have to do to join a user to a room?
|
||||
- See SPEC-30.
|
||||
-------------
|
||||
Users need to be a member of a room in order to send and receive events in that
|
||||
room. There are several states in which a user may be, in relation to a room:
|
||||
|
||||
Users need to join a room in order to send and receive events in that room. A
|
||||
user can join a room by making a request to |/join/<room_alias_or_id>|_ with::
|
||||
- Unrelated (the user cannot send or receive events in the room)
|
||||
- Invited (the user has been invited to participate in the room, but is not
|
||||
yet participating)
|
||||
- Joined (the user can send and receive events in the room)
|
||||
- Banned (the user is not allowed to join the room)
|
||||
|
||||
{}
|
||||
Some rooms require that users be invited to it before they can join; others
|
||||
allow anyone to join.
|
||||
|
||||
Alternatively, a user can make a request to |/rooms/<room_id>/join|_ with the
|
||||
same request content. This is only provided for symmetry with the other
|
||||
membership APIs: ``/rooms/<room id>/invite`` and ``/rooms/<room id>/leave``. If
|
||||
a room alias was specified, it will be automatically resolved to a room ID,
|
||||
which will then be joined. The room ID that was joined will be returned in
|
||||
response::
|
||||
|
||||
{
|
||||
"room_id": "!roomid:domain"
|
||||
}
|
||||
|
||||
The membership state for the joining user can also be modified directly to be
|
||||
``join`` by sending the following request to
|
||||
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
|
||||
|
||||
{
|
||||
"membership": "join"
|
||||
}
|
||||
|
||||
See the `Room events`_ section for more information on ``m.room.member``.
|
||||
|
||||
After the user has joined a room, they will receive subsequent events in that
|
||||
room. This room will now appear as an entry in the |initialSync|_ API.
|
||||
|
||||
Some rooms enforce that a user is *invited* to a room before they can join that
|
||||
room. Other rooms will allow anyone to join the room even if they have not
|
||||
received an invite.
|
||||
|
||||
Inviting users
|
||||
~~~~~~~~~~~~~~
|
||||
.. TODO-doc Invite-join dance
|
||||
- Outline invite join dance. What is it? Why is it required? How does it work?
|
||||
- What does the home server have to do?
|
||||
|
||||
The purpose of inviting users to a room is to notify them that the room exists
|
||||
so they can choose to become a member of that room. Some rooms require that all
|
||||
users who join a room are previously invited to it (an "invite-only" room).
|
||||
Whether a given room is an "invite-only" room is determined by the room config
|
||||
key ``m.room.join_rules``. It can have one of the following values:
|
||||
|
||||
|
@ -936,26 +902,7 @@ key ``m.room.join_rules``. It can have one of the following values:
|
|||
``invite``
|
||||
This room can only be joined if you were invited.
|
||||
|
||||
Only users who have a membership state of ``join`` in a room can invite new
|
||||
users to said room. The person being invited must not be in the ``join`` state
|
||||
in the room. The fully-qualified user ID must be specified when inviting a
|
||||
user, as the user may reside on a different home server. To invite a user, send
|
||||
the following request to |/rooms/<room_id>/invite|_, which will manage the
|
||||
entire invitation process::
|
||||
|
||||
{
|
||||
"user_id": "<user id to invite>"
|
||||
}
|
||||
|
||||
Alternatively, the membership state for this user in this room can be modified
|
||||
directly by sending the following request to
|
||||
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
|
||||
|
||||
{
|
||||
"membership": "invite"
|
||||
}
|
||||
|
||||
See the `Room events`_ section for more information on ``m.room.member``.
|
||||
{{membership_http_api}}
|
||||
|
||||
Leaving rooms
|
||||
~~~~~~~~~~~~~
|
||||
|
|
81
specification/46_receipts.rst
Normal file
81
specification/46_receipts.rst
Normal file
|
@ -0,0 +1,81 @@
|
|||
Receipts
|
||||
========
|
||||
|
||||
Receipts are used to publish which events in a room the user or their devices
|
||||
have interacted with. For example, which events the user has read.
|
||||
|
||||
For efficiency this is done as "up to" markers, i.e. marking a particular event
|
||||
as, say, ``read`` indicates the user has read all events *up to* that event.
|
||||
|
||||
Client-Server API
|
||||
-----------------
|
||||
|
||||
Clients will receive receipts in the following format::
|
||||
|
||||
{
|
||||
"type": "m.receipt",
|
||||
"room_id": <room_id>,
|
||||
"content": {
|
||||
<event_id>: {
|
||||
<receipt_type>: {
|
||||
<user_id>: { "ts": <ts>, ... },
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
For example::
|
||||
|
||||
{
|
||||
"type": "m.receipt",
|
||||
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
|
||||
"content": {
|
||||
"$1435641916114394fHBLK:matrix.org": {
|
||||
"read": {
|
||||
"@erikj:jki.re": { "ts": 1436451550453 },
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
For efficiency, receipts are batched into one event per room. In the initialSync
|
||||
and v2 sync APIs the receipts are listed in a seperate top level ``receipts``
|
||||
key.
|
||||
|
||||
Each ``user_id``, ``receipt_type`` pair must be associated with only a single
|
||||
``event_id``.
|
||||
|
||||
New receipts that come down the event streams are deltas. Deltas update
|
||||
existing mappings, clobbering based on ``user_id``, ``receipt_type`` pairs.
|
||||
|
||||
|
||||
A client can update the markers for its user by issuing a request::
|
||||
|
||||
POST /_matrix/client/v2_alpha/rooms/<room_id>/receipt/read/<event_id>
|
||||
|
||||
Where the contents of the ``POST`` will be included in the content sent to
|
||||
other users. The server will automatically set the ``ts`` field.
|
||||
|
||||
|
||||
Server-Server API
|
||||
-----------------
|
||||
|
||||
Receipts are sent across federation as EDUs with type ``m.receipt``. The
|
||||
format of the EDUs are::
|
||||
|
||||
{
|
||||
<room_id>: {
|
||||
<receipt_type>: {
|
||||
<user_id>: { <content> }
|
||||
},
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
These are always sent as deltas to previously sent reciepts.
|
||||
|
24
specification/47_history_visibility.rst
Normal file
24
specification/47_history_visibility.rst
Normal file
|
@ -0,0 +1,24 @@
|
|||
Room History Visibility
|
||||
=======================
|
||||
|
||||
Whether a member of a room can see the events that happened in a room from
|
||||
before they joined the room is controlled by the ``history_visibility`` key
|
||||
of the ``m.room.history_visibility`` state event. The valid values for
|
||||
``history_visibility`` are:
|
||||
|
||||
- ``shared``
|
||||
- ``invited``
|
||||
- ``joined``
|
||||
|
||||
By default if no ``history_visibility`` is set it is assumed to be ``shared``.
|
||||
|
||||
The rules governing whether a user is allowed to see an event depend solely on
|
||||
the state of the room at that event:
|
||||
|
||||
1. If the user was joined, allow.
|
||||
2. If the user was invited and the ``history_visibility`` was set to
|
||||
``invited`` or ``shared``, allow.
|
||||
3. If the user was neither invited nor joined but the ``history_visibility``
|
||||
was set to ``shared``, allow.
|
||||
4. Otherwise, deny.
|
||||
|
16
supporting-docs/_config.yml
Normal file
16
supporting-docs/_config.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Site settings
|
||||
title: Matrix
|
||||
email: webmaster@matrix.org
|
||||
description: > # this means to ignore newlines until "baseurl:"
|
||||
Matrix.org documentation
|
||||
baseurl: "/docs/guides" # the subpath of your site, e.g. /blog/
|
||||
url: "http://matrix.org" # the base hostname & protocol for your site
|
||||
twitter_username: matrixdotorg
|
||||
github_username: matrix-org
|
||||
|
||||
# Build settings
|
||||
markdown: kramdown
|
||||
|
||||
#defaults:
|
||||
#permalink: /:categories/:title.html # can use this when/if we use jekyll for all docs
|
||||
permalink: :title.html
|
1
supporting-docs/_includes/footer.html
Normal file
1
supporting-docs/_includes/footer.html
Normal file
|
@ -0,0 +1 @@
|
|||
<div id="footer"><div id="footerContent">© 2014-2015 Matrix.org</div></div>
|
16
supporting-docs/_includes/head.html
Normal file
16
supporting-docs/_includes/head.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</title>
|
||||
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
|
||||
|
||||
<link rel="stylesheet" href="http://matrix.org/site.css">
|
||||
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
|
||||
|
||||
<link rel="stylesheet" href="{{ "/css/site_overrides.css" | prepend: site.baseurl }}">
|
||||
<link rel="stylesheet" href="{{ "/css/basic.css" | prepend: site.baseurl }}">
|
||||
<link rel="stylesheet" href="{{ "/css/nature.css" | prepend: site.baseurl }}">
|
||||
|
||||
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" />
|
22
supporting-docs/_includes/nav.html
Normal file
22
supporting-docs/_includes/nav.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div id="header">
|
||||
<div id="headerContent">
|
||||
|
||||
<a href="/#about2" class="navButton">About</a>
|
||||
<a href="/docs/howtos" class="navButton">HOWTOs</a>
|
||||
<a href="/docs/spec" class="navButton">Spec</a>
|
||||
<a href="/docs/api" class="navButton">APIs</a>
|
||||
<a href="/code" class="navButton">Code</a>
|
||||
<a href="/jira" class="navButton">JIRA</a>
|
||||
<a href="/blog/faq" class="navButton">FAQ</a>
|
||||
<a href="/blog" class="navButton">Blog</a>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-54779209-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
20
supporting-docs/_layouts/default.html
Normal file
20
supporting-docs/_layouts/default.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% include head.html %}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-content" id="page">
|
||||
{% include nav.html %}
|
||||
<div class="wrapper" id="wrapper">
|
||||
<div class="document_foo" id="document">
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="push"></div>
|
||||
</div>
|
||||
{% include footer.html %}
|
||||
</body>
|
||||
|
||||
</html>
|
14
supporting-docs/_layouts/page.html
Normal file
14
supporting-docs/_layouts/page.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
<div class="post">
|
||||
|
||||
<header class="post-header">
|
||||
<h1 class="post-title">{{ page.title }}</h1>
|
||||
</header>
|
||||
|
||||
<article class="post-content">
|
||||
{{ content }}
|
||||
</article>
|
||||
|
||||
</div>
|
6
supporting-docs/_layouts/post.html
Normal file
6
supporting-docs/_layouts/post.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
{{ content }}
|
||||
|
1
supporting-docs/_plugins/jekyll-rst-master/.gitignore
vendored
Normal file
1
supporting-docs/_plugins/jekyll-rst-master/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.pyc
|
20
supporting-docs/_plugins/jekyll-rst-master/LICENSE.txt
Normal file
20
supporting-docs/_plugins/jekyll-rst-master/LICENSE.txt
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
Copyright (c) 2011 Greg Thornton, http://xdissent.com
|
||||
|
||||
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.
|
97
supporting-docs/_plugins/jekyll-rst-master/README.rst
Normal file
97
supporting-docs/_plugins/jekyll-rst-master/README.rst
Normal file
|
@ -0,0 +1,97 @@
|
|||
Overview
|
||||
========
|
||||
|
||||
This plugin adds `ReStructuredText`_ support to `Jekyll`_ and `Octopress`_.
|
||||
It renders ReST in posts and pages, and provides a custom directive to
|
||||
support Octopress-compatible syntax highlighting.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
* Jekyll *or* Octopress >= 2.0
|
||||
* Docutils
|
||||
* Pygments
|
||||
* `RbST`_
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
1. Install Docutils and Pygments.
|
||||
|
||||
The most convenient way is to use virtualenv_burrito:
|
||||
|
||||
::
|
||||
|
||||
$ curl -s https://raw.github.com/brainsik/virtualenv-burrito/master/virtualenv-burrito.sh | bash
|
||||
$ source /Users/xdissent/.venvburrito/startup.sh
|
||||
$ mkvirtualenv jekyll-rst
|
||||
$ pip install docutils pygments
|
||||
|
||||
2. Install RbST.
|
||||
|
||||
If you use `bundler`_ with Octopress, add ``gem 'RbST'`` to
|
||||
your ``Gemfile`` in the ``development`` group, then run
|
||||
``bundle install``. Otherwise, ``gem install RbST``.
|
||||
|
||||
3. Install the plugin.
|
||||
|
||||
For Jekyll:
|
||||
|
||||
::
|
||||
|
||||
$ cd <jekyll-project-path>
|
||||
$ git submodule add https://github.com/xdissent/jekyll-rst.git _plugins/jekyll-rst
|
||||
|
||||
For Octopress:
|
||||
|
||||
::
|
||||
|
||||
$ cd <octopress-project-path>
|
||||
$ git submodule add https://github.com/xdissent/jekyll-rst.git plugins/jekyll-rst
|
||||
|
||||
4. Start blogging in ReStructuredText. Any file with the ``.rst`` extension
|
||||
will be parsed as ReST and rendered into HTML.
|
||||
|
||||
.. note:: Be sure to activate the ``jekyll-rst`` virtualenv before generating
|
||||
the site by issuing a ``workon jekyll-rst``. I suggest you follow `Harry
|
||||
Marr's advice`_ and create a ``.venv`` file that will automatically
|
||||
activate the ``jekyll-rst`` virtualenv when you ``cd`` into your project.
|
||||
|
||||
Source Code Highlighting
|
||||
========================
|
||||
|
||||
A ``code-block`` ReST directive is registered and aliased as ``sourcecode``.
|
||||
It adds syntax highlighting to code blocks in your documents::
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
# Output "I love ReST"
|
||||
say = "I love ReST"
|
||||
puts say
|
||||
|
||||
Optional arguments exist to supply a caption, link, and link title::
|
||||
|
||||
.. code-block:: console
|
||||
:caption: Read Hacker News on a budget
|
||||
:url: http://news.ycombinator.com
|
||||
:title: Hacker News
|
||||
|
||||
$ curl http://news.ycombinator.com | less
|
||||
|
||||
Octopress already includes style sheets for syntax highlighting, but you'll
|
||||
need to generate one yourself if using Jekyll::
|
||||
|
||||
$ pygmentize -S default -f html > css/pygments.css
|
||||
|
||||
Octopress Tips
|
||||
==============
|
||||
|
||||
* Use ``.. more`` in your ReST documents to indicate where Octopress's
|
||||
``excerpt`` tag should split your content for summary views.
|
||||
|
||||
.. _ReStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _Jekyll: http://jekyllrb.com/
|
||||
.. _Octopress: http://octopress.com/
|
||||
.. _RbST: http://rubygems.org/gems/RbST
|
||||
.. _bundler: http://gembundler.com/
|
||||
.. _Harry Marr's advice: http://hmarr.com/2010/jan/19/making-virtualenv-play-nice-with-git/
|
30
supporting-docs/_plugins/jekyll-rst-master/converter.rb
Normal file
30
supporting-docs/_plugins/jekyll-rst-master/converter.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
require 'rbst'
|
||||
|
||||
module Jekyll
|
||||
class RestConverter < Converter
|
||||
safe true
|
||||
|
||||
priority :low
|
||||
|
||||
def matches(ext)
|
||||
ext =~ /rst/i
|
||||
end
|
||||
|
||||
def output_ext(ext)
|
||||
".html"
|
||||
end
|
||||
|
||||
def convert(content)
|
||||
RbST.executables = {:html => "#{File.expand_path(File.dirname(__FILE__))}/rst2html.py"}
|
||||
RbST.new(content).to_html(:part => :fragment, :initial_header_level => 2)
|
||||
end
|
||||
end
|
||||
|
||||
module Filters
|
||||
def restify(input)
|
||||
site = @context.registers[:site]
|
||||
converter = site.getConverterImpl(Jekyll::RestConverter)
|
||||
converter.convert(input)
|
||||
end
|
||||
end
|
||||
end
|
97
supporting-docs/_plugins/jekyll-rst-master/directives.py
Normal file
97
supporting-docs/_plugins/jekyll-rst-master/directives.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# Define a new directive `code-block` (aliased as `sourcecode`) that uses the
|
||||
# `pygments` source highlighter to render code in color.
|
||||
#
|
||||
# Incorporates code from the `Pygments`_ documentation for `Using Pygments in
|
||||
# ReST documents`_ and `Octopress`_.
|
||||
#
|
||||
# .. _Pygments: http://pygments.org/
|
||||
# .. _Using Pygments in ReST documents: http://pygments.org/docs/rstdirective/
|
||||
# .. _Octopress: http://octopress.org/
|
||||
|
||||
import re
|
||||
import os
|
||||
import md5
|
||||
import __main__
|
||||
|
||||
# Absolute path to pygments cache dir
|
||||
PYGMENTS_CACHE_DIR = os.path.abspath(os.path.join(os.path.dirname(__main__.__file__), '../../.pygments-cache'))
|
||||
|
||||
# Ensure cache dir exists
|
||||
if not os.path.exists(PYGMENTS_CACHE_DIR):
|
||||
os.makedirs(PYGMENTS_CACHE_DIR)
|
||||
|
||||
from pygments.formatters import HtmlFormatter
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives, Directive
|
||||
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
||||
|
||||
class Pygments(Directive):
|
||||
""" Source code syntax hightlighting.
|
||||
"""
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
string_opts = ['title', 'url', 'caption']
|
||||
option_spec = dict([(key, directives.unchanged) for key in string_opts])
|
||||
has_content = True
|
||||
|
||||
def run(self):
|
||||
self.assert_has_content()
|
||||
try:
|
||||
lexer_name = self.arguments[0]
|
||||
lexer = get_lexer_by_name(lexer_name)
|
||||
except ValueError:
|
||||
# no lexer found - use the text one instead of an exception
|
||||
lexer_name = 'text'
|
||||
lexer = TextLexer()
|
||||
formatter = HtmlFormatter()
|
||||
|
||||
# Construct cache filename
|
||||
cache_file = None
|
||||
content_text = u'\n'.join(self.content)
|
||||
cache_file_name = '%s-%s.html' % (lexer_name, md5.new(content_text).hexdigest())
|
||||
cached_path = os.path.join(PYGMENTS_CACHE_DIR, cache_file_name)
|
||||
|
||||
# Look for cached version, otherwise parse
|
||||
if os.path.exists(cached_path):
|
||||
cache_file = open(cached_path, 'r')
|
||||
parsed = cache_file.read()
|
||||
else:
|
||||
parsed = highlight(content_text, lexer, formatter)
|
||||
|
||||
# Strip pre tag and everything outside it
|
||||
pres = re.compile("<pre>(.+)<\/pre>", re.S)
|
||||
stripped = pres.search(parsed).group(1)
|
||||
|
||||
# Create tabular code with line numbers
|
||||
table = '<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers">'
|
||||
lined = ''
|
||||
for idx, line in enumerate(stripped.splitlines(True)):
|
||||
table += '<span class="line-number">%d</span>\n' % (idx + 1)
|
||||
lined += '<span class="line">%s</span>' % line
|
||||
table += '</pre></td><td class="code"><pre><code class="%s">%s</code></pre></td></tr></table></div>' % (lexer_name, lined)
|
||||
|
||||
# Add wrapper with optional caption and link
|
||||
code = '<figure class="code">'
|
||||
if self.options:
|
||||
caption = ('<span>%s</span>' % self.options['caption']) if 'caption' in self.options else ''
|
||||
title = self.options['title'] if 'title' in self.options else 'link'
|
||||
link = ('<a href="%s">%s</a>' % (self.options['url'], title)) if 'url' in self.options else ''
|
||||
|
||||
if caption or link:
|
||||
code += '<figcaption>%s %s</figcaption>' % (caption, link)
|
||||
code += '%s</figure>' % table
|
||||
|
||||
# Write cache
|
||||
if cache_file is None:
|
||||
cache_file = open(cached_path, 'w')
|
||||
cache_file.write(parsed)
|
||||
cache_file.close()
|
||||
|
||||
return [nodes.raw('', code, format='html')]
|
||||
|
||||
directives.register_directive('code-block', Pygments)
|
||||
directives.register_directive('sourcecode', Pygments)
|
39
supporting-docs/_plugins/jekyll-rst-master/rst2html.py
Normal file
39
supporting-docs/_plugins/jekyll-rst-master/rst2html.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# :Author: David Goodger, the Pygments team, Guenter Milde
|
||||
# :Date: $Date: $
|
||||
# :Copyright: This module has been placed in the public domain.
|
||||
|
||||
# This is a merge of the `Docutils`_ `rst2html` front end with an extension
|
||||
# suggestion taken from the `Pygments`_ documentation, reworked specifically
|
||||
# for `Octopress`_.
|
||||
#
|
||||
# .. _Pygments: http://pygments.org/
|
||||
# .. _Docutils: http://docutils.sourceforge.net/
|
||||
# .. _Octopress: http://octopress.org/
|
||||
|
||||
"""
|
||||
A front end to docutils, producing HTML with syntax colouring using pygments
|
||||
"""
|
||||
|
||||
try:
|
||||
import locale
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except:
|
||||
pass
|
||||
|
||||
from transform import transform
|
||||
from docutils.writers.html4css1 import Writer
|
||||
from docutils.core import default_description
|
||||
from directives import Pygments
|
||||
|
||||
description = ('Generates (X)HTML documents from standalone reStructuredText '
|
||||
'sources. Uses `pygments` to colorize the content of'
|
||||
'"code-block" directives. Needs an adapted stylesheet'
|
||||
+ default_description)
|
||||
|
||||
def main():
|
||||
return transform(writer=Writer(), part='html_body')
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(main())
|
42
supporting-docs/_plugins/jekyll-rst-master/transform.py
Normal file
42
supporting-docs/_plugins/jekyll-rst-master/transform.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import sys
|
||||
from docutils.core import publish_parts
|
||||
from optparse import OptionParser
|
||||
from docutils.frontend import OptionParser as DocutilsOptionParser
|
||||
from docutils.parsers.rst import Parser
|
||||
|
||||
def transform(writer=None, part=None):
|
||||
p = OptionParser(add_help_option=False)
|
||||
|
||||
# Collect all the command line options
|
||||
docutils_parser = DocutilsOptionParser(components=(writer, Parser()))
|
||||
for group in docutils_parser.option_groups:
|
||||
p.add_option_group(group.title, None).add_options(group.option_list)
|
||||
|
||||
p.add_option('--part', default=part)
|
||||
|
||||
opts, args = p.parse_args()
|
||||
|
||||
settings = dict({
|
||||
'file_insertion_enabled': False,
|
||||
'raw_enabled': False,
|
||||
'doctitle_xform': False,
|
||||
})
|
||||
|
||||
if len(args) == 1:
|
||||
try:
|
||||
content = open(args[0], 'r').read()
|
||||
except IOError:
|
||||
content = args[0]
|
||||
else:
|
||||
content = sys.stdin.read()
|
||||
|
||||
parts = publish_parts(
|
||||
source=content,
|
||||
writer=writer,
|
||||
settings_overrides=settings
|
||||
)
|
||||
if 'html_body' in parts:
|
||||
return parts['html_body']
|
||||
if opts.part in parts:
|
||||
return parts[opts.part]
|
||||
return ''
|
51
supporting-docs/_plugins/project_version_tag.rb
Normal file
51
supporting-docs/_plugins/project_version_tag.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
module Jekyll
|
||||
class ProjectVersionTag < Liquid::Tag
|
||||
NO_GIT_MESSAGE = 'Oops, are you sure this is a git project?'
|
||||
UNABLE_TO_PARSE_MESSAGE = 'Sorry, could not read project version at the moment'
|
||||
|
||||
def render(context)
|
||||
if git_repo?
|
||||
current_version.chomp
|
||||
else
|
||||
NO_GIT_MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_version
|
||||
@_current_version ||= begin
|
||||
# attempt to find the latest tag, falling back to last commit
|
||||
version = git_describe || parse_head
|
||||
|
||||
version || UNABLE_TO_PARSE_MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
def git_describe
|
||||
tagged_version = %x{ git describe --tags --always }
|
||||
|
||||
if command_succeeded?
|
||||
tagged_version
|
||||
end
|
||||
end
|
||||
|
||||
def parse_head
|
||||
head_commitish = %x{ git rev-parse --short HEAD }
|
||||
|
||||
if command_succeeded?
|
||||
head_commitish
|
||||
end
|
||||
end
|
||||
|
||||
def command_succeeded?
|
||||
!$?.nil? && $?.success?
|
||||
end
|
||||
|
||||
def git_repo?
|
||||
system('git rev-parse')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_tag('project_version', Jekyll::ProjectVersionTag)
|
652
supporting-docs/_posts/2015-08-10-client-server.rst
Normal file
652
supporting-docs/_posts/2015-08-10-client-server.rst
Normal file
|
@ -0,0 +1,652 @@
|
|||
---
|
||||
layout: post
|
||||
title: Client-Server API
|
||||
categories: guides
|
||||
---
|
||||
|
|
||||
|
||||
.. figure:: http://matrix.org/matrix.png
|
||||
:width: 305px
|
||||
:height: 130px
|
||||
:alt: [matrix]
|
||||
:align: center
|
||||
|
||||
|
||||
How to use the client-server API
|
||||
================================
|
||||
|
||||
.. NOTE::
|
||||
The git version of this document is {% project_version %}
|
||||
|
||||
This guide focuses on how the client-server APIs *provided by the reference
|
||||
home server* can be used. Since this is specific to a home server
|
||||
implementation, there may be variations in relation to registering/logging in
|
||||
which are not covered in extensive detail in this guide.
|
||||
|
||||
If you haven't already, get a home server up and running on
|
||||
``http://localhost:8008``.
|
||||
|
||||
|
||||
Accounts
|
||||
========
|
||||
Before you can send and receive messages, you must **register** for an account.
|
||||
If you already have an account, you must **login** into it.
|
||||
|
||||
.. NOTE::
|
||||
`Try out the fiddle`__
|
||||
|
||||
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/register_login
|
||||
|
||||
Registration
|
||||
------------
|
||||
The aim of registration is to get a user ID and access token which you will need
|
||||
when accessing other APIs::
|
||||
|
||||
curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register"
|
||||
|
||||
{
|
||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
|
||||
"home_server": "localhost",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
|
||||
NB: If a ``user`` is not specified, one will be randomly generated for you.
|
||||
If you do not specify a ``password``, you will be unable to login to the account
|
||||
if you forget the ``access_token``.
|
||||
|
||||
Implementation note: The matrix specification does not enforce how users
|
||||
register with a server. It just specifies the URL path and absolute minimum
|
||||
keys. The reference home server uses a username/password to authenticate user,
|
||||
but other home servers may use different methods. This is why you need to
|
||||
specify the ``type`` of method.
|
||||
|
||||
Login
|
||||
-----
|
||||
The aim when logging in is to get an access token for your existing user ID::
|
||||
|
||||
curl -XGET "http://localhost:8008/_matrix/client/api/v1/login"
|
||||
|
||||
{
|
||||
"flows": [
|
||||
{
|
||||
"type": "m.login.password"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
|
||||
|
||||
{
|
||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
|
||||
"home_server": "localhost",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
|
||||
Implementation note: Different home servers may implement different methods for
|
||||
logging in to an existing account. In order to check that you know how to login
|
||||
to this home server, you must perform a ``GET`` first and make sure you
|
||||
recognise the login type. If you do not know how to login, you can
|
||||
``GET /login/fallback`` which will return a basic webpage which you can use to
|
||||
login. The reference home server implementation support username/password login,
|
||||
but other home servers may support different login methods (e.g. OAuth2).
|
||||
|
||||
|
||||
Communicating
|
||||
=============
|
||||
|
||||
In order to communicate with another user, you must **create a room** with that
|
||||
user and **send a message** to that room.
|
||||
|
||||
.. NOTE::
|
||||
`Try out the fiddle`__
|
||||
|
||||
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/create_room_send_msg
|
||||
|
||||
Creating a room
|
||||
---------------
|
||||
If you want to send a message to someone, you have to be in a room with them. To
|
||||
create a room::
|
||||
|
||||
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
{
|
||||
"room_alias": "#tutorial:localhost",
|
||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
||||
}
|
||||
|
||||
The "room alias" is a human-readable string which can be shared with other users
|
||||
so they can join a room, rather than the room ID which is a randomly generated
|
||||
string. You can have multiple room aliases per room.
|
||||
|
||||
.. TODO(kegan)
|
||||
How to add/remove aliases from an existing room.
|
||||
|
||||
|
||||
Sending messages
|
||||
----------------
|
||||
You can now send messages to this room::
|
||||
|
||||
curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
{
|
||||
"event_id": "YUwRidLecu"
|
||||
}
|
||||
|
||||
The event ID returned is a unique ID which identifies this message.
|
||||
|
||||
NB: There are no limitations to the types of messages which can be exchanged.
|
||||
The only requirement is that ``"msgtype"`` is specified. The Matrix
|
||||
specification outlines the following standard types: ``m.text``, ``m.image``,
|
||||
``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for
|
||||
more information on these types.
|
||||
|
||||
Users and rooms
|
||||
===============
|
||||
|
||||
Each room can be configured to allow or disallow certain rules. In particular,
|
||||
these rules may specify if you require an **invitation** from someone already in
|
||||
the room in order to **join the room**. In addition, you may also be able to
|
||||
join a room **via a room alias** if one was set up.
|
||||
|
||||
.. NOTE::
|
||||
`Try out the fiddle`__
|
||||
|
||||
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/room_memberships
|
||||
|
||||
Inviting a user to a room
|
||||
-------------------------
|
||||
You can directly invite a user to a room like so::
|
||||
|
||||
curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
This informs ``@myfriend:localhost`` of the room ID
|
||||
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
|
||||
|
||||
Joining a room via an invite
|
||||
----------------------------
|
||||
If you receive an invite, you can join the room::
|
||||
|
||||
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
NB: Only the person invited (``@myfriend:localhost``) can change the membership
|
||||
state to ``"join"``. Repeatedly joining a room does nothing.
|
||||
|
||||
Joining a room via an alias
|
||||
---------------------------
|
||||
Alternatively, if you know the room alias for this room and the room config
|
||||
allows it, you can directly join a room via the alias::
|
||||
|
||||
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
{
|
||||
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
|
||||
}
|
||||
|
||||
You will need to use the room ID when sending messages, not the room alias.
|
||||
|
||||
NB: If the room is configured to be an invite-only room, you will still require
|
||||
an invite in order to join the room even though you know the room alias. As a
|
||||
result, it is more common to see a room alias in relation to a public room,
|
||||
which do not require invitations.
|
||||
|
||||
Getting events
|
||||
==============
|
||||
An event is some interesting piece of data that a client may be interested in.
|
||||
It can be a message in a room, a room invite, etc. There are many different ways
|
||||
of getting events, depending on what the client already knows.
|
||||
|
||||
.. NOTE::
|
||||
`Try out the fiddle`__
|
||||
|
||||
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/event_stream
|
||||
|
||||
Getting all state
|
||||
-----------------
|
||||
If the client doesn't know any information on the rooms the user is
|
||||
invited/joined on, they can get all the user's state for all rooms::
|
||||
|
||||
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
{
|
||||
"end": "s39_18_0",
|
||||
"presence": [
|
||||
{
|
||||
"content": {
|
||||
"last_active_ago": 1061436,
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
"type": "m.presence"
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
{
|
||||
"membership": "join",
|
||||
"messages": {
|
||||
"chunk": [
|
||||
{
|
||||
"content": {
|
||||
"@example:localhost": 10,
|
||||
"default": 0
|
||||
},
|
||||
"event_id": "wAumPSTsWF",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.power_levels",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"join_rule": "public"
|
||||
},
|
||||
"event_id": "jrLVqKHKiI",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.join_rules",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"level": 10
|
||||
},
|
||||
"event_id": "WpmTgsNWUZ",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.add_state_level",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"level": 0
|
||||
},
|
||||
"event_id": "qUMBJyKsTQ",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.send_event_level",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"ban_level": 5,
|
||||
"kick_level": 5
|
||||
},
|
||||
"event_id": "YAaDmKvoUW",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.ops_levels",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": null,
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "RJbPMtCutf",
|
||||
"membership": "join",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"ts": 1409665586730,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "hello",
|
||||
"hsob_ts": 1409665660439,
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "YUwRidLecu",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"ts": 1409665660439,
|
||||
"type": "m.room.message",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"membership": "invite"
|
||||
},
|
||||
"event_id": "YjNuBKnPsb",
|
||||
"membership": "invite",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@myfriend:localhost",
|
||||
"ts": 1409666426819,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": null,
|
||||
"membership": "join",
|
||||
"prev": "join"
|
||||
},
|
||||
"event_id": "KWwdDjNZnm",
|
||||
"membership": "join",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"ts": 1409666551582,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": null,
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "JFLVteSvQc",
|
||||
"membership": "join",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"ts": 1409666587265,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
],
|
||||
"end": "s39_18_0",
|
||||
"start": "t1-11_18_0"
|
||||
},
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state": [
|
||||
{
|
||||
"content": {
|
||||
"creator": "@example:localhost"
|
||||
},
|
||||
"event_id": "dMUoqVTZca",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.create",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"@example:localhost": 10,
|
||||
"default": 0
|
||||
},
|
||||
"event_id": "wAumPSTsWF",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.power_levels",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"join_rule": "public"
|
||||
},
|
||||
"event_id": "jrLVqKHKiI",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.join_rules",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"level": 10
|
||||
},
|
||||
"event_id": "WpmTgsNWUZ",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.add_state_level",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"level": 0
|
||||
},
|
||||
"event_id": "qUMBJyKsTQ",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.send_event_level",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"ban_level": 5,
|
||||
"kick_level": 5
|
||||
},
|
||||
"event_id": "YAaDmKvoUW",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.ops_levels",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"membership": "invite"
|
||||
},
|
||||
"event_id": "YjNuBKnPsb",
|
||||
"membership": "invite",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@myfriend:localhost",
|
||||
"ts": 1409666426819,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": null,
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "JFLVteSvQc",
|
||||
"membership": "join",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"ts": 1409666587265,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
This returns all the room information the user is invited/joined on, as well as
|
||||
all of the presences relevant for these rooms. This can be a LOT of data. You
|
||||
may just want the most recent event for each room. This can be achieved by
|
||||
applying query parameters to ``limit`` this request::
|
||||
|
||||
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
{
|
||||
"end": "s39_18_0",
|
||||
"presence": [
|
||||
{
|
||||
"content": {
|
||||
"last_active_ago": 1279484,
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
"type": "m.presence"
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
{
|
||||
"membership": "join",
|
||||
"messages": {
|
||||
"chunk": [
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": null,
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "JFLVteSvQc",
|
||||
"membership": "join",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"ts": 1409666587265,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
],
|
||||
"end": "s39_18_0",
|
||||
"start": "t10-30_18_0"
|
||||
},
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state": [
|
||||
{
|
||||
"content": {
|
||||
"creator": "@example:localhost"
|
||||
},
|
||||
"event_id": "dMUoqVTZca",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.create",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"@example:localhost": 10,
|
||||
"default": 0
|
||||
},
|
||||
"event_id": "wAumPSTsWF",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.power_levels",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"join_rule": "public"
|
||||
},
|
||||
"event_id": "jrLVqKHKiI",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.join_rules",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"level": 10
|
||||
},
|
||||
"event_id": "WpmTgsNWUZ",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.add_state_level",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"level": 0
|
||||
},
|
||||
"event_id": "qUMBJyKsTQ",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.send_event_level",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"ban_level": 5,
|
||||
"kick_level": 5
|
||||
},
|
||||
"event_id": "YAaDmKvoUW",
|
||||
"required_power_level": 10,
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "",
|
||||
"ts": 1409665585188,
|
||||
"type": "m.room.ops_levels",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"membership": "invite"
|
||||
},
|
||||
"event_id": "YjNuBKnPsb",
|
||||
"membership": "invite",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@myfriend:localhost",
|
||||
"ts": 1409666426819,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": null,
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "JFLVteSvQc",
|
||||
"membership": "join",
|
||||
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
|
||||
"state_key": "@example:localhost",
|
||||
"ts": 1409666587265,
|
||||
"type": "m.room.member",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Getting live state
|
||||
------------------
|
||||
Once you know which rooms the client has previously interacted with, you need to
|
||||
listen for incoming events. This can be done like so::
|
||||
|
||||
curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
|
||||
|
||||
{
|
||||
"chunk": [],
|
||||
"end": "s39_18_0",
|
||||
"start": "s39_18_0"
|
||||
}
|
||||
|
||||
This will block waiting for an incoming event, timing out after several seconds.
|
||||
Even if there are no new events (as in the example above), there will be some
|
||||
pagination stream response keys. The client should make subsequent requests
|
||||
using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from``
|
||||
query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access
|
||||
_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the
|
||||
client reopens your app after a period of inactivity, you can resume from where
|
||||
you got up to in the event stream. If it has been a long period of inactivity,
|
||||
there may be LOTS of events waiting for the user. In this case, you may wish to
|
||||
get all state instead and then resume getting live state from a newer end token.
|
||||
|
||||
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
|
||||
in milliseconds. A timeout of 0 will not block.
|
||||
|
||||
|
||||
Example application
|
||||
-------------------
|
||||
The following example demonstrates registration and login, live event streaming,
|
||||
creating and joining rooms, sending messages, getting member lists and getting
|
||||
historical messages for a room. This covers most functionality of a messaging
|
||||
application.
|
||||
|
||||
.. NOTE::
|
||||
`Try out the fiddle`__
|
||||
|
||||
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/example_app
|
120
supporting-docs/_posts/2015-08-14-getting_involved.md
Normal file
120
supporting-docs/_posts/2015-08-14-getting_involved.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
layout: post
|
||||
title: Getting involved
|
||||
categories: guides
|
||||
---
|
||||
|
||||
# How can I get involved?
|
||||
Matrix is an ecosystem consisting of several apps written by lots of people. We at Matrix.org have written one server and a few clients, and people in the community have also written several clients, servers, and Application Services. We are collecting [a list of all known Matrix-apps](https://matrix.org/blog/try-matrix-now/).
|
||||
|
||||
|
|
||||
|
||||
You have a few options when it comes to getting involved: if you just want to use Matrix, you can [register an account on a public server using a public webclient](#reg). If you have a virtual private server (VPS) or similar, you might want to [run a server and/or client yourself](#run). If you want to look under the hood, you can [checkout the code and modify it - or write your own client or server](#checkout). Or you can write an [Application Service](#as), for example a bridge to an existing ecosystem.
|
||||
|
||||
|
|
||||
|
||||
<a class="anchor" id="reg"></a>
|
||||
|
||||
## Access Matrix via a public webclient/server
|
||||
|
||||
The easiest thing to do if you want to just have a play, is to use our reference webclient and create a user on the matrix.org homeserver. You do that by visiting http://matrix.org/beta/, selecting "Create account" and choosing your userID and password on the next page. You can also add your email, but this is optional (adding it will make it easier for your friends to find your Matrix user in the future).
|
||||
|
||||
|
|
||||
|
||||
At the bottom of the account creation page, you can pick the homeserver and identity server you want to use. In this case, we are using matrix.org's homeserver, so just leave it as-is. Your full Matrix-userID will be formed partly from the hostname your server is running as, and partly from an userID you specify when you create the account. For example, if you put bob as your userID, your full Matrix-userID will be @bob:matrix.org ("bob on matrix.org").
|
||||
|
||||
|
|
||||
|
||||
You can use multiple clients with the same user, so you might want to also look at our [Android](https://github.com/matrix-org/matrix-android-console) or [iOS](https://github.com/matrix-org/matrix-ios-console) clients for your mobile phone!
|
||||
|
||||
|
|
||||
|
||||
<a class="anchor" id="run"></a>
|
||||
|
||||
## Run a server and/or client yourself
|
||||
|
||||
### Running your own client:
|
||||
|
||||
You can run your own Matrix client; there are [numerous clients available](https://matrix.org/blog/try-matrix-now/). You can take Matrix.org's [reference client](https://github.com/matrix-org/matrix-angular-sdk) and use it as-is - or modify it any way you want! Since it's written in JavaScript, running a client is [really easy](https://github.com/matrix-org/matrix-angular-sdk#running)!
|
||||
|
||||
|
|
||||
|
||||
### Running your own homeserver:
|
||||
|
||||
One of the core features of Matrix is that anyone can run a homeserver and join the federated network on equal terms (there is no hierarchy). If you want to set up your own homeserver, please see the relevant docs of the server you want to run. If you want to run Matrix.org's reference homeserver, please consult the [readme of the Synapse project](https://github.com/matrix-org/synapse/blob/master/README.rst).
|
||||
|
||||
|
|
||||
|
||||
Note that Synapse comes with a bundled Matrix.org webclient - but you can tell it to use your [development checkout snapshot instead](https://github.com/matrix-org/matrix-angular-sdk#matrix-angular-sdk) (or to not run a webclient at all via the "web_client: false" config option).
|
||||
|
||||
|
|
||||
|
||||
<a class="anchor" id="checkout"></a>
|
||||
|
||||
## Checkout our code - or write your own
|
||||
|
||||
As described above, you can clone our code and [run a server and/or client yourself](#run). Infact, all the code that we at Matrix.org write is available from [our github](http://github.com/matrix-org) - and other servers and clients may also be open sourced - see [our list of all known Matrix-apps](https://matrix.org/blog/try-matrix-now/).
|
||||
|
||||
|
|
||||
|
||||
You can also implement your own client or server - after all, Matrix is at its core "just" a specification of a protocol.
|
||||
|
||||
|
|
||||
|
||||
### Write your own client:
|
||||
|
||||
The [client-server API spec](http://matrix.org/docs/howtos/client-server.html) describes what API calls are available to clients, but a quick step-by-step guide would include:
|
||||
|
||||
|
|
||||
|
||||
1. Get a user either by registering your user in an existing client or running the [new-user script](https://github.com/matrix-org/synapse/blob/master/scripts/register_new_matrix_user) if you are running your own Synapse homeserver.
|
||||
|
||||
2. Assuming the homeserver you are using allows logins by password, log in via the login API:
|
||||
```
|
||||
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
|
||||
```
|
||||
3. If successful, this returns the following, including an `access_token`:
|
||||
|
||||
{
|
||||
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
|
||||
"home_server": "localhost",
|
||||
"user_id": "@example:localhost"
|
||||
}
|
||||
|
||||
4. This ``access_token`` will be used for authentication for the rest of your API calls. Potentially the next step you want is to make a call to the initialSync API and get the last x events from each room your user is in (via the limit parameter):
|
||||
```
|
||||
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
5. In addition to the last x events for all the rooms the user is interested in, this returns all the presences relevant for these rooms. Once you know which rooms the client has previously interacted with, you need to listen for incoming events. This can be done like so:
|
||||
```
|
||||
curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
6. This request will block waiting for an incoming event, timing out after several seconds if there is no event, returning something like this:
|
||||
|
||||
{
|
||||
"chunk": [],
|
||||
"end": "s39_18_0",
|
||||
"start": "s39_18_0"
|
||||
}
|
||||
7. Even if there are no new events (as in the example above), there will be some pagination stream response keys. The client should make subsequent requests using the value of the "end" key (in this case s39_18_0) as the from query parameter e.g.
|
||||
```
|
||||
http://localhost:8008/_matrix/client/api/v1/events?access _token=YOUR_ACCESS_TOKEN&from=s39_18_0
|
||||
```
|
||||
|
||||
8. This ensures that you only get new events. Now you have initial rooms and presence, and a stream of events - a good client should be able to process all these events and present them to the user. And potentially you might want to add functionality to generate events as well (such as messages from the user, for example) - again please consult the [client-server API spec](http://matrix.org/docs/howtos/client-server.html)!
|
||||
|
||||
### Write your own server:
|
||||
|
||||
We are still working on the server-server spec, so the best thing to do if you are interested in writing a server, is to come talk to us in [#matrix:matrix.org](https://matrix.org/beta/#/room/%23matrix:matrix.org).
|
||||
|
||||
If you are interested in how federation works, please see the [federation API chapter in the spec](http://matrix.org/docs/spec/#federation-api).
|
||||
|
||||
|
|
||||
|
||||
<a class="anchor" id="as"></a>
|
||||
|
||||
## Write an Application Service:
|
||||
|
||||
Information about Application services and how they can be used can be found in the [Application services](./application_services.html) document! (This is based on Kegan's excellent blog posts, but now lives here so it can be kept up-to-date!)
|
413
supporting-docs/_posts/2015-08-19-faq.md
Normal file
413
supporting-docs/_posts/2015-08-19-faq.md
Normal file
|
@ -0,0 +1,413 @@
|
|||
---
|
||||
layout: post
|
||||
title: FAQ
|
||||
date: 2015-08-19 16:58:07
|
||||
categories: guides
|
||||
---
|
||||
<link href="/docs/guides/css/faq.css" type="text/css" rel="stylesheet" />
|
||||
|
||||
# FAQ
|
||||
{:.no_toc}
|
||||
|
||||
Categories
|
||||
----------
|
||||
{:.no_toc}
|
||||
|
||||
[General](#general)
|
||||
|
||||
[Quick Start](#quick-start)
|
||||
|
||||
[Standard](#standard)
|
||||
|
||||
[APIs](#apis)
|
||||
|
||||
[Reference Implementations](#reference-implementations)
|
||||
|
||||
|
|
||||
|
||||
FAQ Content
|
||||
-----------
|
||||
{:.no_toc}
|
||||
|
||||
|
||||
* TOC
|
||||
{:toc}
|
||||
|
||||
### General
|
||||
|
||||
##### What is Matrix?
|
||||
|
||||
Matrix is an ambitious new open standard for open, distributed,
|
||||
real-time communication over IP. It defines interoperable Instant
|
||||
Messaging and VoIP, providing pragmatic HTTP APIs and open source
|
||||
reference implementations for creating and running your own real-time
|
||||
communication infrastructure.
|
||||
|
||||
##### What is Matrix's Mission?
|
||||
|
||||
Matrix.org's initial inspiration and goal has been to fix the problem of
|
||||
fragmented IP communications. But Matrix's real potential and ultimate
|
||||
mission is to be a generic messaging and data synchronisation system for
|
||||
the web - allowing people, services and devices to easily communicate
|
||||
with each other with full history.
|
||||
|
||||
##### What does Matrix provide?
|
||||
|
||||
Today Matrix provides a new [open standard](/docs/spec),
|
||||
[APIs](/docs/api) to integrate a service to the Matrix ecosystem and
|
||||
reference [open source
|
||||
implementations](http://github.com/matrix-org/synapse) of the standard.
|
||||
|
||||
##### What does this mean for users?
|
||||
|
||||
The aim is to provide an analogous ecosystem to email - one where you
|
||||
can communicate with pretty much anyone, without caring what app or
|
||||
server they are using, using whichever app & server you chose to use,
|
||||
and a nice neutral identity system like an e-mail address or phone
|
||||
number to discover people to talk to.
|
||||
|
||||
##### What kind of company is Matrix.org?
|
||||
|
||||
Matrix is an open initiative which acts as a neutral custodian of the
|
||||
Matrix standard. It's not actually incorporated anywhere at the moment
|
||||
but we are looking at the best legal structure for the future. We are
|
||||
committed to keeping the Matrix project open.
|
||||
|
||||
##### Who is funding Matrix.org?
|
||||
|
||||
We have been given permission by our employers, Amdocs, to work on
|
||||
Matrix as an independent non-profit initiative.
|
||||
|
||||
##### Who is building Matrix?
|
||||
|
||||
We're a team of ~10 people with decades of experience building custom
|
||||
VoIP and Messaging apps for mobile network operators. Most of us have
|
||||
day jobs at Amdocs or OpenMarket, but we are supported by a mix of
|
||||
freelancers and volunteers.
|
||||
|
||||
##### Why are you called Matrix?
|
||||
|
||||
We are called Matrix because we provide a structure in which all
|
||||
communication can be matrixed together.
|
||||
|
||||
##### Why have you released this as open source?
|
||||
|
||||
We believe that any open standard defining interoperable communication
|
||||
needs to be justified, demonstrated and validated with transparent open
|
||||
source implementations. For Matrix to achieve its mission of making all
|
||||
communications services interoperable we believe it needs to be truly
|
||||
open, giving people access to take all the code we produce and to use
|
||||
and build on top of it.
|
||||
|
||||
##### What do you mean by open?
|
||||
|
||||
Matrix is an open standard, meaning that we have freely published the
|
||||
details for how to interface with Matrix compliant servers and clients,
|
||||
and encourage anyone and everyone to interface with them. We also
|
||||
ensure the standard is not encumbered by any known patent licensing
|
||||
requirements.
|
||||
|
||||
|
|
||||
|
||||
Matrix is also open source, meaning that we have released the source
|
||||
code of the reference servers and clients to the public domain under the
|
||||
[Apache Licence v2](http://www.apache.org/licenses/LICENSE-2.0.html), to
|
||||
encourage anyone and everyone to run their own servers and clients, and
|
||||
enhance them and contribute their enhancements as they see fit.
|
||||
|
||||
##### What does federated mean?
|
||||
|
||||
Federation allows separate deployments of a communication service to
|
||||
communicate with each other - for instance a mail server run by Google
|
||||
federates with a mail server run by Microsoft when you send email from
|
||||
@gmail.com to @outlook.com.
|
||||
|
||||
|
|
||||
|
||||
Federation is different to interoperability, as interoperable clients
|
||||
may simply be running on the same deployment - whereas in federation the
|
||||
deployments themselves are exchanging data in a compatible manner.
|
||||
|
||||
|
|
||||
|
||||
Matrix provides open federation - meaning that anyone on the internet
|
||||
can join into the Matrix ecosystem by deploying their own server.
|
||||
|
||||
##### How is this like e-mail?
|
||||
|
||||
When email was first set up in the early ‘80s, companies like Compuserve
|
||||
and AT&T and Sprint set up isolated email communities which only allowed
|
||||
you to exchange mail with users on the same system. If you got your
|
||||
email from one service and your friend from another, then you couldn't
|
||||
message each other. This is basically the situation we're in today with
|
||||
VoIP and IM.
|
||||
|
||||
##### Why has no-one done this before?
|
||||
|
||||
There have been several attempts before including SIP, XMPP and RCS.
|
||||
All of these have had some level of success, but
|
||||
technological/usability/economic factors have ended up limiting their
|
||||
success in providing true open federation.
|
||||
|
||||
##### What is the difference between Matrix and IRC?
|
||||
|
||||
We love IRC. In fact, as of today the core Matrix team still uses it as
|
||||
our primary communication tool. Between us we've written IRCds, IRC bots
|
||||
and admined dreamforge, UnrealIRCd, epona, ircservices and several
|
||||
others. That said, it has some limitations that Matrix seeks to improve
|
||||
on:
|
||||
|
||||
- Text only
|
||||
- No history
|
||||
- No multiple-device support
|
||||
- No presence support
|
||||
- Fragmented identity model
|
||||
- No open federation
|
||||
- No standard APIs, just an archaic TCP line protocol
|
||||
- Non-standardised federation protocol
|
||||
- No built-in end-to-end encryption
|
||||
- Disruptive net-splits
|
||||
- Non-extensible
|
||||
|
||||
##### What is the difference between Matrix and XMPP?
|
||||
|
||||
The Matrix team used XMPP (Openfire, ejabberd, spectrum, asmack,
|
||||
XMPPFramework) for IM before starting to experiment with open HTTP APIs
|
||||
as an alternative. The main issues with XMPP that drove us in this
|
||||
direction were:
|
||||
|
||||
- Not particularly web-friendly - you can't easily speak XMPP from a
|
||||
web browser. (N.B. Nowadays you have options like XMPP-FTW and
|
||||
Stanza.io that help loads with letting browsers talk XMPP).
|
||||
- Single logical server per MUC is a single point of control and
|
||||
availability. (MUCs can be distributed over multiple physical
|
||||
servers, but they still sit behind a single logical JID and domain.
|
||||
FMUC improves this with a similar approach to Matrix, but at time of
|
||||
writing there are no open implementations.)
|
||||
- History synchronisation is very much a second class citizen feature
|
||||
- Stanzas aren't framed or reliably delivered without extensions. (See
|
||||
[wiki.xmpp.org](http://wiki.xmpp.org/web/Myths#Myth_Four:_XMPP_is_unreliable_without_a_bunch_of_extensions.)
|
||||
for an XMPP take on this)
|
||||
- Multiple device support is limited. (Apparently Carbons and MAM help
|
||||
with this)
|
||||
- Baseline feature set is so minimal that fragmentation of features
|
||||
between clients and servers is common
|
||||
- No strong identity system (i.e. no standard E2E PKI, unless you
|
||||
count X.509 certs, which [are
|
||||
questionable](http://www.thoughtcrime.org/blog/ssl-and-the-future-of-authenticity/))
|
||||
- Not particularly well designed for mobile use cases: push;
|
||||
bandwidth-efficient transports. (Since the time of writing a [Push
|
||||
XEP has appeared](http://xmpp.org/extensions/xep-0357.html), and
|
||||
[wiki.xmpp.org](http://wiki.xmpp.org/web/Myths#Myth_Three:_It.27s_too_bandwidth-inefficient_for_mobile.)
|
||||
claims that XMPP runs fine over a 9600bps + 30s latency link.)
|
||||
|
||||
The whole subject of XMPP vs Matrix seems to bring out the worst in
|
||||
people. We think of the standards as being quite different; at its core
|
||||
Matrix can be thought of as an eventually consistent global JSON db with
|
||||
an HTTP API and pubsub semantics - whilst XMPP can be thought of as a
|
||||
message passing protocol. You can use them both to build chat systems;
|
||||
you can use them both to build pubsub systems; each comes with different
|
||||
tradeoffs. Matrix has a 'kitchen sink' baseline of functionality; XMPP
|
||||
has a deliberately minimal baseline set of functionality. If XMPP does
|
||||
what you need it to do, then we're genuinely happy for you :) Meanwhile,
|
||||
rather than competing, an XMPP Bridge like [Skaverat's xmpptrix
|
||||
beta](https://github.com/SkaveRat/xmpptrix) has potential to let both
|
||||
environments coexist and make the most of each other's benefits.
|
||||
|
||||
##### What is the difference between Matrix and PSYC?
|
||||
|
||||
PSYC is a open federated messaging protocol loosely inspired by IRC. In
|
||||
version 1 it was a standalone protocol, and in version 2 it is being
|
||||
reutilised as the messaging layer on top of GNUnet. We honestly don't
|
||||
know that much about it, beyond trying to use psycd as an XMPP\<-\>IRC
|
||||
bridge in 2010. Matrix differentiates primarily by providing simple HTTP
|
||||
APIs rather than the more exotic compact line protocol in PSYC v1 or the
|
||||
complicated GNUnet stack in v2. Meanwhile, Matrix doesn't provide of
|
||||
the metadata protection guarantees that GNUnet/PSYC aims for.
|
||||
|
||||
|
|
||||
|
||||
See [http://about.psyc.eu/Matrix](http://about.psyc.eu/Matrix) for
|
||||
PSYC's views on Matrix.
|
||||
|
||||
##### What is the difference between Matrix and Tox?
|
||||
|
||||
Tox.im looks to be a very cool clone of Skype - a fully decentralised
|
||||
peer-to-peer network. Matrix is deliberately not peer-to-peer; instead
|
||||
each user has a well-defined homeserver which stores his data and that
|
||||
he can depend upon. Matrix provides HTTP APIs; Tox.im provides C APIs.
|
||||
We haven't actually played with Tox at all yet.
|
||||
|
||||
##### How does Matrix compare with something like Trillian or Pidgin?
|
||||
|
||||
Trillian and Pidgin and similar aggregating IM clients merge all your IM
|
||||
activity into a single user experience. However, your history and
|
||||
identity is still fragmented across the networks. People can't find you
|
||||
easily, and your history is fragmented (other than on the device
|
||||
where the client runs). And rather than being able to chose the right
|
||||
app for the job when communicating with people, you are pushed towards
|
||||
relying on a specific aggregation app.
|
||||
|
||||
##### What Matrix compliant apps are there?
|
||||
|
||||
None yet, other than our examples. It's early days :)
|
||||
|
||||
##### Why do you think existing apps will ever join this?
|
||||
|
||||
We firmly believe it is what is right for the consumer. As people begin
|
||||
to use interoperable communications tools service providers will see the
|
||||
benefit and compete on quality of service, security and features rather
|
||||
than relying on locking people into their walled garden. We believe as
|
||||
soon as users see the availability and benefits of interoperable
|
||||
services they will demand it.
|
||||
|
||||
##### Why aren't you doing this through the IETF? or W3C? or 3GPP?
|
||||
|
||||
We do recognise the advantages of working with existing standards
|
||||
bodies. We have been focused on writing code and getting it out. As
|
||||
Matrix matures it may well be appropriate to work with an official
|
||||
standard body.
|
||||
|
||||
|
|
||||
|
||||
### Quick Start
|
||||
|
||||
##### How do I get an account and get started?
|
||||
|
||||
The quickest way is to just jump to the demo webclient at
|
||||
[http://matrix.org/beta](http://matrix.org/beta) and sign up. Please note that you can point the
|
||||
webclient to access any homeserver - you don't have to use matrix.org,
|
||||
although as of day 1, matrix.org is the only communal homeserver
|
||||
available.
|
||||
|
||||
##### What can I actually do with this?
|
||||
|
||||
The demo webclient provides a simple chatroom interface to Matrix -
|
||||
letting the user interact with users and rooms anywhere within the
|
||||
Matrix federation. Text and image messages are supported, and basic
|
||||
voice-only VoIP calling via WebRTC is supported in one-to-one rooms.
|
||||
|
||||
##### How do I connect my homeserver to the public Matrix network?
|
||||
|
||||
See
|
||||
[http://github.com/matrix-org/synapse](http://github.com/matrix-org/synapse)
|
||||
for details
|
||||
|
||||
##### How do I Matrix-enable my existing app?
|
||||
|
||||
See the [Client-Server API
|
||||
HOWTO](http://matrix.org/docs/howtos/client-server.html) for an example
|
||||
of how to use Matrix's client-server API to let your app communicate
|
||||
with users via Matrix. We're currently working out the best way to
|
||||
integrate your application's existing identity system with Matrix.
|
||||
|
||||
##### How can I write a client on Matrix?
|
||||
|
||||
See the [Client-Server API
|
||||
HOWTO](http://matrix.org/docs/howtos/client-server.html) and the [API
|
||||
docs](/docs/api) and [the Spec](/docs/spec) for all the details you need
|
||||
to write a client.
|
||||
|
||||
##### *How can I help out with this?*
|
||||
|
||||
Install synapse and tell us how you get on. Critique the spec. Write
|
||||
clients. Just come say hi on [\#matrix:matrix.org](/alpha) or the
|
||||
[mailing lists](/mailman/listinfo/matrix-users)!
|
||||
|
||||
##### Where can I get support?
|
||||
|
||||
[\#matrix:matrix.org](/alpha), \#matrix on irc.freenode.net or
|
||||
the [mailing lists](/mailman/listinfo/matrix-users) are your best bets.
|
||||
|
||||
##### How do I register custom matrix event types?
|
||||
|
||||
We're not yet managing a registry of custom matrix event types. If you
|
||||
have any particularly good ones you want to tell the world about, please
|
||||
use the [mailing list](/mailman/listinfo/matrix-users) for now.
|
||||
|
||||
##### How mature is this?
|
||||
|
||||
We started working on Matrix in July 2014, and have opened it to the
|
||||
public in September 2014. It's early days, and under no circumstances
|
||||
should you use Matrix or Synapse for anything other than experimentation
|
||||
and learning at this point. Obviously the spec and apps are maturing
|
||||
rapidly, but as of the time of writing APIs are not frozen and the apps
|
||||
are very much a work in progress.
|
||||
|
||||
|
|
||||
|
||||
*Sorry, the FAQ is still work in progress, the rest of it will up
|
||||
soon!* *In the mean time, don't hesitate to get in touch on
|
||||
[\#matrix:matrix.org](/alpha) or the [mailing
|
||||
lists](/mailman/listinfo/matrix-users)!*
|
||||
|
||||
|
|
||||
|
||||
### Standard
|
||||
|
||||
##### What is a home server?
|
||||
|
||||
##### What is an identity sever?
|
||||
|
||||
##### Where do my conversations get stored?
|
||||
|
||||
##### What is a 3PID?
|
||||
|
||||
##### How do you do VoIP calls on Matrix?
|
||||
|
||||
##### Can I log into other homeservers with my username and password?
|
||||
|
||||
##### Why Apache Licence?
|
||||
|
||||
##### Can I write a Matrix homeserver?
|
||||
|
||||
##### How secure is this?
|
||||
|
||||
##### Why aren't you using an ORM layer like SqlAlchemy?
|
||||
|
||||
|
|
||||
|
||||
### APIs
|
||||
|
||||
##### How do I join the global Matrix federation?
|
||||
|
||||
##### What ports do I have to open up to join the global Matrix federation?
|
||||
|
||||
|
|
||||
|
||||
### Reference Implementations
|
||||
|
||||
##### What is Matrix built on - and why?
|
||||
|
||||
##### How do I run my own home server?
|
||||
|
||||
##### Can I run my own identity server?
|
||||
|
||||
Yes - the reference implementation is
|
||||
[sydent](https://github.com/matrix-org/sydent) and you can run your own
|
||||
ID server cluster that tracks 3rd party to Matrix ID mappings. If you
|
||||
want your server to participate in the global replicated Matrix ID
|
||||
service then please get in touch with us. Meanwhile, we are looking at
|
||||
ways of decentralising the 'official' Matrix identity service so that
|
||||
identity servers are 100% decentralised and can openly federate with
|
||||
each other. **N.B. that you can use Matrix without ever using the
|
||||
identity service - it exists only to map 3rd party IDs (e.g. email
|
||||
addresses) to matrix IDs to aid user discovery**.
|
||||
|
||||
##### What is Synapse?
|
||||
|
||||
##### Why is Synapse in Python/Twisted?
|
||||
|
||||
##### What are Synapse's platform requirements?
|
||||
|
||||
##### What are the Synapse webclient's requirements?
|
||||
|
||||
##### Where is the mobile app?
|
||||
|
||||
##### What decides the room member order on the webclient?
|
||||
|
||||
|
|
||||
|
||||
Any other question? Please contact us on
|
||||
[\#matrix:matrix.org](/alpha) or the [mailing
|
||||
lists](/mailman/listinfo/matrix-users)!
|
143
supporting-docs/_posts/2015-08-21-application_services.md
Normal file
143
supporting-docs/_posts/2015-08-21-application_services.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
layout: post
|
||||
title: Application services
|
||||
categories: guides
|
||||
---
|
||||
|
||||
# Application services
|
||||
|
||||
Application services are distinct modules which which sit alongside a home server providing arbitrary extensible functionality decoupled from the home server implementation. Just like the rest of Matrix, they communicate via HTTP using JSON. Application services function in a very similar way to traditional clients, but they are given much more power than a normal client. They can reserve entire namespaces of room aliases and user IDs for their own purposes. They can silently monitor events in rooms, or any events directed at any user ID. This power allows application services to have extremely useful abilities which overall enhance the end user experience.
|
||||
|
||||
|
|
||||
|
||||
One of the main use cases for application services is protocol bridges. Our Matrix server on Matrix.org links in to various IRC channels and networks. This functionality was initially implemented as a simple bot which resided as a user on the Matrix rooms we wanted to link to freenode channels (#matrix, #matrix-dev, #openwebrtc and #vuc etc). There was nothing special about this bot; it is just treated as a client. However, as we started to rely on it more and more though, we realised that there were things that were impossible for simple client-side bots to do by themselves - for example, the bot could not reserve the virtual user IDs it wanted to create, and could not lazily bridge arbitrary IRC rooms on-the-fly - and this spurred the development of Application Services.
|
||||
|
||||
|
|
||||
|
||||
### Some of the features of the IRC application service we have since implemented include:
|
||||
|
||||
- Specific channel-to-matrix room bridging : This is what the original IRC bot did. You can specify specific channels and specific room IDs, and messages will be bridged.
|
||||
- Dynamic channel-to-matrix room bridging> : This allows Matrix users to join any channel on an IRC network, rather than being forced to use one of the specific channels configured.
|
||||
- Two-way PM support : IRC users can PM the virtual "M-" users and private Matrix rooms will be created. Likewise, Matrix users can invite the virtual "@irc_Nick:domain" user IDs to a room and a PM to the IRC nick will be made.
|
||||
- IRC nick changing support: Matrix users are no longer forced to use "M-" nicks and can change them by sending "!nick" messages directly to the bridge.
|
||||
- Ident support: This allows usernames to be authenticated for virtual IRC clients, which means IRC bans can be targeted at the Matrix user rather than the entire application service.
|
||||
|
||||
|
|
||||
|
||||
### The use of the Application Services API means:
|
||||
|
||||
- The bot can reserve user IDs. This prevents humans from registering for @irc_... user IDs which would then clash with the operation of the bot.
|
||||
- The bot can reserve room aliases. This prevents humans from register for #irc_... aliases which would then clash with the operation of the bot.
|
||||
- The bot can trivially manage hundreds of users. Events are pushed to the application service directly. If you tried to do this as a client-side bot, you would need one event stream connection per virtual user.
|
||||
- The bot can lazily create rooms on demand. This means Matrix users can join non-existent room aliases and have the application service quickly track an IRC channel and create a room with that alias, allowing the join request to succeed.
|
||||
|
||||
|
|
||||
|
||||
### Implementation details:
|
||||
|
||||
- Written in Node.js, designed to be run using <code>forever</code>.
|
||||
- Built on the generic <a href="http://github.com/matrix-org/matrix-appservice-node">matrix-appservice-node</a> framework.
|
||||
- Supports sending metrics in statsd format.
|
||||
- Uses matrix-appservice-node to provide a standardised interface when writing application services, rather than an explicit web framework (though under the hood matrix-appservice-node is using Express).
|
||||
|
||||
|
|
||||
|
||||
At present, the IRC application service is in beta, and is being run on #matrix and #matrix-dev. If you want to give it a go, <a title="Matrix-IRC Application Service" href="https://github.com/matrix-org/matrix-appservice-irc">check it out on Github</a>!
|
||||
|
||||
|
|
||||
|
||||
# What Application services can do for you
|
||||
Application services have enormous potential for creating new and exciting ways to transform and enhance the core Matrix protocol. For example, you could aggregate information from multiple rooms into a summary room, or create throwaway virtual user accounts to proxy messages for a fixed user ID on-the-fly. As you may expect, all of this power assumes a high degree of trust between application services and home servers. Only home server admins can allow an application service to link up with their home server, and the application service is in no way federated to other home servers. You can think of application services as additional logic on the home server itself, without messing around with the book-keeping that home servers have to do. This makes adding useful functionality very easy.
|
||||
|
||||
|
|
||||
|
||||
### Example
|
||||
|
||||
The application service (AS) API itself uses webhooks to communicate from the home server to the AS:
|
||||
|
||||
- Room Alias Query API : The home server hits a URL on your application server to see if a room alias exists.
|
||||
- User Query API : The home server hits a URL on your application server to see if a user ID exists.
|
||||
- Push API : The home server hits a URL on your application server to notify you of new events for your users and rooms.
|
||||
|
||||
A very basic application service may want to log all messages in rooms which have an alias starting with "#logged_" (side note: logging won't work if these rooms are using end-to-end encryption).
|
||||
|
||||
Here's an example of a very basic application service using Python (with Flask and Requests) which logs room activity:
|
||||
|
||||
# app_service.py:
|
||||
|
||||
import json, requests # we will use this later
|
||||
from flask import Flask, jsonify, request
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/transactions/<transaction>", methods=["PUT"])
|
||||
def on_receive_events(transaction):
|
||||
events = request.get_json()["events"]
|
||||
for event in events:
|
||||
print "User: %s Room: %s" % (event["user_id"], event["room_id"])
|
||||
print "Event Type: %s" % event["type"]
|
||||
print "Content: %s" % event["content"]
|
||||
return jsonify({})
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
|
||||
Set your new application service running on port 5000 with:
|
||||
|
||||
python app_service.py
|
||||
|
||||
The home server needs to know that the application service exists before it will send requests to it. This is done via a registration YAML file which is specified in Synapse's main config file e.g. <code>homeserver.yaml</code>. The server admin needs to add the application service registration configuration file as an entry to this file.
|
||||
|
||||
# homeserver.yaml
|
||||
app_service_config_files:
|
||||
- "/path/to/appservice/registration.yaml"
|
||||
|
||||
NB: Note the "-" at the start; this indicates a list element. The registration file <code>registration.yaml</code> should look like:
|
||||
|
||||
# registration.yaml
|
||||
|
||||
# this is the base URL of the application service
|
||||
url: "http://localhost:5000"
|
||||
|
||||
# This is the token that the AS should use as its access_token when using the Client-Server API
|
||||
# This can be anything you want.
|
||||
as_token: wfghWEGh3wgWHEf3478sHFWE
|
||||
|
||||
# This is the token that the HS will use when sending requests to the AS.
|
||||
# This can be anything you want.
|
||||
hs_token: ugw8243igya57aaABGFfgeyu
|
||||
|
||||
# this is the local part of the desired user ID for this AS (in this case @logging:localhost)
|
||||
sender_localpart: logging
|
||||
namespaces:
|
||||
users: []
|
||||
rooms: []
|
||||
aliases:
|
||||
- exclusive: false
|
||||
regex: "#logged_.*"
|
||||
|
||||
**You will need to restart the home server after editing the config file before it will take effect.**
|
||||
|
||||
|
|
||||
|
||||
To test everything is working correctly, go ahead and explicitly create a room with the alias "#logged_test:localhost" and send a message into the room: the HS will relay the message to the AS by PUTing to /transactions/<tid> and you should see your AS print the event on the terminal. This will monitor any room which has an alias prefix of "#logged_", but it won't lazily create room aliases if they don't already exist. This means it will only log messages in the room you created before: #logged_test:localhost. Try joining the room "#logged_test2:localhost" without creating it, and it will fail. Let's fix that and add in lazy room creation:
|
||||
|
||||
@app.route("/rooms/<alias>")
|
||||
def query_alias(alias):
|
||||
alias_localpart = alias.split(":")[0][1:]
|
||||
requests.post(
|
||||
# NB: "TOKEN" is the as_token referred to in registration.yaml
|
||||
"http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=TOKEN",
|
||||
json.dumps({
|
||||
"room_alias_name": alias_localpart
|
||||
}),
|
||||
headers={"Content-Type":"application/json"}
|
||||
)
|
||||
return jsonify({})
|
||||
|
||||
This makes the application service lazily create a room with the requested alias whenever the HS queries the AS for the existence of that alias (when users try to join that room), allowing any room with the alias prefix #logged_ to be sent to the AS. Now try joining the room "#logged_test2:localhost" and it will work as you'd expect. You can see that if this were a real bridge, the AS would have checked for the existence of #logged_test2 in the remote network, and then lazily-created it in Matrix as required.
|
||||
|
||||
|
|
||||
|
||||
Application services are powerful components which extend the functionality of home servers, but they are limited. They can only ever function in a "passive" way. For example, you cannot implement an application service which censors swear words in rooms, because there is no way to prevent the event from being sent. Aside from the fact that censoring will not work when using end-to-end encryption, all federated home servers would also need to reject the event in order to stop developing an inconsistent event graph. To "actively" monitor events, another component called a "Policy Server" is required, which is beyond the scope of this post. Also, Application Services can result in a performance bottleneck, as all events on the homeserver must be ordered and sent to the registered application services. If you are bridging huge amounts of traffic, you may be better off having your bridge directly talk the Server-Server federation API rather than the simpler Application Service API.
|
||||
|
||||
I hope this demonstrates how easy it is to create an application service, along with a few ideas of the kinds of things you can do with them. Obvious uses include build protocol bridges, search engines, invisible bots, etc. For more information on the AS HTTP API, check out the new <a href="http://matrix.org/docs/spec/#application-service-api">Application Service API</a> section in the spec, or the raw drafts and spec in <a href="https://github.com/matrix-org/matrix-doc/" target="_blank">https://github.com/matrix-org/matrix-doc/</a>.
|
18
supporting-docs/_posts/index.html
Normal file
18
supporting-docs/_posts/index.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
{% include basic.css %}
|
||||
{% include nature.css %}
|
||||
|
||||
<div class="post">
|
||||
|
||||
<header class="post-header">
|
||||
<div class="post-title">{{ page.title }}</div>
|
||||
<p class="post-meta">{{ page.date | date: "%b %-d, %Y" }}{% if page.author %} • {{ page.author }}{% endif %}{% if page.meta %} • {{ page.meta }}{% endif %}</p>
|
||||
</header>
|
||||
|
||||
<article class="post-content">
|
||||
{{ content }}
|
||||
</article>
|
||||
|
||||
</div>
|
204
supporting-docs/_sass/_base.scss
Normal file
204
supporting-docs/_sass/_base.scss
Normal file
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
&: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-size: 15px;
|
||||
border: 1px solid $grey-color-light;
|
||||
border-radius: 3px;
|
||||
background-color: #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;
|
||||
}
|
||||
}
|
||||
}
|
236
supporting-docs/_sass/_layout.scss
Normal file
236
supporting-docs/_sass/_layout.scss
Normal file
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
67
supporting-docs/_sass/_syntax-highlighting.scss
Normal file
67
supporting-docs/_sass/_syntax-highlighting.scss
Normal file
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* 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
|
||||
}
|
11
supporting-docs/about.md
Normal file
11
supporting-docs/about.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: page
|
||||
title: About
|
||||
permalink: /about/
|
||||
---
|
||||
|
||||
This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](http://jekyllrb.com/)
|
||||
|
||||
You can find the source code for the Jekyll new theme at: [github.com/jglovier/jekyll-new](https://github.com/jglovier/jekyll-new)
|
||||
|
||||
You can find the source code for Jekyll at [github.com/jekyll/jekyll](https://github.com/jekyll/jekyll)
|
512
supporting-docs/css/basic.css
Normal file
512
supporting-docs/css/basic.css
Normal file
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
* basic.css
|
||||
* ~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- main layout ----------------------------------------------------------- */
|
||||
|
||||
div.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 10px 5px 0 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
float: left;
|
||||
width: 230px;
|
||||
margin-left: -100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul,
|
||||
div.sphinxsidebar ul.want-points {
|
||||
margin-left: 20px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin: 10px 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0 5px 20px;
|
||||
background-image: url(file.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li div.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- index page ------------------------------------------------------------ */
|
||||
|
||||
table.contentstable {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
table.contentstable p.biglink {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
a.biglink {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.linkdescr {
|
||||
font-style: italic;
|
||||
padding-top: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable dl, table.indextable dd {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
div.genindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.document p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.document td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
clear: both;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px 7px 0 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px 7px 0 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.admonition dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.document p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
table.footnote td, table.footnote th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
dt:target, .highlighted {
|
||||
background-color: #fbe54e;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.refcount {
|
||||
color: #060;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #ffa
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
font-size: 1.1em;
|
||||
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 5px 0px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
tt.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.viewcode-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
margin: -1px -10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.document div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* -- printout stylesheet --------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
div.document,
|
||||
div.documentwrapper,
|
||||
div.bodywrapper {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar,
|
||||
div.related,
|
||||
div.footer,
|
||||
#top-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
40
supporting-docs/css/faq.css
Normal file
40
supporting-docs/css/faq.css
Normal file
|
@ -0,0 +1,40 @@
|
|||
.toc {
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #aaaaaa;
|
||||
display: table;
|
||||
padding-top: 16px;
|
||||
padding-right: 16px;
|
||||
padding-bottom: 16px;
|
||||
list-style: none;
|
||||
color: #2EA3F2;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-left: 30px;
|
||||
color: #666;
|
||||
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-top: 48px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
h4, h5 {
|
||||
border-top: 40px solid transparent;
|
||||
margin-top: -40px;
|
||||
-webkit-background-clip: padding-box;
|
||||
-moz-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
font-style: italic;
|
||||
font-size: 16px;
|
||||
padding-top: 16px;
|
||||
color: #333;
|
||||
|
||||
}
|
||||
|
||||
|
52
supporting-docs/css/main.scss
Executable file
52
supporting-docs/css/main.scss
Executable file
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
# Only the main Sass file needs front matter (the dashes are enough)
|
||||
---
|
||||
@charset "utf-8";
|
||||
|
||||
|
||||
|
||||
// Our variables
|
||||
$base-font-family: Helvetica, Arial, sans-serif;
|
||||
$base-font-size: 16px;
|
||||
$small-font-size: $base-font-size * 0.875;
|
||||
$base-line-height: 1.5;
|
||||
|
||||
$spacing-unit: 30px;
|
||||
|
||||
$text-color: #111;
|
||||
$background-color: #fdfdfd;
|
||||
$brand-color: #2a7ae2;
|
||||
|
||||
$grey-color: #828282;
|
||||
$grey-color-light: lighten($grey-color, 40%);
|
||||
$grey-color-dark: darken($grey-color, 25%);
|
||||
|
||||
// Width of the content area
|
||||
$content-width: 1340px;
|
||||
|
||||
$on-palm: 600px;
|
||||
$on-laptop: 800px;
|
||||
|
||||
|
||||
|
||||
// Using media queries with like this:
|
||||
// @include media-query($on-palm) {
|
||||
// .wrapper {
|
||||
// padding-right: $spacing-unit / 2;
|
||||
// padding-left: $spacing-unit / 2;
|
||||
// }
|
||||
// }
|
||||
@mixin media-query($device) {
|
||||
@media screen and (max-width: $device) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Import partials from `sass_dir` (defaults to `_sass`)
|
||||
@import
|
||||
"base",
|
||||
"layout",
|
||||
"syntax-highlighting"
|
||||
;
|
285
supporting-docs/css/nature.css
Normal file
285
supporting-docs/css/nature.css
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* nature.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- nature theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 100%;
|
||||
/*background-color: #111;*/
|
||||
color: #555;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 230px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
/*
|
||||
div.document {
|
||||
background-color: #eee;
|
||||
}
|
||||
*/
|
||||
|
||||
div.document {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 30px 30px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
color: #555;
|
||||
width: 100%;
|
||||
padding: 13px 0;
|
||||
text-align: center;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #444;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.related {
|
||||
background-color: #6BA81E;
|
||||
line-height: 32px;
|
||||
color: #fff;
|
||||
text-shadow: 0px 1px 0 #444;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
div.related a {
|
||||
color: #E2F3CC;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 0.75em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper{
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: Arial, sans-serif;
|
||||
color: #222;
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
background-color: #ddd;
|
||||
text-shadow: 1px 1px 0 white
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4{
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #888;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.topless {
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 20px;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input[type=text]{
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #005B81;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #E32E00;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.document h1,
|
||||
div.document h2,
|
||||
div.document h3,
|
||||
div.document h4,
|
||||
div.document h5,
|
||||
div.document h6 {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #BED4EB;
|
||||
font-weight: normal;
|
||||
color: #212224;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 5px 0 5px 10px;
|
||||
text-shadow: 0px 1px 0 white
|
||||
}
|
||||
|
||||
div.document h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
|
||||
div.document h2 { font-size: 150%; background-color: #C8D5E3; }
|
||||
div.document h3 { font-size: 120%; background-color: #D8DEE3; }
|
||||
div.document h4 { font-size: 110%; background-color: #D8DEE3; }
|
||||
div.document h5 { font-size: 100%; background-color: #D8DEE3; }
|
||||
div.document h6 { font-size: 100%; background-color: #D8DEE3; }
|
||||
|
||||
a.headerlink {
|
||||
color: #c60f0f;
|
||||
font-size: 0.8em;
|
||||
padding: 0 4px 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
background-color: #c60f0f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.document p, div.document dd, div.document li {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title + p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.highlight{
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div.warning {
|
||||
background-color: #ffe4e4;
|
||||
border: 1px solid #f66;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
background-color: White;
|
||||
color: #222;
|
||||
line-height: 1.2em;
|
||||
border: 1px solid #C6C9CB;
|
||||
font-size: 1.1em;
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
|
||||
-moz-box-shadow: 1px 1px 1px #d8d8d8;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
font-size: 1.1em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul li dd {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul li dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li dl dd {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dd ul {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li dd ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td[colspan]:not([colspan="1"]) {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
57
supporting-docs/css/site_overrides.css
Normal file
57
supporting-docs/css/site_overrides.css
Normal file
|
@ -0,0 +1,57 @@
|
|||
p {
|
||||
line-height: 1.5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
a.anchor {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: -50px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #2EA3F2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #2EA3F2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #2EA3F2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #2EA3F2;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
min-height: 100%;
|
||||
height: auto !important; /* This line and the next line are not necessary unless you need IE6 support */
|
||||
height: 100%;
|
||||
margin: 0 auto -33px; /* the bottom margin is the negative value of the footer's height */
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.push {
|
||||
height: 33px;
|
||||
}
|
||||
|
||||
#page {
|
||||
margin-bottom: -33px;
|
||||
}
|
||||
|
||||
#document {
|
||||
margin-top: 10px; /* We want a little whitespace before the page content starts */
|
||||
}
|
||||
|
30
supporting-docs/feed.xml
Normal file
30
supporting-docs/feed.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
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>
|
14
supporting-docs/index.html
Normal file
14
supporting-docs/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
layout: default
|
||||
---
|
||||
<div class="home">
|
||||
|
||||
<h1>Guides</h1>
|
||||
|
||||
<p>Here is a collection of guides that might help you get involved with Matrix.</p>
|
||||
<p>First, there is the <a href="./getting_involved.html" title="Getting Involved">Getting Involved</a> guide, which explains various ways of getting started with Matrix.</p>
|
||||
<p>The <a href="./client-server.html" title="Client-Server API">Client-Server API</a> guide explains in detail how to use the CS API, which is useful if you want to write a client (or modify an existing one) - or if you're just interested in how it works "under the hood".</p>
|
||||
<p>The <a href="./application_services.html" title="Application services">Application services</a> guide introduces and explains Application services, and what they can be used for.
|
||||
<p>Finally there is the <a href="./faq.html" title="FAQ">FAQ</a>, which tries to answer all your questions relating to Matrix!</p>
|
||||
|
||||
</div>
|
|
@ -78,8 +78,13 @@ def main(input_module, file_stream=None, out_dir=None, verbose=False):
|
|||
def wrap(input, wrap=80, initial_indent=""):
|
||||
if len(input) == 0:
|
||||
return initial_indent
|
||||
# TextWrapper collapses newlines into single spaces; we do our own
|
||||
# splitting on newlines to prevent this, so that newlines can actually
|
||||
# be intentionally inserted in text.
|
||||
input_lines = input.split('\n\n')
|
||||
wrapper = TextWrapper(initial_indent=initial_indent, width=wrap)
|
||||
return wrapper.fill(input)
|
||||
output_lines = [wrapper.fill(line) for line in input_lines]
|
||||
return '\n\n'.join(output_lines)
|
||||
|
||||
# make Jinja aware of the templates and filters
|
||||
env = Environment(
|
||||
|
|
|
@ -90,6 +90,12 @@ class MatrixSections(Sections):
|
|||
title_kind="~"
|
||||
)
|
||||
|
||||
def render_membership_http_api(self):
|
||||
return self._render_http_api_group(
|
||||
"membership",
|
||||
title_kind="~"
|
||||
)
|
||||
|
||||
def render_room_events(self):
|
||||
def filterFn(eventType):
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
``{{endpoint.method}} {{endpoint.path}}``
|
||||
{{(5 + (endpoint.path | length) + (endpoint.method | length)) * title_kind}}
|
||||
{% if "alias_for_path" in endpoint -%}
|
||||
``{{endpoint.path}}`` is an alias for `{{endpoint.alias_for_path}}`_.
|
||||
|
||||
.. _`{{endpoint.alias_for_path}}`: #{{endpoint.alias_link}}
|
||||
{% else -%}
|
||||
|
||||
{{endpoint.desc | wrap(80)}}
|
||||
|
||||
|
@ -46,6 +51,20 @@ Example request::
|
|||
|
||||
{{endpoint.example.req | indent_block(2)}}
|
||||
|
||||
Example response::
|
||||
{% if endpoint.example.responses|length > 0 -%}
|
||||
Response{{"s" if endpoint.example.responses|length > 1 else "" }}:
|
||||
|
||||
{{endpoint.example.res | indent_block(2)}}
|
||||
{% endif -%}
|
||||
|
||||
{% for res in endpoint.example.responses -%}
|
||||
|
||||
**Status code {{res["code"]}}:**
|
||||
|
||||
{{res["description"]}}
|
||||
|
||||
Example::
|
||||
|
||||
{{res["example"] | indent_block(2)}}
|
||||
|
||||
{% endfor %}
|
||||
{% endif -%}
|
||||
|
|
|
@ -30,7 +30,7 @@ def get_json_schema_object_fields(obj, enforce_title=False):
|
|||
}
|
||||
tables = [fields]
|
||||
|
||||
props = obj.get("properties")
|
||||
props = obj.get("properties", obj.get("patternProperties"))
|
||||
parents = obj.get("allOf")
|
||||
if not props and not parents:
|
||||
raise Exception(
|
||||
|
@ -105,18 +105,20 @@ class MatrixUnits(Units):
|
|||
for path in api["paths"]:
|
||||
for method in api["paths"][path]:
|
||||
single_api = api["paths"][path][method]
|
||||
full_path = api.get("basePath", "") + path
|
||||
endpoint = {
|
||||
"title": single_api.get("summary", ""),
|
||||
"desc": single_api.get("description", single_api.get("summary", "")),
|
||||
"method": method.upper(),
|
||||
"path": api.get("basePath", "") + path,
|
||||
"path": full_path,
|
||||
"requires_auth": "security" in single_api,
|
||||
"rate_limited": 429 in single_api.get("responses", {}),
|
||||
"req_params": [],
|
||||
"res_tables": [],
|
||||
"example": {
|
||||
"req": "",
|
||||
"res": ""
|
||||
"responses": [],
|
||||
"good_response": ""
|
||||
}
|
||||
}
|
||||
self.log(".o.O.o. Endpoint: %s %s" % (method, path))
|
||||
|
@ -177,11 +179,19 @@ class MatrixUnits(Units):
|
|||
endpoint["req_param_by_loc"][p["loc"]] = []
|
||||
endpoint["req_param_by_loc"][p["loc"]].append(p)
|
||||
|
||||
# add example response if it has one
|
||||
res = single_api["responses"][200] # get the 200 OK response
|
||||
endpoint["example"]["res"] = res.get("examples", {}).get(
|
||||
"application/json", ""
|
||||
)
|
||||
good_response = None
|
||||
for code, res in single_api.get("responses", {}).items():
|
||||
if not good_response and code == 200:
|
||||
good_response = res
|
||||
description = res.get("description", "")
|
||||
example = res.get("examples", {}).get("application/json", "")
|
||||
if description and example:
|
||||
endpoint["example"]["responses"].append({
|
||||
"code": code,
|
||||
"description": description,
|
||||
"example": example,
|
||||
})
|
||||
|
||||
# form example request if it has one. It "has one" if all params
|
||||
# have either "x-example" or a "schema" with an "example".
|
||||
params_missing_examples = [
|
||||
|
@ -216,25 +226,39 @@ class MatrixUnits(Units):
|
|||
)
|
||||
|
||||
# add response params if this API has any.
|
||||
res_type = Units.prop(res, "schema/type")
|
||||
if good_response:
|
||||
res_type = Units.prop(good_response, "schema/type")
|
||||
if res_type and res_type not in ["object", "array"]:
|
||||
# response is a raw string or something like that
|
||||
endpoint["res_tables"].append({
|
||||
"title": None,
|
||||
"rows": [{
|
||||
"key": res["schema"].get("name", ""),
|
||||
"key": good_response["schema"].get("name", ""),
|
||||
"type": res_type,
|
||||
"desc": res.get("description", "")
|
||||
}]
|
||||
})
|
||||
elif res_type and Units.prop(res, "schema/properties"): # object
|
||||
res_tables = get_json_schema_object_fields(res["schema"])
|
||||
elif res_type and Units.prop(good_response, "schema/properties"):
|
||||
# response is an object:
|
||||
schema = good_response["schema"]
|
||||
res_tables = get_json_schema_object_fields(schema)
|
||||
for table in res_tables:
|
||||
if "no-table" not in table:
|
||||
endpoint["res_tables"].append(table)
|
||||
|
||||
endpoints.append(endpoint)
|
||||
|
||||
aliases = single_api.get("x-alias", None)
|
||||
if aliases:
|
||||
alias_link = aliases["canonical-link"]
|
||||
for alias in aliases["aliases"]:
|
||||
endpoints.append({
|
||||
"method": method.upper(),
|
||||
"path": alias,
|
||||
"alias_for_path": full_path,
|
||||
"alias_link": alias_link
|
||||
})
|
||||
|
||||
return {
|
||||
"base": api.get("basePath"),
|
||||
"group": group_name,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue