github.com/decred/politeia@v1.4.0/politeiad/api/v2/v2.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package v2
     6  
     7  import "fmt"
     8  
     9  const (
    10  	// APIRoute is prefixed onto all routes in this package.
    11  	APIRoute = "/v2"
    12  
    13  	// RouteRecordNew creates a new record.
    14  	RouteRecordNew = "/recordnew"
    15  
    16  	// RouteRecordEdit edits a record.
    17  	RouteRecordEdit = "/recordedit"
    18  
    19  	// RouteRecordEditMetadata edits record's metadata.
    20  	RouteRecordEditMetadata = "/recordeditmetadata"
    21  
    22  	// RouteRecordSetStatus sets the status of a record.
    23  	RouteRecordSetStatus = "/recordsetstatus"
    24  
    25  	// RouteRecordTimestamps returns the record timestamps.
    26  	RouteRecordTimestamps = "/recordtimestamps"
    27  
    28  	// RouteRecords retrieves a page of records.
    29  	RouteRecords = "/records"
    30  
    31  	// RouteInventory returns the tokens of records in the inventory
    32  	// categorized by record state and record status.
    33  	RouteInventory = "/inventory"
    34  
    35  	// RouteInventoryOrdered returns a page of record tokens ordered by the
    36  	// timestamp of their most recent status change from newest to
    37  	// oldest. The returned tokens will include all record statuses.
    38  	RouteInventoryOrdered = "/inventoryordered"
    39  
    40  	// RoutePluginWrite executes a plugin command that writes data.
    41  	RoutePluginWrite = "/pluginwrite"
    42  
    43  	// RoutePluginReads executes a read-only plugin command.
    44  	RoutePluginReads = "/pluginreads"
    45  
    46  	// RoutePluginInventory returns all registered plugins.
    47  	RoutePluginInventory = "/plugininventory"
    48  
    49  	// ChallengeSize is the size of a request challenge token in bytes.
    50  	ChallengeSize = 32
    51  )
    52  
    53  // ErrorCodeT represents a user error code.
    54  type ErrorCodeT uint32
    55  
    56  const (
    57  	// ErrorCodeInvalid is an invalid error code.
    58  	ErrorCodeInvalid ErrorCodeT = 0
    59  
    60  	// ErrorCodeRequestPayloadInvalid is returned when a request's payload
    61  	// is invalid.
    62  	ErrorCodeRequestPayloadInvalid ErrorCodeT = 1
    63  
    64  	// ErrorCodeChallengeInvalid is returned when a challenge is invalid.
    65  	ErrorCodeChallengeInvalid ErrorCodeT = 2
    66  
    67  	// ErrorCodeMetadataStreamInvalid is returned when a metadata stream
    68  	// is invalid.
    69  	ErrorCodeMetadataStreamInvalid ErrorCodeT = 3
    70  
    71  	// ErrorCodeMetadataStreamDuplicate is returned when a metadata stream
    72  	// is a duplicate.
    73  	ErrorCodeMetadataStreamDuplicate ErrorCodeT = 4
    74  
    75  	// ErrorCodeFilesEmpty is returned when no files found.
    76  	ErrorCodeFilesEmpty ErrorCodeT = 5
    77  
    78  	// ErrorCodeFileNameInvalid is returned when a file name is invalid.
    79  	ErrorCodeFileNameInvalid ErrorCodeT = 6
    80  
    81  	// ErrorCodeFileNameDuplicate is returned when a file name is a duplicate.
    82  	ErrorCodeFileNameDuplicate ErrorCodeT = 7
    83  
    84  	// ErrorCodeFileDigestInvalid is returned when a file digest is invalid.
    85  	ErrorCodeFileDigestInvalid ErrorCodeT = 8
    86  
    87  	// ErrorCodeFilePayloadInvalid is returned when a file payload is invalid.
    88  	ErrorCodeFilePayloadInvalid ErrorCodeT = 9
    89  
    90  	// ErrorCodeFileMIMETypeInvalid is returned when a file MIME type is
    91  	// invalid.
    92  	ErrorCodeFileMIMETypeInvalid ErrorCodeT = 10
    93  
    94  	// ErrorCodeFileMIMETypeUnsupported is returned when a file MIME type is
    95  	// unsupoorted.
    96  	ErrorCodeFileMIMETypeUnsupported ErrorCodeT = 11
    97  
    98  	// ErrorCodeTokenInvalid is returned when a token is invalid.
    99  	ErrorCodeTokenInvalid ErrorCodeT = 12
   100  
   101  	// ErrorCodeRecordNotFound is returned when a record is not found.
   102  	ErrorCodeRecordNotFound ErrorCodeT = 13
   103  
   104  	// ErrorCodeRecordLocked is returned when a record is locked.
   105  	ErrorCodeRecordLocked ErrorCodeT = 14
   106  
   107  	// ErrorCodeNoRecordChanges is retuned when no record changes found.
   108  	ErrorCodeNoRecordChanges ErrorCodeT = 15
   109  
   110  	// ErrorCodeStatusChangeInvalid is returned when a record status change
   111  	// is invalid.
   112  	ErrorCodeStatusChangeInvalid ErrorCodeT = 16
   113  
   114  	// ErrorCodePluginIDInvalid is returned when a plugin ID is invalid.
   115  	ErrorCodePluginIDInvalid ErrorCodeT = 17
   116  
   117  	// ErrorCodePluginCmdInvalid is returned when a plugin cmd is invalid.
   118  	ErrorCodePluginCmdInvalid ErrorCodeT = 18
   119  
   120  	// ErrorCodePageSizeExceeded is returned when the request's page size
   121  	// exceeds the maximum page size of the request.
   122  	ErrorCodePageSizeExceeded ErrorCodeT = 19
   123  
   124  	// ErrorCodeRecordStateInvalid is returned when the provided state
   125  	// does not match the record state.
   126  	ErrorCodeRecordStateInvalid ErrorCodeT = 20
   127  
   128  	// ErrorCodeRecordStatusInvalid is returned when a record status is
   129  	// invalid.
   130  	ErrorCodeRecordStatusInvalid ErrorCodeT = 21
   131  
   132  	// ErrorCodeDuplicatePayload is returned when a duplicate payload is sent
   133  	// to a plugin, where it tries to write data that already exists. Timestamp
   134  	// data relies on the hash of the payload, therefore duplicate payloads are
   135  	// not allowed since they will cause collisions.
   136  	ErrorCodeDuplicatePayload ErrorCodeT = 22
   137  
   138  	// ErrorCodeLast is used by unit tests to verify that all error codes have
   139  	// a human readable entry in the ErrorCodes map. This error will never be
   140  	// returned.
   141  	ErrorCodeLast ErrorCodeT = 23
   142  )
   143  
   144  var (
   145  	// ErrorCodes contains the human readable error codes.
   146  	ErrorCodes = map[ErrorCodeT]string{
   147  		ErrorCodeInvalid:                 "invalid error",
   148  		ErrorCodeRequestPayloadInvalid:   "request payload invalid",
   149  		ErrorCodeChallengeInvalid:        "invalid challenge",
   150  		ErrorCodeMetadataStreamInvalid:   "metadata stream invalid",
   151  		ErrorCodeMetadataStreamDuplicate: "metadata stream duplicate",
   152  		ErrorCodeFilesEmpty:              "files are empty",
   153  		ErrorCodeFileNameInvalid:         "file name invalid",
   154  		ErrorCodeFileNameDuplicate:       "file name is a duplicate",
   155  		ErrorCodeFileDigestInvalid:       "file digest invalid",
   156  		ErrorCodeFilePayloadInvalid:      "file payload invalid",
   157  		ErrorCodeFileMIMETypeInvalid:     "file mime type invalid",
   158  		ErrorCodeFileMIMETypeUnsupported: "file mime type not supported",
   159  		ErrorCodeTokenInvalid:            "token invalid",
   160  		ErrorCodeRecordNotFound:          "record not found",
   161  		ErrorCodeRecordLocked:            "record is locked",
   162  		ErrorCodeNoRecordChanges:         "no record changes",
   163  		ErrorCodeStatusChangeInvalid:     "status change invalid",
   164  		ErrorCodePluginIDInvalid:         "pluguin id invalid",
   165  		ErrorCodePluginCmdInvalid:        "plugin cmd invalid",
   166  		ErrorCodePageSizeExceeded:        "page size exceeded",
   167  		ErrorCodeRecordStateInvalid:      "record state invalid",
   168  		ErrorCodeRecordStatusInvalid:     "record status invalid",
   169  		ErrorCodeDuplicatePayload:        "duplicate payload",
   170  	}
   171  )
   172  
   173  // UserErrorReply is the reply that the server returns when it encounters an
   174  // error that is caused by something that the user did (malformed input, bad
   175  // timing, etc). The HTTP status code will be 400.
   176  type UserErrorReply struct {
   177  	ErrorCode    ErrorCodeT `json:"errorcode"`
   178  	ErrorContext string     `json:"errorcontext,omitempty"`
   179  }
   180  
   181  // Error satisfies the error interface.
   182  func (e UserErrorReply) Error() string {
   183  	return fmt.Sprintf("user error code: %v", e.ErrorCode)
   184  }
   185  
   186  // PluginErrorReply is the reply that the server returns when it encounters
   187  // a plugin error. The error code will be specific to the plugin.
   188  type PluginErrorReply struct {
   189  	PluginID     string `json:"pluginid"`
   190  	ErrorCode    uint32 `json:"errorcode"`
   191  	ErrorContext string `json:"errorcontext,omitempty"`
   192  }
   193  
   194  // Error satisfies the error interface.
   195  func (e PluginErrorReply) Error() string {
   196  	return fmt.Sprintf("plugin %v error code: %v", e.PluginID, e.ErrorCode)
   197  }
   198  
   199  // ServerErrorReply is the reply that the server returns when it encounters an
   200  // unrecoverable error while executing a command. The HTTP status code will be
   201  // 500 and the ErrorCode field will contain a UNIX timestamp that the user can
   202  // provide to the server admin to track down the error details in the logs.
   203  type ServerErrorReply struct {
   204  	ErrorCode int64 `json:"errorcode"`
   205  }
   206  
   207  // Error satisfies the error interface.
   208  func (e ServerErrorReply) Error() string {
   209  	return fmt.Sprintf("server error: %v", e.ErrorCode)
   210  }
   211  
   212  // RecordStateT represents the state of a record.
   213  type RecordStateT uint32
   214  
   215  const (
   216  	// RecordStateInvalid is an invalid record state.
   217  	RecordStateInvalid RecordStateT = 0
   218  
   219  	// RecordStateUnvetted indicates a record has not been made public.
   220  	RecordStateUnvetted RecordStateT = 1
   221  
   222  	// RecordStateVetted indicates a record has been made public.
   223  	RecordStateVetted RecordStateT = 2
   224  
   225  	// RecordStateLast is used for unit test validation of human readable
   226  	// errors.
   227  	RecordStateLast = 3
   228  )
   229  
   230  var (
   231  	// RecordStates contains the human readable record states.
   232  	RecordStates = map[RecordStateT]string{
   233  		RecordStateInvalid:  "invalid",
   234  		RecordStateUnvetted: "unvetted",
   235  		RecordStateVetted:   "vetted",
   236  	}
   237  )
   238  
   239  // RecordStatusT represents the status of a record.
   240  type RecordStatusT uint32
   241  
   242  const (
   243  	// RecordStatusInvalid is an invalid status code.
   244  	RecordStatusInvalid RecordStatusT = 0
   245  
   246  	// RecordStatusUnreviewed indicates a record has not been made
   247  	// public yet. The state of an unreviewed record will always be
   248  	// unvetted.
   249  	RecordStatusUnreviewed RecordStatusT = 1
   250  
   251  	// RecordStatusPublic indicates a record has been made public. The
   252  	// state of a public record will always be vetted.
   253  	RecordStatusPublic RecordStatusT = 2
   254  
   255  	// RecordStatusCensored indicates a record has been censored. A
   256  	// censored record is locked from any further updates and all
   257  	// record content is permanently deleted. A censored record can
   258  	// have a state of either unvetted or vetted.
   259  	RecordStatusCensored RecordStatusT = 3
   260  
   261  	// RecordStatusArchived indicates a record has been archived. An
   262  	// archived record is locked from any further updates. An archived
   263  	// record can have a state of either unvetted or vetted.
   264  	RecordStatusArchived RecordStatusT = 4
   265  
   266  	// RecordStatusLast is used for unit test validation of human readable
   267  	// errors.
   268  	RecordStatusLast = 5
   269  )
   270  
   271  var (
   272  	// RecordStatuses contains the human readable record statuses.
   273  	RecordStatuses = map[RecordStatusT]string{
   274  		RecordStatusInvalid:    "invalid",
   275  		RecordStatusUnreviewed: "unreviewed",
   276  		RecordStatusPublic:     "public",
   277  		RecordStatusCensored:   "censored",
   278  		RecordStatusArchived:   "archived",
   279  	}
   280  )
   281  
   282  // MetadataStream describes a single metada stream.
   283  type MetadataStream struct {
   284  	PluginID string `json:"pluginid"` // Plugin identity
   285  	StreamID uint32 `json:"streamid"` // Stream identity
   286  	Payload  string `json:"payload"`  // JSON encoded metadata
   287  }
   288  
   289  // File represents a record file.
   290  type File struct {
   291  	Name    string `json:"name"`    // Basename of the file
   292  	MIME    string `json:"mime"`    // MIME type
   293  	Digest  string `json:"digest"`  // SHA256 of decoded Payload
   294  	Payload string `json:"payload"` // Base64 encoded file payload
   295  }
   296  
   297  const (
   298  	// TokenSize is the size of a censorship record token in bytes.
   299  	TokenSize = 8
   300  
   301  	// ShortTokenLength is the length, in characters, of a hex encoded
   302  	// token that has been shortened to improved UX. Short tokens can
   303  	// be used to retrieve record data but cannot be used on any routes
   304  	// that write record data. 7 characters was chosen to match the git
   305  	// abbreviated commitment hash size.
   306  	ShortTokenLength = 7
   307  )
   308  
   309  // CensorshipRecord contains cryptographic proof that a record was accepted for
   310  // review by the server. The proof is verifiable by the client.
   311  type CensorshipRecord struct {
   312  	// Token is a random censorship token that is generated by the
   313  	// server. It serves as a unique identifier for the record.
   314  	Token string `json:"token"`
   315  
   316  	// Merkle is the ordered merkle root of all files in the record.
   317  	Merkle string `json:"merkle"`
   318  
   319  	// Signature is the server signature of the Merkle+Token.
   320  	Signature string `json:"signature"`
   321  }
   322  
   323  // Record represents a record and all of its contents.
   324  type Record struct {
   325  	State     RecordStateT     `json:"state"`     // Record state
   326  	Status    RecordStatusT    `json:"status"`    // Record status
   327  	Version   uint32           `json:"version"`   // Version of this record
   328  	Timestamp int64            `json:"timestamp"` // Last update
   329  	Metadata  []MetadataStream `json:"metadata"`
   330  	Files     []File           `json:"files"`
   331  
   332  	CensorshipRecord CensorshipRecord `json:"censorshiprecord"`
   333  }
   334  
   335  // RecordNew creates a new record. It must include all files that are part of
   336  // the record and it may contain optional metadata.
   337  type RecordNew struct {
   338  	Challenge string           `json:"challenge"` // Random challenge
   339  	Metadata  []MetadataStream `json:"metadata,omitempty"`
   340  	Files     []File           `json:"files"`
   341  }
   342  
   343  // RecordNewReply is the reply to the RecordNew command.
   344  type RecordNewReply struct {
   345  	Response string `json:"response"` // Challenge response
   346  	Record   Record `json:"record"`
   347  }
   348  
   349  // RecordEdit edits and existing record.
   350  //
   351  // MDAppend appends metadata to a metadata stream. MDOverwrite overwrites a
   352  // metadata stream. If the metadata stream does not exist yet for either of
   353  // these arguments, a new metadata stream will be created.
   354  //
   355  // FilesAdd should include files that are being modified or added. FilesDel
   356  // is the filenames of existing files that will be deleted. If a filename is
   357  // provided in FilesDel that does not correspond to an actual record file, it
   358  // will be ignored.
   359  type RecordEdit struct {
   360  	Challenge   string           `json:"challenge"` // Random challenge
   361  	Token       string           `json:"token"`     // Censorship token
   362  	MDAppend    []MetadataStream `json:"mdappend,omitempty"`
   363  	MDOverwrite []MetadataStream `json:"mdoverwrite,omitempty"`
   364  	FilesAdd    []File           `json:"filesadd,omitempty"`
   365  	FilesDel    []string         `json:"filesdel,omitempty"`
   366  }
   367  
   368  // RecordEditReply is the reply to the RecordEdit command.
   369  type RecordEditReply struct {
   370  	Response string `json:"response"` // Challenge response
   371  	Record   Record `json:"record"`
   372  }
   373  
   374  // RecordEditMetadata edits the metadata of a record.
   375  //
   376  // MDAppend appends metadata to a metadata stream. MDOverwrite overwrites a
   377  // metadata stream. If the metadata stream does not exist yet for either of
   378  // these arguments, a new metadata stream will be created.
   379  type RecordEditMetadata struct {
   380  	Challenge   string           `json:"challenge"` // Random challenge
   381  	Token       string           `json:"token"`     // Censorship token
   382  	MDAppend    []MetadataStream `json:"mdappend,omitempty"`
   383  	MDOverwrite []MetadataStream `json:"mdoverwrite,omitempty"`
   384  }
   385  
   386  // RecordEditMetadataReply is the reply to the RecordEditMetadata command.
   387  type RecordEditMetadataReply struct {
   388  	Response string `json:"response"` // Challenge response
   389  	Record   Record `json:"record"`
   390  }
   391  
   392  // RecordSetStatus sets the status of a record.
   393  //
   394  // MDAppend appends metadata to a metadata stream. MDOverwrite overwrites a
   395  // metadata stream. If the metadata stream does not exist yet for either of
   396  // these arguments, a new metadata stream will be created.
   397  type RecordSetStatus struct {
   398  	Challenge   string           `json:"challenge"` // Random challenge
   399  	Token       string           `json:"token"`     // Censorship token
   400  	Status      RecordStatusT    `json:"status"`
   401  	MDAppend    []MetadataStream `json:"mdappend,omitempty"`
   402  	MDOverwrite []MetadataStream `json:"mdoverwrite,omitempty"`
   403  }
   404  
   405  // RecordSetStatusReply is the reply to the RecordSetStatus command.
   406  type RecordSetStatusReply struct {
   407  	Response string `json:"response"` // Challenge response
   408  	Record   Record `json:"record"`
   409  }
   410  
   411  // Proof contains an inclusion proof for the digest in the merkle root. All
   412  // digests are hex encoded SHA256 digests.
   413  //
   414  // The ExtraData field is used by certain types of proofs to include additional
   415  // data that is required to validate the proof.
   416  type Proof struct {
   417  	Type       string   `json:"type"`
   418  	Digest     string   `json:"digest"`
   419  	MerkleRoot string   `json:"merkleroot"`
   420  	MerklePath []string `json:"merklepath"`
   421  	ExtraData  string   `json:"extradata"` // JSON encoded
   422  }
   423  
   424  // Timestamp contains all of the data required to verify that a piece of record
   425  // content was timestamped onto the decred blockchain.
   426  //
   427  // All digests are hex encoded SHA256 digests. The merkle root can be found in
   428  // the OP_RETURN of the specified DCR transaction.
   429  //
   430  // TxID, MerkleRoot, and Proofs will only be populated once the merkle root has
   431  // been included in a DCR tx and the tx has 6 confirmations. The Data field
   432  // will not be populated if the data has been censored.
   433  type Timestamp struct {
   434  	Data       string  `json:"data"` // JSON encoded
   435  	Digest     string  `json:"digest"`
   436  	TxID       string  `json:"txid"`
   437  	MerkleRoot string  `json:"merkleroot"`
   438  	Proofs     []Proof `json:"proofs"`
   439  }
   440  
   441  // RecordTimestamps requests the timestamps for a record. If a version is not
   442  // included the most recent version will be returned.
   443  type RecordTimestamps struct {
   444  	Challenge string `json:"challenge"`         // Random challenge
   445  	Token     string `json:"token"`             // Censorship token
   446  	Version   uint32 `json:"version,omitempty"` // Record version
   447  }
   448  
   449  // RecordTimestampsReply is the reply ot the RecordTimestamps command.
   450  type RecordTimestampsReply struct {
   451  	Response       string    `json:"response"` // Challenge response
   452  	RecordMetadata Timestamp `json:"recordmetadata"`
   453  
   454  	// map[pluginID]map[streamID]Timestamp
   455  	Metadata map[string]map[uint32]Timestamp `json:"metadata"`
   456  
   457  	// map[filename]Timestamp
   458  	Files map[string]Timestamp `json:"files"`
   459  }
   460  
   461  const (
   462  	// RecordsPageSize is the maximum number of records that can be
   463  	// requested using the Records commands.
   464  	RecordsPageSize uint32 = 5
   465  )
   466  
   467  // RecordRequest is used to request a record. It gives the caller granular
   468  // control over what is returned. The only required field is the token. All
   469  // other fields are optional. All record files are returned by default unless
   470  // one of the file arguments is provided.
   471  //
   472  // Version is used to request a specific version of a record. If no version is
   473  // provided then the most recent version of the record will be returned.
   474  //
   475  // Filenames can be used to request specific files. If filenames is provided
   476  // then the specified files will be the only files that are returned.
   477  //
   478  // OmitAllFiles can be used to retrieve a record without any of the record
   479  // files. This supersedes the filenames argument.
   480  type RecordRequest struct {
   481  	Token        string   `json:"token"`
   482  	Version      uint32   `json:"version,omitempty"`
   483  	Filenames    []string `json:"filenames,omitempty"`
   484  	OmitAllFiles bool     `json:"omitallfiles,omitempty"`
   485  }
   486  
   487  // Records retrieves a record. If no version is provided the most recent
   488  // version will be returned.
   489  type Records struct {
   490  	Challenge string          `json:"challenge"` // Random challenge
   491  	Requests  []RecordRequest `json:"requests"`
   492  }
   493  
   494  // RecordsReply is the reply to the Records command. If a record was not found
   495  // or an error occurred while retrieving it the token will not be included in
   496  // the returned map.
   497  //
   498  // **Note** partial record's merkle root is not verifiable - when generating
   499  // the record's merkle all files must be present.
   500  type RecordsReply struct {
   501  	Response string            `json:"response"` // Challenge response
   502  	Records  map[string]Record `json:"records"`  // [token]Record
   503  }
   504  
   505  const (
   506  	// InventoryPageSize is the number of tokens that will be returned
   507  	// per page for all inventory commands.
   508  	InventoryPageSize uint32 = 20
   509  )
   510  
   511  // Inventory requests the tokens of the records in the inventory, categorized
   512  // by record state and record status. The tokens are ordered by the timestamp
   513  // of their most recent status change, sorted from newest to oldest.
   514  //
   515  // The state, status, and page arguments can be provided to request a specific
   516  // page of record tokens.
   517  //
   518  // If no status is provided then a page of tokens for all statuses will be
   519  // returned. All other arguments will be ignored.
   520  type Inventory struct {
   521  	Challenge string        `json:"challenge"` // Random challenge
   522  	State     RecordStateT  `json:"state,omitempty"`
   523  	Status    RecordStatusT `json:"status,omitempty"`
   524  	Page      uint32        `json:"page,omitempty"`
   525  }
   526  
   527  // InventoryReply is the reply to the Inventory command. The map keys are the
   528  // human readable record statuses defined by the RecordStatuses array.
   529  type InventoryReply struct {
   530  	Response string              `json:"response"` // Challenge response
   531  	Unvetted map[string][]string `json:"unvetted"` // [status][]token
   532  	Vetted   map[string][]string `json:"vetted"`   // [status][]token
   533  }
   534  
   535  // InventoryOrdered requests a page of record tokens ordered by the timestamp
   536  // of their most recent status change from newest to oldest. The reply will
   537  // include tokens for all record statuses.
   538  type InventoryOrdered struct {
   539  	Challenge string       `json:"challenge"` // Random challenge
   540  	State     RecordStateT `json:"state"`
   541  	Page      uint32       `json:"page"`
   542  }
   543  
   544  // InventoryOrderedReply is the reply to the InventoryOrdered command.
   545  type InventoryOrderedReply struct {
   546  	Response string   `json:"response"` // Challenge response
   547  	Tokens   []string `json:"tokens"`
   548  }
   549  
   550  // PluginCmd represents plugin command and the command payload. A token is
   551  // required for all plugin writes, but is optional for reads.
   552  type PluginCmd struct {
   553  	Token   string `json:"token,omitempty"`   // Censorship token
   554  	ID      string `json:"id"`                // Plugin identifier
   555  	Command string `json:"command"`           // Plugin command
   556  	Payload string `json:"payload,omitempty"` // Command payload
   557  }
   558  
   559  // PluginWrite executes a plugin command that writes data.
   560  type PluginWrite struct {
   561  	Challenge string    `json:"challenge"` // Random challenge
   562  	Cmd       PluginCmd `json:"cmd"`
   563  }
   564  
   565  // PluginWriteReply is the reply to the PluginWrite command.
   566  type PluginWriteReply struct {
   567  	Response string `json:"response"` // Challenge response
   568  	Payload  string `json:"payload"`  // Response payload
   569  }
   570  
   571  // PluginReads executes a batch of read only plugin commands.
   572  type PluginReads struct {
   573  	Challenge string      `json:"challenge"` // Random challenge
   574  	Cmds      []PluginCmd `json:"cmds"`
   575  }
   576  
   577  // PluginCmdReply is the reply to an individual plugin command that is part of
   578  // a batch of plugin commands. The error will be included in the reply if one
   579  // was encountered.
   580  type PluginCmdReply struct {
   581  	Token   string `json:"token"`   // Censorship token
   582  	ID      string `json:"id"`      // Plugin identifier
   583  	Command string `json:"command"` // Plugin command
   584  	Payload string `json:"payload"` // Response payload
   585  
   586  	// UserError will be populated if a user error is encountered prior
   587  	// to plugin command execution.
   588  	UserError *UserErrorReply `json:"usererror,omitempty"`
   589  
   590  	// PluginError will be populated if a plugin error occurred during
   591  	// plugin command execution.
   592  	PluginError *PluginErrorReply `json:"pluginerror,omitempty"`
   593  }
   594  
   595  // PluginReadsReply is the reply to the PluginReads command.
   596  type PluginReadsReply struct {
   597  	Response string           `json:"response"` // Challenge response
   598  	Replies  []PluginCmdReply `json:"replies"`
   599  }
   600  
   601  // PluginSetting is a structure that holds key/value pairs of a plugin setting.
   602  type PluginSetting struct {
   603  	Key   string `json:"key"`
   604  	Value string `json:"value"`
   605  }
   606  
   607  // Plugin describes a plugin and its settings.
   608  type Plugin struct {
   609  	ID       string          `json:"id"`
   610  	Settings []PluginSetting `json:"settings"`
   611  }
   612  
   613  // PluginInventory retrieves all active plugins and their settings.
   614  type PluginInventory struct {
   615  	Challenge string `json:"challenge"` // Random challenge
   616  }
   617  
   618  // PluginInventoryReply returns all plugins and their settings.
   619  type PluginInventoryReply struct {
   620  	Response string   `json:"response"` // Challenge response
   621  	Plugins  []Plugin `json:"plugins"`
   622  }