From 7f1b9595c116360f72880e87c2b0b16cb3a96ab1 Mon Sep 17 00:00:00 2001 From: Matthieu Grieger Date: Tue, 21 Jun 2016 16:00:13 -0700 Subject: [PATCH] Resolve https://github.com/matthieugrieger/mumbledj/issues/152: Command messages are now set and configured in config.yaml --- CHANGELOG.md | 3 ++ bindata.go | 4 +- bot/config.go | 51 ++++++++++++++++++++++ commands/add.go | 12 +++--- commands/addnext.go | 12 +++--- commands/cachesize.go | 4 +- commands/currenttrack.go | 4 +- commands/forceskip.go | 4 +- commands/forceskipplaylist.go | 6 +-- commands/help.go | 4 +- commands/joinme.go | 4 +- commands/listtracks.go | 6 +-- commands/move.go | 6 +-- commands/nexttrack.go | 6 +-- commands/numcached.go | 4 +- commands/numtracks.go | 4 +- commands/pause.go | 5 ++- commands/reload.go | 2 +- commands/reset.go | 4 +- commands/resume.go | 5 ++- commands/setcomment.go | 4 +- commands/shuffle.go | 6 +-- commands/skip.go | 6 +-- commands/skipplaylist.go | 8 ++-- commands/toggleshuffle.go | 4 +- commands/version.go | 2 +- commands/volume.go | 8 ++-- config.yaml | 81 ++++++++++++++++++++++++++++++++++- main.go | 2 +- 29 files changed, 203 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3174e7..e475040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ MumbleDJ Changelog ================== +### June 21, 2016 -- `v3.0.1` +* Added all strings that are output by commands to `config.yaml` for easier translation and tweaking. + ### June 20, 2016 -- `v3.0.0` * Significantly simplified installation process, now installable via `go install`. * Commands may now have multiple aliases, configurable via config file. diff --git a/bindata.go b/bindata.go index 09a14e7..b9fe00e 100644 --- a/bindata.go +++ b/bindata.go @@ -68,7 +68,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _configYaml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x59\x6d\x6f\xdb\x38\x12\xfe\x9e\x5f\xc1\x3a\x07\x5c\x0b\x64\xdd\xa4\xb7\xdd\x5d\x04\x38\x14\x69\x9a\xbb\xe6\xd0\x5c\x8b\x26\xf7\xe1\x3e\x19\x8c\x44\x59\xdc\x48\xa4\x4e\xa4\xec\xf8\x7e\xfd\x3e\xc3\x37\xd9\x96\x12\xdb\xe9\x66\x17\xa8\x45\x0d\x67\x9e\x79\x1f\x52\xc7\xec\xa6\xab\xef\x2b\xf1\xe9\x5f\x47\xc7\xec\xe3\x8a\xdd\x70\x6b\x4b\x29\x3a\xf6\xcf\x56\x8a\xb9\x68\xb1\x7a\xa9\x9b\x55\x2b\xe7\xa5\x65\xaf\xb3\x37\xec\xdd\xe9\xd9\x2f\x03\x2a\xf6\xfa\xe6\xfa\x8e\x7d\x91\x99\x50\x46\xbc\xc1\x9e\x4c\xab\x42\xce\xa7\x2b\x5e\x57\x47\x47\xbc\x91\xb3\x07\xb1\x32\xe7\x47\x47\x0c\x7f\xc7\xec\xbf\xba\xbb\xeb\xee\x05\xbb\xf8\x76\xcd\xf0\x62\xea\x96\x57\xba\xb3\x58\x3c\x67\x93\x49\xa4\xbb\xd5\x9d\xca\x2f\x2b\xdd\xe5\x9b\xa4\xc7\xec\xdf\x5f\xef\xae\xce\xd9\x5d\x99\x78\x30\x69\x88\x43\xcb\xb2\x4a\x0a\x65\xd9\xf5\x27\x4f\x6a\x88\x45\x46\x2c\x3c\xe3\xa3\x5c\x14\xbc\xab\x6c\x0f\xe6\x93\x5f\x00\xe4\xba\xa6\x9d\x56\x33\x40\xe3\x4d\x03\x46\xb9\x7b\xd2\x76\x53\xec\x75\x41\xa2\x58\xae\x99\xd2\x96\x2d\x39\x36\xf1\xb4\xfd\x7e\xc5\x82\x88\x13\x66\x84\x63\x27\xea\xc6\xae\x98\xb1\xad\x54\x73\xf6\x7a\x32\x79\xe3\xd9\x85\x1d\xc0\xf5\x59\x54\x95\x7e\xc5\xae\x19\xaf\xc1\x89\xe4\xb1\xbb\x55\x23\xd8\xab\x52\x54\x0d\x2b\x74\x8b\xd5\x4a\x1a\xcb\x74\xe1\x76\x71\x95\x9b\xe9\x64\xa0\x40\xc9\x95\x12\x95\xa3\xb7\xb0\x0c\xf8\x38\xe9\xca\xc2\x41\x5d\xa3\x15\x79\x45\x89\xcc\x4a\xad\x46\x15\x5a\x4a\x53\x6e\xef\x0e\x5b\xe8\x27\xad\xb6\x5a\x27\x41\x3b\xf5\xf3\x64\xeb\x0e\xbd\xf4\xe0\x69\x53\x67\x04\xfd\xd3\x54\x7c\xc5\x78\x97\x4b\xcd\x0a\x59\x09\x33\x75\x4e\xb5\x4b\xcd\x4c\xd7\x34\xba\xb5\xf0\x41\x56\x6a\x44\x96\x61\xbc\x15\x6c\x52\x14\x75\x23\xe6\x13\x46\x6c\x26\x7c\x01\x7c\x8b\x89\x97\x47\xac\x44\x3b\x0b\x06\x3a\x4f\xa4\x70\xfa\xff\x3a\xd1\x89\xe4\xf1\xef\x1c\x26\x80\x3a\xdc\xb2\xba\x83\x55\xe1\xee\x1a\x9a\x40\x71\xf1\x98\x09\x91\x7b\xb7\x43\x9d\x39\x85\x36\xc7\x2f\x9e\x3d\x30\xf3\x20\x1b\x2f\xc8\x3d\xcf\xe8\x79\xd6\x12\xab\x73\x76\x3a\x7d\xff\x52\xe6\x84\xda\xf9\xb6\xe7\x1f\x97\x9e\x12\x71\xc3\x1f\x65\xdd\xd5\x01\x57\xde\x39\x0a\xc5\xa4\x82\x43\x60\x0f\xc4\x06\xbb\xf5\x9e\x39\x75\xee\xec\x54\x2b\xc8\x3b\x19\x19\x33\x92\x7b\x51\x35\x7f\x9c\x79\x75\xe2\x3a\x24\x8d\xca\x31\xac\x01\xde\x08\xed\x39\x09\x91\xc6\x6c\x89\x30\x33\x70\x98\xc5\xb7\xe7\xec\x7d\x12\x74\x6d\x98\x29\xbb\xa2\xa8\x28\x80\x84\xe2\xa8\x47\x39\x5b\x96\x42\xa5\x48\x34\x96\xb7\xd6\x7c\x70\xf4\xbc\xb3\xba\x06\xd6\x6c\xe6\x37\x89\x19\xa1\x2e\x78\x65\x44\x64\x78\xa1\x14\xf2\x3e\x13\xc1\x44\x52\x01\x64\xed\xad\x04\xbf\x38\xa6\x62\x2e\x95\x22\x79\xc8\x29\x1f\x7f\x84\xec\x1e\xe4\x41\x4a\x60\x31\x53\x62\x19\xf0\x9f\x83\x5d\x07\x19\x47\x7d\x1e\xa5\x98\xba\xc8\x73\x98\xc0\x78\xb0\xa5\xee\xaa\x1c\x92\x2c\x65\xc6\x66\x16\x79\x9b\x70\x4f\x8d\x18\x3d\x7b\xf7\xeb\xf4\x14\xff\x9d\xa5\x1c\xf9\x86\xa0\xdf\x93\x0d\xe5\x07\x78\xfc\xf2\xf3\xaf\x7f\xfb\xad\xdf\xcf\x8d\x59\xea\x36\x77\x8e\x89\x48\xa1\x27\xf6\x1b\xd1\x2e\x44\x3b\xc8\x7d\x05\xdd\xc3\xa6\x5d\x39\x1d\xe9\xd6\x93\xfa\x3f\x60\xab\x78\x2d\x9c\xc0\xd8\x4d\x3c\x79\x17\x5e\x81\x3c\xbe\xe8\x8b\xbb\x57\x2f\x7a\x38\xaa\xc9\x29\x90\x11\xc7\x1d\x92\xbd\xb7\xf3\x87\xe4\xd8\xb1\xb7\x28\xc5\x28\x0f\x54\x8c\xa1\x9e\x2c\x56\x8e\x69\x26\x5a\x2b\x0b\x99\x71\x2b\xc8\xc7\xb4\xe4\xf5\x27\x9c\x81\x1d\x58\x18\xc4\xa2\x50\xd9\x6a\xca\xae\x2d\x35\x91\x7b\x04\x32\x19\xa0\x12\x7c\x81\xf8\x29\xb1\xa4\xd5\x09\xbb\xef\x2c\xcb\xa5\xa1\xd0\x64\x12\x84\xbe\x62\x52\x45\x2a\xf9\x02\x36\x0a\x0c\xa5\x31\x1d\xa0\x6c\x9a\x9d\x47\xc1\x54\xc8\xb1\xa3\xed\x7c\xe4\xd5\xa8\xda\xb2\x21\x86\x0a\xf1\xad\xa8\xc4\x01\xe8\xa6\x05\xa3\xb6\x5b\x01\xfe\x0f\xd4\xca\x86\xdb\xd2\x45\xc5\x9a\xa2\x54\x43\xc7\xfc\xbb\x4d\x43\x9a\x2a\x57\x8d\x76\x56\x71\xe1\x62\x6c\xb2\x4b\x32\xf5\xe0\xa7\xa4\x87\xfe\xbc\x9f\x40\x10\xaf\xcb\xbb\xc8\x32\xca\x2b\xab\x1f\x30\x5b\xd0\x36\xa9\xa4\x95\xbc\x92\xff\x17\x29\x76\x96\xd2\x96\xc4\xb6\xe1\xa8\x61\x28\x1d\xe8\xc1\xae\x0b\x98\x31\x30\x7c\x83\x21\xf9\x63\x3f\x5c\x7e\xdf\xcc\xef\xdb\x98\x52\xb6\x02\x39\x96\x27\x5e\x55\xab\xf5\xec\x6d\x85\x6d\x57\xeb\x51\xbb\x1e\x1a\xbc\xa0\x2e\x8d\x08\xeb\x43\xc7\xc7\xbc\xdb\x35\x0b\x45\x31\x56\x20\x2f\xf8\xb3\x5e\xa2\xba\x2a\x84\xbb\xac\x85\x89\xf5\x62\x3b\xa1\x9c\xe4\xad\x36\xee\x85\xae\x0b\x08\xd4\x50\xec\xec\x74\xc0\x3f\xb4\x95\x6d\x09\x4b\x4e\x99\xa0\x7e\xba\x17\x76\x29\xc4\xfa\x78\x11\x74\x8d\x4c\xd7\x05\x49\x1a\x47\x16\x1c\x63\xc1\x7b\xaa\xa4\x3c\x2b\xfb\xc6\x7c\x49\x4f\x18\xd9\xd4\x1c\x8e\x31\x24\x67\xe5\x1c\x94\xeb\xa5\xaa\x34\x87\x93\x3c\xa7\x64\x8d\x8d\x9c\x48\xed\x4a\x5b\x5e\xf9\x28\x37\x14\x25\x34\x34\x39\xc6\xb9\x84\x21\xac\x06\x30\xb4\xca\x1b\xf9\x31\xf5\x27\xda\x36\x23\x5a\x80\x3a\x7b\x97\x0a\x29\x6a\x89\xce\x5d\xed\x80\x7d\x7d\x4b\x0f\x16\x10\x15\x6f\x0c\x35\x11\x94\x12\x41\x4e\x04\x64\x8a\xf0\x0c\x55\xa3\x45\x00\x16\xad\xae\x7d\x11\x22\xc1\x27\x24\x0f\x1b\xdb\x10\x8f\xe2\xb1\x01\x92\x19\x71\x3d\x67\xef\x7e\x7e\x42\x5e\xb4\xaa\x00\x0b\x8c\x40\x02\x7d\x2c\x94\x31\xaf\x4d\xe1\x46\x0a\xe2\x94\xa3\x22\x89\xda\x38\x31\xb5\x54\x9d\x15\x26\x4e\x60\xd8\xb5\x69\xf1\x30\x32\x26\x4b\x50\x57\xb0\xa4\x84\x63\x1a\x38\x4d\xd9\x95\x5a\xc8\x56\x2b\x37\xd1\x2e\x78\x2b\xc9\xde\x3e\x59\x5c\x05\xf4\x33\x32\xaa\x7b\xce\x4a\xd1\x86\x9c\x4f\xe6\x45\x72\xfc\xe5\xf3\xd7\x9b\xab\xb7\x53\xc7\xf4\x6d\xed\x2a\x5a\xfe\x3b\x4d\x62\x0b\x5d\x75\xb5\x18\x0c\xdf\x7e\x39\xf0\xf1\x6b\x34\xf2\x24\x5f\x7c\xd1\x4b\xaa\xcb\x9e\x8c\x21\xb3\xf0\x9c\x7b\xf2\xca\xbd\x22\xea\xd3\xb3\x14\xb9\x38\xae\x3c\x45\x5f\xfa\x77\xb4\xe1\x37\x00\xe2\x39\x4c\xd6\x9f\x06\xae\x5c\x68\x31\xbf\xfa\x61\xbb\x7c\xb8\x76\x80\xff\x43\xa5\x70\xe1\x77\xc2\x28\x45\xc2\xd4\x09\x33\x2a\x32\x8d\x78\x44\xd5\x0e\xa5\x88\x5e\x53\x23\x9c\x6e\xc6\xee\x7a\x26\x7f\x09\xc3\xbd\x13\xcb\xa8\x63\x6e\x97\x2e\xd7\x9b\x28\x8f\xe9\xcc\xe0\x86\xc8\x32\x4c\x32\x8e\x9a\x5c\xef\xc0\xb9\x51\xd2\x35\x19\x0c\x38\xe8\x6c\x14\x1d\xda\xcd\x51\x81\x5f\xa8\x37\x26\xcc\xa8\xb2\x6e\x34\x91\x19\x42\x4e\x1d\x34\x20\x0f\x50\xd2\x69\xc3\xed\x76\xa2\xce\xdd\x4f\xfa\xfb\x89\x4d\x6e\x3b\xcc\x74\x34\x00\x4c\xdc\x58\xe4\x89\xfb\x7c\x2e\x51\x90\x33\x77\xfc\x30\x7e\xee\xcd\x85\x91\x73\x45\xfd\x22\x12\xfb\x5c\x51\x34\x9f\x55\xcc\x8a\x47\x8c\xcd\x28\xb3\x7c\xbe\x6d\x81\xaf\x0a\xd5\x54\x2b\x41\xc7\x8a\xc0\xf4\x35\xa9\x5f\xc8\xd6\xd8\x37\x64\x1d\x92\x11\xa6\x94\x56\x14\xf2\x11\x61\xf8\x2a\x15\xe9\x8f\x02\x21\x40\x54\xfd\x49\xca\xa5\x52\x44\xd1\x9f\x36\xe0\xb1\xd4\x82\xd9\x95\x4b\xbe\xe0\xdb\x92\x9b\xc0\xcd\x96\xad\x10\xe1\x90\x8b\xb9\x99\x22\x46\x37\x54\xf8\x82\x75\x8e\x11\x72\x92\x1b\x18\x8b\x5d\x24\x79\xde\x51\xce\xea\x21\x4a\xa2\x55\xa2\xcd\xd7\x10\x4d\xd3\x40\x31\x73\x9e\xf0\xf1\xc2\xfe\x0e\x13\x50\x57\x71\xe1\xe9\xd8\x8c\xec\x3d\xf1\x81\x09\x62\x84\x9e\x33\xd9\x38\x5d\x94\x01\xa7\x64\xad\x6c\xfc\xf4\xff\xa9\x7f\xa0\x6a\xb7\x54\xe9\x44\x18\xcd\x90\x06\x73\x77\x3a\x8d\xab\x30\x6d\x0c\xfa\x34\xe3\xf6\xa1\x12\xcd\x91\x16\x42\xfc\x80\x68\x32\x58\xeb\x57\x7a\xed\x7d\xa5\x8f\xeb\x1b\x88\x27\x98\xbd\x4d\x3a\xa6\xe9\xfe\x90\xe2\x43\x8b\x23\xa0\x72\x89\x1a\x2d\xad\x88\xfd\xcf\x9d\x07\xe3\x09\x1a\x18\x14\xe2\x6e\x2f\xb0\x44\x38\x04\xac\xc6\x10\xbb\xfc\xfe\x61\xc0\xbe\x0d\x32\x92\xeb\x6a\xb3\x4f\xf4\x2d\x0d\x5c\x99\x75\xfd\x6b\xa7\x0e\x89\x74\xa0\x45\x66\x0e\xd4\xe2\x6b\x67\x9b\xce\x7a\x7c\x1b\xdd\xb6\xef\x51\xbe\xcf\xd2\xb4\x4c\xcf\xd2\x37\xc9\x50\x01\x13\xf8\xae\x6d\xd1\x66\x9c\x31\xf6\xc0\xbf\x46\x3d\x54\xc1\xbf\xa4\x6e\xfc\xd4\xbb\x43\x63\x2b\x2a\xb9\x71\x84\xbc\xd7\x9d\xaf\xbd\x81\x69\x3a\x66\xf6\xae\x21\x9d\x29\xf5\xc4\xa3\x3b\x09\x07\x5d\xc1\x03\x43\x24\x4e\xf5\xbb\x15\x4d\xa4\x03\x4d\x8a\x43\x1d\x75\x5d\xbb\x80\xb2\x02\x95\x83\x38\x9a\x21\xf6\x01\xc0\x74\x4c\xdf\x1f\x68\xdc\x32\x02\xb8\xf9\x53\x11\xa7\x4b\x88\x00\x9a\xea\xd0\x6e\x9c\x44\x35\x80\x56\xbe\x34\x1e\xfa\x66\x3b\x72\x27\xf7\xbb\x96\xaa\xde\x23\x19\x3d\xdd\x00\x14\x2d\x1f\x68\xb0\x1b\xbd\x40\x6f\x89\x7d\x0b\x91\x88\x42\xe7\xaf\x43\x83\xcd\xe2\xe5\xa0\x2c\xdc\x39\x99\x4c\x48\xe9\xe8\x6f\x3e\x68\x08\xd4\xb5\x70\xf1\x0a\xd5\xa3\x1a\x0f\xb2\xaa\x76\x2b\x41\x54\x03\x15\x1e\x0e\xc4\x7f\x6b\x75\x70\xb2\x3b\xba\xd0\x20\x85\x51\x1a\x2d\x4e\x5a\xb3\x3d\xbd\x47\x78\x64\xfd\x70\x31\xb3\x13\x64\x4f\x3b\x80\xea\xae\xaa\xe8\xc8\x31\xfa\x66\xb8\xf8\xd2\x88\xd9\x1c\x3c\xc2\x9d\x5a\x70\x4f\xb5\x1a\xad\xeb\x35\xbc\xba\x5b\x37\xa2\x1a\xc0\xac\x5f\x14\x40\xd1\x01\x2e\x7e\xe8\xc1\x47\x54\x8a\x9e\xa6\xd5\x0b\x49\xd3\xe7\x02\xfd\x89\xb7\xf3\x8e\x0e\x08\x11\x2d\xf5\xa8\x3d\xab\x78\x22\x1d\xe0\xa6\x37\xa3\xf5\x7b\xb3\xf3\xfe\x19\xc5\xdb\xf5\xd4\x7d\x2b\xb7\xea\x6a\x7f\x42\xda\x43\xb9\x48\x3a\xd4\x21\xfb\x81\x16\x0b\xb6\xf7\x74\x87\x54\xa4\xd8\xf1\x27\x36\xba\xfe\x92\xe6\xe1\xf9\x26\x8b\xcd\xfb\xa6\x4a\x22\x1d\xc2\xef\xea\xf1\x44\x79\x79\x5b\x1d\x57\xec\xd9\xa4\x68\x38\xc6\xe6\xdd\x6a\x38\xb2\x43\x61\x7d\xa3\x4d\x66\xeb\x3e\x38\x0a\x6e\x05\xdd\x41\xec\x96\xec\xe9\x06\x46\x6a\x0f\xf4\xfd\x77\xc7\x26\x74\xbe\x78\xd2\x70\x21\xec\xae\xd9\x12\x28\x9c\x45\xf7\xc1\x04\xb2\x21\xa4\x51\x03\x3d\x8b\x09\x6c\xcc\x5a\xae\xe0\xb0\xd4\x0a\x14\x20\xd7\x4b\xaa\x2a\x66\x90\xbb\x44\xe8\x11\x76\xfb\x34\x43\x4f\x77\xa8\xc7\xbe\xbb\x5d\x4f\xb9\x0c\x68\xe3\x57\xb6\x9d\xf2\x7b\xda\xe1\xec\xf8\xc4\xba\x39\x34\x9f\x6f\xa3\xf5\xe2\xd7\x42\x64\xae\xfb\x6c\x95\x87\x6a\xa4\x53\x0f\xff\xab\x49\xb7\xe7\x6e\x8c\x76\xcb\x49\x2f\xff\xe1\x63\x0f\xa5\x3c\xe1\x10\x39\xd6\x47\x16\x0f\x0d\x07\x34\x6a\x5d\x63\xea\x37\xfb\xf7\xb4\xfd\xa6\xdf\xd1\xc1\x77\x74\xee\x7d\x36\x9d\x2b\xee\xbe\x1b\xb2\x85\xf6\x67\x3f\x62\xfb\xf4\xf0\x7b\xd8\xdc\xfb\xec\xc8\x3b\x3e\xf1\xfe\x18\xd6\xed\xb1\xd7\xea\xf9\xbc\x12\x7b\x87\xc2\x06\xf9\x00\x70\xff\x76\xec\xd5\xf8\xfa\xc1\xf1\x72\xe7\x85\xf4\x57\xe4\xf1\xd3\x6a\xfa\xf8\xa7\xd5\x5b\x5d\x14\x51\xc3\x85\x68\x8d\xfb\xc8\xb6\x4b\xb7\x40\x38\x80\xb8\xf8\x91\xbe\x14\xed\x1e\x98\x6f\x7c\x9b\x89\x00\xc3\x6d\xe6\x4e\x7c\x8e\x6e\x08\x4f\x1f\x3c\x4d\x5e\x62\x12\x9b\x87\x7c\x0b\xb7\x9b\x68\xfc\x5c\xa5\x69\x8c\x9a\x7f\x1c\xd3\x4e\x98\x1e\xd3\xc7\x6f\xd3\x58\x6b\x97\x92\xc6\xfd\x3f\x02\x00\x00\xff\xff\xb4\xdb\xbe\x75\x05\x22\x00\x00") +var _configYaml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\x5a\xfb\x6f\x1b\x37\xf2\xff\xdd\x7f\x05\xab\x5c\x70\x09\xe0\x2a\x71\xae\x2f\x18\xb9\x14\xae\x93\xbb\xfa\x10\x37\x45\xe2\x16\xe8\x4f\x0b\x4a\xcb\x95\x58\xef\x92\x7b\xcb\x5d\xc9\xea\x5f\x7f\xf3\x20\xb9\x4f\x5b\x92\x13\xe0\xfc\xfd\xa2\x17\x71\x87\x33\xe4\x3c\x3f\x33\xbb\x4f\xc4\x75\x53\x2c\x72\xf5\xf6\x3f\x27\x4f\xc4\x4f\x3b\x71\x2d\xeb\x7a\xad\x55\x23\xfe\x5d\x69\xb5\x52\x15\xac\x5e\xda\x72\x57\xe9\xd5\xba\x16\xcf\x96\xcf\xc5\xab\x97\x67\xdf\x8d\xa8\xc4\xb3\xeb\xab\x1b\xf1\x5e\x2f\x95\x71\xea\x39\xec\x59\x5a\x93\xe9\xd5\x7c\x27\x8b\xfc\xe4\x44\x96\x3a\xb9\x55\x3b\x77\x7e\x72\x22\xe0\xef\x89\xf8\xc3\x36\x37\xcd\x42\x89\x8b\x5f\xaf\x04\x3c\x98\xd3\xf2\xce\x36\x35\x2c\x9e\x8b\xd9\x2c\xd0\x7d\xb2\x8d\x49\x2f\x73\xdb\xa4\x7d\xd2\x27\xe2\x97\x0f\x37\xef\xce\xc5\xcd\x3a\xf2\x10\xda\x21\x87\x4a\x2c\x73\xad\x4c\x2d\xae\xde\x32\xa9\x43\x16\x4b\x64\xc1\x8c\x4f\x52\x95\xc9\x26\xaf\xdb\xc3\xbc\xe5\x05\x38\x72\x51\xe0\xce\xda\x0a\x38\x9a\x2c\x4b\x60\x94\xd2\x2f\x5b\xf7\xc5\x5e\x65\x28\x4a\xa4\x56\x18\x5b\x8b\xad\x84\x4d\x32\x6e\x5f\xec\x84\x17\x71\x2a\x9c\x22\x76\xaa\x28\xeb\x9d\x70\x75\xa5\xcd\x4a\x3c\x9b\xcd\x9e\x33\x3b\xbf\x03\xce\xf5\xb3\xca\x73\xfb\x95\xb8\x12\xb2\x00\x4e\x28\x4f\xdc\xec\x4a\x25\xbe\x5a\xab\xbc\x14\x99\xad\x60\x35\xd7\xae\x16\x36\xa3\x5d\xd2\xa4\x6e\x3e\x1b\x5d\x60\x2d\x8d\x51\x39\xd1\xd7\xa0\x19\xe0\x43\xd2\x4d\x0d\x06\x6a\x4a\x6b\xd0\x2a\x46\x2d\x6b\x6d\xcd\xe4\x85\xb6\xda\xad\x87\xbb\xfd\x16\xfc\x27\xae\x56\xd6\x46\x41\x7b\xef\xc7\x64\x5d\x83\x5e\xf2\xe1\x71\x53\xe3\x14\xfe\x4f\x99\xcb\x9d\x90\x4d\xaa\xad\xc8\x74\xae\xdc\x9c\x8c\x5a\x6f\xad\x70\x4d\x59\xda\xaa\x06\x1b\x2c\xd7\x16\x3c\xcb\x09\x59\x29\x31\xcb\xb2\xa2\x54\xab\x99\x40\x36\x33\xb9\x81\xf3\x6d\x66\x2c\x0f\x59\xa9\x2a\xf1\x0a\x3a\x8f\xa4\x60\xf4\xff\x36\xaa\x51\xd1\xe2\x1f\x25\xa8\x00\xae\x23\x6b\x51\x34\xa0\x55\x30\x77\x01\x37\x81\x8b\xab\xbb\xa5\x52\x29\x9b\x1d\xae\xb3\x42\xd7\x96\xf0\x2f\xb9\xbc\x15\xee\x56\x97\x2c\x88\x7e\x27\xf8\x3b\xa9\x90\xd5\xb9\x78\x39\xff\xf6\xb1\xcc\xf1\xd4\x64\xdb\x96\x7f\x58\xba\x4f\xc4\xb5\xbc\xd3\x45\x53\xf8\x73\xa5\x0d\x51\x18\xa1\x0d\x18\x04\xf4\x01\xbe\x21\x3e\xb1\x65\x5e\x92\x39\x1b\x53\x29\xb4\xce\x12\x95\x19\xc8\x59\x54\x21\xef\x12\xbe\x4e\x58\x07\x49\x93\x72\x9c\x28\xe1\xbc\xe1\x68\x0f\x49\x08\x34\x6e\x20\xc2\x25\xc0\x21\x09\x4f\xcf\xc5\xb7\x51\xd0\x95\x13\x6e\xdd\x64\x59\x8e\x0e\xa4\x8c\x84\x7c\x94\x8a\xed\x5a\x99\xe8\x89\xae\x96\x55\xed\x7e\x24\x7a\xd9\xd4\xb6\x80\xb3\x2e\x13\xde\xa4\x12\x3c\x75\x26\x73\xa7\x02\xc3\x0b\x63\x20\xee\x97\xca\xab\x48\x1b\x38\x64\xc1\x5a\x02\xbb\x10\x53\xb5\xd2\xc6\xa0\x3c\x88\x29\xf6\x3f\x3c\xd9\x02\xc8\xbd\x14\xcf\x22\x31\x6a\xeb\xcf\x7f\x0e\xec\x1a\x90\x71\xd2\xc6\x51\xf4\xa9\x8b\x34\x05\x15\x38\x3e\xec\xda\x36\x79\x0a\x92\x6a\x8c\x8c\x7e\x14\xb1\x4e\x24\x53\x83\x8f\x9e\xbd\xfa\x7e\xfe\x12\xfe\xef\x2c\xc6\xc8\xaf\xe0\xf4\x07\xb2\xc1\xf8\x00\x1e\xdf\x7d\xf3\xfd\x3f\x7e\x68\xf7\x4b\xe7\xb6\xb6\x4a\xc9\x30\xe1\xa4\x70\x4f\xd8\xef\x54\xb5\x51\xd5\x28\xf6\x0d\xdc\xdd\x6f\xda\x17\xd3\x81\xae\x1b\xd4\xbf\x01\x5b\x23\x0b\x45\x02\x43\x35\x61\xf2\xc6\x3f\x02\xf2\xf0\xa0\x4d\xee\x7c\xbd\x60\xe1\x70\x4d\x89\x8e\x0c\x7e\xdc\x40\xb0\xb7\x7a\xfe\x31\x1a\x76\xea\x29\xa4\x62\x48\x0f\x98\x8c\xe1\x7a\x3a\xdb\x11\xd3\xa5\xaa\x6a\x9d\xe9\xa5\xac\x15\xda\x18\x97\xf8\xfe\x78\x4e\xcf\x0e\x58\x38\xf0\x45\x65\x96\xbb\xb9\xb8\xaa\xb1\x88\x2c\xc0\x91\x51\x01\xb9\x92\x1b\xf0\x9f\x35\x2c\x59\x73\x2a\x16\x4d\x2d\x52\xed\xd0\x35\x85\x06\x42\xce\x98\x98\x91\xd6\x72\x03\x3a\xf2\x0c\xb5\x73\x0d\x1c\xa5\xaf\x76\x19\x04\x63\x22\x87\x1d\x55\xc3\x9e\x57\x40\xd6\xd6\x25\x32\x34\xe0\xdf\x06\x53\x1c\x1c\xb4\xaf\xc1\x70\xdb\x81\x83\xff\x0b\x72\x65\x29\xeb\x35\x79\x45\xe7\xa2\x98\x43\xa7\xec\x3b\xa4\xc1\x9b\x1a\xca\x46\x7b\xb3\xb8\x22\x1f\x9b\xed\x93\x8c\x35\xf8\x3e\xe9\xbe\x3e\x1f\x26\x10\x88\xbb\xf2\x2e\x96\x4b\x8c\xab\xda\xde\x02\xb6\xc0\x6d\xda\xe8\x5a\xcb\x5c\xff\xa5\xa2\xef\x6c\x75\xbd\x46\xb6\xa5\x84\x1c\x06\xa9\x03\x6a\x30\x55\x01\x37\x75\x18\xd9\x63\x88\xf6\x38\xec\x5c\xbc\x2f\xe1\x7d\x3d\x94\x32\x70\xe4\x90\x9e\x64\x9e\xef\xba\xd1\x5b\xa9\xba\xda\x75\xbd\xb6\xeb\x1a\x32\xc3\x2a\x0d\x1e\xd6\xba\x0e\xfb\x3c\xed\x4a\x7c\x52\x0c\x19\x88\x05\xff\x6c\xb7\x90\x5d\x0d\xb8\xbb\x2e\x94\x0b\xf9\x62\x18\x50\x24\x79\x50\xc6\x59\x68\x57\x80\xa7\x86\x8b\x9d\xbd\x1c\xf1\xf7\x65\x65\x28\x61\x2b\x31\x12\xcc\xd7\x0b\x55\x6f\x95\xea\xc2\x0b\x7f\xd7\xc0\xb4\x2b\x48\x23\x1c\xd9\x48\x80\x05\xdf\x62\x26\x95\xcb\x75\x5b\x98\x2f\xf1\x17\x40\x36\xb3\x02\xc3\x38\x94\xb3\x23\x03\xa5\x76\x6b\x72\x2b\xc1\x48\xcc\x29\x6a\xa3\x17\x13\xb1\x5c\xd9\x5a\xe6\xec\xe5\x0e\xbd\x04\x41\x13\x31\x4e\x35\x28\xa2\xb6\x70\x30\x28\x95\xd7\xfa\xa7\x58\x9f\x70\x5b\x82\xb4\x70\xa8\xb3\x57\x31\x91\x42\x2e\xb1\x29\xe5\x0e\xd0\x2f\x97\x74\xaf\x01\x95\xcb\xd2\x61\x11\x81\x54\xa2\xd0\x88\x70\x64\xf4\xf0\x25\x64\x8d\x0a\x1c\x30\xab\x6c\xc1\x49\x08\x05\x9f\xa2\x3c\xd8\x58\x79\x7f\x54\x77\x25\x9c\x24\x41\xae\xe7\xe2\xd5\x37\xf7\xc8\x0b\x5a\x55\xc0\x02\x20\x90\x82\x3a\xe6\xd3\x18\xdf\x26\x23\x48\x81\x9c\x52\xc8\x48\xaa\x70\x24\xa6\xd0\xa6\xa9\x95\x0b\x08\x0c\x76\xf5\x35\xee\x21\x63\xd4\x04\x56\x85\x1a\x2f\x41\x4c\x3d\xa7\xb9\x78\x67\x36\xba\xb2\x86\x10\xed\x46\x56\x1a\xf5\xcd\xc1\x42\x19\x90\x31\x32\x64\xf7\x54\xac\x55\xe5\x63\x3e\xaa\x17\x82\xe3\x6f\x3f\x7f\xb8\x7e\xf7\x62\x4e\x4c\x5f\x14\x94\xd1\xd2\x3f\x11\x89\x6d\x6c\xde\x14\x6a\x04\xbe\x79\xd9\xf3\xe1\x35\x84\x3c\xd1\x16\xef\xed\x16\xf3\x32\x93\x09\x88\x2c\xf8\x9d\x32\x79\x4e\x8f\x90\xfa\xe5\x59\xf4\x5c\x68\x57\xee\xa3\x5f\xf3\x33\xdc\xf0\x03\x1c\x48\xa6\xa0\xb2\xb6\x1b\x78\x47\xae\x25\x78\xf5\xc7\x61\xfa\xa0\x72\x00\xff\xef\x33\x05\xb9\xdf\xa9\xc0\x10\xf1\xa8\x13\xd4\x68\x50\x35\xea\x0e\xb2\xb6\x4f\x45\xf8\x18\x0b\xe1\xbc\xef\xbb\xdd\x48\x7e\xef\xc1\x3d\x89\x15\x58\x31\x87\xa9\x8b\x6a\x13\xc6\x31\xf6\x0c\x04\x22\xd7\x1e\xc9\x10\x35\x9a\x9e\x0e\x47\x50\x92\x8a\x0c\x00\x1c\xa8\x6c\xe8\x1d\x96\x70\x94\xe7\xe7\xf3\x8d\xf3\x18\x55\x17\xa5\x45\x32\x87\x27\xc7\x0a\xea\x4f\xee\x8f\x12\xbb\x0d\xda\x4d\xa2\xce\xe9\x9f\xf8\xf7\xb5\x98\x7d\x6a\x00\xd3\x21\x00\x98\x11\x2c\x62\xe2\x36\x9e\xd7\x90\x90\x97\xd4\x7e\x38\xc6\xbd\xa9\x72\x7a\x65\xb0\x5e\x04\x62\x8e\x15\x83\xf8\x2c\x17\xb5\xba\x03\xd8\x0c\x69\x56\xae\x86\x1a\xf8\x60\x20\x9b\x5a\xa3\xb0\xad\xf0\x4c\x9f\xe1\xf5\x33\x5d\xb9\xfa\x39\x6a\x07\x65\x78\x94\x52\xa9\x4c\xdf\x81\x1b\x7e\xe5\x93\x34\x0a\xb3\x26\x09\x9c\xdb\x2b\x18\x1b\xb0\xa9\xaa\x2a\x5b\xc1\x96\x1b\x74\x68\x2e\x0b\x36\x20\x5f\xcd\x40\x94\xba\x08\xe8\xbb\xc2\x66\xf4\x6e\xc8\xd3\x89\xc7\x05\x69\xe4\x71\xc9\x0f\x28\x23\x34\x55\x05\x41\x04\x67\x0f\x54\x6d\xe3\xf6\x93\x02\xb7\x44\xa2\xb6\xbb\xa3\xf0\x0e\x9a\x69\x3b\x20\xf0\xa2\x08\x0b\xc4\x3b\x4a\x08\xde\xdf\xd6\xd2\x79\x6e\xf5\xba\x52\xca\x37\xde\x80\xe5\xd1\x8b\x6d\x89\xc9\xd8\x5f\xf7\x09\x84\x81\x96\x0e\x6e\x2f\x2e\xa2\x3c\x76\x1e\xf2\x04\xef\xb9\xc1\x52\xc1\x0f\x3a\x27\x9a\x47\x90\x93\x90\x77\xb0\x0f\x8b\x7f\x82\x59\xb0\xd2\x51\xc8\x10\x9b\x89\xbd\xa7\x1c\x2c\x40\x0c\xe1\x40\x66\x9c\xa6\x0b\x32\xc0\x51\x96\x95\x2e\xb9\x23\x79\xdb\xfe\xc0\x0c\xbc\x35\xb1\x4b\x0d\x6a\x88\xcd\x02\x75\xcc\x61\x15\x54\x1b\x02\x31\xf0\x8d\x2e\x20\x7e\x87\xb4\x66\x1b\x17\x57\x7c\xcf\x06\xed\xe8\x02\x0b\x24\x36\xf5\x68\x99\xae\x4b\x76\xf2\xba\x3f\x2d\x82\xdf\xac\xf1\x3d\x77\x25\x8d\xcb\x65\xdd\xa2\xc1\xf6\x8f\xd1\x04\xe1\x17\x0b\xfb\x2b\x91\x4b\xb3\x6a\xc8\xcb\xc5\x5b\x8b\x2e\x0e\x01\x5b\x58\x40\x9c\x91\x12\x4f\x43\x5d\x0a\xc1\x1b\x31\x7b\x3a\x13\xcf\x5c\x03\xa6\x87\x63\xcd\x9e\xba\xd9\x29\xfc\x37\x85\xff\xaa\x7a\x39\x7f\x3e\x12\x18\xca\xa7\x6b\x16\xae\xd6\x35\xe5\x22\xe2\x03\xe0\x93\xca\x4b\x2a\x6b\x39\x17\x1f\x51\x28\xc1\x54\xc8\x89\xad\xf0\xad\xce\x73\xb0\x10\xf5\xe8\xed\x2c\xa0\xd0\x6e\xa1\x00\xee\xaa\xd8\xc4\xb4\x81\x14\x7c\xeb\xa4\x73\x06\x4c\x10\x40\x34\x1b\xad\xb5\x2b\xad\x2b\x71\x29\x0f\xeb\x3d\xf3\xcf\xa0\xb9\x72\xb1\x0f\xb7\x6d\x17\xca\xf6\x90\x60\x9e\x54\x43\x11\xd6\xb5\x0a\x00\x67\x18\xaa\xe3\xc8\xf7\xd1\xdf\x54\x79\x0c\xdb\x0b\xf1\xdb\xc7\xf7\xb1\x6b\xc7\xe8\xa3\x11\x10\xa9\x0d\x99\xc2\x5d\xa2\xe1\x67\x43\x46\x50\x60\x75\x3a\x4c\x26\xbf\x58\x41\xeb\x21\x91\x6c\x31\xb7\x64\x38\x92\x6a\xb9\x96\x15\x58\x00\x33\x3a\x08\x7f\xe6\x9e\x0f\x38\x7b\x86\xb5\xb5\x49\x0e\x20\x23\x72\xfe\x03\x67\x5d\xf4\x10\xf6\x30\x5f\xa5\xc9\xb3\x80\x54\x20\x29\x75\x1b\x10\x63\xb8\x41\xd8\x25\x25\x22\x0c\x14\xc4\x45\x20\x13\xc1\xac\x37\x7c\x31\x17\xbf\xd8\x96\x19\x5a\x18\x14\x00\x01\x05\x17\x56\xc3\xab\x42\xec\xfa\x89\x01\x3d\x85\xa3\xbc\x5e\xbc\x79\xea\x5e\xbf\x58\xbc\x61\x7a\x01\xbf\xcf\xe8\x27\xdb\xab\x6b\x91\xf3\xd7\x8b\xea\xcd\x6b\x4d\xf4\xfa\x0d\x9b\x0f\x5c\xb9\x27\x00\x11\x67\xd0\xe3\x03\x22\x9e\xa6\xad\x0c\x77\x9f\xd9\xc9\x36\x80\xed\x06\x5a\x24\x8e\x70\x90\x21\x97\x25\x81\x3b\xac\x82\x0b\xe5\x25\xa5\x0d\xf9\x94\xd7\x62\x05\x0f\x62\x58\x30\x36\x0d\xea\x0e\x69\x1d\xb6\x19\x48\x19\x07\x45\x06\x12\x8e\xa3\xc3\x4c\x85\x07\xa1\x85\xcf\x8e\x0e\xce\x0a\x02\xe5\x12\xd2\xbb\xaf\xb2\x3d\x09\xd7\xc0\x72\xc0\x7b\x62\x9a\x04\x78\xa6\x8d\xe2\xf1\x02\x50\xcd\x7d\x85\x45\xa4\x47\x10\x7a\xef\xc5\x23\xe9\xe8\xea\x4b\x77\xe4\xd5\x3f\x34\x75\xd9\xd4\x7c\xc0\x1e\xe0\x6f\x61\x32\x43\x7d\x6c\xd8\x97\x6d\x55\xf6\x20\x6c\x6f\x82\xf0\xd5\xdb\xf7\x06\x88\x0d\xc2\xd2\x94\x24\x47\x7e\x39\x7f\xb5\x41\x89\xe8\x57\xc1\x27\xfc\x1e\xb2\xd0\x01\xfa\xe9\x50\x8f\x55\xc4\x0f\xb1\xe1\xb8\xef\xd9\xb1\xd9\x35\x28\xb1\x37\x25\x5b\xd8\x86\xe1\x65\xb8\x6f\x98\xa4\xb5\xfe\x82\x3a\xc5\x4a\xae\xee\x68\xd8\x77\xa8\x2e\x59\x0b\x7d\x65\x7a\xe6\xa0\xc0\x90\x1b\x4e\x7d\xfc\x01\xf2\x89\xc1\x1f\xd4\x09\xc7\x84\xec\x75\xab\xcb\xfd\xba\x8c\xa4\x23\x65\x65\xc7\xfa\xda\x55\x41\x81\x54\x2b\xc0\x3a\xc8\xd1\x8d\xd5\xb3\x57\x07\xed\xe4\xb8\xa4\xbc\x36\xd6\x01\xe0\x39\xce\xbd\x78\x72\xbd\xf0\xb2\xca\x7d\x9a\x88\x53\xd5\xc3\x35\x12\xb6\x4c\x68\xa6\xfc\xa2\xaa\x89\x33\xe3\x03\xca\x71\x1c\x7d\x77\xe0\xf8\xd8\x4b\x30\x43\x97\xb2\xe2\x96\x69\x8a\x3f\xfe\xf5\xa6\xe8\x63\x75\xc7\x2c\x79\x9c\xc6\x11\x5f\xee\x57\x32\x52\x8d\xf4\xba\x7e\x6c\x60\xb6\x8d\x5d\xff\xfd\xcf\xc3\xda\x0c\x84\xc9\x5a\xc9\x54\x55\x6d\xcd\xbb\x0c\x6d\x1a\xde\x0b\xd7\xfa\x27\xa5\x83\x25\xf7\xee\xbe\xa0\xbe\x70\x82\x07\x31\xf9\xd3\x6a\x53\x1c\x50\x03\x98\x6e\xa4\x22\x5c\x3e\xd2\xf7\xae\x01\x34\xbb\xd8\x1d\x41\x82\x82\x6a\xcd\x2f\x02\xbd\xa1\xc3\x6b\x31\x9d\xb1\xdf\x80\xdd\xb1\x0a\xf0\xcc\x1f\xc7\x1f\xb6\x50\x94\xc6\xc0\x10\x7b\x95\x4a\xe0\x1d\x8e\x55\xa9\x24\xa7\xc1\xb1\xee\x60\x32\xec\x83\xa9\x59\x94\x86\x41\x7e\x10\x8d\x38\x21\x92\x13\x92\x1e\x82\x14\xd0\x38\x1e\x3a\x69\xdf\x99\xd1\xcb\x40\x83\xfd\xa1\xf1\xf7\xe1\x47\xa1\xa5\xbd\x05\x88\xbe\x5f\xcf\x48\x35\xd2\xf2\xed\x91\x2a\xfe\x54\x5b\x1f\xd2\x34\x57\xc4\x29\x47\xae\xa0\xd7\x01\x08\xe1\x86\xa3\xb5\x10\x27\x78\x5d\xff\xd6\x64\xef\x21\x5b\xda\xd1\x51\xe9\x3d\x12\xce\x03\x27\x9f\x8c\x17\x1f\x1b\x62\xfd\x0e\x3c\xc0\xc1\xd8\xbb\xdf\x03\x93\xa6\x7d\x44\x1b\xee\x05\x70\xee\xb6\x52\x55\xdb\x5e\x98\xf0\x48\xf8\x47\x62\x2b\x5d\xec\x33\xa6\x80\x3f\x39\x99\xf6\x80\xd5\x83\xd5\xf3\x3d\x45\xb2\x13\x8d\xd8\x50\xee\x57\x3f\x52\x8d\x34\x59\x3c\x2a\x0c\x83\x8f\x50\x14\xe2\x0f\x8e\xcb\x18\x08\xb1\xd7\xd9\x00\x22\x95\xd5\xaa\xc1\x01\xe3\x21\x75\xc1\x33\x48\x02\x83\x4e\xcf\x06\xe7\x00\x15\x31\x6c\x09\x72\x46\x3d\x1c\xc6\x9c\xdd\xc4\x6e\x76\xa0\xeb\xc0\x1d\x5f\x23\x01\x42\x21\x40\xd3\xab\x40\xf1\xdc\x41\x40\x7c\xe1\x44\xb4\x03\x76\x28\x29\x81\x56\x1d\x5b\xac\xac\xc9\xb9\x5b\xe3\xb6\xaa\x5d\x05\xaf\x42\xba\xb4\xdb\x60\x77\xaa\x8d\x60\x0b\x22\x06\x3f\x10\x36\x46\xd2\x91\x2d\xf1\xc9\x24\x60\xec\xf7\x1f\x5f\x02\x2d\x52\xcf\xf0\x65\xa1\x62\x82\xd3\xa5\x87\xf1\x00\xca\xa1\x19\xd4\x58\xf2\xb0\x19\x84\xf3\xf5\x10\x68\xf7\xc0\x07\xc2\x4f\x68\x28\x79\x58\x7e\x80\x4d\x02\xe9\x58\xf5\xcb\xcf\x68\x75\x80\xed\x02\x5f\x27\x66\x31\x53\xf1\xf0\x1e\xdf\x84\x6a\x77\xfb\xc8\x66\x07\x1b\x65\x7f\xb1\xee\x18\xb4\xcd\x82\x6d\xbf\x4c\x6f\x09\xf8\xc5\x41\x1a\xd4\x4d\x5b\x3b\x3a\x3a\x34\xfb\x47\xd2\xb1\x8e\x9a\x62\x3a\xf7\x3f\xbe\xc7\x99\xd6\xde\xe3\xf2\x7c\x9c\x84\x44\x75\xf5\xe6\xbd\x83\x31\xc8\x03\x4e\x59\xe6\x4d\x25\xf3\xf8\x91\xc1\x1e\xdd\x4f\xcf\xa4\x89\x61\x89\xa3\x89\xfd\x1a\x27\xb2\x63\x35\xf8\xab\xa4\x51\x40\xff\x53\x89\x43\x52\x37\xed\x88\xf1\xfb\xce\x0f\xa9\x70\x9c\x46\xac\x70\xfc\x9d\x57\x00\x32\x77\x7c\xfc\xf4\x54\xf0\x6c\xf7\xd0\x29\x7c\xbc\x78\x7f\x50\x84\xb0\x9e\x97\xc7\x67\xa6\xbd\x95\xc2\x57\x8a\xfb\xb5\xc5\x74\x23\x1f\xac\x8e\x8c\xdf\x8f\xc4\xc6\x77\x46\x61\x48\x4f\xd9\x93\xde\x9a\xef\x53\x24\x9f\xa2\xed\x62\x46\x1c\xda\x3e\xa6\x57\x63\xc2\xbe\xf6\xd6\x4e\x1d\xd0\x25\x12\xd9\xf8\xce\x93\x5e\xf3\xe0\xa5\x81\x8d\xeb\xd4\x81\xc5\x8e\xe7\xdc\x84\xc0\xf3\x3c\x54\x07\x7a\xe9\xb8\x4f\x05\x44\x9b\xf0\x05\x86\x96\xa6\xd5\x71\x40\xc0\x72\x73\x48\x3b\xc2\x74\xc7\x86\xc4\x47\xda\x75\x74\x4c\x1c\x11\x10\xdc\xab\x3c\x26\x22\xf8\x46\xe3\x90\xf0\xeb\xf7\xc4\x04\x28\x31\x7c\x95\xb8\x57\x67\x2d\xed\x78\x10\x75\xcf\xba\x3b\xb6\xe8\x7d\x0a\xde\x13\xbe\xae\x84\xf2\x46\x9f\xf9\xa5\xbe\x70\xdb\xd8\xf9\xfd\xdd\xc5\xaf\x8d\x68\xe6\x47\xcb\x07\x35\xc9\x88\x34\xf8\xe5\x4b\x1b\x5d\x2c\xad\xfb\x2d\xe4\x7d\xe1\x45\xfb\x86\x78\xd2\x73\x45\xb4\xb8\x7a\x04\x57\xbf\x2f\x4c\xb5\x33\x8b\xef\xaf\xa9\x0d\xc0\x61\x39\x5b\x8a\x3f\x7d\x3b\xc0\x4c\x4c\x38\xb6\x05\xac\x4f\x2c\x1e\x1b\xe0\xd0\x0d\xda\x42\xff\xe5\xb1\xff\xe7\x15\x54\xc0\xd3\x89\x32\xb6\x59\xad\x1f\x7a\x29\x0b\x98\x9b\x68\xa6\x82\xa0\xfb\xe2\x52\x06\x1d\x0d\x8c\xe3\x57\x83\x55\x38\x10\x78\x77\x6b\x0d\x4f\x13\xe3\xe2\xa0\x61\xe3\xe4\x9c\x71\x72\xcc\xf8\x60\xa1\xcd\x25\x7d\xec\x2a\x36\x96\xdf\x67\x21\xdb\x47\xcc\x1a\x7d\x12\x49\x90\x4d\xda\x7d\x6f\xc4\x9d\x48\xc8\x31\xf4\xb8\x23\x06\xe1\xf4\x80\x3f\xfe\x11\xd9\x28\x9b\x0c\x37\x8f\xcf\x18\xd5\x77\xf8\x84\xf2\xc1\xe1\xe4\xf4\x6c\xf2\xf3\xf4\xf9\x7f\x1a\x50\x3e\xde\x40\xf7\x30\x3c\xd6\x46\x1d\x36\xc4\xa7\xb6\xab\x55\xae\x0e\xce\x2c\x3d\xf2\x91\xa1\xda\xa7\x53\x8f\xa6\xd7\x8f\x4e\x3f\x37\x2c\xa4\xfd\xe6\x2e\x7c\xab\x1d\xbf\x26\xb6\xe6\x85\xcd\xb2\xfd\x83\x79\x62\x94\x26\x40\x8b\x43\x85\xc8\xae\x65\x14\x73\x83\x27\x15\x7d\xb6\x3d\x26\xe6\x60\x1e\x06\x75\x4f\x4c\x36\xaa\x72\xf4\x41\xf1\x3e\xb5\x7b\xc2\x91\xf6\x36\x9f\xd3\x13\x05\x97\xf0\xcc\x7b\xdf\xa1\xee\xd3\x5d\x38\x79\xfb\x8d\x6f\xbb\x14\x5d\xd1\xbb\x58\xf8\xfc\x6b\xef\x25\x89\x6e\x7c\x47\x7b\xf4\x84\xef\x92\x6a\x29\xdf\xd2\x7f\x0e\x06\xed\xb1\x34\x71\xfc\x84\xf1\x1a\xe6\x3b\x80\xb4\xa6\x94\xc2\xdb\x68\xa6\xbb\xd5\x07\x4c\x89\x21\xf4\x5d\x77\x30\x8c\x39\xa2\x52\x50\x63\x1c\x86\xa2\x67\xd7\x7b\xd9\x8c\x3b\xc6\xaf\xda\x9b\x1a\x1c\x32\xa9\xf0\x02\x91\xd7\xef\xb4\xdb\xc5\x11\x57\xf8\x4c\x90\xee\x27\x73\xfc\xfa\x98\xdf\x42\x66\xfc\xbe\xdc\xa4\xdd\xdf\x43\xa0\xe2\x07\x2d\xde\x2c\xfd\x6c\x16\xb4\xe5\x1e\x60\xc0\x34\x1d\xa0\xd3\xcf\x3d\x11\xc8\xb4\xca\xf7\x63\xae\xc8\xee\x7f\x01\x00\x00\xff\xff\x68\xa7\xcd\x9c\x81\x34\x00\x00") func configYamlBytes() ([]byte, error) { return bindataRead( @@ -83,7 +83,7 @@ func configYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "config.yaml", size: 8709, mode: os.FileMode(420), modTime: time.Unix(1466393994, 0)} + info := bindataFileInfo{name: "config.yaml", size: 13441, mode: os.FileMode(420), modTime: time.Unix(1466549845, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/bot/config.go b/bot/config.go index 57fcb9d..d74b117 100644 --- a/bot/config.go +++ b/bot/config.go @@ -65,9 +65,19 @@ func SetDefaultConfig() { viper.SetDefault("admins.names", []string{"SuperUser"}) // Command defaults. + viper.SetDefault("commands.prefix", "!") + viper.SetDefault("commands.common_messages.no_tracks_error", "There are no tracks in the queue.") + viper.SetDefault("commands.common_messages.caching_disabled_error", "Caching is currently disabled.") + viper.SetDefault("commands.add.aliases", []string{"add", "a"}) viper.SetDefault("commands.add.is_admin", false) viper.SetDefault("commands.add.description", "Adds a track or playlist from a media site to the queue.") + viper.SetDefault("commands.add.messages.no_url_error", "A URL must be supplied with the add command.") + viper.SetDefault("commands.add.messages.no_valid_tracks_error", "No valid tracks were found with the provided URL(s).") + viper.SetDefault("commands.add.messages.tracks_too_long_error", "Your track(s) were either too long or an error occurred while processing them. No track(s) have been added.") + viper.SetDefault("commands.add.messages.one_track_added", "%s added 1 track to the queue:
%s from %s") + viper.SetDefault("commands.add.messages.many_tracks_added", "%s added %d tracks to the queue.") + viper.SetDefault("commands.add.messages.num_tracks_too_long", "
%d tracks could not be added due to error or because they are too long.") viper.SetDefault("commands.addnext.aliases", []string{"addnext", "an"}) viper.SetDefault("commands.addnext.is_admin", true) @@ -76,26 +86,35 @@ func SetDefaultConfig() { viper.SetDefault("commands.cachesize.aliases", []string{"cachesize", "cs"}) viper.SetDefault("commands.cachesize.is_admin", true) viper.SetDefault("commands.cachesize.description", "Outputs the file size of the cache in MiB if caching is enabled.") + viper.SetDefault("commands.cachesize.messages.current_size", "The current size of the cache is %.2v MiB.") viper.SetDefault("commands.currenttrack.aliases", []string{"currenttrack", "currentsong", "current"}) viper.SetDefault("commands.currenttrack.is_admin", false) viper.SetDefault("commands.currenttrack.description", "Outputs information about the current track in the queue if one exists.") + viper.SetDefault("commands.currenttrack.messages.current_track", "The current track is %s, added by %s.") viper.SetDefault("commands.forceskip.aliases", []string{"forceskip", "fs"}) viper.SetDefault("commands.forceskip.is_admin", true) viper.SetDefault("commands.forceskip.description", "Immediately skips the current track.") + viper.SetDefault("commands.forceskip.messages.track_skipped", "The current track has been forcibly skipped by %s.") viper.SetDefault("commands.forceskipplaylist.aliases", []string{"forceskipplaylist", "fsp"}) viper.SetDefault("commands.forceskipplaylist.is_admin", true) viper.SetDefault("commands.forceskipplaylist.description", "Immediately skips the current playlist.") + viper.SetDefault("commands.forceskipplaylist.messages.no_playlist_error", "The current track is not part of a playlist.") + viper.SetDefault("commands.forceskipplaylist.messages.playlist_skipped", "The current playlist has been forcibly skipped by %s.") viper.SetDefault("commands.help.aliases", []string{"help", "h"}) viper.SetDefault("commands.help.is_admin", false) viper.SetDefault("commands.help.description", "Outputs this list of commands.") + viper.SetDefault("commands.help.messages.commands_header", "
Commands:
") + viper.SetDefault("commands.help.messages.admin_commands.header", "
Admin Commands:
") viper.SetDefault("commands.joinme.aliases", []string{"joinme", "join"}) viper.SetDefault("commands.joinme.is_admin", true) viper.SetDefault("commands.joinme.description", "Moves MumbleDJ into your current channel if not playing audio to someone else.") + viper.SetDefault("commands.joinme.messages.others_are_listening_error", "Users in another channel are listening to me.") + viper.SetDefault("commands.joinme.messages.in_your_channel", "I am now in your channel!") viper.SetDefault("commands.kill.aliases", []string{"kill", "k"}) viper.SetDefault("commands.kill.is_admin", true) @@ -104,66 +123,98 @@ func SetDefaultConfig() { viper.SetDefault("commands.listtracks.aliases", []string{"listtracks", "listsongs", "list", "l"}) viper.SetDefault("commands.listtracks.is_admin", false) viper.SetDefault("commands.listtracks.description", "Outputs a list of the tracks currently in the queue.") + viper.SetDefault("commands.listtracks.messages.invalid_integer_error", "An invalid integer was supplied.") + viper.SetDefault("commands.listtracks.messages.track_listing", "%d: %s, added by %s.
") viper.SetDefault("commands.move.aliases", []string{"move", "m"}) viper.SetDefault("commands.move.is_admin", true) viper.SetDefault("commands.move.description", "Moves the bot into the Mumble channel provided via argument.") + viper.SetDefault("commands.move.messages.no_channel_provided_error", "A destination channel must be supplied to move the bot.") + viper.SetDefault("commands.move.messages.channel_doesnt_exist_error", "The provided channel does not exist.") + viper.SetDefault("commands.move.messages.move_successful", "You have successfully moved the bot to %s.") viper.SetDefault("commands.nexttrack.aliases", []string{"nexttrack", "nextsong", "next"}) viper.SetDefault("commands.nexttrack.is_admin", false) viper.SetDefault("commands.nexttrack.description", "Outputs information about the next track in the queue if one exists.") + viper.SetDefault("commands.nexttrack.messages.current_track_only_error", "The current track is the only track in the queue.") + viper.SetDefault("commands.nexttrack.messages.next_track", "The next track is %s, added by %s.") viper.SetDefault("commands.numcached.aliases", []string{"numcached", "nc"}) viper.SetDefault("commands.numcached.is_admin", true) viper.SetDefault("commands.numcached.description", "Outputs the number of tracks cached on disk if caching is enabled.") + viper.SetDefault("commands.numcached.messages.num_cached", "There are currently %d items stored in the cache.") viper.SetDefault("commands.numtracks.aliases", []string{"numtracks", "numsongs", "nt"}) viper.SetDefault("commands.numtracks.is_admin", false) viper.SetDefault("commands.numtracks.description", "Outputs the number of tracks currently in the queue.") + viper.SetDefault("commands.numtracks.messages.one_track", "There is currently 1 track in the queue.") + viper.SetDefault("commands.numtracks.messages.plural_tracks", "There are currently %d tracks in the queue.") viper.SetDefault("commands.pause.aliases", []string{"pause"}) viper.SetDefault("commands.pause.is_admin", false) viper.SetDefault("commands.pause.description", "Pauses audio playback.") + viper.SetDefault("commands.pause.messages.no_audio_error", "Either the audio is already paused, or there are no tracks in the queue.") + viper.SetDefault("commands.pause.messages.paused", "%s has paused audio playback.") viper.SetDefault("commands.reload.aliases", []string{"reload", "r"}) viper.SetDefault("commands.reload.is_admin", true) viper.SetDefault("commands.reload.description", "Reloads the configuration file.") + viper.SetDefault("commands.reload.messages.reloaded", "The configuration file has been successfully reloaded.") viper.SetDefault("commands.reset.aliases", []string{"reset", "re"}) viper.SetDefault("commands.reset.is_admin", true) viper.SetDefault("commands.reset.description", "Resets the queue by removing all queue items.") + viper.SetDefault("commands.reset.messages.queue_reset", "%s has reset the queue.") viper.SetDefault("commands.resume.aliases", []string{"resume"}) viper.SetDefault("commands.resume.is_admin", false) viper.SetDefault("commands.resume.description", "Resumes audio playback.") + viper.SetDefault("commands.resume.messages.audio_error", "Either the audio is already playing, or there are no tracks in the queue.") + viper.SetDefault("commands.resume.messages.resumed", "%s has resumed audio playback.") viper.SetDefault("commands.setcomment.aliases", []string{"setcomment", "comment", "sc"}) viper.SetDefault("commands.setcomment.is_admin", true) viper.SetDefault("commands.setcomment.description", "Sets the comment displayed next to MumbleDJ's username in Mumble.") + viper.SetDefault("commands.setcomment.messages.comment_removed", "The comment for the bot has been successfully removed.") + viper.SetDefault("commands.setcomment.messages.comment_changed", "The comment for the bot has been successfully changed to the following: %s") viper.SetDefault("commands.shuffle.aliases", []string{"shuffle", "shuf", "sh"}) viper.SetDefault("commands.shuffle.is_admin", true) viper.SetDefault("commands.shuffle.description", "Randomizes the tracks currently in the queue.") + viper.SetDefault("commands.shuffle.messages.not_enough_tracks_error", "There are not enough tracks in the queue to execute a shuffle.") + viper.SetDefault("commands.shuffle.messages.shuffled", "The audio queue has been shuffled.") viper.SetDefault("commands.skip.aliases", []string{"skip", "s"}) viper.SetDefault("commands.skip.is_admin", false) viper.SetDefault("commands.skip.description", "Places a vote to skip the current track.") + viper.SetDefault("commands.skip.messages.already_voted_error", "You have already voted to skip this track.") + viper.SetDefault("commands.skip.messages.voted", "%s has voted to skip the current track.") viper.SetDefault("commands.skipplaylist.aliases", []string{"skipplaylist", "sp"}) viper.SetDefault("commands.skipplaylist.is_admin", false) viper.SetDefault("commands.skipplaylist.description", "Places a vote to skip the current playlist.") + viper.SetDefault("commands.skipplaylist.messages.no_playlist_error", "The current track is not part of a playlist.") + viper.SetDefault("commands.skipplaylist.messages.already_voted_error", "You have already voted to skip this playlist.") + viper.SetDefault("commands.skipplaylist.messages.voted", "%s has voted to skip the current playlist.") viper.SetDefault("commands.toggleshuffle.aliases", []string{"toggleshuffle", "toggleshuf", "togshuf", "tsh"}) viper.SetDefault("commands.toggleshuffle.is_admin", true) viper.SetDefault("commands.toggleshuffle.description", "Toggles automatic track shuffling on/off.") + viper.SetDefault("commands.toggleshuffle.messages.toggled_off", "Automatic shuffling has been toggled off.") + viper.SetDefault("commands.toggleshuffle.messages.toggled_on", "Automatic shuffling has been toggled on.") viper.SetDefault("commands.version.aliases", []string{"version"}) viper.SetDefault("commands.version.is_admin", false) viper.SetDefault("commands.version.description", "Outputs the current version of MumbleDJ.") + viper.SetDefault("commands.version.messages.version", "MumbleDJ version: %s") viper.SetDefault("commands.volume.aliases", []string{"volume", "vol", "v"}) viper.SetDefault("commands.volume.is_admin", false) viper.SetDefault("commands.volume.description", "Changes the volume if an argument is provided, outputs the current volume otherwise.") + viper.SetDefault("commands.volume.messages.parsing_error", "The requested volume could not be parsed.") + viper.SetDefault("commands.volume.messages.out_of_range_error", "Volumes must be between the values %.2f and %.2f.") + viper.SetDefault("commands.volume.messages.current_volume", "The current volume is %.2f.") + viper.SetDefault("commands.volume.messages.volume_changed", "%s has changed the volume to %.2f.") } // ReadConfigFile reads in the config file and updates the configuration accordingly. diff --git a/commands/add.go b/commands/add.go index b0bfda0..9466e3c 100644 --- a/commands/add.go +++ b/commands/add.go @@ -55,7 +55,7 @@ func (c *AddCommand) Execute(user *gumble.User, args ...string) (string, bool, e ) if len(args) == 0 { - return "", true, errors.New("A URL must be supplied with the add command") + return "", true, errors.New(viper.GetString("commands.add.messages.no_url_error")) } for _, arg := range args { @@ -68,7 +68,7 @@ func (c *AddCommand) Execute(user *gumble.User, args ...string) (string, bool, e } if len(allTracks) == 0 { - return "", true, errors.New("No valid tracks were found with the provided URL(s)") + return "", true, errors.New(viper.GetString("commands.add.messages.no_valid_tracks_error")) } numTooLong := 0 @@ -83,15 +83,15 @@ func (c *AddCommand) Execute(user *gumble.User, args ...string) (string, bool, e } if numAdded == 0 { - return "", true, errors.New("Your track(s) were either too long or an error occurred while processing them. No track(s) have been added.") + return "", true, errors.New(viper.GetString("commands.add.messages.tracks_too_long_error")) } else if numAdded == 1 { - return fmt.Sprintf("%s added 1 track to the queue:
\"%s\" from %s", + return fmt.Sprintf(viper.GetString("commands.add.messages.one_track_added"), user.Name, lastTrackAdded.GetTitle(), lastTrackAdded.GetService()), false, nil } - retString := fmt.Sprintf("%s added %d tracks to the queue.", user.Name, numAdded) + retString := fmt.Sprintf(viper.GetString("commands.add.messages.many_tracks_added"), user.Name, numAdded) if numTooLong != 0 { - retString += fmt.Sprintf("
%d tracks could not be added due to error or because they are too long.", numTooLong) + retString += fmt.Sprintf(viper.GetString("commands.add.messages.num_tracks_too_long"), numTooLong) } return retString, false, nil } diff --git a/commands/addnext.go b/commands/addnext.go index 6449fce..ea9ed30 100644 --- a/commands/addnext.go +++ b/commands/addnext.go @@ -55,7 +55,7 @@ func (c *AddNextCommand) Execute(user *gumble.User, args ...string) (string, boo ) if len(args) == 0 { - return "", true, errors.New("A URL must be supplied with the addnext command") + return "", true, errors.New(viper.GetString("commands.add.messages.no_url_error")) } for _, arg := range args { @@ -68,7 +68,7 @@ func (c *AddNextCommand) Execute(user *gumble.User, args ...string) (string, boo } if len(allTracks) == 0 { - return "", true, errors.New("No valid tracks were found with the provided URL(s)") + return "", true, errors.New(viper.GetString("commands.add.messages.no_valid_tracks_error")) } numTooLong := 0 @@ -84,15 +84,15 @@ func (c *AddNextCommand) Execute(user *gumble.User, args ...string) (string, boo } if numAdded == 0 { - return "", true, errors.New("Your track(s) were either too long or an error occurred while processing them. No track(s) have been added.") + return "", true, errors.New(viper.GetString("commands.add.messages.tracks_too_long_error")) } else if numAdded == 1 { - return fmt.Sprintf("%s added 1 track to the queue:
\"%s\" from %s", + return fmt.Sprintf(viper.GetString("commands.add.messages.one_track_added"), user.Name, lastTrackAdded.GetTitle(), lastTrackAdded.GetService()), false, nil } - retString := fmt.Sprintf("%s added %d tracks to the queue.", user.Name, numAdded) + retString := fmt.Sprintf(viper.GetString("commands.add.messages.many_tracks_added"), user.Name, numAdded) if numTooLong != 0 { - retString += fmt.Sprintf("
%d tracks could not be added due to error or because they are too long.", numTooLong) + retString += fmt.Sprintf(viper.GetString("commands.add.messages.num_tracks_too_long"), numTooLong) } return retString, false, nil } diff --git a/commands/cachesize.go b/commands/cachesize.go index a157255..27bfcad 100644 --- a/commands/cachesize.go +++ b/commands/cachesize.go @@ -47,9 +47,9 @@ func (c *CacheSizeCommand) Execute(user *gumble.User, args ...string) (string, b const bytesInMiB = 1048576 if !viper.GetBool("cache.enabled") { - return "", true, errors.New("Caching is currently disabled") + return "", true, errors.New(viper.GetString("commands.common_messages.caching_disabled_error")) } DJ.Cache.UpdateStatistics() - return fmt.Sprintf("The current size of the cache is %.2v MiB.", DJ.Cache.TotalFileSize/bytesInMiB), true, nil + return fmt.Sprintf(viper.GetString("commands.cachesize.messages.current_size"), DJ.Cache.TotalFileSize/bytesInMiB), true, nil } diff --git a/commands/currenttrack.go b/commands/currenttrack.go index 420fd95..6320804 100644 --- a/commands/currenttrack.go +++ b/commands/currenttrack.go @@ -52,9 +52,9 @@ func (c *CurrentTrackCommand) Execute(user *gumble.User, args ...string) (string ) if currentTrack, err = DJ.Queue.CurrentTrack(); err != nil { - return "", true, errors.New("There are no tracks in the queue") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } - return fmt.Sprintf("The current track is \"%s\", added by %s.", + return fmt.Sprintf(viper.GetString("commands.currenttrack.messages.current_track"), currentTrack.GetTitle(), currentTrack.GetSubmitter()), true, nil } diff --git a/commands/forceskip.go b/commands/forceskip.go index d718416..02754b0 100644 --- a/commands/forceskip.go +++ b/commands/forceskip.go @@ -45,11 +45,11 @@ func (c *ForceSkipCommand) IsAdminCommand() bool { // return "This is a private message!", true, nil func (c *ForceSkipCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if DJ.Queue.Length() == 0 { - return "", true, errors.New("The queue is currently empty. There are no tracks to skip") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } DJ.Queue.Skip() - return fmt.Sprintf("The current track has been forcibly skipped by %s.", + return fmt.Sprintf(viper.GetString("commands.forceskip.messages.track_skipped"), user.Name), false, nil } diff --git a/commands/forceskipplaylist.go b/commands/forceskipplaylist.go index 83e6540..496ca48 100644 --- a/commands/forceskipplaylist.go +++ b/commands/forceskipplaylist.go @@ -52,15 +52,15 @@ func (c *ForceSkipPlaylistCommand) Execute(user *gumble.User, args ...string) (s ) if currentTrack, err = DJ.Queue.CurrentTrack(); err != nil { - return "", true, errors.New("The queue is currently empty. There are no playlists to skip") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } if playlist := currentTrack.GetPlaylist(); playlist == nil { - return "", true, errors.New("The current track is not part of a playlist") + return "", true, errors.New(viper.GetString("commands.forceskipplaylist.messages.no_playlist_error")) } DJ.Queue.SkipPlaylist() - return fmt.Sprintf("The current playlist has been forcibly skipped by %s.", + return fmt.Sprintf(viper.GetString("commands.forceskipplaylist.messages.playlist_skipped"), user.Name), false, nil } diff --git a/commands/help.go b/commands/help.go index df60e87..66f0e7e 100644 --- a/commands/help.go +++ b/commands/help.go @@ -58,7 +58,7 @@ func (c *HelpCommand) Execute(user *gumble.User, args ...string) (string, bool, } } - totalString = "
Commands:
" + regularCommands + totalString = viper.GetString("commands.help.messages.commands_header") + regularCommands isAdmin := false if viper.GetBool("admins.enabled") { @@ -68,7 +68,7 @@ func (c *HelpCommand) Execute(user *gumble.User, args ...string) (string, bool, } if isAdmin { - totalString += "
Admin Commands:
" + adminCommands + totalString += viper.GetString("commands.help.messages.admin_commands_header") + adminCommands } return totalString, true, nil diff --git a/commands/joinme.go b/commands/joinme.go index 63045cc..a50f512 100644 --- a/commands/joinme.go +++ b/commands/joinme.go @@ -47,12 +47,12 @@ func (c *JoinMeCommand) IsAdminCommand() bool { func (c *JoinMeCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if DJ.AudioStream != nil && DJ.AudioStream.State() == gumbleffmpeg.StatePlaying && len(DJ.Client.Self.Channel.Users) > 1 { - return "", true, errors.New("Users in another channel are listening to me.") + return "", true, errors.New(viper.GetString("commands.joinme.messages.others_are_listening_error")) } DJ.Client.Do(func() { DJ.Client.Self.Move(user.Channel) }) - return "I am now in your channel!", true, nil + return viper.GetString("commands.joinme.messages.in_your_channel"), true, nil } diff --git a/commands/listtracks.go b/commands/listtracks.go index 93e7c58..c58865f 100644 --- a/commands/listtracks.go +++ b/commands/listtracks.go @@ -49,7 +49,7 @@ func (c *ListTracksCommand) IsAdminCommand() bool { // return "This is a private message!", true, nil func (c *ListTracksCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if DJ.Queue.Length() == 0 { - return "", true, errors.New("There are no tracks currently in the queue") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } numTracksToList := DJ.Queue.Length() @@ -57,14 +57,14 @@ func (c *ListTracksCommand) Execute(user *gumble.User, args ...string) (string, if parsedNum, err := strconv.Atoi(args[0]); err == nil { numTracksToList = parsedNum } else { - return "", true, errors.New("An invalid integer was supplied") + return "", true, errors.New(viper.GetString("commands.listtracks.messages.invalid_integer_error")) } } var buffer bytes.Buffer DJ.Queue.Traverse(func(i int, track interfaces.Track) { if i < numTracksToList { - buffer.WriteString(fmt.Sprintf("%d: \"%s\", added by %s.
", + buffer.WriteString(fmt.Sprintf(viper.GetString("commands.listtracks.messages.track_listing"), i+1, track.GetTitle(), track.GetSubmitter())) } }) diff --git a/commands/move.go b/commands/move.go index b58d891..f3ec8fb 100644 --- a/commands/move.go +++ b/commands/move.go @@ -46,7 +46,7 @@ func (c *MoveCommand) IsAdminCommand() bool { // return "This is a private message!", true, nil func (c *MoveCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if len(args) == 0 { - return "", true, errors.New("A destination channel must be supplied to move the bot") + return "", true, errors.New(viper.GetString("commands.move.messages.no_channel_provided_error")) } channel := "" for _, arg := range args { @@ -58,8 +58,8 @@ func (c *MoveCommand) Execute(user *gumble.User, args ...string) (string, bool, DJ.Client.Self.Move(DJ.Client.Channels.Find(channels...)) }) } else { - return "", true, errors.New("The provided channel does not exist") + return "", true, errors.New(viper.GetString("commands.move.messages.channel_doesnt_exist_error")) } - return fmt.Sprintf("You have successfully moved the bot to %s.", channel), true, nil + return fmt.Sprintf(viper.GetString("commands.move.messages.move_successful"), channel), true, nil } diff --git a/commands/nexttrack.go b/commands/nexttrack.go index 53fe51f..412d300 100644 --- a/commands/nexttrack.go +++ b/commands/nexttrack.go @@ -47,14 +47,14 @@ func (c *NextTrackCommand) IsAdminCommand() bool { func (c *NextTrackCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { length := DJ.Queue.Length() if length == 0 { - return "", true, errors.New("There are no tracks in the queue") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } if length == 1 { - return "", true, errors.New("The current track is the only track in the queue") + return "", true, errors.New(viper.GetString("commands.nexttrack.messages.current_track_only_error")) } nextTrack, _ := DJ.Queue.PeekNextTrack() - return fmt.Sprintf("The next track is \"%s\", added by %s.", + return fmt.Sprintf(viper.GetString("commands.nexttrack.messages.next_track"), nextTrack.GetTitle(), nextTrack.GetSubmitter()), true, nil } diff --git a/commands/numcached.go b/commands/numcached.go index 0334f3e..4fb4232 100644 --- a/commands/numcached.go +++ b/commands/numcached.go @@ -46,10 +46,10 @@ func (c *NumCachedCommand) IsAdminCommand() bool { // return "This is a private message!", true, nil func (c *NumCachedCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if !viper.GetBool("cache.enabled") { - return "", true, errors.New("Caching is currently disabled") + return "", true, errors.New(viper.GetString("commands.common_messages.caching_disabled_error")) } DJ.Cache.UpdateStatistics() - return fmt.Sprintf("There are currently %d items stored in the cache.", + return fmt.Sprintf(viper.GetString("commands.numcached.messages.num_cached"), DJ.Cache.NumAudioFiles), true, nil } diff --git a/commands/numtracks.go b/commands/numtracks.go index 9543477..28a9f96 100644 --- a/commands/numtracks.go +++ b/commands/numtracks.go @@ -46,8 +46,8 @@ func (c *NumTracksCommand) IsAdminCommand() bool { func (c *NumTracksCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { length := DJ.Queue.Length() if length == 1 { - return "There is currently 1 track in the queue.", true, nil + return viper.GetString("commands.numtracks.messages.one_track"), true, nil } - return fmt.Sprintf("There are currently %d tracks in the queue.", length), true, nil + return fmt.Sprintf(viper.GetString("commands.numtracks.messages.plural_tracks"), length), true, nil } diff --git a/commands/pause.go b/commands/pause.go index e55e7f0..b81918d 100644 --- a/commands/pause.go +++ b/commands/pause.go @@ -8,6 +8,7 @@ package commands import ( + "errors" "fmt" "github.com/layeh/gumble/gumble" @@ -45,7 +46,7 @@ func (c *PauseCommand) IsAdminCommand() bool { func (c *PauseCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { err := DJ.Queue.PauseCurrent() if err != nil { - return "", true, err + return "", true, errors.New(viper.GetString("commands.pause.messages.no_audio_error")) } - return fmt.Sprintf("%s has paused audio playback.", user.Name), false, nil + return fmt.Sprintf(viper.GetString("commands.pause.messages.paused"), user.Name), false, nil } diff --git a/commands/reload.go b/commands/reload.go index 099f98b..75c9d54 100644 --- a/commands/reload.go +++ b/commands/reload.go @@ -47,6 +47,6 @@ func (c *ReloadCommand) Execute(user *gumble.User, args ...string) (string, bool return "", true, err } - return "The configuration in the configuration file has been reloaded successfully.", + return viper.GetString("commands.reload.messages.reloaded"), true, nil } diff --git a/commands/reset.go b/commands/reset.go index 0267ae8..01892b1 100644 --- a/commands/reset.go +++ b/commands/reset.go @@ -45,7 +45,7 @@ func (c *ResetCommand) IsAdminCommand() bool { // return "This is a private message!", true, nil func (c *ResetCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if DJ.Queue.Length() == 0 { - return "", true, errors.New("The queue is already empty") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } if DJ.AudioStream != nil { @@ -59,5 +59,5 @@ func (c *ResetCommand) Execute(user *gumble.User, args ...string) (string, bool, return "", true, err } - return fmt.Sprintf("%s has reset the queue.", user.Name), false, nil + return fmt.Sprintf(viper.GetString("commands.reset.messages.queue_reset"), user.Name), false, nil } diff --git a/commands/resume.go b/commands/resume.go index a7b7831..a83f995 100644 --- a/commands/resume.go +++ b/commands/resume.go @@ -8,6 +8,7 @@ package commands import ( + "errors" "fmt" "github.com/layeh/gumble/gumble" @@ -45,7 +46,7 @@ func (c *ResumeCommand) IsAdminCommand() bool { func (c *ResumeCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { err := DJ.Queue.ResumeCurrent() if err != nil { - return "", true, err + return "", true, errors.New(viper.GetString("commands.resume.messages.audio_error")) } - return fmt.Sprintf("%s has resumed audio playback.", user.Name), false, nil + return fmt.Sprintf(viper.GetString("commands.resume.messages.resumed"), user.Name), false, nil } diff --git a/commands/setcomment.go b/commands/setcomment.go index 97ee7b5..d09291c 100644 --- a/commands/setcomment.go +++ b/commands/setcomment.go @@ -48,7 +48,7 @@ func (c *SetCommentCommand) Execute(user *gumble.User, args ...string) (string, DJ.Client.Do(func() { DJ.Client.Self.SetComment("") }) - return "The comment for the bot has been successfully removed.", true, nil + return viper.GetString("commands.setcomment.messages.comment_removed"), true, nil } var newComment string @@ -61,6 +61,6 @@ func (c *SetCommentCommand) Execute(user *gumble.User, args ...string) (string, DJ.Client.Self.SetComment(newComment) }) - return fmt.Sprintf("The comment for the bot has been successfully changed to the following: %s", + return fmt.Sprintf(viper.GetString("commands.setcomment.messages.comment_changed"), newComment), true, nil } diff --git a/commands/shuffle.go b/commands/shuffle.go index 87c055d..61f6a8f 100644 --- a/commands/shuffle.go +++ b/commands/shuffle.go @@ -45,13 +45,13 @@ func (c *ShuffleCommand) IsAdminCommand() bool { func (c *ShuffleCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { length := DJ.Queue.Length() if length == 0 { - return "", true, errors.New("There are no tracks currently in the queue") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } if length <= 2 { - return "", true, errors.New("There are not enough tracks in the queue to execute a shuffle") + return "", true, errors.New(viper.GetString("commands.shuffle.messages.not_enough_tracks_error")) } DJ.Queue.ShuffleTracks() - return "The audio queue has been shuffled.", false, nil + return viper.GetString("commands.shuffle.messages.shuffled"), false, nil } diff --git a/commands/skip.go b/commands/skip.go index 55f05ee..4de3153 100644 --- a/commands/skip.go +++ b/commands/skip.go @@ -45,11 +45,11 @@ func (c *SkipCommand) IsAdminCommand() bool { // return "This is a private message!", true, nil func (c *SkipCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if DJ.Queue.Length() == 0 { - return "", true, errors.New("The queue is currently empty. There is no track to skip") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } if err := DJ.Skips.AddTrackSkip(user); err != nil { - return "", true, errors.New("You have already voted to skip this track") + return "", true, errors.New(viper.GetString("commands.skip.messages.already_voted_error")) } - return fmt.Sprintf("%s has voted to skip the current track.", user.Name), false, nil + return fmt.Sprintf(viper.GetString("commands.skip.messages.voted"), user.Name), false, nil } diff --git a/commands/skipplaylist.go b/commands/skipplaylist.go index 00a9fdf..d119c9e 100644 --- a/commands/skipplaylist.go +++ b/commands/skipplaylist.go @@ -52,15 +52,15 @@ func (c *SkipPlaylistCommand) Execute(user *gumble.User, args ...string) (string ) if currentTrack, err = DJ.Queue.CurrentTrack(); err != nil { - return "", true, errors.New("The queue is currently empty. There is no playlist to skip") + return "", true, errors.New(viper.GetString("commands.common_messages.no_tracks_error")) } if playlist := currentTrack.GetPlaylist(); playlist == nil { - return "", true, errors.New("The current track is not part of a playlist") + return "", true, errors.New(viper.GetString("commands.skipplaylist.messages.no_playlist_error")) } if err := DJ.Skips.AddPlaylistSkip(user); err != nil { - return "", true, errors.New("You have already voted to skip this playlist") + return "", true, errors.New(viper.GetString("commands.skipplaylist.messages.already_voted_error")) } - return fmt.Sprintf("%s has voted to skip the current playlist.", user.Name), false, nil + return fmt.Sprintf(viper.GetString("commands.skipplaylist.messages.voted"), user.Name), false, nil } diff --git a/commands/toggleshuffle.go b/commands/toggleshuffle.go index 19053b6..a6d3699 100644 --- a/commands/toggleshuffle.go +++ b/commands/toggleshuffle.go @@ -43,8 +43,8 @@ func (c *ToggleShuffleCommand) IsAdminCommand() bool { func (c *ToggleShuffleCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if viper.GetBool("queue.automatic_shuffle_on") { viper.Set("queue.automatic_shuffle_on", false) - return "Automatic shuffling has been toggled off.", false, nil + return viper.GetString("commands.toggleshuffle.messages.toggled_off"), false, nil } viper.Set("queue.automatic_shuffle_on", true) - return "Automatic shuffling has been toggled on.", false, nil + return viper.GetString("commands.toggleshuffle.messages.toggled_on"), false, nil } diff --git a/commands/version.go b/commands/version.go index 75c919e..0052b67 100644 --- a/commands/version.go +++ b/commands/version.go @@ -43,5 +43,5 @@ func (c *VersionCommand) IsAdminCommand() bool { // Example return statement: // return "This is a private message!", true, nil func (c *VersionCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { - return fmt.Sprintf("MumbleDJ version: %s", DJ.Version), true, nil + return fmt.Sprintf(viper.GetString("commands.version.messages.version"), DJ.Version), true, nil } diff --git a/commands/volume.go b/commands/volume.go index 465b537..ee0061e 100644 --- a/commands/volume.go +++ b/commands/volume.go @@ -47,16 +47,16 @@ func (c *VolumeCommand) IsAdminCommand() bool { func (c *VolumeCommand) Execute(user *gumble.User, args ...string) (string, bool, error) { if len(args) == 0 { // Send the user the current volume level. - return fmt.Sprintf("The current volume is %.2f.", DJ.Volume), true, nil + return fmt.Sprintf(viper.GetString("commands.volume.messages.current_volume"), DJ.Volume), true, nil } newVolume, err := strconv.ParseFloat(args[0], 32) if err != nil { - return "", true, errors.New("An error occurred while parsing the requested volume") + return "", true, errors.New(viper.GetString("commands.volume.messages.parsing_error")) } if newVolume < viper.GetFloat64("volume.lowest") || newVolume > viper.GetFloat64("volume.highest") { - return "", true, fmt.Errorf("Volumes must be between the values %.2f and %.2f", + return "", true, fmt.Errorf(viper.GetString("commands.volume.messages.out_of_range_error"), viper.GetFloat64("volume.lowest"), viper.GetFloat64("volume.highest")) } @@ -67,6 +67,6 @@ func (c *VolumeCommand) Execute(user *gumble.User, args ...string) (string, bool } DJ.Volume = newVolume32 - return fmt.Sprintf("%s has changed the volume to %.2f.", + return fmt.Sprintf(viper.GetString("commands.volume.messages.volume_changed"), user.Name, newVolume32), false, nil } diff --git a/config.yaml b/config.yaml index 9cd636f..49b39bc 100644 --- a/config.yaml +++ b/config.yaml @@ -140,17 +140,31 @@ commands: # NOTE: Only one character (the first) is used. prefix: "!" + common_messages: + no_tracks_error: "There are no tracks in the queue." + caching_disabled_error: "Caching is currently disabled." + # Below is a list of the commands supported by MumbleDJ. Each command has # three configurable options: # aliases: A list of names that can be used to execute the command. # is_admin: true = only admins can execute the command, false = anyone can execute the command. # description: Description shown for the command when the help command is executed. + # messages: Various messages that may be sent as a text message from the command. Useful for translating + # strings to other languages. Do NOT remove strings that begin with "%" (such as "%s", "%d", etc.) + # as they substituted with runtime data. Removing these strings will cause the bot to misbehave. add: aliases: - "add" - "a" is_admin: false description: "Adds a track or playlist from a media site to the queue." + messages: + no_url_error: "A URL must be supplied with the add command." + no_valid_tracks_error: "No valid tracks were found with the provided URL(s)." + tracks_too_long_error: "Your track(s) were either too long or an error occurred while processing them. No track(s) have been added." + one_track_added: "%s added 1 track to the queue:
%s from %s" + many_tracks_added: "%s added %d tracks to the queue." + num_tracks_too_long: "
%d tracks could not be added due to error or because they are too long." addnext: aliases: @@ -158,6 +172,7 @@ commands: - "an" is_admin: true description: "Adds a track or playlist from a media site as the next item in the queue." + # addnext uses the messages defined for add. cachesize: aliases: @@ -165,6 +180,8 @@ commands: - "cs" is_admin: true description: "Outputs the file size of the cache in MiB if caching is enabled." + messages: + current_size: "The current size of the cache is %.2v MiB." currenttrack: aliases: @@ -173,6 +190,8 @@ commands: - "current" is_admin: false description: "Outputs information about the current track in the queue if one exists." + messages: + current_track: "The current track is %s, added by %s." forceskip: aliases: @@ -180,6 +199,8 @@ commands: - "fs" is_admin: true description: "Immediately skips the current track." + messages: + track_skipped: "The current track has been forcibly skipped by %s." forceskipplaylist: aliases: @@ -187,6 +208,9 @@ commands: - "fsp" is_admin: true description: "Immediately skips the current playlist." + messages: + no_playlist_error: "The current track is not part of a playlist." + playlist_skipped: "The current playlist has been forcibly skipped by %s." help: aliases: @@ -194,6 +218,9 @@ commands: - "h" is_admin: false description: "Outputs this list of commands." + messages: + commands_header: "
Commands:
" + admin_commands_header: "
Admin Commands:
" joinme: aliases: @@ -201,6 +228,9 @@ commands: - "join" is_admin: true description: "Moves MumbleDJ into your current channel if not playing audio to someone else." + messages: + others_are_listening_error: "Users in another channel are listening to me." + in_your_channel: "I am now in your channel!" kill: aliases: @@ -217,6 +247,9 @@ commands: - "l" is_admin: false description: "Outputs a list of the tracks currently in the queue." + messages: + invalid_integer_error: "An invalid integer was supplied." + track_listing: "%d: %s, added by %s.
" move: aliases: @@ -224,6 +257,10 @@ commands: - "m" is_admin: true description: "Moves the bot into the Mumble channel provided via argument." + messages: + no_channel_provided_error: "A destination channel must be supplied to move the bot." + channel_doesnt_exist_error: "The provided channel does not exist." + move_successful: "You have successfully moved the bot to %s." nexttrack: aliases: @@ -232,6 +269,9 @@ commands: - "next" is_admin: false description: "Outputs information about the next track in the queue if one exists." + messages: + current_track_only_error: "The current track is the only track in the queue." + next_track: "The next track is %s, added by %s." numcached: aliases: @@ -239,6 +279,8 @@ commands: - "nc" is_admin: true description: "Outputs the number of tracks cached on disk if caching is enabled." + messages: + num_cached: "There are currently %d items stored in the cache." numtracks: aliases: @@ -247,12 +289,18 @@ commands: - "nt" is_admin: false description: "Outputs the number of tracks currently in the queue." + messages: + one_track: "There is currently 1 track in the queue." + plural_tracks: "There are currently %d tracks in the queue." pause: aliases: - "pause" is_admin: false description: "Pauses audio playback." + messages: + no_audio_error: "Either the audio is already paused, or there are no tracks in the queue." + paused: "%s has paused audio playback." reload: aliases: @@ -260,6 +308,8 @@ commands: - "r" is_admin: true description: "Reloads the configuration file." + messages: + reloaded: "The configuration file has been successfully reloaded." reset: aliases: @@ -267,12 +317,17 @@ commands: - "re" is_admin: true description: "Resets the queue by removing all queue items." + messages: + queue_reset: "%s has reset the queue." resume: aliases: - "resume" is_admin: false description: "Resumes audio playback." + messages: + audio_error: "Either the audio is already playing, or there are no tracks in the queue." + resumed: "%s has resumed audio playback." setcomment: aliases: @@ -281,6 +336,9 @@ commands: - "sc" is_admin: true description: "Sets the comment displayed next to MumbleDJ's username in Mumble." + messages: + comment_removed: "The comment for the bot has been successfully removed." + comment_changed: "The comment for the bot has been successfully changed to the following: %s" shuffle: aliases: @@ -289,6 +347,9 @@ commands: - "sh" is_admin: true description: "Randomizes the tracks currently in the queue." + messages: + not_enough_tracks_error: "There are not enough tracks in the queue to execute a shuffle." + shuffled: "The audio queue has been shuffled." skip: aliases: @@ -296,6 +357,9 @@ commands: - "s" is_admin: false description: "Places a vote to skip the current track." + messages: + already_voted_error: "You have already voted to skip this track." + voted: "%s has voted to skip the current track." skipplaylist: aliases: @@ -303,6 +367,10 @@ commands: - "sp" is_admin: false description: "Places a vote to skip the current playlist." + messages: + no_playlist_error: "The current track is not part of a playlist." + already_voted_error: "You have already voted to skip this playlist." + voted: "%s has voted to skip the current playlist." toggleshuffle: aliases: @@ -312,6 +380,10 @@ commands: - "tsh" is_admin: true description: "Toggles automatic track shuffling on/off." + messages: + toggled_off: "Automatic shuffling has been toggled off." + toggled_on: "Automatic shuffling has been toggled on." + version: aliases: @@ -319,10 +391,17 @@ commands: - "v" is_admin: false description: "Outputs the current version of MumbleDJ." + messages: + version: "MumbleDJ version: %s" volume: aliases: - "volume" - "vol" is_admin: false - description: "Changes the volume if an argument is provided, outputs the current volume otherwise." \ No newline at end of file + description: "Changes the volume if an argument is provided, outputs the current volume otherwise." + messages: + parsing_error: "The requested volume could not be parsed." + out_of_range_error: "Volumes must be between the values %.2f and %.2f." + current_volume: "The current volume is %.2f." + volume_changed: "%s has changed the volume to %.2f." \ No newline at end of file diff --git a/main.go b/main.go index 60f121e..eb39731 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ func init() { services.DJ = DJ bot.DJ = DJ - DJ.Version = "3.0.0" + DJ.Version = "3.0.1" logrus.SetLevel(logrus.WarnLevel) }