Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
010bb6a102 | |||
530e08b9ee | |||
9cf42df1ea | |||
b81e1e482a | |||
3d7011e504 | |||
3d2e443108 | |||
b002eef285 | |||
c566d5d61d | |||
13a31c30d9 | |||
508a41ee45 | |||
1794e2680a | |||
c704ebb042 | |||
bb66740fd1 | |||
4010132884 | |||
77543e3aed | |||
360bed00f9 | |||
164e071e7f | |||
01914f070d | |||
00e40dc153 | |||
47efab2e56 | |||
c171a620e0 | |||
ca81475fa5 | |||
52b2b6677f | |||
5013ab6db1 | |||
855f4d3e64 | |||
540efa03c4 | |||
7ad76273c0 | |||
c1e1f2c327 | |||
4eda7ef396 | |||
fad2e030d4 | |||
3e90e6c86d | |||
71bfe92dae | |||
613d9fca6e | |||
9267c661dc | |||
f8e12f5938 | |||
497934888c | |||
24960f23b9 | |||
aab1ad3e99 | |||
815b518c2c | |||
5e674254f0 | |||
24b26aa85f | |||
f25594e962 | |||
075e404079 | |||
eee260f154 | |||
a3618b97ae | |||
e4d5d2737b | |||
0085cea5a1 | |||
63cf65100d | |||
67e32661f1 | |||
95eff71e2e | |||
8526805c2f | |||
6aaa2b521d | |||
0d9290d037 | |||
6fedb23363 | |||
fc643483be | |||
6c3a5777c4 | |||
c5a9a5b1c1 | |||
6a816fbff6 | |||
3c105c493b | |||
50d735aaa3 | |||
34d89c75b2 | |||
71f79fd84e |
@ -19,17 +19,17 @@ builds:
|
||||
- 6
|
||||
- 7
|
||||
|
||||
archive:
|
||||
files:
|
||||
- none*
|
||||
archives:
|
||||
- files:
|
||||
- none*
|
||||
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
replacements:
|
||||
amd64: x64
|
||||
darwin: mac
|
||||
replacements:
|
||||
amd64: x64
|
||||
darwin: mac
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
@ -13,12 +13,12 @@ matrix:
|
||||
- go: tip
|
||||
|
||||
install:
|
||||
- go get github.com/jteeuwen/go-bindata/...
|
||||
- GO111MODULE=off go get github.com/jteeuwen/go-bindata/...
|
||||
- cd client
|
||||
- nvm install 11.2.0
|
||||
- nvm use 11.2.0
|
||||
- nvm install 12.4.0
|
||||
- nvm use 12.4.0
|
||||
- npm install -g yarn
|
||||
- yarn global add gulp@next
|
||||
- yarn global add gulp
|
||||
- yarn
|
||||
|
||||
script:
|
||||
|
11
README.md
11
README.md
@ -51,7 +51,6 @@ docker run -p <http port>:80 -p <https port>:443 -v <path>:/data khlieng/dispatc
|
||||
### Server
|
||||
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/khlieng/dispatch
|
||||
go install
|
||||
```
|
||||
|
||||
@ -62,9 +61,9 @@ This requires [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com).
|
||||
Fetch the dependencies:
|
||||
|
||||
```bash
|
||||
go get github.com/jteeuwen/go-bindata/...
|
||||
yarn global add gulp@next
|
||||
cd $GOPATH/src/github.com/khlieng/dispatch/client
|
||||
GO111MODULE=off go get github.com/jteeuwen/go-bindata/...
|
||||
yarn global add gulp-cli
|
||||
cd client
|
||||
yarn
|
||||
```
|
||||
|
||||
@ -94,11 +93,11 @@ The libraries this project is built with.
|
||||
|
||||
### Server
|
||||
|
||||
- [Bolt](https://github.com/boltdb/bolt)
|
||||
- [Bolt](https://github.com/etcd-io/bbolt)
|
||||
- [Bleve](https://github.com/blevesearch/bleve)
|
||||
- [Cobra](https://github.com/spf13/cobra)
|
||||
- [Viper](https://github.com/spf13/viper)
|
||||
- [Lego](https://github.com/xenolf/lego)
|
||||
- [CertMagic](https://github.com/mholt/certmagic)
|
||||
|
||||
### Client
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -13,9 +13,13 @@
|
||||
"no-param-reassign": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-restricted-globals": 1,
|
||||
"no-underscore-dangle": 1,
|
||||
"react/destructuring-assignment": 0,
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/prop-types": 0
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"react/prop-types": 0,
|
||||
"react/state-in-constructor": 0,
|
||||
"react/static-property-placement": 0
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
|
@ -1,3 +1,5 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
62
client/css/fontello.css
vendored
62
client/css/fontello.css
vendored
@ -1,62 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('/font/fontello.woff2?48901973') format('woff2'),
|
||||
url('/font/fontello.woff?48901973') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^='icon-']:before,
|
||||
[class*=' icon-']:before {
|
||||
font-family: 'fontello';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: 0.2em;
|
||||
text-align: center;
|
||||
/* opacity: .8; */
|
||||
|
||||
/* For safety - reset parent styles, that can break glyph codes*/
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
|
||||
/* fix buttons height, for twitter bootstrap */
|
||||
line-height: 1em;
|
||||
|
||||
/* Animation center compensation - margins should be symmetric */
|
||||
/* remove if not needed */
|
||||
margin-left: 0.2em;
|
||||
|
||||
/* you can be more comfortable with increased icons size */
|
||||
/* font-size: 120%; */
|
||||
|
||||
/* Font smoothing. That was taken from TWBS */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Uncomment for 3D effect */
|
||||
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
||||
}
|
||||
|
||||
.icon-cancel:before {
|
||||
content: '\e800';
|
||||
} /* '' */
|
||||
.icon-menu:before {
|
||||
content: '\e801';
|
||||
} /* '' */
|
||||
.icon-cog:before {
|
||||
content: '\e802';
|
||||
} /* '' */
|
||||
.icon-search:before {
|
||||
content: '\e803';
|
||||
} /* '' */
|
||||
.icon-user:before {
|
||||
content: '\f061';
|
||||
} /* '' */
|
||||
.icon-ellipsis:before {
|
||||
content: '\f141';
|
||||
} /* '' */
|
@ -19,6 +19,15 @@ h6 {
|
||||
font-family: Montserrat, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #0066ff;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
input {
|
||||
font: 16px Roboto Mono, monospace;
|
||||
border: none;
|
||||
@ -38,6 +47,9 @@ textarea {
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: #6bb758;
|
||||
@ -57,6 +69,33 @@ button:active {
|
||||
background: #6bb758;
|
||||
}
|
||||
|
||||
.button-normal {
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.button-normal:hover {
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.button-normal:active {
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
background: none;
|
||||
width: 40px;
|
||||
color: #222;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.icon-button:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.icon-button:active {
|
||||
background: none;
|
||||
}
|
||||
|
||||
label {
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
@ -128,6 +167,10 @@ i[class*=' icon-']:before {
|
||||
color: #f6546a !important;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.textinput {
|
||||
display: block;
|
||||
position: relative;
|
||||
@ -225,7 +268,8 @@ i[class*=' icon-']:before {
|
||||
top: 0;
|
||||
bottom: 50px;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tablist p {
|
||||
@ -233,6 +277,9 @@ i[class*=' icon-']:before {
|
||||
padding: 3px 15px;
|
||||
padding-right: 10px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tablist p:last-child {
|
||||
@ -248,12 +295,6 @@ i[class*=' icon-']:before {
|
||||
border-left: 5px solid #6bb758;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab-server {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -267,11 +308,29 @@ i[class*=' icon-']:before {
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
margin-top: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 15px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.tab-label span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tab-label button {
|
||||
width: 24px;
|
||||
height: 100%;
|
||||
font-size: 20px;
|
||||
background: none;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tab-label button:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.side-buttons {
|
||||
@ -284,20 +343,13 @@ i[class*=' icon-']:before {
|
||||
border-top: 1px solid #1d1d1d;
|
||||
}
|
||||
|
||||
.side-buttons i {
|
||||
flex: 100%;
|
||||
color: #999;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
border-left: 1px solid #1d1d1d;
|
||||
}
|
||||
|
||||
.side-buttons button {
|
||||
font-size: 24px;
|
||||
background: #222;
|
||||
color: #999;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.side-buttons i:hover {
|
||||
.side-buttons button:hover {
|
||||
color: #ccc;
|
||||
background: #1d1d1d;
|
||||
}
|
||||
@ -331,7 +383,7 @@ i[class*=' icon-']:before {
|
||||
|
||||
.connect-form {
|
||||
margin: auto 20px;
|
||||
padding-top: 20px;
|
||||
padding: 20px 0;
|
||||
width: 350px;
|
||||
text-align: center;
|
||||
}
|
||||
@ -368,11 +420,6 @@ input::-webkit-inner-spin-button {
|
||||
|
||||
.connect-form label {
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.connect-form button {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.connect-form-address {
|
||||
@ -401,16 +448,14 @@ input::-webkit-inner-spin-button {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.connect-form i {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
.connect-form-button-optionals {
|
||||
font-size: 24px;
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.connect-form i:hover {
|
||||
.connect-form-button-optionals:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@ -425,6 +470,7 @@ input::-webkit-inner-spin-button {
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
font-size: 20px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.chat-channel .chat-title-bar {
|
||||
@ -433,21 +479,10 @@ input::-webkit-inner-spin-button {
|
||||
|
||||
.navicon {
|
||||
display: none;
|
||||
padding: 0 15px;
|
||||
line-height: 50px;
|
||||
font-size: 20px;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-title-bar i {
|
||||
padding: 0 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-server .icon-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-server .userlist,
|
||||
.chat-private .userlist {
|
||||
display: none;
|
||||
@ -458,17 +493,16 @@ input::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-leave {
|
||||
border-left: 1px solid #ddd;
|
||||
.chat-title-bar .icon-button:not(.navicon) {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.button-leave:hover {
|
||||
background: #ddd;
|
||||
.chat-title-bar .icon-button:hover {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.button-userlist {
|
||||
display: none;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.chat-server .button-userlist,
|
||||
@ -476,20 +510,24 @@ input::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.editable-wrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.editable-wrap-editable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
margin-left: 10px;
|
||||
padding: 0 5px;
|
||||
margin-left: 15px;
|
||||
font: 24px Montserrat, sans-serif;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.chat-server .chat-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input.chat-title {
|
||||
background: none;
|
||||
cursor: text !important;
|
||||
@ -498,7 +536,8 @@ input.chat-title {
|
||||
.chat-topic-wrap {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin: 0 15px;
|
||||
margin-left: 15px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.chat-topic {
|
||||
@ -507,21 +546,16 @@ input.chat-title {
|
||||
top: 3px;
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-topic a {
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chat-topic a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.userlist-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@ -529,14 +563,11 @@ input.chat-title {
|
||||
height: 50px;
|
||||
border-left: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
padding: 0 15px;
|
||||
font-family: Montserrat, sans-serif;
|
||||
}
|
||||
|
||||
.userlist-bar i {
|
||||
margin-right: 3px;
|
||||
.userlist-bar svg {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.search {
|
||||
@ -565,17 +596,19 @@ input.chat-title {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.search i {
|
||||
padding: 15px;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.search-input-icon {
|
||||
font-size: 20px;
|
||||
align-self: center;
|
||||
margin: 0 15px;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
@ -595,8 +628,6 @@ input.chat-title {
|
||||
top: 50px;
|
||||
bottom: 50px;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-channel .messagebox {
|
||||
@ -615,6 +646,24 @@ input.chat-title {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.messagebox-topdate-container {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.messagebox-topdate {
|
||||
position: relative;
|
||||
top: -12px;
|
||||
background: #f0f0f0;
|
||||
color: #999;
|
||||
border-radius: 50vh;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 4px 15px;
|
||||
}
|
||||
@ -637,6 +686,18 @@ input.chat-title {
|
||||
color: #ff6698;
|
||||
}
|
||||
|
||||
.message-date {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.message-date hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@ -649,15 +710,6 @@ input.chat-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.message a {
|
||||
text-decoration: none;
|
||||
color: #0066ff;
|
||||
}
|
||||
|
||||
.message a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.message-input-wrap {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@ -695,7 +747,7 @@ input.message-input-nick.invalid {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 15px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.userlist {
|
||||
@ -706,7 +758,7 @@ input.message-input-nick.invalid {
|
||||
width: 200px;
|
||||
border-left: 1px solid #ddd;
|
||||
background: #f0f0f0;
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
@ -754,7 +806,7 @@ input.message-input-nick.invalid {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.settings button {
|
||||
.settings-button {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
@ -798,8 +850,8 @@ input.message-input-nick.invalid {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.settings-button {
|
||||
margin-top: 10px;
|
||||
.settings-file:last-of-type {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -819,14 +871,180 @@ input.message-input-nick.invalid {
|
||||
}
|
||||
|
||||
.suspense-fallback {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font: 700 64px 'Montserrat', sans-serif;
|
||||
height: 100%;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.suspense-modal-fallback {
|
||||
position: fixed;
|
||||
right: 15px;
|
||||
bottom: 3px;
|
||||
z-index: 1;
|
||||
font: 700 64px 'Montserrat', sans-serif;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.33);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.modal-overlay-opening {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-overlay-closing {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: 600px;
|
||||
min-width: 0;
|
||||
padding: 15px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
outline: none;
|
||||
margin: 15px;
|
||||
text-align: center;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
transform: translateY(-20px);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.modal-opening {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.modal-closing {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
.modal p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
margin: 0 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
width: auto !important;
|
||||
height: auto;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.modal-channel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 120px);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-channel input {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal-channel-button-join {
|
||||
margin: 0 !important;
|
||||
width: 60px !important;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.modal-channel-input-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-channel-close {
|
||||
background: #fff;
|
||||
width: 40px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.modal-channel-close:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.modal-channel-result {
|
||||
margin: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.modal-channel-result-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal-channel-topic {
|
||||
font-size: 12px;
|
||||
font-family: Roboto Mono, monospace;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.modal-channel-name {
|
||||
cursor: pointer;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.modal-channel-users {
|
||||
font-size: 16px;
|
||||
flex: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.modal-channel-results {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.modal-channel-button-more {
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tablist {
|
||||
width: 200px;
|
||||
@ -839,7 +1057,7 @@ input.message-input-nick.invalid {
|
||||
}
|
||||
|
||||
.navicon {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-container.off-canvas {
|
||||
@ -854,8 +1072,12 @@ input.message-input-nick.invalid {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.chat-topic {
|
||||
font-size: 12px;
|
||||
.chat-title-bar .editable-wrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-topic-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.userlist-bar {
|
||||
@ -896,4 +1118,15 @@ input.message-input-nick.invalid {
|
||||
.button-install {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.modal-channel {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ var br = require('brotli');
|
||||
var del = require('del');
|
||||
|
||||
function brotli(opts) {
|
||||
return through.obj(function(file, enc, callback) {
|
||||
return through.obj(function (file, enc, callback) {
|
||||
if (file.isNull()) {
|
||||
return callback(null, file);
|
||||
}
|
||||
@ -40,7 +40,7 @@ function js(cb) {
|
||||
|
||||
process.env['NODE_ENV'] = 'production';
|
||||
|
||||
compiler.run(function(err, stats) {
|
||||
compiler.run(function (err, stats) {
|
||||
if (err) throw new gutil.PluginError('webpack', err);
|
||||
|
||||
gutil.log(
|
||||
@ -104,13 +104,13 @@ function serve() {
|
||||
app.use(
|
||||
'*',
|
||||
proxy('localhost:1337', {
|
||||
proxyReqPathResolver: function(req) {
|
||||
proxyReqPathResolver: function (req) {
|
||||
return req.originalUrl;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
app.listen(3000, function(err) {
|
||||
app.listen(3000, function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
@ -120,9 +120,13 @@ function serve() {
|
||||
});
|
||||
}
|
||||
|
||||
const assets = gulp.parallel(js, config, public);
|
||||
|
||||
const build = gulp.series(clean, assets, compress, cleanup, bindata);
|
||||
const build = gulp.series(
|
||||
clean,
|
||||
gulp.parallel(js, config),
|
||||
compress,
|
||||
cleanup,
|
||||
bindata
|
||||
);
|
||||
|
||||
const dev = gulp.series(
|
||||
clean,
|
||||
|
@ -79,7 +79,9 @@ export default createCommandMiddleware(COMMAND, {
|
||||
topic({ dispatch, getState, server, channel }, ...newTopic) {
|
||||
if (newTopic.length > 0) {
|
||||
dispatch(setTopic(newTopic.join(' '), channel, server));
|
||||
} else if (channel) {
|
||||
return;
|
||||
}
|
||||
if (channel) {
|
||||
const { topic } = getState().channels[server][channel];
|
||||
if (topic) {
|
||||
return text(topic);
|
||||
@ -185,9 +187,6 @@ export default createCommandMiddleware(COMMAND, {
|
||||
},
|
||||
|
||||
[notFoundHandler](ctx, command, ...params) {
|
||||
if (command === command.toUpperCase()) {
|
||||
return this.raw(ctx, command, ...params);
|
||||
}
|
||||
return error(`=> /${command}: No such command`);
|
||||
return this.raw(ctx, command, ...params);
|
||||
}
|
||||
});
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import React, { Suspense, lazy, useState, useEffect } from 'react';
|
||||
import Route from 'containers/Route';
|
||||
import AppInfo from 'components/AppInfo';
|
||||
import TabList from 'components/TabList';
|
||||
import cn from 'classnames';
|
||||
|
||||
const Modals = lazy(() => import('components/modals'));
|
||||
const Chat = lazy(() => import('containers/Chat'));
|
||||
const Connect = lazy(() => import('containers/Connect'));
|
||||
const Settings = lazy(() => import('containers/Settings'));
|
||||
@ -18,8 +19,20 @@ const App = ({
|
||||
select,
|
||||
push,
|
||||
hideMenu,
|
||||
newVersionAvailable
|
||||
openModal,
|
||||
newVersionAvailable,
|
||||
hasOpenModals
|
||||
}) => {
|
||||
const [renderModals, setRenderModals] = useState(false);
|
||||
if (!renderModals && hasOpenModals) {
|
||||
setRenderModals(true);
|
||||
}
|
||||
|
||||
const [starting, setStarting] = useState(true);
|
||||
useEffect(() => {
|
||||
setTimeout(() => setStarting(false), 1000);
|
||||
}, []);
|
||||
|
||||
const mainClass = cn('main-container', {
|
||||
'off-canvas': showTabList
|
||||
});
|
||||
@ -32,7 +45,7 @@ const App = ({
|
||||
|
||||
return (
|
||||
<div className="wrap" onClick={handleClick}>
|
||||
{!connected && (
|
||||
{!starting && !connected && (
|
||||
<AppInfo type="error">
|
||||
Connection lost, attempting to reconnect...
|
||||
</AppInfo>
|
||||
@ -52,12 +65,10 @@ const App = ({
|
||||
showTabList={showTabList}
|
||||
select={select}
|
||||
push={push}
|
||||
openModal={openModal}
|
||||
/>
|
||||
<div className={mainClass}>
|
||||
<Suspense
|
||||
maxDuration={1000}
|
||||
fallback={<div className="suspense-fallback">...</div>}
|
||||
>
|
||||
<Suspense fallback={<div className="suspense-fallback">...</div>}>
|
||||
<Route name="chat">
|
||||
<Chat />
|
||||
</Route>
|
||||
@ -68,6 +79,11 @@ const App = ({
|
||||
<Settings />
|
||||
</Route>
|
||||
</Suspense>
|
||||
<Suspense
|
||||
fallback={<div className="suspense-modal-fallback">...</div>}
|
||||
>
|
||||
{renderModals && <Modals />}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import get from 'lodash/get';
|
||||
import { FiPlus, FiUser, FiSettings } from 'react-icons/fi';
|
||||
import Button from 'components/ui/Button';
|
||||
import TabListItem from './TabListItem';
|
||||
import TabListItem from 'containers/TabListItem';
|
||||
import { count } from 'utils';
|
||||
|
||||
export default class TabList extends PureComponent {
|
||||
handleTabClick = (server, target) => this.props.select(server, target);
|
||||
@ -11,7 +14,14 @@ export default class TabList extends PureComponent {
|
||||
handleSettingsClick = () => this.props.push('/settings');
|
||||
|
||||
render() {
|
||||
const { tab, channels, servers, privateChats, showTabList } = this.props;
|
||||
const {
|
||||
tab,
|
||||
channels,
|
||||
servers,
|
||||
privateChats,
|
||||
showTabList,
|
||||
openModal
|
||||
} = this.props;
|
||||
const tabs = [];
|
||||
|
||||
const className = classnames('tablist', {
|
||||
@ -32,13 +42,40 @@ export default class TabList extends PureComponent {
|
||||
/>
|
||||
);
|
||||
|
||||
server.channels.forEach(name =>
|
||||
const chanCount = count(server.channels, c => c.joined);
|
||||
const chanLimit =
|
||||
get(srv.features, ['CHANLIMIT', '#'], 0) || srv.features.MAXCHANNELS;
|
||||
|
||||
let chanLabel;
|
||||
if (chanLimit > 0) {
|
||||
chanLabel = (
|
||||
<span>
|
||||
<span className="success">{chanCount}</span>/{chanLimit}
|
||||
</span>
|
||||
);
|
||||
} else if (chanCount > 0) {
|
||||
chanLabel = <span className="success">{chanCount}</span>;
|
||||
}
|
||||
|
||||
tabs.push(
|
||||
<div
|
||||
key={`${address}-chans}`}
|
||||
className="tab-label"
|
||||
onClick={() => openModal('channel', address)}
|
||||
>
|
||||
<span>CHANNELS {chanLabel}</span>
|
||||
<Button title="Join Channel">+</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
server.channels.forEach(({ name, joined }) =>
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + name}
|
||||
server={address}
|
||||
target={name}
|
||||
content={name}
|
||||
joined={joined}
|
||||
selected={tab.server === address && tab.name === name}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
@ -48,7 +85,13 @@ export default class TabList extends PureComponent {
|
||||
if (privateChats[address] && privateChats[address].length > 0) {
|
||||
tabs.push(
|
||||
<div key={`${address}-pm}`} className="tab-label">
|
||||
Private messages
|
||||
<span>
|
||||
DIRECT MESSAGES{' '}
|
||||
<span style={{ color: '#6bb758' }}>
|
||||
{privateChats[address].length}
|
||||
</span>
|
||||
</span>
|
||||
{/* <Button>+</Button> */}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -71,9 +114,9 @@ export default class TabList extends PureComponent {
|
||||
<div className={className}>
|
||||
<div className="tab-container">{tabs}</div>
|
||||
<div className="side-buttons">
|
||||
<Button onClick={this.handleConnectClick}>+</Button>
|
||||
<i className="icon-user" />
|
||||
<i className="icon-cog" onClick={this.handleSettingsClick} />
|
||||
<Button icon={FiPlus} onClick={this.handleConnectClick} />
|
||||
<Button icon={FiUser} />
|
||||
<Button icon={FiSettings} onClick={this.handleSettingsClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { memo } from 'react';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const TabListItem = ({
|
||||
@ -7,12 +7,15 @@ const TabListItem = ({
|
||||
server,
|
||||
selected,
|
||||
connected,
|
||||
joined,
|
||||
error,
|
||||
onClick
|
||||
}) => {
|
||||
const className = classnames({
|
||||
'tab-server': !target,
|
||||
success: !target && connected,
|
||||
error: !target && !connected,
|
||||
error: (!target && !connected) || (!joined && error),
|
||||
disabled: !!target && !error && joined === false,
|
||||
selected
|
||||
});
|
||||
|
||||
@ -23,4 +26,4 @@ const TabListItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TabListItem);
|
||||
export default TabListItem;
|
||||
|
152
client/js/components/modals/AddChannel.js
Normal file
152
client/js/components/modals/AddChannel.js
Normal file
@ -0,0 +1,152 @@
|
||||
import React, { memo, useState, useEffect, useRef } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { FiUsers, FiX } from 'react-icons/fi';
|
||||
import useModal from 'components/modals/useModal';
|
||||
import Button from 'components/ui/Button';
|
||||
import { join } from 'state/channels';
|
||||
import { select } from 'state/tab';
|
||||
import { searchChannels } from 'state/channelSearch';
|
||||
import { linkify } from 'utils';
|
||||
|
||||
const Channel = memo(({ server, name, topic, userCount, joined }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = () => dispatch(join([name], server));
|
||||
|
||||
return (
|
||||
<div className="modal-channel-result">
|
||||
<div className="modal-channel-result-header">
|
||||
<h2 className="modal-channel-name" onClick={handleClick}>
|
||||
{name}
|
||||
</h2>
|
||||
<FiUsers />
|
||||
<span className="modal-channel-users">{userCount}</span>
|
||||
{joined ? (
|
||||
<span style={{ color: '#6bb758' }}>Joined</span>
|
||||
) : (
|
||||
<Button
|
||||
className="modal-channel-button-join"
|
||||
category="normal"
|
||||
onClick={handleClick}
|
||||
>
|
||||
Join
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="modal-channel-topic">{linkify(topic)}</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const AddChannel = () => {
|
||||
const [modal, server, closeModal] = useModal('channel');
|
||||
|
||||
const channels = useSelector(state => state.channels);
|
||||
const search = useSelector(state => state.channelSearch);
|
||||
const dispatch = useDispatch();
|
||||
const [q, setQ] = useState('');
|
||||
|
||||
const inputEl = useRef();
|
||||
const resultsEl = useRef();
|
||||
const prevSearch = useRef('');
|
||||
|
||||
useEffect(() => {
|
||||
if (modal.isOpen) {
|
||||
dispatch(searchChannels(server, ''));
|
||||
setTimeout(() => inputEl.current.focus(), 0);
|
||||
} else {
|
||||
setQ('');
|
||||
}
|
||||
}, [modal.isOpen]);
|
||||
|
||||
const handleSearch = e => {
|
||||
let nextQ = e.target.value.trim().toLowerCase();
|
||||
setQ(nextQ);
|
||||
|
||||
if (nextQ !== q) {
|
||||
resultsEl.current.scrollTop = 0;
|
||||
|
||||
while (nextQ.charAt(0) === '#') {
|
||||
nextQ = nextQ.slice(1);
|
||||
}
|
||||
|
||||
if (nextQ !== prevSearch.current) {
|
||||
prevSearch.current = nextQ;
|
||||
dispatch(searchChannels(server, nextQ));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKey = e => {
|
||||
if (e.key === 'Enter') {
|
||||
let channel = e.target.value.trim();
|
||||
|
||||
if (channel !== '') {
|
||||
closeModal(false);
|
||||
|
||||
if (channel.charAt(0) !== '#') {
|
||||
channel = `#${channel}`;
|
||||
}
|
||||
|
||||
dispatch(join([channel], server));
|
||||
dispatch(select(server, channel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadMore = () =>
|
||||
dispatch(searchChannels(server, q, search.results.length));
|
||||
|
||||
let hasMore = !search.end;
|
||||
if (hasMore) {
|
||||
if (search.results.length < 10) {
|
||||
hasMore = false;
|
||||
} else if (
|
||||
search.results.length > 10 &&
|
||||
(search.results.length - 10) % 50 !== 0
|
||||
) {
|
||||
hasMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal {...modal}>
|
||||
<div className="modal-channel-input-wrap">
|
||||
<input
|
||||
ref={inputEl}
|
||||
type="text"
|
||||
value={q}
|
||||
placeholder="Enter channel name"
|
||||
onKeyDown={handleKey}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<Button
|
||||
icon={FiX}
|
||||
className="modal-close modal-channel-close"
|
||||
onClick={closeModal}
|
||||
/>
|
||||
</div>
|
||||
<div ref={resultsEl} className="modal-channel-results">
|
||||
{search.results.map(channel => (
|
||||
<Channel
|
||||
key={`${server} ${channel.name}`}
|
||||
server={server}
|
||||
joined={channels[server]?.[channel.name]?.joined}
|
||||
{...channel}
|
||||
/>
|
||||
))}
|
||||
{hasMore && (
|
||||
<Button
|
||||
className="modal-channel-button-more"
|
||||
onClick={handleLoadMore}
|
||||
>
|
||||
Load more
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddChannel;
|
26
client/js/components/modals/Confirm.js
Normal file
26
client/js/components/modals/Confirm.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import useModal from 'components/modals/useModal';
|
||||
import Button from 'components/ui/Button';
|
||||
|
||||
const Confirm = () => {
|
||||
const [modal, payload, closeModal] = useModal('confirm');
|
||||
const { question, confirmation, onConfirm } = payload;
|
||||
|
||||
const handleConfirm = () => {
|
||||
closeModal(false);
|
||||
onConfirm();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal {...modal}>
|
||||
<p>{question}</p>
|
||||
<Button onClick={handleConfirm}>{confirmation || 'OK'}</Button>
|
||||
<Button category="normal" onClick={closeModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Confirm;
|
26
client/js/components/modals/Topic.js
Normal file
26
client/js/components/modals/Topic.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import Button from 'components/ui/Button';
|
||||
import useModal from 'components/modals/useModal';
|
||||
import { getSelectedChannel } from 'state/channels';
|
||||
import { linkify } from 'utils';
|
||||
|
||||
const Topic = () => {
|
||||
const [modal, channel, closeModal] = useModal('topic');
|
||||
|
||||
const topic = useSelector(state => getSelectedChannel(state)?.topic);
|
||||
|
||||
return (
|
||||
<Modal {...modal}>
|
||||
<div className="modal-header">
|
||||
<h2>Topic in {channel}</h2>
|
||||
<Button icon={FiX} className="modal-close" onClick={closeModal} />
|
||||
</div>
|
||||
<p className="modal-content">{linkify(topic)}</p>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Topic;
|
14
client/js/components/modals/index.js
Normal file
14
client/js/components/modals/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React, { memo } from 'react';
|
||||
import AddChannel from 'components/modals/AddChannel';
|
||||
import Confirm from 'components/modals/Confirm';
|
||||
import Topic from 'components/modals/Topic';
|
||||
|
||||
const Modals = () => (
|
||||
<>
|
||||
<AddChannel />
|
||||
<Confirm />
|
||||
<Topic />
|
||||
</>
|
||||
);
|
||||
|
||||
export default memo(Modals, () => true);
|
46
client/js/components/modals/useModal.js
Normal file
46
client/js/components/modals/useModal.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { useCallback } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { closeModal } from 'state/modals';
|
||||
|
||||
Modal.setAppElement('#root');
|
||||
|
||||
const defaultPayload = {};
|
||||
|
||||
export default function useModal(name) {
|
||||
const isOpen = useSelector(state => state.modals[name]?.isOpen || false);
|
||||
const payload = useSelector(
|
||||
state => state.modals[name]?.payload || defaultPayload
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleRequestClose = useCallback(
|
||||
(dismissed = true) => {
|
||||
dispatch(closeModal(name));
|
||||
|
||||
if (dismissed && payload.onDismiss) {
|
||||
payload.onDismiss();
|
||||
}
|
||||
},
|
||||
[payload.onDismiss]
|
||||
);
|
||||
|
||||
const modalProps = {
|
||||
isOpen,
|
||||
contentLabel: name,
|
||||
onRequestClose: handleRequestClose,
|
||||
className: {
|
||||
base: `modal modal-${name}`,
|
||||
afterOpen: 'modal-opening',
|
||||
beforeClose: 'modal-closing'
|
||||
},
|
||||
overlayClassName: {
|
||||
base: 'modal-overlay',
|
||||
afterOpen: 'modal-overlay-opening',
|
||||
beforeClose: 'modal-overlay-closing'
|
||||
},
|
||||
closeTimeoutMS: 200
|
||||
};
|
||||
|
||||
return [modalProps, payload, handleRequestClose];
|
||||
}
|
@ -65,6 +65,7 @@ export default class Chat extends Component {
|
||||
addFetchedMessages,
|
||||
fetchMessages,
|
||||
inputActions,
|
||||
openModal,
|
||||
runCommand,
|
||||
sendMessage,
|
||||
toggleSearch,
|
||||
@ -86,6 +87,7 @@ export default class Chat extends Component {
|
||||
status={status}
|
||||
tab={tab}
|
||||
title={title}
|
||||
openModal={openModal}
|
||||
onCloseClick={this.handleCloseClick}
|
||||
onTitleChange={this.handleTitleChange}
|
||||
onToggleSearch={toggleSearch}
|
||||
@ -97,6 +99,7 @@ export default class Chat extends Component {
|
||||
hasMoreMessages={hasMoreMessages}
|
||||
messages={messages}
|
||||
tab={tab}
|
||||
hideTopDate={search.show}
|
||||
onAddMore={addFetchedMessages}
|
||||
onFetchMore={fetchMessages}
|
||||
onNickClick={this.handleNickClick}
|
||||
|
@ -1,14 +1,17 @@
|
||||
import React, { memo } from 'react';
|
||||
import Navicon from 'containers/Navicon';
|
||||
import { FiUsers, FiSearch, FiX } from 'react-icons/fi';
|
||||
import Navicon from 'components/ui/Navicon';
|
||||
import Button from 'components/ui/Button';
|
||||
import Editable from 'components/ui/Editable';
|
||||
import { isValidServerName } from 'state/servers';
|
||||
import { isChannel, linkify } from 'utils';
|
||||
import { isChannel } from 'utils';
|
||||
|
||||
const ChatTitle = ({
|
||||
status,
|
||||
title,
|
||||
tab,
|
||||
channel,
|
||||
openModal,
|
||||
onTitleChange,
|
||||
onToggleSearch,
|
||||
onToggleUserList,
|
||||
@ -26,10 +29,7 @@ const ChatTitle = ({
|
||||
let serverError = null;
|
||||
if (!tab.name && status.error) {
|
||||
serverError = (
|
||||
<span className="chat-topic error">
|
||||
Error:
|
||||
{status.error}
|
||||
</span>
|
||||
<span className="chat-topic error">Error: {status.error}</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -47,24 +47,29 @@ const ChatTitle = ({
|
||||
<span className="chat-title">{title}</span>
|
||||
</Editable>
|
||||
<div className="chat-topic-wrap">
|
||||
<span className="chat-topic">
|
||||
{channel && linkify(channel.topic)}
|
||||
</span>
|
||||
{channel && channel.topic && (
|
||||
<span
|
||||
className="chat-topic"
|
||||
onClick={() => openModal('topic', channel.name)}
|
||||
>
|
||||
{channel.topic}
|
||||
</span>
|
||||
)}
|
||||
{serverError}
|
||||
</div>
|
||||
<i className="icon-search" title="Search" onClick={onToggleSearch} />
|
||||
<i
|
||||
className="icon-cancel button-leave"
|
||||
title={closeTitle}
|
||||
onClick={onCloseClick}
|
||||
{tab.name && (
|
||||
<Button icon={FiSearch} title="Search" onClick={onToggleSearch} />
|
||||
)}
|
||||
<Button icon={FiX} title={closeTitle} onClick={onCloseClick} />
|
||||
<Button
|
||||
icon={FiUsers}
|
||||
className="button-userlist"
|
||||
onClick={onToggleUserList}
|
||||
/>
|
||||
<i className="icon-user button-userlist" onClick={onToggleUserList} />
|
||||
</div>
|
||||
<div className="userlist-bar">
|
||||
<i className="icon-user" />
|
||||
<span className="chat-usercount">
|
||||
{channel && channel.users.length}
|
||||
</span>
|
||||
<FiUsers />
|
||||
{channel && channel.users.length}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -7,6 +7,15 @@ const Message = ({ message, coloredNick, style, onNickClick }) => {
|
||||
[`message-${message.type}`]: message.type
|
||||
});
|
||||
|
||||
if (message.type === 'date') {
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
{message.content}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
style = {
|
||||
...style,
|
||||
paddingLeft: `${window.messageIndent + 15}px`,
|
||||
|
@ -2,6 +2,7 @@ import React, { PureComponent, createRef } from 'react';
|
||||
import { VariableSizeList as List } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { formatDate, measureScrollBarWidth } from 'utils';
|
||||
import { getScrollPos, saveScrollPos } from 'utils/scrollPosition';
|
||||
import { windowHeight } from 'utils/size';
|
||||
import Message from './Message';
|
||||
@ -12,8 +13,24 @@ const fetchThreshold = 600;
|
||||
// this is done to prevent the scroll from jumping all over the place
|
||||
const scrollbackDebounce = 150;
|
||||
|
||||
const scrollBarWidth = `${measureScrollBarWidth()}px`;
|
||||
|
||||
const hasSameLastMessage = (m1, m2) => {
|
||||
if (m1.length === 0 || m2.length === 0) {
|
||||
if (m1.length === 0 && m2.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return m1[m1.length - 1].id === m2[m2.length - 1].id;
|
||||
};
|
||||
|
||||
export default class MessageBox extends PureComponent {
|
||||
state = { topDate: '' };
|
||||
|
||||
list = createRef();
|
||||
|
||||
outer = createRef();
|
||||
|
||||
addMore = debounce(() => {
|
||||
@ -29,6 +46,8 @@ export default class MessageBox extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { messages } = this.props;
|
||||
|
||||
if (prevProps.tab !== this.props.tab) {
|
||||
this.loadScrollPos(true);
|
||||
}
|
||||
@ -36,8 +55,11 @@ export default class MessageBox extends PureComponent {
|
||||
if (this.nextScrollTop > 0) {
|
||||
this.list.current.scrollTo(this.nextScrollTop);
|
||||
this.nextScrollTop = 0;
|
||||
} else if (this.bottom) {
|
||||
this.list.current.scrollToItem(this.props.messages.length + 1);
|
||||
} else if (
|
||||
this.bottom &&
|
||||
!hasSameLastMessage(messages, prevProps.messages)
|
||||
) {
|
||||
this.list.current.scrollToItem(messages.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +109,8 @@ export default class MessageBox extends PureComponent {
|
||||
return 100;
|
||||
}
|
||||
return 7;
|
||||
} else if (index === messages.length + 1) {
|
||||
}
|
||||
if (index === messages.length + 1) {
|
||||
return 7;
|
||||
}
|
||||
return messages[index - 1].height;
|
||||
@ -98,7 +121,8 @@ export default class MessageBox extends PureComponent {
|
||||
|
||||
if (index === 0) {
|
||||
return 'top';
|
||||
} else if (index === messages.length + 1) {
|
||||
}
|
||||
if (index === messages.length + 1) {
|
||||
return 'bottom';
|
||||
}
|
||||
return messages[index - 1].id;
|
||||
@ -177,6 +201,17 @@ export default class MessageBox extends PureComponent {
|
||||
this.bottom = scrollOffset + clientHeight >= scrollHeight - 20;
|
||||
};
|
||||
|
||||
handleItemsRendered = ({ visibleStartIndex }) => {
|
||||
const startIndex = visibleStartIndex === 0 ? 0 : visibleStartIndex - 1;
|
||||
const firstVisibleMessage = this.props.messages[startIndex];
|
||||
|
||||
if (firstVisibleMessage && firstVisibleMessage.date) {
|
||||
this.setState({ topDate: formatDate(firstVisibleMessage.date) });
|
||||
} else {
|
||||
this.setState({ topDate: '' });
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown = () => {
|
||||
this.mouseDown = true;
|
||||
};
|
||||
@ -203,7 +238,8 @@ export default class MessageBox extends PureComponent {
|
||||
);
|
||||
}
|
||||
return null;
|
||||
} else if (index === messages.length + 1) {
|
||||
}
|
||||
if (index === messages.length + 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -221,12 +257,27 @@ export default class MessageBox extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { messages, hideTopDate } = this.props;
|
||||
const { topDate } = this.state;
|
||||
|
||||
const dateContainerStyle = {
|
||||
right: scrollBarWidth
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="messagebox"
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}
|
||||
>
|
||||
<div
|
||||
className="messagebox-topdate-container"
|
||||
style={dateContainerStyle}
|
||||
>
|
||||
{!hideTopDate && topDate && (
|
||||
<span className="messagebox-topdate">{topDate}</span>
|
||||
)}
|
||||
</div>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<List
|
||||
@ -234,12 +285,13 @@ export default class MessageBox extends PureComponent {
|
||||
outerRef={this.outer}
|
||||
width={width}
|
||||
height={height}
|
||||
itemCount={this.props.messages.length + 2}
|
||||
itemCount={messages.length + 2}
|
||||
itemKey={this.getItemKey}
|
||||
itemSize={this.getRowHeight}
|
||||
estimatedItemSize={32}
|
||||
initialScrollOffset={this.initialScrollTop}
|
||||
onScroll={this.handleScroll}
|
||||
onItemsRendered={this.handleItemsRendered}
|
||||
className="messagebox-window"
|
||||
overscanCount={5}
|
||||
>
|
||||
|
@ -1,17 +1,15 @@
|
||||
import React, { memo, useRef, useEffect } from 'react';
|
||||
import { FiSearch } from 'react-icons/fi';
|
||||
import SearchResult from './SearchResult';
|
||||
|
||||
const Search = ({ search, onSearch }) => {
|
||||
const inputEl = useRef();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (search.show) {
|
||||
inputEl.current.focus();
|
||||
}
|
||||
},
|
||||
[search.show]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (search.show) {
|
||||
inputEl.current.focus();
|
||||
}
|
||||
}, [search.show]);
|
||||
|
||||
const style = {
|
||||
display: search.show ? 'block' : 'none'
|
||||
@ -25,7 +23,7 @@ const Search = ({ search, onSearch }) => {
|
||||
return (
|
||||
<div className="search" style={style}>
|
||||
<div className="search-input-wrap">
|
||||
<i className="icon-search" />
|
||||
<FiSearch className="search-input-icon" />
|
||||
<input
|
||||
ref={inputEl}
|
||||
className="search-input"
|
||||
|
@ -28,7 +28,8 @@ export default class UserList extends PureComponent {
|
||||
|
||||
if (index === 0) {
|
||||
return 12;
|
||||
} else if (index === users.length + 1) {
|
||||
}
|
||||
if (index === users.length + 1) {
|
||||
return 10;
|
||||
}
|
||||
return 24;
|
||||
@ -39,7 +40,8 @@ export default class UserList extends PureComponent {
|
||||
|
||||
if (index === 0) {
|
||||
return 'top';
|
||||
} else if (index === users.length + 1) {
|
||||
}
|
||||
if (index === users.length + 1) {
|
||||
return 'bottom';
|
||||
}
|
||||
return index;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Form, withFormik } from 'formik';
|
||||
import Navicon from 'containers/Navicon';
|
||||
import Navicon from 'components/ui/Navicon';
|
||||
import Button from 'components/ui/Button';
|
||||
import Checkbox from 'components/ui/formik/Checkbox';
|
||||
import TextInput from 'components/ui/TextInput';
|
||||
import Error from 'components/ui/formik/Error';
|
||||
import { isValidNick, isValidChannel, isValidUsername, isInt } from 'utils';
|
||||
import { FiMoreHorizontal } from 'react-icons/fi';
|
||||
|
||||
const getSortedDefaultChannels = createSelector(
|
||||
defaults => defaults.channels,
|
||||
@ -37,12 +38,39 @@ class Connect extends Component {
|
||||
return (
|
||||
<div>
|
||||
{!hexIP && <TextInput name="username" />}
|
||||
<TextInput name="password" type="password" />
|
||||
<TextInput name="realname" />
|
||||
<TextInput name="password" type="password" noTrim />
|
||||
<TextInput name="realname" noTrim />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
transformPort = port => {
|
||||
if (!port) {
|
||||
return this.props.values.tls ? 6697 : 6667;
|
||||
}
|
||||
return port;
|
||||
};
|
||||
|
||||
transformChannels = channels => {
|
||||
const comma = channels[channels.length - 1] === ',';
|
||||
|
||||
channels = channels
|
||||
.split(',')
|
||||
.map(channel => {
|
||||
channel = channel.trim();
|
||||
if (channel) {
|
||||
if (isValidChannel(channel, false) && channel[0] !== '#') {
|
||||
channel = `#${channel}`;
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
})
|
||||
.filter(s => s)
|
||||
.join(',');
|
||||
|
||||
return comma ? `${channels},` : channels;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { defaults, values } = this.props;
|
||||
const { readOnly, showDetails } = defaults;
|
||||
@ -70,10 +98,15 @@ class Connect extends Component {
|
||||
form = (
|
||||
<Form className="connect-form">
|
||||
<h1>Connect</h1>
|
||||
<TextInput name="name" autoCapitalize="words" />
|
||||
<TextInput name="name" autoCapitalize="words" noTrim />
|
||||
<div className="connect-form-address">
|
||||
<TextInput name="host" noError />
|
||||
<TextInput name="port" type="number" noError />
|
||||
<TextInput
|
||||
name="port"
|
||||
type="number"
|
||||
blurTransform={this.transformPort}
|
||||
noError
|
||||
/>
|
||||
<Checkbox
|
||||
name="tls"
|
||||
label="SSL"
|
||||
@ -84,9 +117,13 @@ class Connect extends Component {
|
||||
<Error name="host" />
|
||||
<Error name="port" />
|
||||
<TextInput name="nick" />
|
||||
<TextInput name="channels" />
|
||||
<TextInput name="channels" transform={this.transformChannels} />
|
||||
{this.state.showOptionals && this.renderOptionals()}
|
||||
<i className="icon-ellipsis" onClick={this.handleShowClick} />
|
||||
<Button
|
||||
className="connect-form-button-optionals"
|
||||
icon={FiMoreHorizontal}
|
||||
onClick={this.handleShowClick}
|
||||
/>
|
||||
<Button type="submit">Connect</Button>
|
||||
</Form>
|
||||
);
|
||||
@ -120,16 +157,10 @@ export default withFormik({
|
||||
username: '',
|
||||
password: defaults.password ? ' ' : '',
|
||||
realname: '',
|
||||
tls: defaults.ssl
|
||||
tls: defaults.ssl || false
|
||||
};
|
||||
},
|
||||
validate: values => {
|
||||
Object.keys(values).forEach(k => {
|
||||
if (typeof values[k] === 'string') {
|
||||
values[k] = values[k].trim();
|
||||
}
|
||||
});
|
||||
|
||||
const errors = {};
|
||||
|
||||
if (!values.host) {
|
||||
@ -138,9 +169,7 @@ export default withFormik({
|
||||
errors.host = 'Invalid host';
|
||||
}
|
||||
|
||||
if (!values.port) {
|
||||
values.port = values.tls ? 6697 : 6667;
|
||||
} else if (!isInt(values.port, 1, 65535)) {
|
||||
if (!isInt(values.port, 1, 65535)) {
|
||||
errors.port = 'Invalid port';
|
||||
}
|
||||
|
||||
@ -154,29 +183,24 @@ export default withFormik({
|
||||
errors.username = 'Invalid username';
|
||||
}
|
||||
|
||||
values.channels = values.channels
|
||||
.split(',')
|
||||
.map(channel => {
|
||||
channel = channel.trim();
|
||||
if (channel) {
|
||||
if (isValidChannel(channel, false)) {
|
||||
if (channel[0] !== '#') {
|
||||
channel = `#${channel}`;
|
||||
}
|
||||
} else {
|
||||
errors.channels = 'Invalid channel(s)';
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
})
|
||||
.filter(s => s)
|
||||
.join(',');
|
||||
const channels = values.channels.split(',');
|
||||
for (let i = channels.length - 1; i >= 0; i--) {
|
||||
if (i === channels.length - 1 && channels[i] === '') {
|
||||
/* eslint-disable-next-line no-continue */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValidChannel(channels[i])) {
|
||||
errors.channels = 'Invalid channel(s)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
},
|
||||
handleSubmit: (values, { props }) => {
|
||||
const { connect, select, join } = props;
|
||||
const channels = values.channels.split(',');
|
||||
const channels = values.channels ? values.channels.split(',') : [];
|
||||
delete values.channels;
|
||||
|
||||
values.port = `${values.port}`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import Navicon from 'containers/Navicon';
|
||||
import Navicon from 'components/ui/Navicon';
|
||||
import Button from 'components/ui/Button';
|
||||
import Checkbox from 'components/ui/Checkbox';
|
||||
import FileInput from 'components/ui/FileInput';
|
||||
@ -17,14 +17,11 @@ const Settings = ({
|
||||
const status = settings.uploadingCert ? 'Uploading...' : 'Upload';
|
||||
const error = settings.certError;
|
||||
|
||||
const handleInstallClick = useCallback(
|
||||
async () => {
|
||||
installable.prompt();
|
||||
await installable.userChoice;
|
||||
onInstall();
|
||||
},
|
||||
[installable]
|
||||
);
|
||||
const handleInstallClick = useCallback(async () => {
|
||||
installable.prompt();
|
||||
await installable.userChoice;
|
||||
onInstall();
|
||||
}, [installable]);
|
||||
|
||||
return (
|
||||
<div className="settings-container">
|
||||
@ -32,7 +29,10 @@ const Settings = ({
|
||||
<Navicon />
|
||||
<h1>Settings</h1>
|
||||
{installable && (
|
||||
<Button className="button-install" onClick={handleInstallClick}>
|
||||
<Button
|
||||
className="settings-button button-install"
|
||||
onClick={handleInstallClick}
|
||||
>
|
||||
<h2>Install</h2>
|
||||
</Button>
|
||||
)}
|
||||
|
@ -1,7 +1,19 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
const Button = ({ children, ...props }) => (
|
||||
<button type="button" {...props}>
|
||||
const Button = ({ children, category, className, icon: Icon, ...props }) => (
|
||||
<button
|
||||
className={cn(
|
||||
{
|
||||
[`button-${category}`]: category,
|
||||
'icon-button': Icon && !children
|
||||
},
|
||||
className
|
||||
)}
|
||||
type="button"
|
||||
{...props}
|
||||
>
|
||||
{Icon && <Icon />}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PureComponent, createRef } from 'react';
|
||||
import cn from 'classnames';
|
||||
import { stringWidth } from 'utils';
|
||||
|
||||
export default class Editable extends PureComponent {
|
||||
@ -75,7 +76,7 @@ export default class Editable extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, className, value } = this.props;
|
||||
const { children, className, editable, value } = this.props;
|
||||
|
||||
const style = {
|
||||
width: this.state.width,
|
||||
@ -86,7 +87,7 @@ export default class Editable extends PureComponent {
|
||||
return this.state.editing ? (
|
||||
<input
|
||||
ref={this.inputEl}
|
||||
className={className}
|
||||
className={`editable-wrap ${className}`}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.handleBlur}
|
||||
@ -97,7 +98,14 @@ export default class Editable extends PureComponent {
|
||||
spellCheck={false}
|
||||
/>
|
||||
) : (
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
<div
|
||||
className={cn('editable-wrap', {
|
||||
'editable-wrap-editable': editable
|
||||
})}
|
||||
onClick={this.startEditing}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,19 @@
|
||||
import React from 'react';
|
||||
import { FiMenu } from 'react-icons/fi';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Button from 'components/ui/Button';
|
||||
import { toggleMenu } from 'state/ui';
|
||||
|
||||
const Navicon = ({ onClick }) => (
|
||||
<i className="icon-menu navicon" onClick={onClick} />
|
||||
);
|
||||
const Navicon = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="navicon"
|
||||
icon={FiMenu}
|
||||
onClick={() => dispatch(toggleMenu())}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navicon;
|
||||
|
@ -4,6 +4,24 @@ import classnames from 'classnames';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import Error from 'components/ui/formik/Error';
|
||||
|
||||
const getValue = (e, trim) => {
|
||||
let v = e.target.value;
|
||||
|
||||
if (trim) {
|
||||
v = v.trim();
|
||||
}
|
||||
|
||||
if (e.target.type === 'number') {
|
||||
v = parseFloat(v);
|
||||
/* eslint-disable-next-line no-self-compare */
|
||||
if (v !== v) {
|
||||
v = '';
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
};
|
||||
|
||||
export default class TextInput extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -38,49 +56,83 @@ export default class TextInput extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, label = capitalize(name), noError, ...props } = this.props;
|
||||
const {
|
||||
name,
|
||||
label = capitalize(name),
|
||||
noError,
|
||||
noTrim,
|
||||
transform,
|
||||
blurTransform,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<FastField
|
||||
name={name}
|
||||
render={({ field, form }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="textinput">
|
||||
<input
|
||||
className={field.value && 'value'}
|
||||
type="text"
|
||||
name={name}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
ref={this.input}
|
||||
onFocus={this.handleFocus}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
<span
|
||||
className={classnames('textinput-1', {
|
||||
value: field.value,
|
||||
error: form.touched[name] && form.errors[name]
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
className={classnames('textinput-2', {
|
||||
value: field.value,
|
||||
error: form.touched[name] && form.errors[name]
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
{!noError && <Error name={name} />}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
render={({ field, form }) => (
|
||||
<>
|
||||
<div className="textinput">
|
||||
<input
|
||||
className={field.value && 'value'}
|
||||
type="text"
|
||||
name={name}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
ref={this.input}
|
||||
onFocus={this.handleFocus}
|
||||
{...field}
|
||||
{...props}
|
||||
onChange={e => {
|
||||
let v = getValue(e, !noTrim);
|
||||
if (transform) {
|
||||
v = transform(v);
|
||||
}
|
||||
|
||||
if (v !== field.value) {
|
||||
form.setFieldValue(name, v);
|
||||
|
||||
if (props.onChange) {
|
||||
props.onChange(e);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onBlur={e => {
|
||||
if (blurTransform) {
|
||||
const v = blurTransform(getValue(e));
|
||||
|
||||
if (v && v !== field.value) {
|
||||
form.setFieldValue(name, v, false);
|
||||
}
|
||||
}
|
||||
|
||||
field.onBlur(e);
|
||||
if (props.onBlur) {
|
||||
props.onBlur(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className={classnames('textinput-1', {
|
||||
value: field.value,
|
||||
error: form.touched[name] && form.errors[name]
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
className={classnames('textinput-2', {
|
||||
value: field.value,
|
||||
error: form.touched[name] && form.errors[name]
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
{!noError && <Error name={name} />}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -5,22 +5,19 @@ import Checkbox from 'components/ui/Checkbox';
|
||||
const FormikCheckbox = ({ name, onChange, ...props }) => (
|
||||
<FastField
|
||||
name={name}
|
||||
render={({ field, form }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
name={name}
|
||||
checked={field.value}
|
||||
onChange={e => {
|
||||
form.setFieldTouched(name, true);
|
||||
field.onChange(e);
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
name={name}
|
||||
checked={field.value}
|
||||
onChange={e => {
|
||||
field.onChange(e);
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { createStructuredSelector } from 'reselect';
|
||||
import App from 'components/App';
|
||||
import { getConnected } from 'state/app';
|
||||
import { getSortedChannels } from 'state/channels';
|
||||
import { openModal, getHasOpenModals } from 'state/modals';
|
||||
import { getPrivateChats } from 'state/privateChats';
|
||||
import { getServers } from 'state/servers';
|
||||
import { getSelectedTab, select } from 'state/tab';
|
||||
@ -16,12 +17,10 @@ const mapState = createStructuredSelector({
|
||||
servers: getServers,
|
||||
showTabList: getShowTabList,
|
||||
tab: getSelectedTab,
|
||||
newVersionAvailable: state => state.app.newVersionAvailable
|
||||
newVersionAvailable: state => state.app.newVersionAvailable,
|
||||
hasOpenModals: getHasOpenModals
|
||||
});
|
||||
|
||||
const mapDispatch = { push, select, hideMenu };
|
||||
const mapDispatch = { push, select, hideMenu, openModal };
|
||||
|
||||
export default connect(
|
||||
mapState,
|
||||
mapDispatch
|
||||
)(App);
|
||||
export default connect(mapState, mapDispatch)(App);
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
fetchMessages,
|
||||
addFetchedMessages
|
||||
} from 'state/messages';
|
||||
import { openModal } from 'state/modals';
|
||||
import { openPrivateChat, closePrivateChat } from 'state/privateChats';
|
||||
import { getSearch, searchMessages, toggleSearch } from 'state/search';
|
||||
import {
|
||||
@ -58,6 +59,7 @@ const mapDispatch = dispatch => ({
|
||||
closePrivateChat,
|
||||
disconnect,
|
||||
fetchMessages,
|
||||
openModal,
|
||||
openPrivateChat,
|
||||
part,
|
||||
runCommand,
|
||||
@ -83,7 +85,4 @@ const mapDispatch = dispatch => ({
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapState,
|
||||
mapDispatch
|
||||
)(Chat);
|
||||
export default connect(mapState, mapDispatch)(Chat);
|
||||
|
@ -17,7 +17,4 @@ const mapDispatch = {
|
||||
select
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapState,
|
||||
mapDispatch
|
||||
)(Connect);
|
||||
export default connect(mapState, mapDispatch)(Connect);
|
||||
|
@ -1,12 +0,0 @@
|
||||
import Navicon from 'components/ui/Navicon';
|
||||
import { toggleMenu } from 'state/ui';
|
||||
import connect from 'utils/connect';
|
||||
|
||||
const mapDispatch = {
|
||||
onClick: toggleMenu
|
||||
};
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatch
|
||||
)(Navicon);
|
@ -24,7 +24,4 @@ const mapDispatch = {
|
||||
onInstall: () => appSet('installable', null)
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapState,
|
||||
mapDispatch
|
||||
)(Settings);
|
||||
export default connect(mapState, mapDispatch)(Settings);
|
||||
|
17
client/js/containers/TabListItem.js
Normal file
17
client/js/containers/TabListItem.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import get from 'lodash/get';
|
||||
import TabListItem from 'components/TabListItem';
|
||||
import connect from 'utils/connect';
|
||||
|
||||
const mapState = createStructuredSelector({
|
||||
error: (state, { server, target }) => {
|
||||
const messages = get(state, ['messages', server, target]);
|
||||
|
||||
if (messages && messages.length > 0) {
|
||||
return messages[messages.length - 1].type === 'error';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(mapState)(TabListItem);
|
@ -11,7 +11,6 @@ import routes from './routes';
|
||||
import runModules from './modules';
|
||||
import { register } from './serviceWorker';
|
||||
import '../css/fonts.css';
|
||||
import '../css/fontello.css';
|
||||
import '../css/style.css';
|
||||
|
||||
const production = process.env.NODE_ENV === 'production';
|
||||
|
@ -27,7 +27,7 @@ export default function createCommandMiddleware(type, handlers) {
|
||||
return store => next => action => {
|
||||
if (action.type === type) {
|
||||
const words = action.command.slice(1).split(' ');
|
||||
const command = words[0];
|
||||
const command = words[0].toLowerCase();
|
||||
const params = words.slice(1);
|
||||
|
||||
if (command in handlers) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { socket as socketActions } from 'state/actions';
|
||||
import { getWrapWidth, appSet } from 'state/app';
|
||||
import { getConnected, getWrapWidth, appSet } from 'state/app';
|
||||
import { searchChannels } from 'state/channelSearch';
|
||||
import { addMessages } from 'state/messages';
|
||||
import { setSettings } from 'state/settings';
|
||||
import { when } from 'utils/observe';
|
||||
@ -12,6 +13,13 @@ function loadState({ store }, env) {
|
||||
type: socketActions.SERVERS,
|
||||
data: env.servers
|
||||
});
|
||||
|
||||
when(store, getConnected, () =>
|
||||
// Cache top channels for each server
|
||||
env.servers.forEach(({ host }) =>
|
||||
store.dispatch(searchChannels(host, ''))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (env.channels) {
|
||||
|
@ -7,9 +7,10 @@ import {
|
||||
addMessage,
|
||||
addMessages
|
||||
} from 'state/messages';
|
||||
import { openModal } from 'state/modals';
|
||||
import { reconnect } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { find, normalizeChannel } from 'utils';
|
||||
import { find } from 'utils';
|
||||
|
||||
function withReason(message, reason) {
|
||||
return message + (reason ? ` (${reason})` : '');
|
||||
@ -45,22 +46,7 @@ export default function handleSocket({
|
||||
},
|
||||
|
||||
join({ user, server, channels }) {
|
||||
const state = getState();
|
||||
const tab = state.tab.selected;
|
||||
const [joinedChannel] = channels;
|
||||
if (tab.server && tab.name) {
|
||||
const { nick } = state.servers[tab.server];
|
||||
if (
|
||||
tab.server === server &&
|
||||
nick === user &&
|
||||
tab.name !== joinedChannel &&
|
||||
normalizeChannel(tab.name) === normalizeChannel(joinedChannel)
|
||||
) {
|
||||
dispatch(select(server, joinedChannel));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(inform(`${user} joined the channel`, server, joinedChannel));
|
||||
dispatch(inform(`${user} joined the channel`, server, channels[0]));
|
||||
},
|
||||
|
||||
part({ user, server, channel, reason }) {
|
||||
@ -75,10 +61,12 @@ export default function handleSocket({
|
||||
},
|
||||
|
||||
nick({ server, oldNick, newNick }) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(
|
||||
broadcast(`${oldNick} changed nick to ${newNick}`, server, channels)
|
||||
);
|
||||
if (oldNick) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(
|
||||
broadcast(`${oldNick} changed nick to ${newNick}`, server, channels)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
topic({ server, channel, topic, nick }) {
|
||||
@ -93,7 +81,12 @@ export default function handleSocket({
|
||||
},
|
||||
|
||||
motd({ content, server }) {
|
||||
dispatch(addMessages(content.map(line => ({ content: line })), server));
|
||||
dispatch(
|
||||
addMessages(
|
||||
content.map(line => ({ content: line })),
|
||||
server
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
whois(data) {
|
||||
@ -120,16 +113,22 @@ export default function handleSocket({
|
||||
dispatch(addMessage(message, tab.server, tab.name));
|
||||
},
|
||||
|
||||
error({ server, target, message }) {
|
||||
dispatch(addMessage({ content: message, type: 'error' }, server, target));
|
||||
},
|
||||
|
||||
connection_update({ server, errorType }) {
|
||||
if (
|
||||
errorType === 'verify' &&
|
||||
window.confirm(
|
||||
'The server is using a self-signed certificate, continue anyway?'
|
||||
)
|
||||
) {
|
||||
if (errorType === 'verify') {
|
||||
dispatch(
|
||||
reconnect(server, {
|
||||
skipVerify: true
|
||||
openModal('confirm', {
|
||||
question:
|
||||
'The server is using a self-signed certificate, continue anyway?',
|
||||
onConfirm: () =>
|
||||
dispatch(
|
||||
reconnect(server, {
|
||||
skipVerify: true
|
||||
})
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -140,6 +139,16 @@ export default function handleSocket({
|
||||
}
|
||||
};
|
||||
|
||||
const afterHandlers = {
|
||||
channel_forward(forward) {
|
||||
const { selected } = getState().tab;
|
||||
|
||||
if (selected.server === forward.server && selected.name === forward.old) {
|
||||
dispatch(select(forward.server, forward.new, true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.onMessage((type, data) => {
|
||||
let action;
|
||||
if (Array.isArray(data)) {
|
||||
@ -157,5 +166,9 @@ export default function handleSocket({
|
||||
}
|
||||
|
||||
dispatch(action);
|
||||
|
||||
if (type in afterHandlers) {
|
||||
afterHandlers[type](data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -23,10 +23,8 @@ function registerValidSW(swUrl, config) {
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
} else if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -46,10 +46,12 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
@ -63,6 +65,7 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
}
|
||||
@ -84,10 +87,12 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: true,
|
||||
users: []
|
||||
}
|
||||
@ -110,6 +115,7 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [
|
||||
{ mode: '', nick: 'nick3', renderName: 'nick3' },
|
||||
@ -117,6 +123,7 @@ describe('channel reducer', () => {
|
||||
]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
@ -136,6 +143,7 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [
|
||||
{ mode: '', nick: 'user3', renderName: 'user3' },
|
||||
@ -219,11 +227,11 @@ describe('channel reducer', () => {
|
||||
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: { joined: true, topic: 'the topic', users: [] },
|
||||
chan2: { joined: true, users: [] }
|
||||
chan1: { name: 'chan1', joined: true, topic: 'the topic', users: [] },
|
||||
chan2: { name: 'chan2', joined: true, users: [] }
|
||||
},
|
||||
srv2: {
|
||||
chan1: { joined: true, users: [] }
|
||||
chan1: { name: 'chan1', joined: true, users: [] }
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -322,18 +330,35 @@ describe('getSortedChannels', () => {
|
||||
'bob.com': {},
|
||||
'127.0.0.1': {
|
||||
'#chan1': {
|
||||
name: '#chan1',
|
||||
users: [],
|
||||
topic: 'cake'
|
||||
},
|
||||
'#pie': {},
|
||||
'##apples': {}
|
||||
'#pie': {
|
||||
name: '#pie'
|
||||
},
|
||||
'##apples': {
|
||||
name: '##apples'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
address: '127.0.0.1',
|
||||
channels: ['##apples', '#chan1', '#pie']
|
||||
channels: [
|
||||
{
|
||||
name: '##apples'
|
||||
},
|
||||
{
|
||||
name: '#chan1',
|
||||
users: [],
|
||||
topic: 'cake'
|
||||
},
|
||||
{
|
||||
name: '#pie'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
address: 'bob.com',
|
||||
|
@ -82,7 +82,10 @@ describe('message reducer', () => {
|
||||
server: 'srv',
|
||||
tab: '#chan1',
|
||||
prepend: true,
|
||||
messages: [{ id: 1 }, { id: 2 }]
|
||||
messages: [
|
||||
{ id: 1, date: new Date() },
|
||||
{ id: 2, date: new Date() }
|
||||
]
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
@ -92,6 +95,90 @@ describe('message reducer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('adds date markers when prepending messages', () => {
|
||||
let state = {
|
||||
srv: {
|
||||
'#chan1': [{ id: 0, date: new Date(1999, 0, 1) }]
|
||||
}
|
||||
};
|
||||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGES,
|
||||
server: 'srv',
|
||||
tab: '#chan1',
|
||||
prepend: true,
|
||||
messages: [
|
||||
{ id: 1, date: new Date(1990, 0, 2) },
|
||||
{ id: 2, date: new Date(1990, 0, 3) }
|
||||
]
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
srv: {
|
||||
'#chan1': [
|
||||
{ id: 1 },
|
||||
{ type: 'date' },
|
||||
{ id: 2 },
|
||||
{ type: 'date' },
|
||||
{ id: 0 }
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a date marker when adding a message', () => {
|
||||
let state = {
|
||||
srv: {
|
||||
'#chan1': [{ id: 0, date: new Date(1999, 0, 1) }]
|
||||
}
|
||||
};
|
||||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGE,
|
||||
server: 'srv',
|
||||
tab: '#chan1',
|
||||
message: { id: 1, date: new Date(1990, 0, 2) }
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
srv: {
|
||||
'#chan1': [{ id: 0 }, { type: 'date' }, { id: 1 }]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('adds date markers when adding messages', () => {
|
||||
let state = {
|
||||
srv: {
|
||||
'#chan1': [{ id: 0, date: new Date(1999, 0, 1) }]
|
||||
}
|
||||
};
|
||||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGES,
|
||||
server: 'srv',
|
||||
tab: '#chan1',
|
||||
messages: [
|
||||
{ id: 1, date: new Date(1990, 0, 2) },
|
||||
{ id: 2, date: new Date(1990, 0, 3) },
|
||||
{ id: 3, date: new Date(1990, 0, 3) }
|
||||
]
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
srv: {
|
||||
'#chan1': [
|
||||
{ id: 0 },
|
||||
{ type: 'date' },
|
||||
{ id: 1 },
|
||||
{ type: 'date' },
|
||||
{ id: 2 },
|
||||
{ id: 3 }
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('adds messages to the correct tabs when broadcasting', () => {
|
||||
let state = {
|
||||
app: appReducer(undefined, { type: '' })
|
||||
|
@ -16,7 +16,8 @@ describe('server reducer', () => {
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
||||
@ -30,7 +31,8 @@ describe('server reducer', () => {
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
||||
@ -47,7 +49,8 @@ describe('server reducer', () => {
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
},
|
||||
'127.0.0.2': {
|
||||
name: 'srv',
|
||||
@ -56,7 +59,8 @@ describe('server reducer', () => {
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -216,7 +220,8 @@ describe('server reducer', () => {
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: true
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
},
|
||||
'127.0.0.2': {
|
||||
name: 'stuffz',
|
||||
@ -224,7 +229,8 @@ describe('server reducer', () => {
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -247,7 +253,8 @@ describe('server reducer', () => {
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: true
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
||||
@ -266,7 +273,8 @@ describe('server reducer', () => {
|
||||
status: {
|
||||
connected: false,
|
||||
error: 'Bad stuff happened'
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,8 @@ export const KICK = 'KICK';
|
||||
export const PART = 'PART';
|
||||
export const SET_TOPIC = 'SET_TOPIC';
|
||||
|
||||
export const CHANNEL_SEARCH = 'CHANNEL_SEARCH';
|
||||
|
||||
export const INPUT_HISTORY_ADD = 'INPUT_HISTORY_ADD';
|
||||
export const INPUT_HISTORY_DECREMENT = 'INPUT_HISTORY_DECREMENT';
|
||||
export const INPUT_HISTORY_INCREMENT = 'INPUT_HISTORY_INCREMENT';
|
||||
@ -19,6 +21,9 @@ export const FETCH_MESSAGES = 'FETCH_MESSAGES';
|
||||
export const RAW = 'RAW';
|
||||
export const UPDATE_MESSAGE_HEIGHT = 'UPDATE_MESSAGE_HEIGHT';
|
||||
|
||||
export const OPEN_MODAL = 'OPEN_MODAL';
|
||||
export const CLOSE_MODAL = 'CLOSE_MODAL';
|
||||
|
||||
export const CLOSE_PRIVATE_CHAT = 'CLOSE_PRIVATE_CHAT';
|
||||
export const OPEN_PRIVATE_CHAT = 'OPEN_PRIVATE_CHAT';
|
||||
|
||||
@ -61,7 +66,11 @@ export const socket = createSocketActions([
|
||||
'cert_fail',
|
||||
'cert_success',
|
||||
'channels',
|
||||
'channel_forward',
|
||||
'channel_search',
|
||||
'connection_update',
|
||||
'error',
|
||||
'features',
|
||||
'join',
|
||||
'message',
|
||||
'mode',
|
||||
|
@ -1,4 +1,3 @@
|
||||
import assign from 'lodash/assign';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import * as actions from './actions';
|
||||
|
||||
@ -10,7 +9,7 @@ export const getWindowWidth = state => state.app.windowWidth;
|
||||
export const getConnectDefaults = state => state.app.connectDefaults;
|
||||
|
||||
const initialState = {
|
||||
connected: true,
|
||||
connected: false,
|
||||
wrapWidth: 0,
|
||||
charWidth: 0,
|
||||
windowWidth: 0,
|
||||
@ -31,7 +30,7 @@ const initialState = {
|
||||
export default createReducer(initialState, {
|
||||
[actions.APP_SET](state, { key, value }) {
|
||||
if (typeof key === 'object') {
|
||||
assign(state, key);
|
||||
Object.assign(state, key);
|
||||
} else {
|
||||
state[key] = value;
|
||||
}
|
||||
|
47
client/js/state/channelSearch.js
Normal file
47
client/js/state/channelSearch.js
Normal file
@ -0,0 +1,47 @@
|
||||
import createReducer from 'utils/createReducer';
|
||||
import * as actions from 'state/actions';
|
||||
|
||||
const initialState = {
|
||||
results: [],
|
||||
end: false,
|
||||
topCache: {}
|
||||
};
|
||||
|
||||
export default createReducer(initialState, {
|
||||
[actions.socket.CHANNEL_SEARCH](state, { results, start, server, q }) {
|
||||
if (results) {
|
||||
state.end = false;
|
||||
|
||||
if (start > 0) {
|
||||
state.results.push(...results);
|
||||
} else {
|
||||
state.results = results;
|
||||
|
||||
if (!q) {
|
||||
state.topCache[server] = results;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.end = true;
|
||||
}
|
||||
},
|
||||
|
||||
[actions.OPEN_MODAL](state, { name, payload }) {
|
||||
if (name === 'channel') {
|
||||
state.results = state.topCache[payload] || [];
|
||||
state.end = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function searchChannels(server, q, start) {
|
||||
return {
|
||||
type: actions.CHANNEL_SEARCH,
|
||||
server,
|
||||
q,
|
||||
socket: {
|
||||
type: 'channel_search',
|
||||
data: { server, q, start }
|
||||
}
|
||||
};
|
||||
}
|
@ -61,7 +61,7 @@ function init(state, server, channel) {
|
||||
state[server] = {};
|
||||
}
|
||||
if (channel && !state[server][channel]) {
|
||||
state[server][channel] = { users: [], joined: false };
|
||||
state[server][channel] = { name: channel, users: [], joined: false };
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,18 +91,14 @@ export function compareUsers(a, b) {
|
||||
|
||||
export const getChannels = state => state.channels;
|
||||
|
||||
export const getSortedChannels = createSelector(
|
||||
getChannels,
|
||||
channels =>
|
||||
sortBy(
|
||||
Object.keys(channels).map(server => ({
|
||||
address: server,
|
||||
channels: sortBy(Object.keys(channels[server]), channel =>
|
||||
channel.toLowerCase()
|
||||
)
|
||||
})),
|
||||
server => server.address.toLowerCase()
|
||||
)
|
||||
export const getSortedChannels = createSelector(getChannels, channels =>
|
||||
sortBy(
|
||||
Object.keys(channels).map(server => ({
|
||||
address: server,
|
||||
channels: sortBy(channels[server], channel => channel.name.toLowerCase())
|
||||
})),
|
||||
server => server.address.toLowerCase()
|
||||
)
|
||||
);
|
||||
|
||||
export const getSelectedChannel = createSelector(
|
||||
@ -135,10 +131,16 @@ export default createReducer(
|
||||
[actions.socket.JOIN](state, { server, channels, user }) {
|
||||
const channel = channels[0];
|
||||
init(state, server, channel);
|
||||
state[server][channel].name = channel;
|
||||
state[server][channel].joined = true;
|
||||
state[server][channel].users.push(createUser(user));
|
||||
},
|
||||
|
||||
[actions.socket.CHANNEL_FORWARD](state, action) {
|
||||
init(state, action.server, action.new);
|
||||
delete state[action.server][action.old];
|
||||
},
|
||||
|
||||
[actions.socket.PART](state, { server, channel, user }) {
|
||||
if (state[server][channel]) {
|
||||
removeUser(state[server][channel].users, user);
|
||||
@ -229,16 +231,27 @@ export function join(channels, server) {
|
||||
}
|
||||
|
||||
export function part(channels, server) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
return (dispatch, getState) => {
|
||||
const action = {
|
||||
type: actions.PART,
|
||||
channels,
|
||||
server,
|
||||
socket: {
|
||||
server
|
||||
};
|
||||
|
||||
const state = getState().channels[server];
|
||||
const joined = channels.filter(c => state[c] && state[c].joined);
|
||||
|
||||
if (joined.length > 0) {
|
||||
action.socket = {
|
||||
type: 'part',
|
||||
data: { channels, server }
|
||||
}
|
||||
});
|
||||
data: {
|
||||
channels: joined,
|
||||
server
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
dispatch(action);
|
||||
dispatch(updateSelection());
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import app from './app';
|
||||
import channels from './channels';
|
||||
import channelSearch from './channelSearch';
|
||||
import input from './input';
|
||||
import messages from './messages';
|
||||
import modals from './modals';
|
||||
import privateChats from './privateChats';
|
||||
import search from './search';
|
||||
import servers from './servers';
|
||||
@ -18,8 +20,10 @@ export default function createReducer(router) {
|
||||
router,
|
||||
app,
|
||||
channels,
|
||||
channelSearch,
|
||||
input,
|
||||
messages,
|
||||
modals,
|
||||
privateChats,
|
||||
search,
|
||||
servers,
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
messageHeight,
|
||||
linkify,
|
||||
timestamp,
|
||||
isChannel
|
||||
isChannel,
|
||||
formatDate
|
||||
} from 'utils';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getApp } from './app';
|
||||
@ -43,22 +44,90 @@ function init(state, server, tab) {
|
||||
}
|
||||
}
|
||||
|
||||
let nextID = 0;
|
||||
|
||||
function createDateMessage(date) {
|
||||
const message = {
|
||||
id: nextID,
|
||||
type: 'date',
|
||||
content: formatDate(date),
|
||||
height: 40
|
||||
};
|
||||
|
||||
nextID++;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
function isSameDay(d1, d2) {
|
||||
return (
|
||||
d1.getDate() === d2.getDate() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getFullYear() === d2.getFullYear()
|
||||
);
|
||||
}
|
||||
|
||||
function reducerPrependMessages(messages, server, tab, state) {
|
||||
const msgs = [];
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
if (i > 0 && !isSameDay(messages[i - 1].date, messages[i].date)) {
|
||||
msgs.push(createDateMessage(messages[i].date));
|
||||
}
|
||||
msgs.push(messages[i]);
|
||||
}
|
||||
|
||||
const m = state[server][tab];
|
||||
|
||||
if (m.length > 0) {
|
||||
const lastNewMessage = msgs[msgs.length - 1];
|
||||
const firstMessage = m[0];
|
||||
if (
|
||||
firstMessage.date &&
|
||||
!isSameDay(firstMessage.date, lastNewMessage.date)
|
||||
) {
|
||||
msgs.push(createDateMessage(firstMessage.date));
|
||||
}
|
||||
}
|
||||
|
||||
m.unshift(...msgs);
|
||||
}
|
||||
|
||||
function reducerAddMessage(message, server, tab, state) {
|
||||
const messages = state[server][tab];
|
||||
|
||||
if (messages.length > 0) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (lastMessage.date && !isSameDay(lastMessage.date, message.date)) {
|
||||
messages.push(createDateMessage(message.date));
|
||||
}
|
||||
}
|
||||
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.ADD_MESSAGE](state, { server, tab, message }) {
|
||||
init(state, server, tab);
|
||||
state[server][tab].push(message);
|
||||
reducerAddMessage(message, server, tab, state);
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGES](state, { server, tab, messages, prepend }) {
|
||||
if (prepend) {
|
||||
init(state, server, tab);
|
||||
state[server][tab].unshift(...messages);
|
||||
reducerPrependMessages(messages, server, tab, state);
|
||||
} else {
|
||||
if (!messages[0].tab) {
|
||||
init(state, server, tab);
|
||||
}
|
||||
|
||||
messages.forEach(message => {
|
||||
init(state, server, message.tab || tab);
|
||||
state[server][message.tab || tab].push(message);
|
||||
if (message.tab) {
|
||||
init(state, server, message.tab);
|
||||
}
|
||||
reducerAddMessage(message, server, message.tab || tab, state);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -71,6 +140,12 @@ export default createReducer(
|
||||
channels.forEach(channel => delete state[server][channel]);
|
||||
},
|
||||
|
||||
[actions.socket.CHANNEL_FORWARD](state, { server, old }) {
|
||||
if (state[server]) {
|
||||
delete state[server][old];
|
||||
}
|
||||
},
|
||||
|
||||
[actions.UPDATE_MESSAGE_HEIGHT](
|
||||
state,
|
||||
{ wrapWidth, charWidth, windowWidth }
|
||||
@ -78,6 +153,10 @@ export default createReducer(
|
||||
Object.keys(state).forEach(server =>
|
||||
Object.keys(state[server]).forEach(target =>
|
||||
state[server][target].forEach(message => {
|
||||
if (message.type === 'date') {
|
||||
return;
|
||||
}
|
||||
|
||||
message.height = messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
@ -100,15 +179,15 @@ export default createReducer(
|
||||
}
|
||||
);
|
||||
|
||||
let nextID = 0;
|
||||
|
||||
function initMessage(message, tab, state) {
|
||||
if (message.time) {
|
||||
message.time = timestamp(new Date(message.time * 1000));
|
||||
message.date = new Date(message.time * 1000);
|
||||
} else {
|
||||
message.time = timestamp();
|
||||
message.date = new Date();
|
||||
}
|
||||
|
||||
message.time = timestamp(message.date);
|
||||
|
||||
if (!message.id) {
|
||||
message.id = nextID;
|
||||
nextID++;
|
||||
@ -118,6 +197,8 @@ function initMessage(message, tab, state) {
|
||||
message.channel = true;
|
||||
}
|
||||
|
||||
message.content = message.content || '';
|
||||
|
||||
// Collapse multiple adjacent spaces into a single one
|
||||
message.content = message.content.replace(/\s\s+/g, ' ');
|
||||
|
||||
|
47
client/js/state/modals.js
Normal file
47
client/js/state/modals.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import * as actions from './actions';
|
||||
|
||||
export const getModals = state => state.modals;
|
||||
|
||||
export const getHasOpenModals = createSelector(getModals, modals => {
|
||||
const keys = Object.keys(modals);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (modals[keys[i]].isOpen) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.OPEN_MODAL](state, { name, payload = {} }) {
|
||||
state[name] = {
|
||||
isOpen: true,
|
||||
payload
|
||||
};
|
||||
},
|
||||
|
||||
[actions.CLOSE_MODAL](state, { name }) {
|
||||
state[name].isOpen = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function openModal(name, payload) {
|
||||
return {
|
||||
type: actions.OPEN_MODAL,
|
||||
name,
|
||||
payload
|
||||
};
|
||||
}
|
||||
|
||||
export function closeModal(name) {
|
||||
return {
|
||||
type: actions.CLOSE_MODAL,
|
||||
name
|
||||
};
|
||||
}
|
@ -45,7 +45,8 @@ export default createReducer(
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
}
|
||||
},
|
||||
features: {}
|
||||
};
|
||||
}
|
||||
},
|
||||
@ -79,8 +80,8 @@ export default createReducer(
|
||||
|
||||
[actions.socket.SERVERS](state, { data }) {
|
||||
if (data) {
|
||||
data.forEach(({ host, name, nick, status }) => {
|
||||
state[host] = { name, nick, status, editedNick: null };
|
||||
data.forEach(({ host, name = host, nick, status, features = {} }) => {
|
||||
state[host] = { name, nick, status, features, editedNick: null };
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -90,6 +91,17 @@ export default createReducer(
|
||||
state[server].status.connected = connected;
|
||||
state[server].status.error = error;
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.FEATURES](state, { server, features }) {
|
||||
const srv = state[server];
|
||||
if (srv) {
|
||||
srv.features = features;
|
||||
|
||||
if (features.NETWORK && srv.name === server) {
|
||||
srv.name = features.NETWORK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import assign from 'lodash/assign';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import * as actions from './actions';
|
||||
|
||||
@ -41,7 +40,7 @@ export default createReducer(
|
||||
|
||||
[actions.SETTINGS_SET](state, { key, value, settings }) {
|
||||
if (settings) {
|
||||
assign(state, settings);
|
||||
Object.assign(state, settings);
|
||||
} else {
|
||||
state[key] = value;
|
||||
}
|
||||
@ -80,7 +79,7 @@ export function setCert(fileName, cert) {
|
||||
return {
|
||||
type: actions.SET_CERT,
|
||||
fileName,
|
||||
cert: cert
|
||||
cert
|
||||
};
|
||||
}
|
||||
|
||||
@ -88,7 +87,7 @@ export function setKey(fileName, key) {
|
||||
return {
|
||||
type: actions.SET_KEY,
|
||||
fileName,
|
||||
key: key
|
||||
key
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,13 @@
|
||||
workbox.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
import { skipWaiting, clientsClaim } from 'workbox-core';
|
||||
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
|
||||
import { NavigationRoute, registerRoute } from 'workbox-routing';
|
||||
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {
|
||||
skipWaiting();
|
||||
clientsClaim();
|
||||
|
||||
precacheAndRoute(self.__WB_MANIFEST, {
|
||||
ignoreUrlParametersMatching: [/.*/]
|
||||
});
|
||||
|
||||
const handler = createHandlerBoundToURL('/');
|
||||
registerRoute(new NavigationRoute(handler));
|
||||
|
@ -3,11 +3,6 @@ import { connect } from 'react-redux';
|
||||
const strictEqual = (a, b) => a === b;
|
||||
|
||||
export default (mapState, mapDispatch) =>
|
||||
connect(
|
||||
mapState,
|
||||
mapDispatch,
|
||||
null,
|
||||
{
|
||||
areStatePropsEqual: strictEqual
|
||||
}
|
||||
);
|
||||
connect(mapState, mapDispatch, null, {
|
||||
areStatePropsEqual: strictEqual
|
||||
});
|
||||
|
@ -8,10 +8,7 @@ export function normalizeChannel(channel) {
|
||||
return channel;
|
||||
}
|
||||
|
||||
return channel
|
||||
.split('#')
|
||||
.join('')
|
||||
.toLowerCase();
|
||||
return channel.split('#').join('').toLowerCase();
|
||||
}
|
||||
|
||||
export function isChannel(name) {
|
||||
@ -137,6 +134,9 @@ export function timestamp(date = new Date()) {
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
|
||||
const dateFmt = new Intl.DateTimeFormat(window.navigator.language);
|
||||
export const formatDate = dateFmt.format;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
@ -167,7 +167,7 @@ export function measureScrollBarWidth() {
|
||||
}
|
||||
|
||||
export function findIndex(arr, pred) {
|
||||
if (!arr) {
|
||||
if (!Array.isArray(arr) || typeof pred !== 'function') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -187,3 +187,17 @@ export function find(arr, pred) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function count(arr, pred) {
|
||||
if (!Array.isArray(arr) || typeof pred !== 'function') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let c = 0;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (pred(arr[i])) {
|
||||
c++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ const autolinker = new Autolinker({
|
||||
});
|
||||
|
||||
export default function linkify(text) {
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
let matches = autolinker.parseText(text);
|
||||
|
||||
if (matches.length === 0) {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import createHistory from 'history/createBrowserHistory';
|
||||
import history from 'history/browser';
|
||||
import UrlPattern from 'url-pattern';
|
||||
|
||||
const history = createHistory();
|
||||
|
||||
export const LOCATION_CHANGED = 'ROUTER_LOCATION_CHANGED';
|
||||
export const PUSH = 'ROUTER_PUSH';
|
||||
export const REPLACE = 'ROUTER_REPLACE';
|
||||
@ -71,7 +69,7 @@ function match(routes, location) {
|
||||
for (let j = 0; j < keys.length; j++) {
|
||||
params[keys[j]] = decodeURIComponent(params[keys[j]]);
|
||||
}
|
||||
return locationChanged(routes[i].name, params, decode(location));
|
||||
return locationChanged(routes[i].name, params, decode({ ...location }));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -97,7 +95,7 @@ export default function initRouter(routes, store) {
|
||||
matched = { location: {} };
|
||||
}
|
||||
|
||||
history.listen(location => {
|
||||
history.listen(({ location }) => {
|
||||
const nextMatch = match(patterns, location);
|
||||
if (
|
||||
nextMatch &&
|
||||
|
@ -1,4 +1,5 @@
|
||||
let width, height;
|
||||
let width;
|
||||
let height;
|
||||
const listeners = [];
|
||||
|
||||
function update() {
|
||||
|
@ -12,72 +12,78 @@
|
||||
"iOS >= 10.3"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.1.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.0.0",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-core": "^7.0.0-0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-loader": "^8.0.4",
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.8.3",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.8.3",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.9.0",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^25.5.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"brotli": "^1.3.1",
|
||||
"css-loader": "^1.0.1",
|
||||
"cssnano": "^4.1.7",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-config-prettier": "^3.1.0",
|
||||
"eslint-import-resolver-webpack": "^0.10.1",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.2",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"express": "^4.16.4",
|
||||
"express-http-proxy": "^1.5.0",
|
||||
"gulp": "4.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"css-loader": "^3.5.3",
|
||||
"cssnano": "^4.1.10",
|
||||
"del": "^5.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.1.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-import-resolver-webpack": "^0.12.1",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-http-proxy": "^1.6.0",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-util": "^3.0.8",
|
||||
"jest": "^23.6.0",
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"postcss-flexbugs-fixes": "^4.1.0",
|
||||
"jest": "^25.5.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"postcss-flexbugs-fixes": "^4.2.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.4.0",
|
||||
"prettier": "1.15.3",
|
||||
"react-test-renderer": "^16.7.0-alpha.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^1.1.0",
|
||||
"through2": "^3.0.0",
|
||||
"webpack": "^4.25.1",
|
||||
"webpack-dev-middleware": "^3.4.0",
|
||||
"webpack-hot-middleware": "^2.24.3",
|
||||
"webpack-plugin-hash-output": "^3.1.0",
|
||||
"workbox-webpack-plugin": "^3.6.3"
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "2.0.5",
|
||||
"react-test-renderer": "16.13.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^2.3.6",
|
||||
"through2": "^3.0.1",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-plugin-hash-output": "^3.2.1",
|
||||
"workbox-webpack-plugin": "^5.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"autolinker": "^1.7.1",
|
||||
"autolinker": "^3.14.1",
|
||||
"backo": "^1.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"fontfaceobserver": "^2.0.9",
|
||||
"formik": "^1.3.1",
|
||||
"history": "4.5.1",
|
||||
"hsluv": "^0.0.3",
|
||||
"immer": "^1.7.3",
|
||||
"js-cookie": "^2.1.4",
|
||||
"lodash": "^4.17.11",
|
||||
"react": "^16.7.0-alpha.0",
|
||||
"react-dom": "^16.7.0-alpha.0",
|
||||
"react-hot-loader": "^4.4.0",
|
||||
"react-redux": "^6.0.0-beta.2",
|
||||
"formik": "^2.1.4",
|
||||
"history": "^5.0.0-beta.8",
|
||||
"hsluv": "^0.1.0",
|
||||
"immer": "^6.0.3",
|
||||
"js-cookie": "^2.2.1",
|
||||
"lodash": "^4.17.15",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-hot-loader": "^4.12.20",
|
||||
"react-icons": "^3.7.0",
|
||||
"react-modal": "^3.11.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.2.2",
|
||||
"redux": "^4.0.1",
|
||||
"react-window": "^1.8.5",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"url-pattern": "^1.0.3"
|
||||
"url-pattern": "^1.0.3",
|
||||
"workbox-core": "^5.1.3",
|
||||
"workbox-precaching": "^5.1.3",
|
||||
"workbox-routing": "^5.1.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prettier": "prettier --write {.*,*.js,css/*.css,**/*.test.js}",
|
||||
@ -85,10 +91,9 @@
|
||||
"test": "jest",
|
||||
"test:verbose": "jest --verbose",
|
||||
"test:watch": "jest --watch",
|
||||
"gen:install": "GO111MODULE=off go get -u github.com/andyleap/gencode github.com/mailru/easyjson/... github.com/SlinSo/egon/cmd/egon",
|
||||
"gen:install": "GO111MODULE=off go get -u github.com/andyleap/gencode github.com/mailru/easyjson/...",
|
||||
"gen:binary": "gencode go -package storage -schema ../storage/storage.schema -unsafe",
|
||||
"gen:json": "easyjson -all -lower_camel_case -omit_empty ../server/json.go ../server/index_data.go && easyjson -lower_camel_case -omit_empty ../storage/user.go",
|
||||
"gen:template": "egon -s -m ../server"
|
||||
"gen:json": "easyjson -all -lower_camel_case -omit_empty ../server/json.go ../server/index_data.go && easyjson -lower_camel_case -omit_empty ../storage/user.go"
|
||||
},
|
||||
"jest": {
|
||||
"moduleNameMapper": {
|
||||
@ -96,6 +101,9 @@
|
||||
"^containers(.*)$": "<rootDir>/js/containers$1",
|
||||
"^state(.*)$": "<rootDir>/js/state$1",
|
||||
"^utils(.*)$": "<rootDir>/js/utils$1"
|
||||
}
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/?!(history)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
@ -17,5 +17,5 @@
|
||||
"background_color": "#f0f0f0",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#f0f0f0"
|
||||
"theme_color": "#222"
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ var cssnano = require('cssnano');
|
||||
var TerserPlugin = require('terser-webpack-plugin');
|
||||
var { InjectManifest } = require('workbox-webpack-plugin');
|
||||
var HashOutputPlugin = require('webpack-plugin-hash-output');
|
||||
var CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
@ -80,16 +81,21 @@ module.exports = {
|
||||
chunkFilename: '[name].[contenthash].css'
|
||||
}),
|
||||
new HashOutputPlugin(),
|
||||
new CopyPlugin(['public']),
|
||||
new InjectManifest({
|
||||
swSrc: './js/sw.js',
|
||||
importWorkboxFrom: 'local',
|
||||
globDirectory: './public',
|
||||
globPatterns: ['*', 'font/*.woff2'],
|
||||
additionalManifestEntries: [
|
||||
{
|
||||
url: '/',
|
||||
revision: '__INDEX_REVISON__'
|
||||
}
|
||||
],
|
||||
exclude: [
|
||||
/\.map$/,
|
||||
/^manifest.*\.js(?:on)?$/,
|
||||
/^boot.*\.js$/,
|
||||
/^runtime.*\.js$/
|
||||
/^runtime.*\.js$/,
|
||||
/\.txt$/
|
||||
]
|
||||
})
|
||||
],
|
||||
@ -98,9 +104,7 @@ module.exports = {
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
safari10: true
|
||||
},
|
||||
cache: true,
|
||||
parallel: true
|
||||
}
|
||||
})
|
||||
],
|
||||
splitChunks: {
|
||||
|
10262
client/yarn.lock
10262
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -12,9 +12,11 @@ import (
|
||||
|
||||
var (
|
||||
configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Use: "config [editor]",
|
||||
Short: "Edit config file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
editors = append(args, editors...)
|
||||
|
||||
if editor := findEditor(); editor != "" {
|
||||
process := exec.Command(editor, storage.Path.Config())
|
||||
process.Stdin = os.Stdin
|
||||
@ -27,7 +29,7 @@ var (
|
||||
},
|
||||
}
|
||||
|
||||
editors = []string{"nano", "notepad", "vi", "emacs"}
|
||||
editors = []string{"nano", "code", "vi", "emacs", "notepad"}
|
||||
)
|
||||
|
||||
func findEditor() string {
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
"runtime"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/khlieng/dispatch/assets"
|
||||
"github.com/khlieng/dispatch/config"
|
||||
"github.com/khlieng/dispatch/server"
|
||||
"github.com/khlieng/dispatch/storage"
|
||||
"github.com/khlieng/dispatch/storage/bleve"
|
||||
@ -29,6 +29,7 @@ const logo = `
|
||||
%s
|
||||
Commit: %s
|
||||
Build Date: %s
|
||||
Runtime: %s
|
||||
|
||||
`
|
||||
|
||||
@ -36,41 +37,25 @@ var rootCmd = &cobra.Command{
|
||||
Use: "dispatch",
|
||||
Short: "Web-based IRC client in Go.",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
if v, _ := cmd.Flags().GetBool("version"); v {
|
||||
if viper.GetBool("version") {
|
||||
printVersion()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cmd.Use == "dispatch" {
|
||||
fmt.Printf(logo, version.Tag, version.Commit, version.Date)
|
||||
if cmd == cmd.Root() {
|
||||
fmt.Printf(logo, version.Tag, version.Commit, version.Date, runtime.Version())
|
||||
}
|
||||
|
||||
storage.Initialize(viper.GetString("dir"))
|
||||
storage.Initialize(viper.GetString("dir"), viper.GetString("data"), viper.GetString("conf"))
|
||||
|
||||
initConfig(storage.Path.Config(), viper.GetBool("reset_config"))
|
||||
|
||||
viper.SetConfigName("config")
|
||||
viper.AddConfigPath(storage.Path.Root())
|
||||
viper.ReadInConfig()
|
||||
|
||||
viper.WatchConfig()
|
||||
|
||||
prev := time.Now()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
now := time.Now()
|
||||
// fsnotify sometimes fires twice
|
||||
if now.Sub(prev) > time.Second {
|
||||
log.Println("New config loaded")
|
||||
prev = now
|
||||
}
|
||||
})
|
||||
initConfig(storage.Path.Config(), viper.GetBool("reset-config"))
|
||||
},
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if viper.GetBool("dev") {
|
||||
log.Println("Running in development mode, access client at http://localhost:3000")
|
||||
}
|
||||
log.Println("Storing data at", storage.Path.Root())
|
||||
log.Println("Storing data at", storage.Path.DataRoot())
|
||||
|
||||
db, err := boltdb.New(storage.Path.Database())
|
||||
if err != nil {
|
||||
@ -78,19 +63,28 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
srv := server.Dispatch{
|
||||
Store: db,
|
||||
SessionStore: db,
|
||||
cfg, cfgUpdated := config.LoadConfig()
|
||||
dispatch := server.New(cfg)
|
||||
|
||||
GetMessageStore: func(user *storage.User) (storage.MessageStore, error) {
|
||||
return boltdb.New(storage.Path.Log(user.Username))
|
||||
},
|
||||
GetMessageSearchProvider: func(user *storage.User) (storage.MessageSearchProvider, error) {
|
||||
return bleve.New(storage.Path.Index(user.Username))
|
||||
},
|
||||
go func() {
|
||||
for {
|
||||
dispatch.SetConfig(<-cfgUpdated)
|
||||
log.Println("New config loaded")
|
||||
}
|
||||
}()
|
||||
|
||||
dispatch.Store = db
|
||||
dispatch.SessionStore = db
|
||||
|
||||
dispatch.GetMessageStore = func(user *storage.User) (storage.MessageStore, error) {
|
||||
return boltdb.New(storage.Path.Log(user.Username))
|
||||
}
|
||||
|
||||
srv.Run()
|
||||
dispatch.GetMessageSearchProvider = func(user *storage.User) (storage.MessageSearchProvider, error) {
|
||||
return bleve.New(storage.Path.Index(user.Username))
|
||||
}
|
||||
|
||||
dispatch.Run()
|
||||
},
|
||||
}
|
||||
|
||||
@ -103,6 +97,8 @@ func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
|
||||
rootCmd.PersistentFlags().String("data", storage.DefaultDirectory(), "directory to store data in")
|
||||
rootCmd.PersistentFlags().String("conf", storage.DefaultDirectory(), "directory to store configuration in")
|
||||
rootCmd.PersistentFlags().String("dir", storage.DefaultDirectory(), "directory to store config and data in")
|
||||
rootCmd.PersistentFlags().Bool("reset-config", false, "reset to the default configuration, overwriting the current one")
|
||||
rootCmd.Flags().StringP("address", "a", "", "interface to which the server will bind")
|
||||
@ -110,14 +106,11 @@ func init() {
|
||||
rootCmd.Flags().Bool("dev", false, "development mode")
|
||||
rootCmd.Flags().BoolP("version", "v", false, "show version")
|
||||
|
||||
viper.BindPFlag("dir", rootCmd.PersistentFlags().Lookup("dir"))
|
||||
viper.BindPFlag("reset_config", rootCmd.PersistentFlags().Lookup("reset-config"))
|
||||
viper.BindPFlag("address", rootCmd.Flags().Lookup("address"))
|
||||
viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
|
||||
viper.BindPFlag("dev", rootCmd.Flags().Lookup("dev"))
|
||||
viper.BindPFlags(rootCmd.PersistentFlags())
|
||||
viper.BindPFlags(rootCmd.Flags())
|
||||
|
||||
viper.SetDefault("hexIP", false)
|
||||
viper.SetDefault("verify_client_certificates", true)
|
||||
viper.SetDefault("verify_certificates", true)
|
||||
}
|
||||
|
||||
func initConfig(configPath string, overwrite bool) {
|
||||
|
@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/khlieng/dispatch/version"
|
||||
"github.com/spf13/cobra"
|
||||
@ -17,5 +18,5 @@ var versionCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
fmt.Printf("%s\nCommit: %s\nBuild Date: %s\n", version.Tag, version.Commit, version.Date)
|
||||
fmt.Printf("%s\nCommit: %s\nBuild Date: %s\nRuntime: %s\n", version.Tag, version.Commit, version.Date, runtime.Version())
|
||||
}
|
||||
|
@ -22,25 +22,19 @@ readonly = false
|
||||
show_details = false
|
||||
|
||||
[https]
|
||||
enabled = false
|
||||
enabled = true
|
||||
port = 443
|
||||
# Redirect all http traffic to https
|
||||
redirect = true
|
||||
# Path to your cert and private key if you are not using
|
||||
# the Let's Encrypt integration
|
||||
cert = ""
|
||||
key = ""
|
||||
|
||||
[letsencrypt]
|
||||
# Your domain or subdomain
|
||||
# Your domain or subdomain, if not set a certificate will be
|
||||
# fetched for whatever domain dispatch gets accessed through
|
||||
domain = ""
|
||||
# An email address lets you recover your accounts private key
|
||||
email = ""
|
||||
# The port Let's Encrypt listens on, comment this out to let it bind
|
||||
# to port 80 as needed, doing so means dispatch itself cannot use port 80
|
||||
port = 5001
|
||||
# Have dispatch proxy traffic from port 80 to the Let's Encrypt port
|
||||
proxy = true
|
||||
|
||||
# Not implemented
|
||||
[auth]
|
||||
@ -52,19 +46,19 @@ login = true
|
||||
# Enable username/password registration
|
||||
registration = true
|
||||
|
||||
[auth.github]
|
||||
[auth.providers.github]
|
||||
key = ""
|
||||
secret = ""
|
||||
|
||||
[auth.facebook]
|
||||
[auth.providers.facebook]
|
||||
key = ""
|
||||
secret = ""
|
||||
|
||||
[auth.google]
|
||||
[auth.providers.google]
|
||||
key = ""
|
||||
secret = ""
|
||||
|
||||
[auth.twitter]
|
||||
[auth.providers.twitter]
|
||||
key = ""
|
||||
secret = ""
|
||||
|
||||
@ -74,3 +68,7 @@ enabled = false
|
||||
max_age = 31536000
|
||||
include_subdomains = false
|
||||
preload = false
|
||||
|
||||
# Add your own HTTP headers to the index page
|
||||
[headers]
|
||||
# X-Example = "Rainbows"
|
||||
|
95
config/config.go
Normal file
95
config/config.go
Normal file
@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/khlieng/dispatch/storage"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Address string
|
||||
Port string
|
||||
Dev bool
|
||||
HexIP bool
|
||||
VerifyCertificates bool `mapstructure:"verify_certificates"`
|
||||
Headers map[string]string
|
||||
Defaults Defaults
|
||||
HTTPS HTTPS
|
||||
LetsEncrypt LetsEncrypt
|
||||
Auth Auth
|
||||
}
|
||||
|
||||
type Defaults struct {
|
||||
Name string
|
||||
Host string
|
||||
Port int
|
||||
Channels []string
|
||||
Password string
|
||||
SSL bool
|
||||
ReadOnly bool
|
||||
ShowDetails bool `mapstructure:"show_details"`
|
||||
}
|
||||
|
||||
type HTTPS struct {
|
||||
Enabled bool
|
||||
Port string
|
||||
Cert string
|
||||
Key string
|
||||
HSTS HSTS
|
||||
}
|
||||
|
||||
type HSTS struct {
|
||||
Enabled bool
|
||||
MaxAge string `mapstructure:"max_age"`
|
||||
IncludeSubdomains bool `mapstructure:"include_subdomains"`
|
||||
Preload bool
|
||||
}
|
||||
|
||||
type LetsEncrypt struct {
|
||||
Domain string
|
||||
Email string
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Anonymous bool
|
||||
Login bool
|
||||
Registration bool
|
||||
Providers map[string]Provider
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
Key string
|
||||
Secret string
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, chan *Config) {
|
||||
viper.SetConfigName("config")
|
||||
viper.AddConfigPath(storage.Path.ConfigRoot())
|
||||
viper.ReadInConfig()
|
||||
|
||||
config := &Config{}
|
||||
viper.Unmarshal(config)
|
||||
|
||||
viper.WatchConfig()
|
||||
|
||||
configCh := make(chan *Config, 1)
|
||||
|
||||
prev := time.Now()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
now := time.Now()
|
||||
// fsnotify sometimes fires twice
|
||||
if now.Sub(prev) > time.Second {
|
||||
config := &Config{}
|
||||
err := viper.Unmarshal(config)
|
||||
if err == nil {
|
||||
configCh <- config
|
||||
}
|
||||
|
||||
prev = now
|
||||
}
|
||||
})
|
||||
|
||||
return config, configCh
|
||||
}
|
85
go.mod
85
go.mod
@ -1,57 +1,50 @@
|
||||
module github.com/khlieng/dispatch
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/RoaringBitmap/roaring v0.4.16 // indirect
|
||||
github.com/blevesearch/bleve v0.0.0-20180525174403-1d6d47ed3ad9
|
||||
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v0.0.0-20141230013033-23a2c8e5cf1f // indirect
|
||||
github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f // indirect
|
||||
github.com/boltdb/bolt v0.0.0-20180302180052-fd01fc79c553
|
||||
github.com/couchbase/vellum v0.0.0-20181205055959-dd17fb852690 // indirect
|
||||
github.com/RoaringBitmap/roaring v0.4.23 // indirect
|
||||
github.com/blevesearch/bleve v1.0.7
|
||||
github.com/caddyserver/certmagic v0.10.12
|
||||
github.com/cenkalti/backoff/v4 v4.0.2 // indirect
|
||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
|
||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
|
||||
github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
|
||||
github.com/dsnet/compress v0.0.1
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd // indirect
|
||||
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493 // indirect
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmhodges/levigo v0.0.0-20161115193449-c42d9e0ca023 // indirect
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
|
||||
github.com/go-acme/lego/v3 v3.6.0 // indirect
|
||||
github.com/golang/protobuf v1.4.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/jmhodges/levigo v1.0.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/kjk/betterguid v0.0.0-20170621091430-c442874ba63a
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329
|
||||
github.com/miekg/dns v1.0.15 // indirect
|
||||
github.com/mitchellh/go-homedir v1.0.0
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/philhofer/fwd v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 // indirect
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/spf13/cast v1.3.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/viper v1.3.0
|
||||
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 // indirect
|
||||
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect
|
||||
github.com/tinylib/msgp v0.0.0-20180215042507-3b5c87ab5fb0 // indirect
|
||||
github.com/willf/bitset v1.1.9 // indirect
|
||||
github.com/xenolf/lego v1.2.1
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.2.1 // indirect
|
||||
github.com/klauspost/cpuid v1.2.3
|
||||
github.com/mailru/easyjson v0.7.2-0.20200424172602-f0a000e7a8e0
|
||||
github.com/miekg/dns v1.1.29 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.0 // indirect
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7 // indirect
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tdewolff/minify/v2 v2.7.4
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
|
||||
github.com/tinylib/msgp v1.1.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc // indirect
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect
|
||||
gopkg.in/ini.v1 v1.55.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.0 // indirect
|
||||
)
|
||||
|
717
go.sum
717
go.sum
@ -1,158 +1,749 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/RoaringBitmap/roaring v0.4.16 h1:NholfewybRLOwACgfqfzn/N5xa6keKNs4fP00t0cwLo=
|
||||
github.com/RoaringBitmap/roaring v0.4.16/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
github.com/RoaringBitmap/roaring v0.4.23 h1:gpyfd12QohbqhFO4NVDUdoPOCXsyahYRQhINmlHxKeo=
|
||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/blevesearch/bleve v0.0.0-20180525174403-1d6d47ed3ad9 h1:q25+axgzH1KX+j63v3yrkY1VHc6PkyTfpnzOmtAH154=
|
||||
github.com/blevesearch/bleve v0.0.0-20180525174403-1d6d47ed3ad9/go.mod h1:Y2lmIkzV6mcNfAnAdOd+ZxHkHchhBfU/xroGIp61wfw=
|
||||
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 h1:U6vnxZrTfItfiUiYx0lf/LgHjRSfaKK5QHSom3lEbnA=
|
||||
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ=
|
||||
github.com/blevesearch/go-porterstemmer v0.0.0-20141230013033-23a2c8e5cf1f h1:J9ZVHbB2X6JNxbKw/f3Y4E9Xq+Ro+zPiivzgmi3RTvg=
|
||||
github.com/blevesearch/go-porterstemmer v0.0.0-20141230013033-23a2c8e5cf1f/go.mod h1:haWQqFT3RdOGz7PJuM3or/pWNJS1pKkoZJWCkWu0DVA=
|
||||
github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f h1:kqbi9lqXLLs+zfWlgo1PIiRQ86n33K1JKotjj4rSYOg=
|
||||
github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f/go.mod h1:IInt5XRvpiGE09KOk9mmCMLjHhydIhNPKPPFLFBB7L8=
|
||||
github.com/boltdb/bolt v0.0.0-20180302180052-fd01fc79c553 h1:yvSJ8qbaWLeS7COhu2KJ0epn4mmc+aGeBP7Dpg7xQTY=
|
||||
github.com/boltdb/bolt v0.0.0-20180302180052-fd01fc79c553/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blevesearch/bleve v1.0.7 h1:4PspZE7XABMSKcVpzAKp0E05Yer1PIYmTWk+1ngNr/c=
|
||||
github.com/blevesearch/bleve v1.0.7/go.mod h1:3xvmBtaw12Y4C9iA1RTzwWCof5j5HjydjCTiDE2TeE0=
|
||||
github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040 h1:SjYVcfJVZoCfBlg+fkaq2eoZHTf5HaJfaTeTkOtyfHQ=
|
||||
github.com/blevesearch/blevex v0.0.0-20190916190636-152f0fe5c040/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/mmap-go v1.0.2 h1:JtMHb+FgQCTTYIhtMvimw15dJwu1Y5lrZDMOFXVWPk0=
|
||||
github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
|
||||
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/zap/v11 v11.0.7 h1:nnmAOP6eXBkqEa1Srq1eqA5Wmn4w+BZjLdjynNxvd+M=
|
||||
github.com/blevesearch/zap/v11 v11.0.7/go.mod h1:bJoY56fdU2m/IP4LLz/1h4jY2thBoREvoqbuJ8zhm9k=
|
||||
github.com/blevesearch/zap/v12 v12.0.7 h1:y8FWSAYkdc4p1dn4YLxNNr1dxXlSUsakJh2Fc/r6cj4=
|
||||
github.com/blevesearch/zap/v12 v12.0.7/go.mod h1:70DNK4ZN4tb42LubeDbfpp6xnm8g3ROYVvvZ6pEoXD8=
|
||||
github.com/caddyserver/certmagic v0.10.12 h1:aZtgzcIssiMSlP0jDdpDBbBzQ5INf5eKL9T6Nf3YzKM=
|
||||
github.com/caddyserver/certmagic v0.10.12/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs=
|
||||
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/couchbase/vellum v0.0.0-20181205055959-dd17fb852690 h1:fnU3vaWPeZsCWD292/lS4rXcYZUvxo196Kjoo6W92tk=
|
||||
github.com/couchbase/vellum v0.0.0-20181205055959-dd17fb852690/go.mod h1:prYTC8EgTu3gwbqJihkud9zRXISvyulAplQ6exdCo1g=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/couchbase/ghistogram v0.1.0 h1:b95QcQTCzjTUocDXp/uMgSNQi8oj1tGwnJ4bODWZnps=
|
||||
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
|
||||
github.com/couchbase/moss v0.1.0 h1:HCL+xxHUwmOaL44kMM/gU08OW6QGCui1WVFO58bjhNI=
|
||||
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||
github.com/couchbase/vellum v1.0.1 h1:qrj9ohvZedvc51S5KzPfJ6P6z0Vqzv7Lx7k3mVc2WOk=
|
||||
github.com/couchbase/vellum v1.0.1/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
|
||||
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
|
||||
github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8=
|
||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso=
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 h1:MZRmHqDBd0vxNwenEbKSQqRVT24d3C05ft8kduSwlqM=
|
||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 h1:eX+pdPPlD279OWgdx7f6KqIRSONuK7egk+jDx7OM3Ac=
|
||||
github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76/go.mod h1:KjxHHirfLaw19iGT70HvVjHQsL1vq1SRQB4yOsAfy2s=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd h1:r04MMPyLHj/QwZuMJ5+7tJcBr1AQjpiAK/rZWRrQT7o=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493 h1:OTanQnFt0bi5iLFSdbEVA/idR6Q2WhCm+deb7ir2CcM=
|
||||
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a h1:FQqoVvjbiUioBBFUL5up+h+GdCa/AnJsL/1bIs/veSI=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/go-acme/lego/v3 v3.4.0 h1:deB9NkelA+TfjGHVw8J7iKl/rMtffcGMWSMmptvMv0A=
|
||||
github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M=
|
||||
github.com/go-acme/lego/v3 v3.6.0 h1:Rv0MrX3DpVp9Xg77yR7x+PCksLLph3Ut/69/9Kim8ac=
|
||||
github.com/go-acme/lego/v3 v3.6.0/go.mod h1:sB/T7hfyz0HYIBvPmz/C8jIaxF6scbbiGKTzbQ22V6A=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jmhodges/levigo v0.0.0-20161115193449-c42d9e0ca023 h1:y5P5G9cANJZt3MXlMrgELo5mNLZPXH8aGFFFG7IzPU0=
|
||||
github.com/jmhodges/levigo v0.0.0-20161115193449-c42d9e0ca023/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
|
||||
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kjk/betterguid v0.0.0-20170621091430-c442874ba63a h1:b+Gt8sQs//Sl5Dcem5zP9Qc2FgEUAygREa2AAa2Vmcw=
|
||||
github.com/kjk/betterguid v0.0.0-20170621091430-c442874ba63a/go.mod h1:uxRAhHE1nl34DpWgfe0CYbNYbCnYplaB6rZH9ReWtUk=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kljensen/snowball v0.6.0 h1:6DZLCcZeL0cLfodx+Md4/OLC6b/bfurWUOUGs1ydfOU=
|
||||
github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
|
||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/miekg/dns v1.0.15 h1:9+UupePBQCG6zf1q/bGmTO1vumoG13jsrbWOSX1W6Tw=
|
||||
github.com/miekg/dns v1.0.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
|
||||
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mailru/easyjson v0.7.2-0.20200424172602-f0a000e7a8e0 h1:kBQYXw1PdcnwYP5hntk8LEDPdq++fubPN76BlfGLdIM=
|
||||
github.com/mailru/easyjson v0.7.2-0.20200424172602-f0a000e7a8e0/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.0 h1:iDwIio/3gk2QtLLEsqU5lInaMzos0hDTz8a6lazSFVw=
|
||||
github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw=
|
||||
github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 h1:/NRJ5vAYoqz+7sG51ubIDHXeWO8DlTSrToPu6q11ziA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7 h1:FUL3b97ZY2EPqg2NbXKuMHs5pXJB9hjj1fDHnF2vl28=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190512091148-babf20351dd7/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.0 h1:cO6QlTTeK9RQDhFAbGLV5e3fHXbRpin/Gi8qfL4rdLk=
|
||||
github.com/spf13/viper v1.3.0/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 h1:JNEGSiWg6D3lcBCMCBqN3ELniXujt+0QNHLhNnO0w3s=
|
||||
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
||||
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||
github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM=
|
||||
github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 h1:GnOzE5fEFN3b2zDhJJABEofdb51uMRNb8eqIVtdducs=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 h1:HOxvxvnntLiPn123Fk+twfUhCQdMDaqmb0cclArW0T0=
|
||||
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
|
||||
github.com/tinylib/msgp v0.0.0-20180215042507-3b5c87ab5fb0 h1:uAwzi+JwkDdOtQZVqPYljFvJr7i43ZgUYXKypk9Eibk=
|
||||
github.com/tinylib/msgp v0.0.0-20180215042507-3b5c87ab5fb0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tdewolff/minify/v2 v2.7.4 h1:r0OZQ3QzWeDS5cXq53Bk4IFIBDZ7fiXIkw1a4bHONsw=
|
||||
github.com/tdewolff/minify/v2 v2.7.4/go.mod h1:BkDSm8aMMT0ALGmpt7j3Ra7nLUgZL0qhyrAHXwxcy5w=
|
||||
github.com/tdewolff/parse/v2 v2.4.2 h1:Bu2Qv6wepkc+Ou7iB/qHjAhEImlAP5vedzlQRUdj3BI=
|
||||
github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
|
||||
github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/willf/bitset v1.1.9 h1:GBtFynGY9ZWZmEC9sWuu41/7VBXPFCOAbCbqTflOg9c=
|
||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xenolf/lego v1.2.1 h1:wAsBCIaTDlgYbR/yVuP0gzcnZrA94NVc84K6vfOIyyA=
|
||||
github.com/xenolf/lego v1.2.1/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
|
||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc h1:ZGI/fILM2+ueot/UixBSoj9188jCAxVHEZEGhqq67I4=
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
|
||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa h1:yMbJOvnfYkO1dSAviTu/ZguZWLBTXx4xE3LYrxUCCiA=
|
||||
golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/square/go-jose.v2 v2.2.1 h1:uRIz/V7RfMsMgGnCp+YybIdstDIz8wc0H283wHQfwic=
|
||||
gopkg.in/square/go-jose.v2 v2.2.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
|
||||
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
196
pkg/https/https.go
Normal file
196
pkg/https/https.go
Normal file
@ -0,0 +1,196 @@
|
||||
package https
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/khlieng/dispatch/pkg/netutil"
|
||||
"github.com/klauspost/cpuid"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Addr string
|
||||
PortHTTP string
|
||||
PortHTTPS string
|
||||
HTTPOnly bool
|
||||
|
||||
StoragePath string
|
||||
Domain string
|
||||
Email string
|
||||
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
|
||||
func Serve(handler http.Handler, cfg Config) error {
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
httpSrv := &http.Server{
|
||||
Addr: net.JoinHostPort(cfg.Addr, cfg.PortHTTP),
|
||||
}
|
||||
|
||||
if !cfg.HTTPOnly {
|
||||
httpSrv.ReadTimeout = 5 * time.Second
|
||||
httpSrv.WriteTimeout = 5 * time.Second
|
||||
|
||||
httpsSrv := &http.Server{
|
||||
Addr: net.JoinHostPort(cfg.Addr, cfg.PortHTTPS),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
redirect := HTTPSRedirect(cfg.PortHTTPS, handler)
|
||||
|
||||
if cfg.Cert != "" || cfg.Key != "" {
|
||||
httpSrv.Handler = redirect
|
||||
httpsSrv.TLSConfig = TLSConfig(nil)
|
||||
|
||||
go func() {
|
||||
errCh <- httpSrv.ListenAndServe()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
errCh <- httpsSrv.ListenAndServeTLS(cfg.Cert, cfg.Key)
|
||||
}()
|
||||
} else {
|
||||
if cfg.StoragePath != "" {
|
||||
certmagic.Default.Storage = &certmagic.FileStorage{
|
||||
Path: cfg.StoragePath,
|
||||
}
|
||||
}
|
||||
|
||||
certmagic.Default.MustStaple = true
|
||||
|
||||
magic := certmagic.NewDefault()
|
||||
|
||||
acme := certmagic.NewACMEManager(magic, certmagic.ACMEManager{
|
||||
Agreed: true,
|
||||
Email: cfg.Email,
|
||||
})
|
||||
|
||||
magic.Issuer = acme
|
||||
|
||||
domains := []string{cfg.Domain}
|
||||
if cfg.Domain == "" {
|
||||
domains = []string{}
|
||||
magic.OnDemand = maxObtain(3)
|
||||
}
|
||||
|
||||
err := magic.ManageSync(domains)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpSrv.Handler = acme.HTTPChallengeHandler(redirect)
|
||||
httpsSrv.TLSConfig = TLSConfig(magic.TLSConfig())
|
||||
|
||||
go func() {
|
||||
errCh <- httpSrv.ListenAndServe()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
errCh <- httpsSrv.ListenAndServeTLS("", "")
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
httpSrv.ReadTimeout = 5 * time.Second
|
||||
httpSrv.WriteTimeout = 10 * time.Second
|
||||
httpSrv.IdleTimeout = 120 * time.Second
|
||||
httpSrv.Handler = handler
|
||||
|
||||
return httpSrv.ListenAndServe()
|
||||
}
|
||||
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func HTTPSRedirect(portHTTPS string, fallback http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
host, _, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
host = r.Host
|
||||
}
|
||||
|
||||
if fallback != nil && netutil.IsPrivate(host) {
|
||||
fallback.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(host, portHTTPS),
|
||||
Path: r.RequestURI,
|
||||
}
|
||||
|
||||
w.Header().Set("Connection", "close")
|
||||
w.Header().Set("Location", u.String())
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
}
|
||||
}
|
||||
|
||||
func TLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
|
||||
tlsConfig.MinVersion = tls.VersionTLS12
|
||||
tlsConfig.CipherSuites = defaultCipherSuites()
|
||||
tlsConfig.CurvePreferences = []tls.CurveID{
|
||||
tls.X25519,
|
||||
tls.CurveP256,
|
||||
}
|
||||
tlsConfig.PreferServerCipherSuites = true
|
||||
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
func defaultCipherSuites() []uint16 {
|
||||
if cpuid.CPU.AesNi() {
|
||||
return []uint16{
|
||||
tls.TLS_FALLBACK_SCSV,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
}
|
||||
|
||||
return []uint16{
|
||||
tls.TLS_FALLBACK_SCSV,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
|
||||
}
|
||||
|
||||
func maxObtain(limit int) *certmagic.OnDemandConfig {
|
||||
requested := []string{}
|
||||
|
||||
return &certmagic.OnDemandConfig{
|
||||
DecisionFunc: func(name string) error {
|
||||
for _, n := range requested {
|
||||
if name == n {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(requested) == limit {
|
||||
return fmt.Errorf("OnDemand cert limit reached")
|
||||
}
|
||||
|
||||
requested = append(requested, name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
125
pkg/irc/case.go
Normal file
125
pkg/irc/case.go
Normal file
@ -0,0 +1,125 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
// ASCII maps a-z as the lower case of A-Z
|
||||
ASCII = "ascii"
|
||||
// RFC1459 maps a-z and {, |, }, ~ as the lower case of A-Z and [, \, ], ^
|
||||
RFC1459 = "rfc1459"
|
||||
// RFC1459Strict maps a-z and {, |, } as the lower case of A-Z and [, \, ]
|
||||
RFC1459Strict = "strict-rfc1459"
|
||||
)
|
||||
|
||||
func (c *Client) Casefold(s string) string {
|
||||
mapping := c.Features.String("CASEMAPPING")
|
||||
if mapping == "" {
|
||||
mapping = RFC1459
|
||||
}
|
||||
return Casefold(mapping, s)
|
||||
}
|
||||
|
||||
func (c *Client) EqualFold(s1, s2 string) bool {
|
||||
mapping := c.Features.String("CASEMAPPING")
|
||||
if mapping == "" {
|
||||
mapping = RFC1459
|
||||
}
|
||||
return EqualFold(mapping, s1, s2)
|
||||
}
|
||||
|
||||
func Casefold(mapping, s string) string {
|
||||
switch mapping {
|
||||
case ASCII:
|
||||
return toLower(s, 'Z')
|
||||
case RFC1459:
|
||||
return toLower(s, '^')
|
||||
case RFC1459Strict:
|
||||
return toLower(s, ']')
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func EqualFold(mapping, s1, s2 string) bool {
|
||||
switch mapping {
|
||||
case ASCII:
|
||||
return equalFold(s1, s2, 'Z')
|
||||
case RFC1459:
|
||||
return equalFold(s1, s2, '^')
|
||||
case RFC1459Strict:
|
||||
return equalFold(s1, s2, ']')
|
||||
}
|
||||
|
||||
return s1 == s2
|
||||
}
|
||||
|
||||
func toLower(s string, end byte) string {
|
||||
hasUpper := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if hasUpper = 'A' <= c && c <= end; hasUpper {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasUpper {
|
||||
return s
|
||||
}
|
||||
|
||||
b := make([]byte, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
|
||||
// Skip Unicode characters
|
||||
if c >= utf8.RuneSelf {
|
||||
_, size := utf8.DecodeRuneInString(s[i:])
|
||||
for cEnd := i + size; i < cEnd; i++ {
|
||||
b[i] = s[i]
|
||||
}
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
if 'A' <= c && c <= end {
|
||||
c += 32
|
||||
}
|
||||
b[i] = c
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func equalFold(s1, s2 string, end rune) bool {
|
||||
for s1 != "" && s2 != "" {
|
||||
var r1, r2 rune
|
||||
if s1[0] < utf8.RuneSelf {
|
||||
r1, s1 = rune(s1[0]), s1[1:]
|
||||
} else {
|
||||
r, size := utf8.DecodeRuneInString(s1)
|
||||
r1, s1 = r, s1[size:]
|
||||
}
|
||||
if s2[0] < utf8.RuneSelf {
|
||||
r2, s2 = rune(s2[0]), s2[1:]
|
||||
} else {
|
||||
r, size := utf8.DecodeRuneInString(s2)
|
||||
r2, s2 = r, s2[size:]
|
||||
}
|
||||
|
||||
if r1 == r2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if r2 < r1 {
|
||||
r2, r1 = r1, r2
|
||||
}
|
||||
|
||||
if 'A' <= r1 && r1 <= end && r2 == r1+32 {
|
||||
continue
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return s1 == s2
|
||||
}
|
27
pkg/irc/case_test.go
Normal file
27
pkg/irc/case_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCasefold(t *testing.T) {
|
||||
assert.Equal(t, "caላke[^", Casefold(ASCII, "CaላkE[^"))
|
||||
assert.Equal(t, "caላke{~", Casefold(RFC1459, "CaላkE[^"))
|
||||
assert.Equal(t, "caላke{^", Casefold(RFC1459Strict, "CaላkE[^"))
|
||||
}
|
||||
|
||||
func TestEqualFold(t *testing.T) {
|
||||
assert.True(t, EqualFold(ASCII, "caላke[^", "CaላkE[^"))
|
||||
assert.False(t, EqualFold(ASCII, "caላke{~", "CaላkE[^"))
|
||||
|
||||
assert.True(t, EqualFold(RFC1459, "caላke{~", "CaላkE[^"))
|
||||
assert.False(t, EqualFold(RFC1459, "cላke[^", "CaላkE[^"))
|
||||
|
||||
assert.True(t, EqualFold(RFC1459Strict, "caላke{^", "CaላkE[^"))
|
||||
assert.False(t, EqualFold(RFC1459Strict, "caላke[~", "CaላkE[^"))
|
||||
|
||||
assert.True(t, EqualFold(ASCII, "", ""))
|
||||
assert.False(t, EqualFold(ASCII, "", " "))
|
||||
}
|
@ -6,31 +6,33 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jpillora/backoff"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Server string
|
||||
Host string
|
||||
TLS bool
|
||||
TLSConfig *tls.Config
|
||||
Password string
|
||||
Username string
|
||||
Realname string
|
||||
Server string
|
||||
Host string
|
||||
TLS bool
|
||||
TLSConfig *tls.Config
|
||||
Password string
|
||||
Username string
|
||||
Realname string
|
||||
HandleNickInUse func(string) string
|
||||
|
||||
Messages chan *Message
|
||||
ConnectionChanged chan ConnectionState
|
||||
HandleNickInUse func(string) string
|
||||
|
||||
nick string
|
||||
channels []string
|
||||
Support *iSupport
|
||||
Features *Features
|
||||
nick string
|
||||
channels []string
|
||||
|
||||
conn net.Conn
|
||||
connected bool
|
||||
registered bool
|
||||
dialer *net.Dialer
|
||||
reader *bufio.Reader
|
||||
recvBuf []byte
|
||||
scan *bufio.Scanner
|
||||
backoff *backoff.Backoff
|
||||
out chan string
|
||||
|
||||
@ -43,7 +45,7 @@ type Client struct {
|
||||
func NewClient(nick, username string) *Client {
|
||||
return &Client{
|
||||
nick: nick,
|
||||
Support: newISupport(),
|
||||
Features: NewFeatures(),
|
||||
Username: username,
|
||||
Realname: nick,
|
||||
Messages: make(chan *Message, 32),
|
||||
@ -51,7 +53,10 @@ func NewClient(nick, username string) *Client {
|
||||
out: make(chan string, 32),
|
||||
quit: make(chan struct{}),
|
||||
reconnect: make(chan struct{}),
|
||||
recvBuf: make([]byte, 0, 4096),
|
||||
backoff: &backoff.Backoff{
|
||||
Min: 500 * time.Millisecond,
|
||||
Max: 30 * time.Second,
|
||||
Jitter: true,
|
||||
},
|
||||
}
|
||||
@ -117,6 +122,7 @@ func (c *Client) Join(channels ...string) {
|
||||
|
||||
func (c *Client) Part(channels ...string) {
|
||||
c.Write("PART " + strings.Join(channels, ","))
|
||||
c.removeChannels(channels...)
|
||||
}
|
||||
|
||||
func (c *Client) Topic(channel string, topic ...string) {
|
||||
@ -151,6 +157,10 @@ func (c *Client) Away(message string) {
|
||||
c.Write("AWAY :" + message)
|
||||
}
|
||||
|
||||
func (c *Client) List() {
|
||||
c.Write("LIST")
|
||||
}
|
||||
|
||||
func (c *Client) writePass(password string) {
|
||||
c.write("PASS " + password)
|
||||
}
|
||||
@ -177,6 +187,18 @@ func (c *Client) addChannel(channel string) {
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) removeChannels(channels ...string) {
|
||||
c.lock.Lock()
|
||||
for _, removeCh := range channels {
|
||||
for i, ch := range c.channels {
|
||||
if c.EqualFold(removeCh, ch) {
|
||||
c.channels = append(c.channels[:i], c.channels[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) flushChannels() {
|
||||
c.lock.Lock()
|
||||
if len(c.channels) > 0 {
|
||||
|
@ -162,6 +162,8 @@ func TestFlushChannels(t *testing.T) {
|
||||
c.flushChannels()
|
||||
assert.Equal(t, <-out, "JOIN #chan1\r\n")
|
||||
c.addChannel("#chan2")
|
||||
c.addChannel("#chan4")
|
||||
c.removeChannels("#chan4")
|
||||
c.addChannel("#chan3")
|
||||
c.flushChannels()
|
||||
assert.Equal(t, <-out, "JOIN #chan2,#chan3\r\n")
|
||||
|
@ -2,6 +2,7 @@ package irc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
@ -78,6 +79,7 @@ func (c *Client) run() {
|
||||
c.sendRecv.Wait()
|
||||
c.reconnect = make(chan struct{})
|
||||
|
||||
time.Sleep(c.backoff.Duration())
|
||||
c.tryConnect()
|
||||
}
|
||||
}
|
||||
@ -119,9 +121,6 @@ func (c *Client) tryConnect() {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.backoff.Reset()
|
||||
|
||||
c.flushChannels()
|
||||
return
|
||||
}
|
||||
|
||||
@ -151,7 +150,8 @@ func (c *Client) connect() error {
|
||||
|
||||
c.connected = true
|
||||
c.connChange(true, nil)
|
||||
c.reader = bufio.NewReader(c.conn)
|
||||
c.scan = bufio.NewScanner(c.conn)
|
||||
c.scan.Buffer(c.recvBuf, cap(c.recvBuf))
|
||||
|
||||
c.register()
|
||||
|
||||
@ -185,25 +185,24 @@ func (c *Client) recv() {
|
||||
defer c.sendRecv.Done()
|
||||
|
||||
for {
|
||||
line, err := c.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if !c.scan.Scan() {
|
||||
select {
|
||||
case <-c.quit:
|
||||
return
|
||||
|
||||
default:
|
||||
c.connChange(false, nil)
|
||||
|
||||
if !c.Registered() {
|
||||
time.Sleep(15 * time.Second)
|
||||
}
|
||||
|
||||
c.Reconnect()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg := parseMessage(line)
|
||||
b := bytes.Trim(c.scan.Bytes(), " ")
|
||||
if len(b) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
msg := ParseMessage(string(b))
|
||||
if msg == nil {
|
||||
close(c.quit)
|
||||
c.connChange(false, ErrBadProtocol)
|
||||
@ -215,23 +214,26 @@ func (c *Client) recv() {
|
||||
go c.write("PONG :" + msg.LastParam())
|
||||
|
||||
case Join:
|
||||
if msg.Nick == c.GetNick() {
|
||||
if c.EqualFold(msg.Nick, c.GetNick()) {
|
||||
c.addChannel(msg.Params[0])
|
||||
}
|
||||
|
||||
case Nick:
|
||||
if msg.Nick == c.GetNick() {
|
||||
if c.EqualFold(msg.Nick, c.GetNick()) {
|
||||
c.setNick(msg.LastParam())
|
||||
}
|
||||
|
||||
case ReplyWelcome:
|
||||
c.setNick(msg.Params[0])
|
||||
c.setRegistered(true)
|
||||
c.flushChannels()
|
||||
|
||||
c.backoff.Reset()
|
||||
c.sendRecv.Add(1)
|
||||
go c.send()
|
||||
|
||||
case ReplyISupport:
|
||||
c.Support.parse(msg.Params)
|
||||
c.Features.Parse(msg.Params)
|
||||
|
||||
case ErrNicknameInUse:
|
||||
if c.HandleNickInUse != nil {
|
||||
|
@ -129,19 +129,21 @@ func TestRecv(t *testing.T) {
|
||||
buf.WriteString("CMD\r\n")
|
||||
buf.WriteString("PING :test\r\n")
|
||||
buf.WriteString("001 foo\r\n")
|
||||
c.reader = bufio.NewReader(buf)
|
||||
c.scan = bufio.NewScanner(buf)
|
||||
|
||||
c.sendRecv.Add(1)
|
||||
go c.recv()
|
||||
|
||||
assert.Equal(t, "PONG :test\r\n", <-conn.hook)
|
||||
assert.Equal(t, &Message{Command: "CMD"}, <-c.Messages)
|
||||
assert.Equal(t, &Message{Command: Ping, Params: []string{"test"}}, <-c.Messages)
|
||||
assert.Equal(t, &Message{Command: ReplyWelcome, Params: []string{"foo"}}, <-c.Messages)
|
||||
}
|
||||
|
||||
func TestRecvTriggersReconnect(t *testing.T) {
|
||||
c := testClient()
|
||||
c.conn = &mockConn{}
|
||||
c.reader = bufio.NewReader(bytes.NewBufferString("001 bob\r\n"))
|
||||
c.scan = bufio.NewScanner(bytes.NewBufferString("001 bob\r\n"))
|
||||
done := make(chan struct{})
|
||||
ok := false
|
||||
go func() {
|
||||
@ -210,24 +212,32 @@ func waitConnAndClose(t *testing.T, c *Client) {
|
||||
}
|
||||
|
||||
var testCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
|
||||
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
|
||||
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
|
||||
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
|
||||
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
|
||||
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
|
||||
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
||||
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
|
||||
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
|
||||
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
|
||||
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
|
||||
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
|
||||
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
|
||||
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
|
||||
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
|
||||
fblo6RBxUQ==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var testKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
|
||||
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
|
||||
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
|
||||
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
|
||||
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
|
||||
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
|
||||
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
|
||||
MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9
|
||||
SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB
|
||||
l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB
|
||||
AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet
|
||||
3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb
|
||||
uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H
|
||||
qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp
|
||||
jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY
|
||||
fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U
|
||||
fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU
|
||||
y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX
|
||||
qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo
|
||||
f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
@ -28,6 +28,8 @@ const (
|
||||
ReplyWhoisIdle = "317"
|
||||
ReplyEndOfWhois = "318"
|
||||
ReplyWhoisChannels = "319"
|
||||
ReplyList = "322"
|
||||
ReplyListEnd = "323"
|
||||
ReplyNoTopic = "331"
|
||||
ReplyTopic = "332"
|
||||
ReplyNamReply = "353"
|
||||
@ -37,4 +39,5 @@ const (
|
||||
ReplyEndOfMotd = "376"
|
||||
ErrErroneousNickname = "432"
|
||||
ErrNicknameInUse = "433"
|
||||
ErrForward = "470"
|
||||
)
|
||||
|
134
pkg/irc/feature.go
Normal file
134
pkg/irc/feature.go
Normal file
@ -0,0 +1,134 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Features struct {
|
||||
m map[string]interface{}
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewFeatures() *Features {
|
||||
return &Features{
|
||||
m: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Features) Map() map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
f.lock.Lock()
|
||||
for k, v := range f.m {
|
||||
m[k] = v
|
||||
}
|
||||
f.lock.Unlock()
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *Features) Parse(params []string) {
|
||||
f.lock.Lock()
|
||||
for _, param := range params[1 : len(params)-1] {
|
||||
key, val := splitParam(param)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if key[0] == '-' {
|
||||
delete(f.m, key[1:])
|
||||
} else {
|
||||
if t, ok := featureTransforms[key]; ok {
|
||||
f.m[key] = t(val)
|
||||
} else {
|
||||
f.m[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.lock.Unlock()
|
||||
}
|
||||
|
||||
func (f *Features) Has(key string) bool {
|
||||
f.lock.Lock()
|
||||
_, has := f.m[key]
|
||||
f.lock.Unlock()
|
||||
return has
|
||||
}
|
||||
|
||||
func (f *Features) Get(key string) interface{} {
|
||||
f.lock.Lock()
|
||||
v := f.m[key]
|
||||
f.lock.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (f *Features) String(key string) string {
|
||||
if v, ok := f.Get(key).(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *Features) Int(key string) int {
|
||||
if v, ok := f.Get(key).(int); ok {
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type featureTransform func(interface{}) interface{}
|
||||
|
||||
func toInt(v interface{}) interface{} {
|
||||
s := v.(string)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(s)
|
||||
return i
|
||||
}
|
||||
|
||||
func toCharList(v interface{}) interface{} {
|
||||
s := v.(string)
|
||||
list := make([]string, len(s))
|
||||
for i := range s {
|
||||
list[i] = s[i : i+1]
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func parseChanlimit(v interface{}) interface{} {
|
||||
limits := map[string]int{}
|
||||
|
||||
pairs := strings.Split(v.(string), ",")
|
||||
for _, p := range pairs {
|
||||
pair := strings.Split(p, ":")
|
||||
|
||||
if len(pair) == 2 {
|
||||
prefixes := pair[0]
|
||||
limit, _ := strconv.Atoi(pair[1])
|
||||
|
||||
for i := range prefixes {
|
||||
limits[prefixes[i:i+1]] = limit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return limits
|
||||
}
|
||||
|
||||
var featureTransforms = map[string]featureTransform{
|
||||
"AWAYLEN": toInt,
|
||||
"CHANLIMIT": parseChanlimit,
|
||||
"CHANNELLEN": toInt,
|
||||
"CHANTYPES": toCharList,
|
||||
"HOSTLEN": toInt,
|
||||
"KICKLEN": toInt,
|
||||
"MAXCHANNELS": toInt,
|
||||
"MAXTARGETS": toInt,
|
||||
"MODES": toInt,
|
||||
"NICKLEN": toInt,
|
||||
"TOPICLEN": toInt,
|
||||
"USERLEN": toInt,
|
||||
}
|
49
pkg/irc/feature_test.go
Normal file
49
pkg/irc/feature_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseFeatures(t *testing.T) {
|
||||
s := NewFeatures()
|
||||
featureTransforms["CAKE"] = toInt
|
||||
s.Parse([]string{"bob", "CAKE=31", "PIE", ":durr"})
|
||||
assert.Equal(t, 31, s.Int("CAKE"))
|
||||
assert.Equal(t, "", s.String("CAKE"))
|
||||
assert.True(t, s.Has("CAKE"))
|
||||
assert.True(t, s.Has("PIE"))
|
||||
assert.False(t, s.Has("APPLES"))
|
||||
assert.Equal(t, "", s.String("APPLES"))
|
||||
assert.Equal(t, 0, s.Int("APPLES"))
|
||||
|
||||
s.Parse([]string{"bob", "-PIE", ":hurr"})
|
||||
assert.False(t, s.Has("PIE"))
|
||||
|
||||
s.Parse([]string{"bob", "CAKE=1337", ":durr"})
|
||||
assert.Equal(t, 1337, s.Int("CAKE"))
|
||||
|
||||
s.Parse([]string{"bob", "CAKE=", ":durr"})
|
||||
assert.Equal(t, "", s.String("CAKE"))
|
||||
assert.True(t, s.Has("CAKE"))
|
||||
|
||||
delete(featureTransforms, "CAKE")
|
||||
s.Parse([]string{"bob", "CAKE===", ":durr"})
|
||||
assert.Equal(t, "==", s.String("CAKE"))
|
||||
|
||||
s.Parse([]string{"bob", "-CAKE=31", ":durr"})
|
||||
assert.False(t, s.Has("CAKE"))
|
||||
|
||||
s.Parse([]string{"bob", "CHANLIMIT=#&:50", ":durr"})
|
||||
assert.Equal(t, map[string]int{"#": 50, "&": 50}, s.Get("CHANLIMIT"))
|
||||
|
||||
s.Parse([]string{"bob", "CHANLIMIT=#:50,&:25", ":durr"})
|
||||
assert.Equal(t, map[string]int{"#": 50, "&": 25}, s.Get("CHANLIMIT"))
|
||||
|
||||
s.Parse([]string{"bob", "CHANLIMIT=&:50,#:", ":durr"})
|
||||
assert.Equal(t, map[string]int{"#": 0, "&": 50}, s.Get("CHANLIMIT"))
|
||||
|
||||
s.Parse([]string{"bob", "CHANTYPES=#&", ":durr"})
|
||||
assert.Equal(t, []string{"#", "&"}, s.Get("CHANTYPES"))
|
||||
}
|
@ -2,9 +2,6 @@ package irc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
@ -22,8 +19,7 @@ func (m *Message) LastParam() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseMessage(line string) *Message {
|
||||
line = strings.Trim(line, "\r\n ")
|
||||
func ParseMessage(line string) *Message {
|
||||
msg := Message{}
|
||||
|
||||
if strings.HasPrefix(line, "@") {
|
||||
@ -35,21 +31,24 @@ func parseMessage(line string) *Message {
|
||||
|
||||
if len(tags) > 0 {
|
||||
msg.Tags = map[string]string{}
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
key, val := splitParam(tag)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
for _, tag := range tags {
|
||||
key, val := splitParam(tag)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if val != "" {
|
||||
msg.Tags[key] = unescapeTag(val)
|
||||
} else {
|
||||
msg.Tags[key] = ""
|
||||
if val != "" {
|
||||
msg.Tags[key] = unescapeTag(val)
|
||||
} else {
|
||||
msg.Tags[key] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for line[next+1] == ' ' {
|
||||
next++
|
||||
}
|
||||
line = line[next+1:]
|
||||
}
|
||||
|
||||
@ -73,7 +72,7 @@ func parseMessage(line string) *Message {
|
||||
|
||||
cmdEnd := len(line)
|
||||
trailing := ""
|
||||
if i := strings.Index(line, " :"); i > 0 {
|
||||
if i := strings.Index(line, " :"); i >= 0 {
|
||||
cmdEnd = i
|
||||
trailing = line[i+2:]
|
||||
}
|
||||
@ -94,55 +93,6 @@ func parseMessage(line string) *Message {
|
||||
return &msg
|
||||
}
|
||||
|
||||
type iSupport struct {
|
||||
support map[string]string
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func newISupport() *iSupport {
|
||||
return &iSupport{
|
||||
support: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (i *iSupport) parse(params []string) {
|
||||
i.lock.Lock()
|
||||
for _, param := range params[1 : len(params)-1] {
|
||||
key, val := splitParam(param)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if key[0] == '-' {
|
||||
delete(i.support, key[1:])
|
||||
} else {
|
||||
i.support[key] = val
|
||||
}
|
||||
}
|
||||
i.lock.Unlock()
|
||||
}
|
||||
|
||||
func (i *iSupport) Has(key string) bool {
|
||||
i.lock.Lock()
|
||||
_, has := i.support[key]
|
||||
i.lock.Unlock()
|
||||
return has
|
||||
}
|
||||
|
||||
func (i *iSupport) Get(key string) string {
|
||||
i.lock.Lock()
|
||||
v := i.support[key]
|
||||
i.lock.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (i *iSupport) GetInt(key string) int {
|
||||
i.lock.Lock()
|
||||
v := cast.ToInt(i.support[key])
|
||||
i.lock.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func splitParam(param string) (string, string) {
|
||||
parts := strings.SplitN(param, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
@ -151,11 +101,14 @@ func splitParam(param string) (string, string) {
|
||||
return parts[0], ""
|
||||
}
|
||||
|
||||
var unescapeTagReplacer = strings.NewReplacer(
|
||||
"\\:", ";",
|
||||
"\\s", " ",
|
||||
"\\\\", "\\",
|
||||
"\\r", "\r",
|
||||
"\\n", "\n",
|
||||
)
|
||||
|
||||
func unescapeTag(s string) string {
|
||||
s = strings.Replace(s, "\\:", ";", -1)
|
||||
s = strings.Replace(s, "\\s", " ", -1)
|
||||
s = strings.Replace(s, "\\\\", "\\", -1)
|
||||
s = strings.Replace(s, "\\r", "\r", -1)
|
||||
s = strings.Replace(s, "\\n", "\n", -1)
|
||||
return s
|
||||
return unescapeTagReplacer.Replace(s)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ func TestParseMessage(t *testing.T) {
|
||||
expected *Message
|
||||
}{
|
||||
{
|
||||
":user CMD #chan :some message\r\n",
|
||||
":user CMD #chan :some message",
|
||||
&Message{
|
||||
Prefix: "user",
|
||||
Nick: "user",
|
||||
@ -20,7 +20,7 @@ func TestParseMessage(t *testing.T) {
|
||||
Params: []string{"#chan", "some message"},
|
||||
},
|
||||
}, {
|
||||
":nick!user@host.com CMD a b\r\n",
|
||||
":nick!user@host.com CMD a b",
|
||||
&Message{
|
||||
Prefix: "nick!user@host.com",
|
||||
Nick: "nick",
|
||||
@ -28,80 +28,80 @@ func TestParseMessage(t *testing.T) {
|
||||
Params: []string{"a", "b"},
|
||||
},
|
||||
}, {
|
||||
"CMD a b :\r\n",
|
||||
"CMD a b :",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"a", "b", ""},
|
||||
},
|
||||
}, {
|
||||
"CMD a b\r\n",
|
||||
"CMD a b",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"a", "b"},
|
||||
},
|
||||
}, {
|
||||
"CMD\r\n",
|
||||
"CMD",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
},
|
||||
}, {
|
||||
"CMD :tests and stuff\r\n",
|
||||
"CMD :tests and stuff",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"tests and stuff"},
|
||||
},
|
||||
}, {
|
||||
":nick@host.com CMD\r\n",
|
||||
":nick@host.com CMD",
|
||||
&Message{
|
||||
Prefix: "nick@host.com",
|
||||
Nick: "nick",
|
||||
Command: "CMD",
|
||||
},
|
||||
}, {
|
||||
":ni@ck!user!name@host!.com CMD\r\n",
|
||||
":ni@ck!user!name@host!.com CMD",
|
||||
&Message{
|
||||
Prefix: "ni@ck!user!name@host!.com",
|
||||
Nick: "ni@ck",
|
||||
Command: "CMD",
|
||||
},
|
||||
}, {
|
||||
"CMD #cake pie \r\n",
|
||||
"CMD #cake pie ",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"#cake", "pie"},
|
||||
},
|
||||
}, {
|
||||
" CMD #cake pie\r\n",
|
||||
" CMD #cake pie",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"#cake", "pie"},
|
||||
},
|
||||
}, {
|
||||
"CMD #cake ::pie\r\n",
|
||||
"CMD #cake ::pie",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"#cake", ":pie"},
|
||||
},
|
||||
}, {
|
||||
"CMD #cake : pie\r\n",
|
||||
"CMD #cake : pie",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"#cake", " pie"},
|
||||
},
|
||||
}, {
|
||||
"CMD #cake :pie :P <3\r\n",
|
||||
"CMD #cake :pie :P <3",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"#cake", "pie :P <3"},
|
||||
},
|
||||
}, {
|
||||
"CMD #cake :pie!\r\n",
|
||||
"CMD #cake :pie!",
|
||||
&Message{
|
||||
Command: "CMD",
|
||||
Params: []string{"#cake", "pie!"},
|
||||
},
|
||||
}, {
|
||||
"@x=y CMD\r\n",
|
||||
"@x=y CMD",
|
||||
&Message{
|
||||
Tags: map[string]string{
|
||||
"x": "y",
|
||||
@ -109,7 +109,7 @@ func TestParseMessage(t *testing.T) {
|
||||
Command: "CMD",
|
||||
},
|
||||
}, {
|
||||
"@x=y :nick!user@host.com CMD\r\n",
|
||||
"@x=y :nick!user@host.com CMD",
|
||||
&Message{
|
||||
Tags: map[string]string{
|
||||
"x": "y",
|
||||
@ -119,7 +119,7 @@ func TestParseMessage(t *testing.T) {
|
||||
Command: "CMD",
|
||||
},
|
||||
}, {
|
||||
"@x=y :nick!user@host.com CMD :pie and cake\r\n",
|
||||
"@x=y :nick!user@host.com CMD :pie and cake",
|
||||
&Message{
|
||||
Tags: map[string]string{
|
||||
"x": "y",
|
||||
@ -130,7 +130,19 @@ func TestParseMessage(t *testing.T) {
|
||||
Params: []string{"pie and cake"},
|
||||
},
|
||||
}, {
|
||||
"@x=y;a=b CMD\r\n",
|
||||
"@x=y :nick!user@host.com CMD beans rainbows :pie and cake",
|
||||
&Message{
|
||||
Tags: map[string]string{
|
||||
"x": "y",
|
||||
},
|
||||
Prefix: "nick!user@host.com",
|
||||
Nick: "nick",
|
||||
Command: "CMD",
|
||||
Params: []string{"beans", "rainbows", "pie and cake"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"@x=y;a=b CMD",
|
||||
&Message{
|
||||
Tags: map[string]string{
|
||||
"x": "y",
|
||||
@ -139,7 +151,7 @@ func TestParseMessage(t *testing.T) {
|
||||
Command: "CMD",
|
||||
},
|
||||
}, {
|
||||
"@x=y;a=\\\\\\:\\s\\r\\n CMD\r\n",
|
||||
"@x=y;a=\\\\\\:\\s\\r\\n CMD",
|
||||
&Message{
|
||||
Tags: map[string]string{
|
||||
"x": "y",
|
||||
@ -151,49 +163,27 @@ func TestParseMessage(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
assert.Equal(t, tc.expected, parseMessage(tc.input))
|
||||
assert.Equal(t, tc.expected, ParseMessage(tc.input))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseMessage(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseMessage("@x=y :nick!user@host.com CMD beans rainbows :pie and cake")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastParam(t *testing.T) {
|
||||
assert.Equal(t, "some message", parseMessage(":user CMD #chan :some message\r\n").LastParam())
|
||||
assert.Equal(t, "", parseMessage("NO_PARAMS").LastParam())
|
||||
assert.Equal(t, "some message", ParseMessage(":user CMD #chan :some message").LastParam())
|
||||
assert.Equal(t, "", ParseMessage("NO_PARAMS").LastParam())
|
||||
}
|
||||
|
||||
func TestBadMessagePanic(t *testing.T) {
|
||||
parseMessage("@\r\n")
|
||||
parseMessage("@ :\r\n")
|
||||
parseMessage("@ :\r\n")
|
||||
parseMessage(":user\r\n")
|
||||
parseMessage(":\r\n")
|
||||
parseMessage(":")
|
||||
parseMessage("")
|
||||
}
|
||||
|
||||
func TestParseISupport(t *testing.T) {
|
||||
s := newISupport()
|
||||
s.parse([]string{"bob", "CAKE=31", "PIE", ":durr"})
|
||||
assert.Equal(t, 31, s.GetInt("CAKE"))
|
||||
assert.Equal(t, "31", s.Get("CAKE"))
|
||||
assert.True(t, s.Has("CAKE"))
|
||||
assert.True(t, s.Has("PIE"))
|
||||
assert.False(t, s.Has("APPLES"))
|
||||
assert.Equal(t, "", s.Get("APPLES"))
|
||||
assert.Equal(t, 0, s.GetInt("APPLES"))
|
||||
|
||||
s.parse([]string{"bob", "-PIE", ":hurr"})
|
||||
assert.False(t, s.Has("PIE"))
|
||||
|
||||
s.parse([]string{"bob", "CAKE=1337", ":durr"})
|
||||
assert.Equal(t, 1337, s.GetInt("CAKE"))
|
||||
|
||||
s.parse([]string{"bob", "CAKE=", ":durr"})
|
||||
assert.Equal(t, "", s.Get("CAKE"))
|
||||
assert.True(t, s.Has("CAKE"))
|
||||
|
||||
s.parse([]string{"bob", "CAKE===", ":durr"})
|
||||
assert.Equal(t, "==", s.Get("CAKE"))
|
||||
|
||||
s.parse([]string{"bob", "-CAKE=31", ":durr"})
|
||||
assert.False(t, s.Has("CAKE"))
|
||||
func TestBadMessage(t *testing.T) {
|
||||
assert.Nil(t, ParseMessage("@"))
|
||||
assert.Nil(t, ParseMessage("@ :"))
|
||||
assert.Nil(t, ParseMessage("@ :"))
|
||||
assert.Nil(t, ParseMessage("@ :"))
|
||||
assert.Nil(t, ParseMessage(":user"))
|
||||
assert.Nil(t, ParseMessage(":"))
|
||||
assert.Nil(t, ParseMessage(""))
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package letsencrypt
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Directory string
|
||||
|
||||
func (d Directory) Domain(domain string) string {
|
||||
return filepath.Join(string(d), "certs", domain)
|
||||
}
|
||||
|
||||
func (d Directory) Cert(domain string) string {
|
||||
return filepath.Join(d.Domain(domain), "cert.pem")
|
||||
}
|
||||
|
||||
func (d Directory) Key(domain string) string {
|
||||
return filepath.Join(d.Domain(domain), "key.pem")
|
||||
}
|
||||
|
||||
func (d Directory) Meta(domain string) string {
|
||||
return filepath.Join(d.Domain(domain), "metadata.json")
|
||||
}
|
||||
|
||||
func (d Directory) User(email string) string {
|
||||
if email == "" {
|
||||
email = defaultUser
|
||||
}
|
||||
return filepath.Join(string(d), "users", email)
|
||||
}
|
||||
|
||||
func (d Directory) UserRegistration(email string) string {
|
||||
return filepath.Join(d.User(email), "registration.json")
|
||||
}
|
||||
|
||||
func (d Directory) UserKey(email string) string {
|
||||
return filepath.Join(d.User(email), "key.pem")
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
package letsencrypt
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
const URL = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
const KeySize = 2048
|
||||
|
||||
var directory Directory
|
||||
|
||||
func Run(dir, domain, email, port string) (*state, error) {
|
||||
directory = Directory(dir)
|
||||
|
||||
user, err := getUser(email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := acme.NewClient(URL, &user, acme.RSA2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.SetHTTPAddress(port)
|
||||
|
||||
if user.Registration == nil {
|
||||
user.Registration, err = client.Register(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = saveUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s := &state{
|
||||
client: client,
|
||||
domain: domain,
|
||||
}
|
||||
|
||||
if certExists(domain) {
|
||||
if !s.renew() {
|
||||
err = s.loadCert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s.refreshOCSP()
|
||||
} else {
|
||||
err = s.obtain()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
go s.maintain()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type state struct {
|
||||
client *acme.Client
|
||||
domain string
|
||||
cert *tls.Certificate
|
||||
certPEM []byte
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (s *state) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
s.lock.Lock()
|
||||
cert := s.cert
|
||||
s.lock.Unlock()
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (s *state) getCertPEM() []byte {
|
||||
s.lock.Lock()
|
||||
certPEM := s.certPEM
|
||||
s.lock.Unlock()
|
||||
|
||||
return certPEM
|
||||
}
|
||||
|
||||
func (s *state) setCert(meta *acme.CertificateResource) {
|
||||
cert, err := tls.X509KeyPair(meta.Certificate, meta.PrivateKey)
|
||||
if err == nil {
|
||||
s.lock.Lock()
|
||||
if s.cert != nil {
|
||||
cert.OCSPStaple = s.cert.OCSPStaple
|
||||
}
|
||||
|
||||
s.cert = &cert
|
||||
s.certPEM = meta.Certificate
|
||||
s.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) setOCSP(ocsp []byte) {
|
||||
cert := tls.Certificate{
|
||||
OCSPStaple: ocsp,
|
||||
}
|
||||
|
||||
s.lock.Lock()
|
||||
if s.cert != nil {
|
||||
cert.Certificate = s.cert.Certificate
|
||||
cert.PrivateKey = s.cert.PrivateKey
|
||||
}
|
||||
s.cert = &cert
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) obtain() error {
|
||||
cert, err := s.client.ObtainCertificate([]string{s.domain}, true, nil, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.setCert(cert)
|
||||
s.refreshOCSP()
|
||||
|
||||
err = saveCert(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *state) renew() bool {
|
||||
cert, err := ioutil.ReadFile(directory.Cert(s.domain))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
exp, err := acme.GetPEMCertExpiration(cert)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
daysLeft := int(exp.Sub(time.Now().UTC()).Hours() / 24)
|
||||
|
||||
if daysLeft <= 30 {
|
||||
metaBytes, err := ioutil.ReadFile(directory.Meta(s.domain))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
key, err := ioutil.ReadFile(directory.Key(s.domain))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var meta acme.CertificateResource
|
||||
err = json.Unmarshal(metaBytes, &meta)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
meta.Certificate = cert
|
||||
meta.PrivateKey = key
|
||||
|
||||
newMeta, err := s.client.RenewCertificate(meta, true, false)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
s.setCert(newMeta)
|
||||
|
||||
err = saveCert(newMeta)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) refreshOCSP() {
|
||||
ocsp, resp, err := acme.GetOCSPForCert(s.getCertPEM())
|
||||
if err == nil && resp.Status == acme.OCSPGood {
|
||||
s.setOCSP(ocsp)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) maintain() {
|
||||
renew := time.Tick(24 * time.Hour)
|
||||
ocsp := time.Tick(1 * time.Hour)
|
||||
for {
|
||||
select {
|
||||
case <-renew:
|
||||
s.renew()
|
||||
|
||||
case <-ocsp:
|
||||
s.refreshOCSP()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) loadCert() error {
|
||||
cert, err := ioutil.ReadFile(directory.Cert(s.domain))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := ioutil.ReadFile(directory.Key(s.domain))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.setCert(&acme.CertificateResource{
|
||||
Certificate: cert,
|
||||
PrivateKey: key,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func certExists(domain string) bool {
|
||||
if _, err := os.Stat(directory.Cert(domain)); err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err := os.Stat(directory.Key(domain)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func saveCert(cert *acme.CertificateResource) error {
|
||||
err := os.MkdirAll(directory.Domain(cert.Domain), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(directory.Cert(cert.Domain), cert.Certificate, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(directory.Key(cert.Domain), cert.PrivateKey, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(&cert, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(directory.Meta(cert.Domain), jsonBytes, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
package letsencrypt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
const defaultUser = "default"
|
||||
|
||||
type User struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u User) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
|
||||
func (u User) GetRegistration() *acme.RegistrationResource {
|
||||
return u.Registration
|
||||
}
|
||||
|
||||
func (u User) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
}
|
||||
|
||||
func newUser(email string) (User, error) {
|
||||
var err error
|
||||
user := User{Email: email}
|
||||
user.key, err = rsa.GenerateKey(rand.Reader, KeySize)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func getUser(email string) (User, error) {
|
||||
var user User
|
||||
|
||||
reg, err := os.Open(directory.UserRegistration(email))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return newUser(email)
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
defer reg.Close()
|
||||
|
||||
err = json.NewDecoder(reg).Decode(&user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
user.key, err = loadRSAPrivateKey(directory.UserKey(email))
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func saveUser(user User) error {
|
||||
err := os.MkdirAll(directory.User(user.Email), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = saveRSAPrivateKey(user.key, directory.UserKey(user.Email))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(&user, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(directory.UserRegistration(user.Email), jsonBytes, 0600)
|
||||
}
|
||||
|
||||
func loadRSAPrivateKey(file string) (crypto.PrivateKey, error) {
|
||||
keyBytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBlock, _ := pem.Decode(keyBytes)
|
||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
}
|
||||
|
||||
func saveRSAPrivateKey(key crypto.PrivateKey, file string) error {
|
||||
pemKey := pem.Block{
|
||||
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey)),
|
||||
}
|
||||
keyOut, err := os.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer keyOut.Close()
|
||||
return pem.Encode(keyOut, &pemKey)
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package letsencrypt
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func tempdir() string {
|
||||
f, _ := ioutil.TempDir("", "")
|
||||
return f
|
||||
}
|
||||
|
||||
func testUser(t *testing.T, email string) {
|
||||
user, err := newUser(email)
|
||||
assert.Nil(t, err)
|
||||
key := user.GetPrivateKey()
|
||||
assert.NotNil(t, key)
|
||||
|
||||
err = saveUser(user)
|
||||
assert.Nil(t, err)
|
||||
|
||||
user, err = getUser(email)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, email, user.GetEmail())
|
||||
assert.Equal(t, key, user.GetPrivateKey())
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
directory = Directory(tempdir())
|
||||
|
||||
testUser(t, "test@test.com")
|
||||
testUser(t, "")
|
||||
}
|
40
pkg/netutil/netutil.go
Normal file
40
pkg/netutil/netutil.go
Normal file
@ -0,0 +1,40 @@
|
||||
package netutil
|
||||
|
||||
import "net"
|
||||
|
||||
var privateNets []*net.IPNet
|
||||
|
||||
func init() {
|
||||
for _, cidr := range []string{
|
||||
"127.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"::1/128",
|
||||
"fe80::/10",
|
||||
"fc00::/7",
|
||||
} {
|
||||
_, network, _ := net.ParseCIDR(cidr)
|
||||
privateNets = append(privateNets, network)
|
||||
}
|
||||
}
|
||||
|
||||
func IsPrivate(host string) bool {
|
||||
if host == "localhost" {
|
||||
return true
|
||||
}
|
||||
return IsPrivateIP(net.ParseIP(host))
|
||||
}
|
||||
|
||||
func IsPrivateIP(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, privateNet := range privateNets {
|
||||
if privateNet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Generated by egon.
|
||||
// 🚫Edit at your own risk.
|
||||
|
||||
package server
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func IndexTemplate(w io.Writer, cssPath string, inlineScript string, scripts []string) error {
|
||||
io.WriteString(w, "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"theme-color\" content=\"#f0f0f0\"><title>Dispatch</title><meta name=\"description\" content=\"Web-based IRC client.\"><link rel=\"preload\" href=\"/init\" as=\"fetch\" crossorigin><script>")
|
||||
io.WriteString(w, inlineScript )
|
||||
io.WriteString(w, "</script><link rel=\"preload\" href=\"/font/fontello.woff2?48901973\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"preload\" href=\"/font/RobotoMono-Regular.woff2\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"preload\" href=\"/font/Montserrat-Regular.woff2\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"preload\" href=\"/font/Montserrat-Bold.woff2\" as=\"font\" type=\"font/woff2\" crossorigin><link rel=\"preload\" href=\"/font/RobotoMono-Bold.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>")
|
||||
if cssPath != "" {
|
||||
io.WriteString(w, "<link href=\"/")
|
||||
io.WriteString(w, cssPath )
|
||||
io.WriteString(w, "\" rel=\"stylesheet\">")
|
||||
}
|
||||
io.WriteString(w, "<link rel=\"manifest\" href=\"/manifest.json\"></head><body><div id=\"root\"></div>")
|
||||
for _, script := range scripts {
|
||||
io.WriteString(w, "<script src=\"/")
|
||||
io.WriteString(w, script )
|
||||
io.WriteString(w, "\"></script>")
|
||||
}
|
||||
io.WriteString(w, "<noscript>This page needs JavaScript enabled to function.</noscript></body></html>")
|
||||
return nil
|
||||
}
|
@ -1,43 +1,48 @@
|
||||
<%! cssPath string, inlineScript string, scripts []string %>
|
||||
package server
|
||||
|
||||
type indexTemplateData struct {
|
||||
InlineScript string
|
||||
Stylesheet string
|
||||
Scripts []string
|
||||
}
|
||||
|
||||
const indexTemplate = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#f0f0f0">
|
||||
<meta name="theme-color" content="#222">
|
||||
|
||||
<title>Dispatch</title>
|
||||
<meta name="description" content="Web-based IRC client.">
|
||||
|
||||
<link rel="preload" href="/init" as="fetch" crossorigin>
|
||||
|
||||
<script>
|
||||
<%== inlineScript %>
|
||||
</script>
|
||||
{{if .InlineScript}}
|
||||
<script>{{.InlineScript}}</script>
|
||||
{{end}}
|
||||
|
||||
{{range .Scripts}}
|
||||
<script src="{{.}}" defer></script>
|
||||
{{end}}
|
||||
|
||||
<link rel="preload" href="/font/fontello.woff2?48901973" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/font/RobotoMono-Regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/font/Montserrat-Regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/font/Montserrat-Bold.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/font/RobotoMono-Bold.woff2" as="font" type="font/woff2" crossorigin>
|
||||
|
||||
<% if cssPath != "" { %>
|
||||
<link href="/<%== cssPath %>" rel="stylesheet">
|
||||
<% } %>
|
||||
{{if .Stylesheet}}
|
||||
<link href="{{.Stylesheet}}" rel="stylesheet">
|
||||
{{end}}
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<% for _, script := range scripts { %>
|
||||
<script src="/<%== script %>"></script>
|
||||
<% } %>
|
||||
|
||||
<noscript>This page needs JavaScript enabled to function.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>`
|
@ -5,9 +5,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/khlieng/dispatch/config"
|
||||
"github.com/khlieng/dispatch/storage"
|
||||
"github.com/khlieng/dispatch/version"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type connectDefaults struct {
|
||||
@ -28,7 +28,7 @@ type dispatchVersion struct {
|
||||
}
|
||||
|
||||
type indexData struct {
|
||||
Defaults connectDefaults
|
||||
Defaults *config.Defaults
|
||||
Servers []Server
|
||||
Channels []*storage.Channel
|
||||
HexIP bool
|
||||
@ -43,9 +43,12 @@ type indexData struct {
|
||||
Messages *Messages
|
||||
}
|
||||
|
||||
func getIndexData(r *http.Request, path string, state *State) *indexData {
|
||||
func (d *Dispatch) getIndexData(r *http.Request, path string, state *State) *indexData {
|
||||
cfg := d.Config()
|
||||
|
||||
data := indexData{
|
||||
HexIP: viper.GetBool("hexIP"),
|
||||
Defaults: &cfg.Defaults,
|
||||
HexIP: cfg.HexIP,
|
||||
Version: dispatchVersion{
|
||||
Tag: version.Tag,
|
||||
Commit: version.Commit,
|
||||
@ -53,15 +56,8 @@ func getIndexData(r *http.Request, path string, state *State) *indexData {
|
||||
},
|
||||
}
|
||||
|
||||
data.Defaults = connectDefaults{
|
||||
Name: viper.GetString("defaults.name"),
|
||||
Host: viper.GetString("defaults.host"),
|
||||
Port: viper.GetInt("defaults.port"),
|
||||
Channels: viper.GetStringSlice("defaults.channels"),
|
||||
Password: viper.GetString("defaults.password") != "",
|
||||
SSL: viper.GetBool("defaults.ssl"),
|
||||
ReadOnly: viper.GetBool("defaults.readonly"),
|
||||
ShowDetails: viper.GetBool("defaults.show_details"),
|
||||
if data.Defaults.Password != "" {
|
||||
data.Defaults.Password = "******"
|
||||
}
|
||||
|
||||
if state == nil {
|
||||
@ -81,10 +77,16 @@ func getIndexData(r *http.Request, path string, state *State) *indexData {
|
||||
server.Username = ""
|
||||
server.Realname = ""
|
||||
|
||||
data.Servers = append(data.Servers, Server{
|
||||
s := Server{
|
||||
Server: server,
|
||||
Status: newConnectionUpdate(server.Host, connections[server.Host]),
|
||||
})
|
||||
}
|
||||
|
||||
if i, ok := state.irc[server.Host]; ok {
|
||||
s.Features = i.Features.Map()
|
||||
}
|
||||
|
||||
data.Servers = append(data.Servers, s)
|
||||
}
|
||||
|
||||
channels, err := state.user.GetChannels()
|
||||
|
@ -4,6 +4,7 @@ package server
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
config "github.com/khlieng/dispatch/config"
|
||||
storage "github.com/khlieng/dispatch/storage"
|
||||
easyjson "github.com/mailru/easyjson"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
@ -29,7 +30,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
@ -38,8 +39,14 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
|
||||
}
|
||||
switch key {
|
||||
case "defaults":
|
||||
if data := in.Raw(); in.Ok() {
|
||||
in.AddError((out.Defaults).UnmarshalJSON(data))
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
out.Defaults = nil
|
||||
} else {
|
||||
if out.Defaults == nil {
|
||||
out.Defaults = new(config.Defaults)
|
||||
}
|
||||
easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in, out.Defaults)
|
||||
}
|
||||
case "servers":
|
||||
if in.IsNull() {
|
||||
@ -49,7 +56,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
|
||||
in.Delim('[')
|
||||
if out.Servers == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.Servers = make([]Server, 0, 1)
|
||||
out.Servers = make([]Server, 0, 0)
|
||||
} else {
|
||||
out.Servers = []Server{}
|
||||
}
|
||||
@ -90,7 +97,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
|
||||
if v2 == nil {
|
||||
v2 = new(storage.Channel)
|
||||
}
|
||||
easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in, &*v2)
|
||||
easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in, v2)
|
||||
}
|
||||
out.Channels = append(out.Channels, v2)
|
||||
in.WantComma()
|
||||
@ -153,15 +160,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
if true {
|
||||
if in.Defaults != nil {
|
||||
const prefix string = ",\"defaults\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Raw((in.Defaults).MarshalJSON())
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
easyjson7e607aefEncodeGithubComKhliengDispatchConfig(out, *in.Defaults)
|
||||
}
|
||||
if len(in.Servers) != 0 {
|
||||
const prefix string = ",\"servers\":"
|
||||
@ -292,7 +295,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in *jlexer.Lexer, out
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
@ -322,12 +325,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out *jwriter.Writer,
|
||||
_ = first
|
||||
if in.Server != "" {
|
||||
const prefix string = ",\"server\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
out.String(string(in.Server))
|
||||
}
|
||||
if in.Name != "" {
|
||||
@ -352,6 +351,163 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out *jwriter.Writer,
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
func easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in *jlexer.Lexer, out *config.Defaults) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "name":
|
||||
out.Name = string(in.String())
|
||||
case "host":
|
||||
out.Host = string(in.String())
|
||||
case "port":
|
||||
out.Port = int(in.Int())
|
||||
case "channels":
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
out.Channels = nil
|
||||
} else {
|
||||
in.Delim('[')
|
||||
if out.Channels == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.Channels = make([]string, 0, 4)
|
||||
} else {
|
||||
out.Channels = []string{}
|
||||
}
|
||||
} else {
|
||||
out.Channels = (out.Channels)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v7 string
|
||||
v7 = string(in.String())
|
||||
out.Channels = append(out.Channels, v7)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
}
|
||||
case "password":
|
||||
out.Password = string(in.String())
|
||||
case "ssl":
|
||||
out.SSL = bool(in.Bool())
|
||||
case "readOnly":
|
||||
out.ReadOnly = bool(in.Bool())
|
||||
case "showDetails":
|
||||
out.ShowDetails = bool(in.Bool())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjson7e607aefEncodeGithubComKhliengDispatchConfig(out *jwriter.Writer, in config.Defaults) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
if in.Name != "" {
|
||||
const prefix string = ",\"name\":"
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
out.String(string(in.Name))
|
||||
}
|
||||
if in.Host != "" {
|
||||
const prefix string = ",\"host\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.String(string(in.Host))
|
||||
}
|
||||
if in.Port != 0 {
|
||||
const prefix string = ",\"port\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Int(int(in.Port))
|
||||
}
|
||||
if len(in.Channels) != 0 {
|
||||
const prefix string = ",\"channels\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
{
|
||||
out.RawByte('[')
|
||||
for v8, v9 := range in.Channels {
|
||||
if v8 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
out.String(string(v9))
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
}
|
||||
if in.Password != "" {
|
||||
const prefix string = ",\"password\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.String(string(in.Password))
|
||||
}
|
||||
if in.SSL {
|
||||
const prefix string = ",\"ssl\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Bool(bool(in.SSL))
|
||||
}
|
||||
if in.ReadOnly {
|
||||
const prefix string = ",\"readOnly\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Bool(bool(in.ReadOnly))
|
||||
}
|
||||
if in.ShowDetails {
|
||||
const prefix string = ",\"showDetails\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Bool(bool(in.ShowDetails))
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
func easyjson7e607aefDecodeGithubComKhliengDispatchServer1(in *jlexer.Lexer, out *dispatchVersion) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
@ -363,7 +519,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer1(in *jlexer.Lexer, out
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
@ -393,12 +549,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer1(out *jwriter.Writer,
|
||||
_ = first
|
||||
if in.Tag != "" {
|
||||
const prefix string = ",\"tag\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
out.String(string(in.Tag))
|
||||
}
|
||||
if in.Commit != "" {
|
||||
@ -458,7 +610,7 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer2(in *jlexer.Lexer, out
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
@ -488,9 +640,9 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer2(in *jlexer.Lexer, out
|
||||
out.Channels = (out.Channels)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v7 string
|
||||
v7 = string(in.String())
|
||||
out.Channels = append(out.Channels, v7)
|
||||
var v10 string
|
||||
v10 = string(in.String())
|
||||
out.Channels = append(out.Channels, v10)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
@ -519,12 +671,8 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer2(out *jwriter.Writer,
|
||||
_ = first
|
||||
if in.Name != "" {
|
||||
const prefix string = ",\"name\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
out.String(string(in.Name))
|
||||
}
|
||||
if in.Host != "" {
|
||||
@ -557,11 +705,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer2(out *jwriter.Writer,
|
||||
}
|
||||
{
|
||||
out.RawByte('[')
|
||||
for v8, v9 := range in.Channels {
|
||||
if v8 > 0 {
|
||||
for v11, v12 := range in.Channels {
|
||||
if v11 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
out.String(string(v9))
|
||||
out.String(string(v12))
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ package server
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/khlieng/dispatch/pkg/irc"
|
||||
"github.com/khlieng/dispatch/storage"
|
||||
)
|
||||
@ -21,7 +20,10 @@ func createNickInUseHandler(i *irc.Client, state *State) func(string) string {
|
||||
})
|
||||
}
|
||||
|
||||
state.printError("Nickname", nick, "is already in use, using", newNick, "instead")
|
||||
state.sendJSON("error", IRCError{
|
||||
Server: i.Host,
|
||||
Message: fmt.Sprintf("Nickname %s is already in use, using %s instead", nick, newNick),
|
||||
})
|
||||
|
||||
return newNick
|
||||
}
|
||||
@ -38,7 +40,9 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
|
||||
address = net.JoinHostPort(server.Host, server.Port)
|
||||
}
|
||||
|
||||
if viper.GetBool("hexIP") {
|
||||
cfg := state.srv.Config()
|
||||
|
||||
if cfg.HexIP {
|
||||
i.Username = hex.EncodeToString(srcIP)
|
||||
} else if i.Username == "" {
|
||||
i.Username = server.Nick
|
||||
@ -49,16 +53,16 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
|
||||
}
|
||||
|
||||
if server.Password == "" &&
|
||||
viper.GetString("defaults.password") != "" &&
|
||||
address == viper.GetString("defaults.host") {
|
||||
i.Password = viper.GetString("defaults.password")
|
||||
cfg.Defaults.Password != "" &&
|
||||
address == cfg.Defaults.Host {
|
||||
i.Password = cfg.Defaults.Password
|
||||
} else {
|
||||
i.Password = server.Password
|
||||
}
|
||||
|
||||
if i.TLS {
|
||||
i.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: !viper.GetBool("verify_certificates"),
|
||||
InsecureSkipVerify: !cfg.VerifyCertificates,
|
||||
}
|
||||
|
||||
if cert := state.user.GetCertificate(); cert != nil {
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@ -14,6 +15,7 @@ import (
|
||||
|
||||
var excludedErrors = []string{
|
||||
irc.ErrNicknameInUse,
|
||||
irc.ErrForward,
|
||||
}
|
||||
|
||||
type ircHandler struct {
|
||||
@ -23,6 +25,7 @@ type ircHandler struct {
|
||||
whois WhoisReply
|
||||
userBuffers map[string][]string
|
||||
motdBuffer MOTD
|
||||
listBuffer storage.ChannelListIndex
|
||||
|
||||
handlers map[string]func(*irc.Message)
|
||||
}
|
||||
@ -66,7 +69,21 @@ func (i *ircHandler) run() {
|
||||
|
||||
func (i *ircHandler) dispatchMessage(msg *irc.Message) {
|
||||
if msg.Command[0] == '4' && !isExcludedError(msg.Command) {
|
||||
i.state.printError(formatIRCError(msg))
|
||||
err := IRCError{
|
||||
Server: i.client.Host,
|
||||
Message: msg.LastParam(),
|
||||
}
|
||||
|
||||
if len(msg.Params) > 2 {
|
||||
for i := 1; i < len(msg.Params); i++ {
|
||||
if isChannel(msg.Params[i]) {
|
||||
err.Target = msg.Params[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i.state.sendJSON("error", err)
|
||||
}
|
||||
|
||||
if handler, ok := i.handlers[msg.Command]; ok {
|
||||
@ -183,6 +200,12 @@ func (i *ircHandler) info(msg *irc.Message) {
|
||||
New: msg.Params[0],
|
||||
})
|
||||
|
||||
_, needsUpdate := channelIndexes.Get(i.client.Host)
|
||||
if needsUpdate {
|
||||
i.listBuffer = storage.NewMapChannelListIndex()
|
||||
i.client.List()
|
||||
}
|
||||
|
||||
go i.state.user.SetNick(msg.Params[0], i.client.Host)
|
||||
}
|
||||
|
||||
@ -193,6 +216,22 @@ func (i *ircHandler) info(msg *irc.Message) {
|
||||
})
|
||||
}
|
||||
|
||||
func (i *ircHandler) features(msg *irc.Message) {
|
||||
i.state.sendJSON("features", Features{
|
||||
Server: i.client.Host,
|
||||
Features: i.client.Features.Map(),
|
||||
})
|
||||
|
||||
if name := i.client.Features.String("NETWORK"); name != "" {
|
||||
go func() {
|
||||
server, err := i.state.user.GetServer(i.client.Host)
|
||||
if err == nil && server.Name == "" {
|
||||
i.state.user.SetServerName(name, server.Host)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ircHandler) whoisUser(msg *irc.Message) {
|
||||
i.whois.Nick = msg.Params[1]
|
||||
i.whois.Username = msg.Params[2]
|
||||
@ -281,14 +320,55 @@ func (i *ircHandler) motdEnd(msg *irc.Message) {
|
||||
i.motdBuffer = MOTD{}
|
||||
}
|
||||
|
||||
func (i *ircHandler) list(msg *irc.Message) {
|
||||
if i.listBuffer == nil && i.state.Bool("update_chanlist_"+i.client.Host) {
|
||||
i.listBuffer = storage.NewMapChannelListIndex()
|
||||
}
|
||||
|
||||
if i.listBuffer != nil {
|
||||
c, _ := strconv.Atoi(msg.Params[2])
|
||||
i.listBuffer.Add(&storage.ChannelListItem{
|
||||
Name: msg.Params[1],
|
||||
UserCount: c,
|
||||
Topic: msg.LastParam(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ircHandler) listEnd(msg *irc.Message) {
|
||||
if i.listBuffer != nil {
|
||||
i.state.Set("update_chanlist_"+i.client.Host, false)
|
||||
|
||||
go func(idx storage.ChannelListIndex) {
|
||||
idx.Finish()
|
||||
channelIndexes.Set(i.client.Host, idx)
|
||||
}(i.listBuffer)
|
||||
|
||||
i.listBuffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ircHandler) badNick(msg *irc.Message) {
|
||||
i.state.sendJSON("nick_fail", NickFail{
|
||||
Server: i.client.Host,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *ircHandler) forward(msg *irc.Message) {
|
||||
if len(msg.Params) > 2 {
|
||||
i.state.sendJSON("channel_forward", ChannelForward{
|
||||
Server: i.client.Host,
|
||||
Old: msg.Params[1],
|
||||
New: msg.Params[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ircHandler) error(msg *irc.Message) {
|
||||
i.state.printError(msg.LastParam())
|
||||
i.state.sendJSON("error", IRCError{
|
||||
Server: i.client.Host,
|
||||
Message: msg.LastParam(),
|
||||
})
|
||||
}
|
||||
|
||||
func (i *ircHandler) initHandlers() {
|
||||
@ -305,6 +385,7 @@ func (i *ircHandler) initHandlers() {
|
||||
irc.ReplyWelcome: i.info,
|
||||
irc.ReplyYourHost: i.info,
|
||||
irc.ReplyCreated: i.info,
|
||||
irc.ReplyISupport: i.features,
|
||||
irc.ReplyLUserClient: i.info,
|
||||
irc.ReplyLUserOp: i.info,
|
||||
irc.ReplyLUserUnknown: i.info,
|
||||
@ -321,7 +402,10 @@ func (i *ircHandler) initHandlers() {
|
||||
irc.ReplyMotdStart: i.motdStart,
|
||||
irc.ReplyMotd: i.motd,
|
||||
irc.ReplyEndOfMotd: i.motdEnd,
|
||||
irc.ReplyList: i.list,
|
||||
irc.ReplyListEnd: i.listEnd,
|
||||
irc.ErrErroneousNickname: i.badNick,
|
||||
irc.ErrForward: i.forward,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ func TestMain(m *testing.M) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
storage.Initialize(tempdir)
|
||||
storage.Initialize(tempdir, "", "")
|
||||
|
||||
db, err := boltdb.New(storage.Path.Database())
|
||||
if err != nil {
|
||||
|
@ -21,7 +21,13 @@ type WSResponse struct {
|
||||
|
||||
type Server struct {
|
||||
*storage.Server
|
||||
Status ConnectionUpdate
|
||||
Status ConnectionUpdate
|
||||
Features map[string]interface{}
|
||||
}
|
||||
|
||||
type Features struct {
|
||||
Server string
|
||||
Features map[string]interface{}
|
||||
}
|
||||
|
||||
type ServerName struct {
|
||||
@ -192,3 +198,26 @@ type Error struct {
|
||||
Server string
|
||||
Message string
|
||||
}
|
||||
|
||||
type IRCError struct {
|
||||
Server string
|
||||
Target string
|
||||
Message string
|
||||
}
|
||||
|
||||
type ChannelSearch struct {
|
||||
Server string
|
||||
Q string
|
||||
Start int
|
||||
}
|
||||
|
||||
type ChannelSearchResult struct {
|
||||
ChannelSearch
|
||||
Results []*storage.ChannelListItem
|
||||
}
|
||||
|
||||
type ChannelForward struct {
|
||||
Server string
|
||||
Old string
|
||||
New string
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user