Compare commits
70 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 | |||
8f1105bc59 | |||
0e46fbcc82 | |||
c1ca29511e | |||
35c2d682e3 | |||
aca380629f | |||
007fc80e72 | |||
cca3f5bc93 | |||
f05e405fb1 |
@ -1,3 +1,5 @@
|
||||
.git
|
||||
dist
|
||||
dispatch
|
||||
client/dist
|
||||
client/node_modules
|
||||
client/node_modules
|
||||
client/yarn-error.log
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,4 +3,3 @@ dispatch
|
||||
client/dist
|
||||
client/node_modules
|
||||
client/yarn-error.log
|
||||
ca-certificates.crt
|
||||
|
@ -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"
|
||||
@ -37,7 +37,6 @@ checksum:
|
||||
changelog:
|
||||
filters:
|
||||
exclude:
|
||||
- ^v.*
|
||||
- "(?i)^update.*dep"
|
||||
- Merge pull request
|
||||
- Merge branch
|
||||
|
@ -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:
|
||||
|
@ -6,7 +6,7 @@ RUN apk add --update git make build-base && \
|
||||
|
||||
WORKDIR /go/src/github.com/khlieng/dispatch
|
||||
COPY . /go/src/github.com/khlieng/dispatch
|
||||
RUN go build .
|
||||
RUN chmod +x install.sh && ./install.sh
|
||||
|
||||
# Runtime
|
||||
FROM alpine
|
||||
@ -14,7 +14,7 @@ FROM alpine
|
||||
RUN apk add --update ca-certificates && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY --from=build /go/src/github.com/khlieng/dispatch/dispatch /dispatch
|
||||
COPY --from=build /go/bin/dispatch /dispatch
|
||||
|
||||
EXPOSE 80/tcp
|
||||
|
||||
|
17
README.md
17
README.md
@ -18,9 +18,9 @@ There is a few different ways of getting it:
|
||||
|
||||
### 1. Binary
|
||||
|
||||
- **[Windows (x64)](https://github.com/khlieng/dispatch/releases/download/v0.5.4/dispatch_0.5.4_windows_x64.zip)**
|
||||
- **[macOS (x64)](https://github.com/khlieng/dispatch/releases/download/v0.5.4/dispatch_0.5.4_mac_x64.tar.gz)**
|
||||
- **[Linux (x64)](https://github.com/khlieng/dispatch/releases/download/v0.5.4/dispatch_0.5.4_linux_x64.tar.gz)**
|
||||
- **[Windows (x64)](https://release.khlieng.com/khlieng/dispatch/windows_x64)**
|
||||
- **[macOS (x64)](https://release.khlieng.com/khlieng/dispatch/mac_x64)**
|
||||
- **[Linux (x64)](https://release.khlieng.com/khlieng/dispatch/linux_x64)**
|
||||
- [Other versions](https://github.com/khlieng/dispatch/releases)
|
||||
|
||||
### 2. Go
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +80,7 @@ export default class MessageBox extends PureComponent {
|
||||
if (prevProps.messages[0] !== this.props.messages[0]) {
|
||||
const { messages, hasMoreMessages } = this.props;
|
||||
|
||||
if (prevProps.tab === this.props.tab) {
|
||||
if (prevProps.tab === this.props.tab && prevProps.messages.length > 0) {
|
||||
const addedMessages = messages.length - prevProps.messages.length;
|
||||
let addedHeight = 0;
|
||||
for (let i = 0; i < addedMessages; i++) {
|
||||
@ -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,6 +1,7 @@
|
||||
import documentTitle from './documentTitle';
|
||||
import fonts from './fonts';
|
||||
import initialState from './initialState';
|
||||
import route from './route';
|
||||
import socket from './socket';
|
||||
import storage from './storage';
|
||||
import widthUpdates from './widthUpdates';
|
||||
@ -8,6 +9,7 @@ import widthUpdates from './widthUpdates';
|
||||
export default function runModules(ctx) {
|
||||
fonts(ctx);
|
||||
initialState(ctx);
|
||||
route(ctx);
|
||||
|
||||
documentTitle(ctx);
|
||||
socket(ctx);
|
||||
|
@ -1,22 +1,11 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import Cookie from 'js-cookie';
|
||||
import { socket as socketActions } from 'state/actions';
|
||||
import { getWrapWidth, setConnectDefaults, 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 { select, updateSelection } from 'state/tab';
|
||||
import { find } from 'utils';
|
||||
import { when } from 'utils/observe';
|
||||
import { replace } from 'utils/router';
|
||||
|
||||
function loadState({ store }, env) {
|
||||
store.dispatch(setConnectDefaults(env.defaults));
|
||||
store.dispatch(
|
||||
appSet({
|
||||
hexIP: env.hexIP,
|
||||
version: env.version
|
||||
})
|
||||
);
|
||||
store.dispatch(setSettings(env.settings, true));
|
||||
|
||||
if (env.servers) {
|
||||
@ -25,52 +14,12 @@ function loadState({ store }, env) {
|
||||
data: env.servers
|
||||
});
|
||||
|
||||
const { router } = store.getState();
|
||||
|
||||
if (!router.route || router.route === 'chat') {
|
||||
const tabs = [];
|
||||
|
||||
if (router.route === 'chat') {
|
||||
tabs.push(router.params);
|
||||
}
|
||||
|
||||
const cookie = Cookie.get('tab');
|
||||
if (cookie) {
|
||||
const [server, name = null] = cookie.split(/;(.+)/);
|
||||
tabs.push({
|
||||
server,
|
||||
name
|
||||
});
|
||||
}
|
||||
|
||||
let found = false;
|
||||
let i = 0;
|
||||
|
||||
while (!found) {
|
||||
const tab = tabs[i];
|
||||
i++;
|
||||
|
||||
if (
|
||||
tab.name &&
|
||||
find(
|
||||
env.channels,
|
||||
chan => chan.server === tab.server && chan.name === tab.name
|
||||
)
|
||||
) {
|
||||
found = true;
|
||||
store.dispatch(select(tab.server, tab.name, true));
|
||||
} else if (find(env.servers, srv => srv.host === tab.server)) {
|
||||
found = true;
|
||||
store.dispatch(select(tab.server, null, true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
store.dispatch(updateSelection());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
store.dispatch(replace('/connect'));
|
||||
when(store, getConnected, () =>
|
||||
// Cache top channels for each server
|
||||
env.servers.forEach(({ host }) =>
|
||||
store.dispatch(searchChannels(host, ''))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (env.channels) {
|
||||
@ -87,16 +36,26 @@ function loadState({ store }, env) {
|
||||
});
|
||||
}
|
||||
|
||||
// Wait until wrapWidth gets initialized so that height calculations
|
||||
// only happen once for these messages
|
||||
when(store, getWrapWidth, () => {
|
||||
if (env.messages) {
|
||||
store.dispatch(
|
||||
appSet({
|
||||
connectDefaults: env.defaults,
|
||||
initialized: true,
|
||||
hexIP: env.hexIP,
|
||||
version: env.version
|
||||
})
|
||||
);
|
||||
|
||||
if (env.messages) {
|
||||
// Wait until wrapWidth gets initialized so that height calculations
|
||||
// only happen once for these messages
|
||||
when(store, getWrapWidth, () => {
|
||||
const { messages, server, to, next } = env.messages;
|
||||
store.dispatch(addMessages(messages, server, to, false, next));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default async function initialState(ctx) {
|
||||
const env = await window.__init__;
|
||||
ctx.socket.connect();
|
||||
|
22
client/js/modules/route.js
Normal file
22
client/js/modules/route.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { updateSelection } from 'state/tab';
|
||||
import { observe, when } from 'utils/observe';
|
||||
|
||||
export default function route({ store }) {
|
||||
let first = true;
|
||||
|
||||
when(
|
||||
store,
|
||||
state => state.app.initialized,
|
||||
() =>
|
||||
observe(
|
||||
store,
|
||||
state => state.router,
|
||||
router => {
|
||||
if (!router.route || router.route === 'chat') {
|
||||
store.dispatch(updateSelection(first));
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
@ -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,9 +46,13 @@ 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' }]
|
||||
}
|
||||
}
|
||||
@ -61,6 +65,8 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
}
|
||||
}
|
||||
@ -81,9 +87,13 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: true,
|
||||
users: []
|
||||
}
|
||||
}
|
||||
@ -105,12 +115,16 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [
|
||||
{ mode: '', nick: 'nick3', renderName: 'nick3' },
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
@ -118,7 +132,8 @@ describe('channel reducer', () => {
|
||||
});
|
||||
|
||||
it('handles SOCKET_USERS', () => {
|
||||
const state = reducer(undefined, {
|
||||
let state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
||||
state = reducer(state, {
|
||||
type: actions.socket.USERS,
|
||||
server: 'srv',
|
||||
channel: 'chan1',
|
||||
@ -128,6 +143,8 @@ describe('channel reducer', () => {
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [
|
||||
{ mode: '', nick: 'user3', renderName: 'user3' },
|
||||
{ mode: '', nick: 'user2', renderName: 'user2' },
|
||||
@ -141,18 +158,18 @@ describe('channel reducer', () => {
|
||||
});
|
||||
|
||||
it('handles SOCKET_TOPIC', () => {
|
||||
const state = reducer(undefined, {
|
||||
let state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
||||
state = reducer(state, {
|
||||
type: actions.socket.TOPIC,
|
||||
server: 'srv',
|
||||
channel: 'chan1',
|
||||
topic: 'the topic'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
expect(state).toMatchObject({
|
||||
srv: {
|
||||
chan1: {
|
||||
topic: 'the topic',
|
||||
users: []
|
||||
topic: 'the topic'
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -165,7 +182,7 @@ describe('channel reducer', () => {
|
||||
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick1', 'o', ''));
|
||||
|
||||
expect(state).toEqual({
|
||||
expect(state).toMatchObject({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
@ -183,7 +200,7 @@ describe('channel reducer', () => {
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick2', 'o', ''));
|
||||
state = reducer(state, socket_mode('srv', 'chan2', 'not_there', 'x', ''));
|
||||
|
||||
expect(state).toEqual({
|
||||
expect(state).toMatchObject({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
@ -210,11 +227,11 @@ describe('channel reducer', () => {
|
||||
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: { topic: 'the topic', users: [] },
|
||||
chan2: { users: [] }
|
||||
chan1: { name: 'chan1', joined: true, topic: 'the topic', users: [] },
|
||||
chan2: { name: 'chan2', joined: true, users: [] }
|
||||
},
|
||||
srv2: {
|
||||
chan1: { users: [] }
|
||||
chan1: { name: 'chan1', joined: true, users: [] }
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -313,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;
|
||||
}
|
||||
@ -59,7 +58,3 @@ export function setConnected(connected) {
|
||||
export function setCharWidth(width) {
|
||||
return appSet('charWidth', width);
|
||||
}
|
||||
|
||||
export function setConnectDefaults(defaults) {
|
||||
return appSet('connectDefaults', defaults);
|
||||
}
|
||||
|
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: [] };
|
||||
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(
|
||||
@ -124,6 +120,10 @@ export const getSelectedChannelUsers = createSelector(
|
||||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.JOIN](state, { server, channels }) {
|
||||
channels.forEach(channel => init(state, server, channel));
|
||||
},
|
||||
|
||||
[actions.PART](state, { server, channels }) {
|
||||
channels.forEach(channel => delete state[server][channel]);
|
||||
},
|
||||
@ -131,9 +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);
|
||||
@ -160,12 +167,10 @@ export default createReducer(
|
||||
},
|
||||
|
||||
[actions.socket.USERS](state, { server, channel, users }) {
|
||||
init(state, server, channel);
|
||||
state[server][channel].users = users.map(nick => loadUser(nick));
|
||||
},
|
||||
|
||||
[actions.socket.TOPIC](state, { server, channel, topic }) {
|
||||
init(state, server, channel);
|
||||
state[server][channel].topic = topic;
|
||||
},
|
||||
|
||||
@ -191,6 +196,7 @@ export default createReducer(
|
||||
if (data) {
|
||||
data.forEach(({ server, name, topic }) => {
|
||||
init(state, server, name);
|
||||
state[server][name].joined = true;
|
||||
state[server][name].topic = topic;
|
||||
});
|
||||
}
|
||||
@ -225,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,9 @@
|
||||
import get from 'lodash/get';
|
||||
import Cookie from 'js-cookie';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { push, replace, LOCATION_CHANGED } from 'utils/router';
|
||||
import * as actions from './actions';
|
||||
import { find } from '../utils';
|
||||
|
||||
const initialState = {
|
||||
selected: {},
|
||||
@ -54,11 +57,43 @@ export function select(server, name, doReplace) {
|
||||
return navigate(`/${server}`);
|
||||
}
|
||||
|
||||
export function updateSelection() {
|
||||
export function tabExists(
|
||||
{ server, name },
|
||||
{ servers, channels, privateChats }
|
||||
) {
|
||||
return (
|
||||
(name && get(channels, [server, name])) ||
|
||||
(!name && server && servers[server]) ||
|
||||
(name && find(privateChats[server], nick => nick === name))
|
||||
);
|
||||
}
|
||||
|
||||
function parseTabCookie() {
|
||||
const cookie = Cookie.get('tab');
|
||||
if (cookie) {
|
||||
const [server, name = null] = cookie.split(/;(.+)/);
|
||||
return { server, name };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function updateSelection(tryCookie) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const { history } = state.tab;
|
||||
|
||||
if (tabExists(state.tab.selected, state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tryCookie) {
|
||||
const tab = parseTabCookie();
|
||||
if (tab && tabExists(tab, state)) {
|
||||
return dispatch(select(tab.server, tab.name, true));
|
||||
}
|
||||
}
|
||||
|
||||
const { servers } = state;
|
||||
const { history } = state.tab;
|
||||
const { server } = state.tab.selected;
|
||||
const serverAddrs = Object.keys(servers);
|
||||
|
||||
@ -66,7 +101,7 @@ export function updateSelection() {
|
||||
dispatch(replace('/connect'));
|
||||
} else if (
|
||||
history.length > 0 &&
|
||||
history[history.length - 1] !== state.tab.selected
|
||||
tabExists(history[history.length - 1], state)
|
||||
) {
|
||||
const tab = history[history.length - 1];
|
||||
dispatch(select(tab.server, tab.name, true));
|
||||
|
@ -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.2",
|
||||
"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: {
|
||||
|
10269
client/yarn.lock
10269
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
|
||||
}
|
88
go.mod
88
go.mod
@ -1,60 +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-20180910213445-01d5c56e6095 // 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/mitchellh/mapstructure v1.1.2 // indirect
|
||||
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/pflag v1.0.3 // indirect
|
||||
github.com/spf13/viper v1.2.1
|
||||
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/crypto v0.0.0-20181127143415-eb0de9b17e85 // indirect
|
||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.1.9 // 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
|
||||
)
|
||||
|
737
go.sum
737
go.sum
@ -1,156 +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/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/couchbase/vellum v0.0.0-20180910213445-01d5c56e6095 h1:dh7mqP7LS9voSd1Wx515giC2lPjPVduBpujISaftHrc=
|
||||
github.com/couchbase/vellum v0.0.0-20180910213445-01d5c56e6095/go.mod h1:prYTC8EgTu3gwbqJihkud9zRXISvyulAplQ6exdCo1g=
|
||||
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/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/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/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
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/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
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.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M=
|
||||
github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
|
||||
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/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=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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/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-20181129055619-fae4c4e3ad76 h1:xx5MUFyRQRbPk6VjWjIE1epE/K5AoDD8QUN116NCy8k=
|
||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/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/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/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-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.1.9 h1:YCFbL5T2gbmC2sMG12s1x2PAlTK5TZNte3hjZEIcCAg=
|
||||
gopkg.in/square/go-jose.v2 v2.1.9/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=
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/sh -
|
||||
|
||||
Import="github.com/khlieng/dispatch/version"
|
||||
|
||||
|
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>`
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user