Compare commits
68 Commits
fixbinarie
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
eea24e3047 | ||
![]() |
e91312a468 | ||
![]() |
065a93d56f | ||
![]() |
fbbf36d1a1 | ||
![]() |
3675251706 | ||
![]() |
6f61b673a1 | ||
![]() |
4566b2021f | ||
![]() |
e7c5b96a6a | ||
![]() |
b18403ea71 | ||
![]() |
c9cbab6769 | ||
![]() |
176aba3c99 | ||
![]() |
d814c48dce | ||
![]() |
3af82e3e8e | ||
![]() |
8a8d7b1e97 | ||
![]() |
2ac33b7d2c | ||
![]() |
a54031de9e | ||
![]() |
a49dea57d8 | ||
![]() |
114c6aa80c | ||
![]() |
414c2fcf89 | ||
![]() |
9d860692fa | ||
![]() |
98cb66559a | ||
![]() |
e14795818f | ||
![]() |
c394ea6735 | ||
![]() |
c94884fb9f | ||
![]() |
784039998f | ||
![]() |
6981b10763 | ||
![]() |
d170e01d38 | ||
![]() |
5787059d11 | ||
![]() |
2e4ff30276 | ||
![]() |
f18765a41a | ||
![]() |
d23f7cf93d | ||
![]() |
6d64a46466 | ||
![]() |
14ed3a6633 | ||
![]() |
cb46494733 | ||
![]() |
e3fea6c97b | ||
![]() |
9b70d25143 | ||
![]() |
7a20037194 | ||
![]() |
5fa7214853 | ||
![]() |
e905b44fb4 | ||
![]() |
84a36a0095 | ||
![]() |
aa4907d8ae | ||
![]() |
facfcba232 | ||
![]() |
d3285748f9 | ||
![]() |
283ef104a4 | ||
![]() |
04d907d1e9 | ||
![]() |
d74a6780fe | ||
![]() |
d7e9ef230a | ||
![]() |
75f224a7c0 | ||
![]() |
caab002d51 | ||
![]() |
7ff892bba9 | ||
![]() |
233238b709 | ||
![]() |
59e0792db1 | ||
![]() |
962b6645c1 | ||
![]() |
cee8bf9957 | ||
![]() |
9d93bca179 | ||
![]() |
ccae795335 | ||
![]() |
862eb429d4 | ||
![]() |
9e075dde67 | ||
![]() |
20be29bcef | ||
![]() |
34c3be0a88 | ||
![]() |
be246a3bc4 | ||
![]() |
4fb452b2c0 | ||
![]() |
d707382a78 | ||
![]() |
7620a3c282 | ||
![]() |
18a3e2f2c3 | ||
![]() |
d046a9863f | ||
![]() |
a1450a81d6 | ||
![]() |
d594386658 |
8
.dependabot/config.yml
Normal file
8
.dependabot/config.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
version: 1
|
||||
update_configs:
|
||||
- package_manager: "go:modules"
|
||||
directory: "/"
|
||||
update_schedule: "daily"
|
||||
- package_manager: "docker"
|
||||
directory: "/"
|
||||
update_schedule: "weekly"
|
30
.drone.yml
30
.drone.yml
@@ -1,30 +0,0 @@
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/github.com/prologic/eris
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
image: golang
|
||||
commands:
|
||||
- go get -d
|
||||
- go build .
|
||||
|
||||
docker:
|
||||
image: plugins/docker
|
||||
repo: r.mills.io/prologic/eris
|
||||
registry: r.mills.io
|
||||
secrets: [ docker_username, docker_password ]
|
||||
|
||||
notify:
|
||||
image: drillster/drone-email
|
||||
host: mail.mills.io
|
||||
from: drone@mills.io
|
||||
skip_verify: true
|
||||
when:
|
||||
status: [ success, changed, failure ]
|
||||
|
||||
secrets:
|
||||
registry_username:
|
||||
external: true
|
||||
registry_password:
|
||||
external: true
|
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
35
.github/workflows/build.yml
vendored
Normal file
35
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
go-version:
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
os:
|
||||
- "ubuntu-latest"
|
||||
- "macos-latest"
|
||||
- "windows-latest"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup Go ${{ matrix.go-version }}
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
id: go
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: |
|
||||
go build -v .
|
||||
- name: Test
|
||||
run: |
|
||||
go test -v -race .
|
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
*~*
|
||||
bin
|
||||
*~
|
||||
*.db
|
||||
*.bak
|
||||
*.pem
|
||||
|
||||
eris
|
||||
/bin
|
||||
/dist
|
||||
/eris
|
||||
/coverage.txt
|
||||
|
45
.gitmodules
vendored
45
.gitmodules
vendored
@@ -1,45 +0,0 @@
|
||||
[submodule "vendor/github.com/sirupsen/logrus"]
|
||||
path = vendor/github.com/sirupsen/logrus
|
||||
url = https://github.com/sirupsen/logrus
|
||||
[submodule "vendor/golang.org/x/crypto"]
|
||||
path = vendor/golang.org/x/crypto
|
||||
url = https://go.googlesource.com/crypto
|
||||
[submodule "vendor/golang.org/x/sys"]
|
||||
path = vendor/golang.org/x/sys
|
||||
url = https://go.googlesource.com/sys
|
||||
[submodule "vendor/github.com/DanielOaks/girc-go"]
|
||||
path = vendor/github.com/DanielOaks/girc-go
|
||||
url = https://github.com/DanielOaks/girc-go
|
||||
[submodule "vendor/github.com/goshuirc/e-nfa"]
|
||||
path = vendor/github.com/goshuirc/e-nfa
|
||||
url = https://github.com/goshuirc/e-nfa
|
||||
[submodule "vendor/github.com/imdario/mergo"]
|
||||
path = vendor/github.com/imdario/mergo
|
||||
url = https://github.com/imdario/mergo
|
||||
[submodule "vendor/golang.org/x/text"]
|
||||
path = vendor/golang.org/x/text
|
||||
url = https://go.googlesource.com/text
|
||||
[submodule "vendor/gopkg.in/yaml.v2"]
|
||||
path = vendor/gopkg.in/yaml.v2
|
||||
url = https://gopkg.in/yaml.v2
|
||||
[submodule "vendor/github.com/prometheus/client_golang"]
|
||||
path = vendor/github.com/prometheus/client_golang
|
||||
url = https://github.com/prometheus/client_golang
|
||||
[submodule "vendor/github.com/beorn7/perks"]
|
||||
path = vendor/github.com/beorn7/perks
|
||||
url = https://github.com/beorn7/perks
|
||||
[submodule "vendor/github.com/golang/protobuf"]
|
||||
path = vendor/github.com/golang/protobuf
|
||||
url = https://github.com/golang/protobuf
|
||||
[submodule "vendor/github.com/prometheus/client_model"]
|
||||
path = vendor/github.com/prometheus/client_model
|
||||
url = https://github.com/prometheus/client_model
|
||||
[submodule "vendor/github.com/prometheus/common"]
|
||||
path = vendor/github.com/prometheus/common
|
||||
url = https://github.com/prometheus/common
|
||||
[submodule "vendor/github.com/matttproud/golang_protobuf_extensions"]
|
||||
path = vendor/github.com/matttproud/golang_protobuf_extensions
|
||||
url = https://github.com/matttproud/golang_protobuf_extensions
|
||||
[submodule "vendor/github.com/prometheus/procfs"]
|
||||
path = vendor/github.com/prometheus/procfs
|
||||
url = https://github.com/prometheus/procfs
|
31
.goreleaser.yml
Normal file
31
.goreleaser.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
builds:
|
||||
- binary: eris
|
||||
flags: -tags "static_build"
|
||||
ldflags: -w -X mail.Version={{.Version}} -X main.Commit={{.Commit}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- darwin
|
||||
- freebsd
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- i386
|
||||
- amd64
|
||||
- arm
|
||||
- amd64
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
sign:
|
||||
artifacts: checksum
|
||||
archive:
|
||||
wrap_in_directory: true
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- "*.pem"
|
||||
- "*.yml"
|
||||
- "LICENSE"
|
||||
- "README.md"
|
@@ -1,8 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
29
.yamllint.yml
Normal file
29
.yamllint.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
yaml-files:
|
||||
- '*.yaml'
|
||||
- '*.yml'
|
||||
- '.yamllint'
|
||||
|
||||
rules:
|
||||
braces: enable
|
||||
brackets: enable
|
||||
colons: enable
|
||||
commas: enable
|
||||
comments: disable
|
||||
comments-indentation: disable
|
||||
document-end: disable
|
||||
document-start:
|
||||
level: warning
|
||||
empty-lines: enable
|
||||
empty-values: disable
|
||||
hyphens: enable
|
||||
indentation: enable
|
||||
key-duplicates: enable
|
||||
key-ordering: disable
|
||||
line-length: disable
|
||||
new-line-at-end-of-file: enable
|
||||
new-lines: enable
|
||||
octal-values: enable
|
||||
quoted-strings: disable
|
||||
trailing-spaces: enable
|
||||
truthy: disable
|
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at prologic@shortcircuit.net.au. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
@@ -46,4 +46,4 @@ $ git push -u origin my-feature
|
||||
|
||||
When describing your bug report; please be concise and as detailed as you can
|
||||
so we can easily work out what the problem is. It's also very helpful if you
|
||||
are able to provide a test case that repeatedly demonstrates the bug at hand:
|
||||
are able to provide a test case that repeatedly demonstrates the bug at hand.
|
||||
|
@@ -2,7 +2,6 @@
|
||||
FROM golang:alpine AS build
|
||||
|
||||
ARG TAG
|
||||
ARG BUILD
|
||||
|
||||
ENV APP eris
|
||||
ENV REPO prologic/$APP
|
||||
@@ -12,7 +11,7 @@ RUN apk add --update git make build-base && \
|
||||
|
||||
WORKDIR /go/src/github.com/$REPO
|
||||
COPY . /go/src/github.com/$REPO
|
||||
RUN make TAG=$TAG BUILD=$BUILD build
|
||||
RUN make TAG=$TAG build
|
||||
|
||||
# Runtime
|
||||
FROM alpine
|
||||
|
18
Makefile
18
Makefile
@@ -6,7 +6,6 @@ APP=eris
|
||||
PACKAGE=irc
|
||||
REPO?=prologic/$(APP)
|
||||
TAG?=latest
|
||||
BUILD?=-dev
|
||||
|
||||
all: dev
|
||||
|
||||
@@ -17,17 +16,24 @@ deps:
|
||||
@go get ./...
|
||||
|
||||
build: clean deps
|
||||
@echo " -> Building $(TAG)$(BUILD)"
|
||||
@echo "github.com/$(REPO)/${PACKAGE}.GitCommit=$(COMMIT)"
|
||||
@echo " -> Building $(REPO) $(TAG)@$(COMMIT)"
|
||||
@go build -tags "netgo static_build" -installsuffix netgo \
|
||||
-ldflags "-w -X github.com/$(REPO)/${PACKAGE}.GitCommit=$(COMMIT) -X github.com/$(REPO)/${PACKAGE}.Build=$(BUILD)" .
|
||||
-ldflags "-w -X github.com/$(REPO)/${PACKAGE}.GitCommit=$(COMMIT)"
|
||||
@echo "Built $$(./$(APP) -v)"
|
||||
|
||||
image:
|
||||
@docker build --build-arg TAG=$(TAG) --build-arg BUILD=$(BUILD) -t $(REPO):$(TAG) .
|
||||
@docker build --build-arg TAG=$(TAG) -t $(REPO):$(TAG) .
|
||||
@echo "Image created: $(REPO):$(TAG)"
|
||||
|
||||
profile:
|
||||
@go test -cpuprofile cpu.prof -memprofile mem.prof -v -bench ./...
|
||||
|
||||
bench:
|
||||
@go test -v -bench ./...
|
||||
|
||||
test:
|
||||
@go test -v -cover -race $(TEST_ARGS)
|
||||
@go test -v -cover -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... -race ./...
|
||||
|
||||
clean:
|
||||
@rm -rf $(APP)
|
||||
@git clean -f -d -X
|
||||
|
3
PULL_REQUEST_TEMPLATE.md
Normal file
3
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,3 @@
|
||||
<one line description here>
|
||||
|
||||
Fixes #xx
|
95
README.md
95
README.md
@@ -1,10 +1,10 @@
|
||||
# eris - IRC Server / Daemon written in Go
|
||||
eris - IRC Server / Daemon written in Go
|
||||
|
||||
[](https://travis-ci.org/prologic/eris)
|
||||
[](https://goreportcard.com/report/github.com/prologic/eris)
|
||||
[](https://coveralls.io/r/prologic/eris)
|
||||
[](https://godoc.org/github.com/prologic/eris)
|
||||
[](https://github.com/prologic/eris/wiki)
|
||||
[](https://cloud.drone.io/prologic/eris)
|
||||
[](https://codecov.io/gh/prologic/eris)
|
||||
[](https://goreportcard.com/report/prologic/eris)
|
||||
[](https://godoc.org/github.com/prologic/eris)
|
||||
[](https://sourcegraph.com/github.com/prologic/eris?badge)
|
||||
|
||||
> This project and repository is based off of [ergonomadic](https://github.com/edmund-huber/ergonomadic)
|
||||
> and much of my original contributions were made in my [fork of ergonomadic](https://github.com/prologic/ergonomadic)
|
||||
@@ -25,7 +25,7 @@ The connotation here is that IRC (*Internet Relay Chat*) is a place of chaos,
|
||||
strife and discord. IRC is a place where you argue and get into arguments for
|
||||
the sake of argument.
|
||||
|
||||
So `eris` is an IRC daemon written from scratch in Go to factiliate discord
|
||||
So `eris` is an IRC daemon written from scratch in Go to facilitate discord
|
||||
and have arguments for the sake of argument!
|
||||
|
||||
Pull requests and issues are welcome.
|
||||
@@ -35,9 +35,9 @@ Discussion at:
|
||||
* /server irc.mills.io +6697 (*use TLS/SSL*)
|
||||
* /join #lobby
|
||||
|
||||
Or (*not recommended*)P
|
||||
Or (**not recommended**):
|
||||
|
||||
* /server irc.mills.io (*default port 6667, non-TLS)
|
||||
* /server irc.mills.io (*default port 6667, non-TLS*)
|
||||
* /join #lobby
|
||||
|
||||
## Features
|
||||
@@ -54,6 +54,41 @@ Or (*not recommended*)P
|
||||
* Simple IRC operator privileges (*overrides most things*)
|
||||
* Secure connection tracking (+z) and SecureOnly user mode (+Z)
|
||||
* Secure channels (+Z)
|
||||
* Three layers of channel privacy, Public, Private (+p) and Secret (s)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```#!bash
|
||||
$ go get github.com/prologic/eris
|
||||
$ cat > ircd.yml <<EOF
|
||||
network:
|
||||
name: Test
|
||||
server:
|
||||
name: Test
|
||||
listen:
|
||||
- ":6667"
|
||||
EOF
|
||||
$ eris
|
||||
```
|
||||
|
||||
If you want TLS (**recommended**) then:
|
||||
|
||||
```#!bash
|
||||
$ go get github.com/prologic/mksslcert
|
||||
$ mksslcert
|
||||
```
|
||||
|
||||
This generates a self-signed cert `cert.pem` and `key.pem` into the `$PWD`.
|
||||
|
||||
Then add a `tlslisten` block to your config:
|
||||
|
||||
```#!yaml
|
||||
server:
|
||||
tlslisten:
|
||||
":6697":
|
||||
key: key.pem
|
||||
cert: cert.pem
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -73,6 +108,14 @@ $ go install github.com/prologic/mkpasswd
|
||||
$ mkpasswd
|
||||
```
|
||||
|
||||
Self-signed certificates can also be generated using the `mksslcert` tool
|
||||
from [prologic/mksslcert](https://github.com/prologic/mksslcert):
|
||||
|
||||
```#!bash
|
||||
$ go install github.com/prologic/mksslcert
|
||||
$ mksslcert
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
To run simply run the `eris` binary (*assuming a `ircd.yml` in the current directory*):
|
||||
@@ -93,15 +136,45 @@ You may want to customize the configuration however and create your own image ba
|
||||
$ docker stack deploy -c docker-compose.yml eris
|
||||
```
|
||||
|
||||
Which assumes a `ircd.yml` coniguration fiel int he current directory which Docker will use to distribute as the configuration. The `docker-compose.yml` (*Docker Stackfile*) is available at the root of this repository.
|
||||
Which assumes a `ircd.yml` coniguration file in the current directory which Docker will use to distribute as the configuration. The `docker-compose.yml` (*Docker Stackfile*) is available at the root of this repository.
|
||||
|
||||
## Related Proejcts
|
||||
## Related Projects
|
||||
|
||||
There are a number of supported accompanying services that are being developed alongside Eris:
|
||||
|
||||
* [Soter](https://github.com/prologic/soter) -- An IRC Bot that persists channel modes and topics.
|
||||
* [Cadmus](https://github.com/prologic/cadmus) -- An IRC Bot that logs channels and provides an interface for viewing and searching logs
|
||||
|
||||
## Recommended Clients
|
||||
|
||||
### CLI / Terminal
|
||||
|
||||
* [irccat](https://github.com/prologic/irccat)
|
||||
* [irssi](https://irssi.org/)
|
||||
|
||||
### Cloud
|
||||
|
||||
* [IRCCloud](https://www.irccloud.com/)
|
||||
|
||||
### Desktop
|
||||
|
||||
* [HexChat (Linux)](https://hexchat.github.io/)
|
||||
* [Textual (OSX)](https://www.codeux.com/textual/)
|
||||
* [mIRC (Windows)](https://www.mirc.com/)
|
||||
|
||||
### Mobile
|
||||
|
||||
* [Palaver (iOS)](https://palaverapp.com/) -- SASL, TLS, Server Password, Push Notifications, IRCv3 (*Also supports custom image upload service(s) for better privacy of shared photos/images over IRC*)
|
||||
|
||||
### Web
|
||||
|
||||
* [Dispatch](https://github.com/khlieng/dispatch) -- TLS, Multiple Servers and Users, Client Certificates
|
||||
|
||||
## Related Projects
|
||||
|
||||
* [cadmus](https://github.com/prologic/cadmus) -- an IRC Bot written in Go that logs IRC Channels and provides an interface to view and search those logs
|
||||
* [soter](https://github.com/prologic/soter) -- an IRC Bot written in Go that protects IRC Channels by persisting channel modes and topics
|
||||
|
||||
## License
|
||||
|
||||
eris is licensed under the MIT License.
|
||||
|
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-architect
|
19
go.mod
Normal file
19
go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
module github.com/prologic/eris
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/DanielOaks/girc-go v0.0.0-20180430075055-8d136c4f9287
|
||||
github.com/google/uuid v1.1.0 // indirect
|
||||
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 // indirect
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3
|
||||
github.com/prometheus/client_golang v0.9.4
|
||||
github.com/renstrom/shortuuid v3.0.0+incompatible
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/stretchr/testify v1.6.0
|
||||
github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
|
||||
golang.org/x/text v0.3.4
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
100
go.sum
Normal file
100
go.sum
Normal file
@@ -0,0 +1,100 @@
|
||||
github.com/DanielOaks/girc-go v0.0.0-20180430075055-8d136c4f9287 h1:xOE8jDDulcwdPG+coLps6seNn6yERt5xgKSATNqWUM0=
|
||||
github.com/DanielOaks/girc-go v0.0.0-20180430075055-8d136c4f9287/go.mod h1:nn+Gr++RLey8iGwfvI84UO5oZal6Muz7qPxDII0BsQ8=
|
||||
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/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
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/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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
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/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
|
||||
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 h1:KmRLPRstEJiE/9OjumKqI8Rccip8Qmyw2FwyTFxtVqs=
|
||||
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940/go.mod h1:VOmrX6cmj7zwUeexC9HzznUdTIObHqIXUrWNYS+Ik7w=
|
||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
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.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3 h1:2YMbJ6WbdQI9K73chxh9OWMDsZ2PNjAIRGTonp3T0l0=
|
||||
github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3/go.mod h1:LQkXsHRSPIEklPCq8OMQAzYNS2NGtYStdNE/ej1oJU8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pkg/errors v0.8.0/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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.4 h1:Y8E/JaaPbmFSW2V81Ab/d8yZFYQQGbni1b1jPcG9Y6A=
|
||||
github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/renstrom/shortuuid v3.0.0+incompatible h1:F6T1U7bWlI3FTV+JE8HyeR7bkTeYZJntqQLA9ST4HOQ=
|
||||
github.com/renstrom/shortuuid v3.0.0+incompatible/go.mod h1:n18Ycpn8DijG+h/lLBQVnGKv1BCtTeXo8KKSbBOrQ8c=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
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/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
|
||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3 h1:389FrrKIAlxqQMTscCQ7VH3JAVuxb/pe53v2LBiA7z8=
|
||||
github.com/thoj/go-ircevent v0.0.0-20180816043103-14f3614f28c3/go.mod h1:QYOctLs5qEsaIrA/PKEc4YqAv2SozbxNEX0vMPs84p4=
|
||||
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/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
1403
grafana/Eris-1525253970771.json
Normal file
1403
grafana/Eris-1525253970771.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -51,7 +51,7 @@ func (channel *Channel) Names(client *Client) {
|
||||
}
|
||||
|
||||
func (channel *Channel) ClientIsOperator(client *Client) bool {
|
||||
return client.flags[Operator] || channel.members.HasMode(client, ChannelOperator)
|
||||
return client.modes.Has(Operator) || channel.members.HasMode(client, ChannelOperator)
|
||||
}
|
||||
|
||||
func (channel *Channel) Nicks(target *Client) []string {
|
||||
@@ -96,7 +96,7 @@ func (channel *Channel) String() string {
|
||||
|
||||
// <mode> <mode params>
|
||||
func (channel *Channel) ModeString(client *Client) (str string) {
|
||||
isMember := client.flags[Operator] || channel.members.Has(client)
|
||||
isMember := client.modes.Has(Operator) || channel.members.Has(client)
|
||||
showKey := isMember && (channel.key != "")
|
||||
showUserLimit := channel.userLimit > 0
|
||||
|
||||
@@ -155,16 +155,16 @@ func (channel *Channel) Join(client *Client, key Text) {
|
||||
return
|
||||
}
|
||||
|
||||
isInvited := channel.lists[InviteMask].Match(client.UserHost())
|
||||
isInvited := channel.lists[InviteMask].Match(client.UserHost(false))
|
||||
if !isOperator && channel.flags.Has(InviteOnly) && !isInvited {
|
||||
client.ErrInviteOnlyChan(channel)
|
||||
return
|
||||
}
|
||||
|
||||
if channel.lists[BanMask].Match(client.UserHost()) &&
|
||||
if channel.lists[BanMask].Match(client.UserHost(false)) &&
|
||||
!isInvited &&
|
||||
!isOperator &&
|
||||
!channel.lists[ExceptMask].Match(client.UserHost()) {
|
||||
!channel.lists[ExceptMask].Match(client.UserHost(false)) {
|
||||
client.ErrBannedFromChan(channel)
|
||||
return
|
||||
}
|
||||
@@ -244,7 +244,7 @@ func (channel *Channel) CanSpeak(client *Client) bool {
|
||||
channel.members.HasMode(client, ChannelOperator)) {
|
||||
return false
|
||||
}
|
||||
if channel.flags.Has(SecureChan) && !client.flags[SecureConn] {
|
||||
if channel.flags.Has(SecureChan) && !client.modes.Has(SecureConn) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -374,7 +374,7 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
|
||||
return channel.applyModeMask(client, change.mode, change.op,
|
||||
NewName(change.arg))
|
||||
|
||||
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Private, SecureChan:
|
||||
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Private, Secret, SecureChan:
|
||||
return channel.applyModeFlag(client, change.mode, change.op)
|
||||
|
||||
case Key:
|
||||
@@ -508,12 +508,12 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
||||
}
|
||||
|
||||
if channel.flags.Has(InviteOnly) {
|
||||
channel.lists[InviteMask].Add(invitee.UserHost())
|
||||
channel.lists[InviteMask].Add(invitee.UserHost(false))
|
||||
}
|
||||
|
||||
inviter.RplInviting(invitee, channel.name)
|
||||
invitee.Reply(RplInviteMsg(inviter, invitee, channel.name))
|
||||
if invitee.flags[Away] {
|
||||
if invitee.modes.Has(Away) {
|
||||
inviter.RplAway(invitee)
|
||||
}
|
||||
}
|
||||
|
282
irc/client.go
282
irc/client.go
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -14,6 +15,30 @@ const (
|
||||
QUIT_TIMEOUT = time.Minute // how long after idle before a client is kicked
|
||||
)
|
||||
|
||||
type SyncBool struct {
|
||||
sync.RWMutex
|
||||
|
||||
value bool
|
||||
}
|
||||
|
||||
func NewSyncBool(value bool) *SyncBool {
|
||||
return &SyncBool{value: value}
|
||||
}
|
||||
|
||||
func (sb *SyncBool) Get() bool {
|
||||
sb.RLock()
|
||||
defer sb.RUnlock()
|
||||
|
||||
return sb.value
|
||||
}
|
||||
|
||||
func (sb *SyncBool) Set(value bool) {
|
||||
sb.Lock()
|
||||
defer sb.Unlock()
|
||||
|
||||
sb.value = value
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
atime time.Time
|
||||
authorized bool
|
||||
@@ -22,10 +47,11 @@ type Client struct {
|
||||
capState CapState
|
||||
channels *ChannelSet
|
||||
ctime time.Time
|
||||
flags map[UserMode]bool
|
||||
hasQuit bool
|
||||
modes *UserModeSet
|
||||
hasQuit *SyncBool
|
||||
hops uint
|
||||
hostname Name
|
||||
hostmask Name // Cloacked hostname (SHA256)
|
||||
pingTime time.Time
|
||||
idleTimer *time.Timer
|
||||
nick Name
|
||||
@@ -41,14 +67,15 @@ type Client struct {
|
||||
|
||||
func NewClient(server *Server, conn net.Conn) *Client {
|
||||
now := time.Now()
|
||||
client := &Client{
|
||||
c := &Client{
|
||||
atime: now,
|
||||
authorized: len(server.password) == 0,
|
||||
capState: CapNone,
|
||||
capabilities: make(CapabilitySet),
|
||||
channels: NewChannelSet(),
|
||||
ctime: now,
|
||||
flags: make(map[UserMode]bool),
|
||||
modes: NewUserModeSet(),
|
||||
hasQuit: NewSyncBool(false),
|
||||
sasl: NewSaslState(),
|
||||
server: server,
|
||||
socket: NewSocket(conn),
|
||||
@@ -56,44 +83,50 @@ func NewClient(server *Server, conn net.Conn) *Client {
|
||||
}
|
||||
|
||||
if _, ok := conn.(*tls.Conn); ok {
|
||||
client.flags[SecureConn] = true
|
||||
c.modes.Set(SecureConn)
|
||||
}
|
||||
|
||||
client.Touch()
|
||||
go client.writeloop()
|
||||
go client.readloop()
|
||||
c.Touch()
|
||||
go c.writeloop()
|
||||
go c.readloop()
|
||||
|
||||
return client
|
||||
return c
|
||||
}
|
||||
|
||||
//
|
||||
// command goroutine
|
||||
//
|
||||
|
||||
func (client *Client) writeloop() {
|
||||
for reply := range client.replies {
|
||||
client.socket.Write(reply)
|
||||
func (c *Client) writeloop() {
|
||||
for {
|
||||
select {
|
||||
case reply, ok := <-c.replies:
|
||||
if !ok || reply == "" || c.socket == nil {
|
||||
return
|
||||
}
|
||||
c.socket.Write(reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) readloop() {
|
||||
func (c *Client) readloop() {
|
||||
var command Command
|
||||
var err error
|
||||
var line string
|
||||
|
||||
// Set the hostname for this client.
|
||||
client.hostname = AddrLookupHostname(client.socket.conn.RemoteAddr())
|
||||
c.hostname = AddrLookupHostname(c.socket.conn.RemoteAddr())
|
||||
c.hostmask = NewName(SHA256(c.hostname.String()))
|
||||
|
||||
for err == nil {
|
||||
if line, err = client.socket.Read(); err != nil {
|
||||
if line, err = c.socket.Read(); err != nil {
|
||||
command = NewQuitCommand("connection closed")
|
||||
|
||||
} else if command, err = ParseCommand(line); err != nil {
|
||||
switch err {
|
||||
case ErrParseCommand:
|
||||
//TODO(dan): use the real failed numeric for this (400)
|
||||
client.Reply(RplNotice(client.server, client,
|
||||
NewText("failed to parse command")))
|
||||
c.Reply(RplNotice(c.server, c, NewText("failed to parse command")))
|
||||
|
||||
case NotEnoughArgsError:
|
||||
// TODO
|
||||
@@ -103,7 +136,7 @@ func (client *Client) readloop() {
|
||||
continue
|
||||
|
||||
} else if checkPass, ok := command.(checkPasswordCommand); ok {
|
||||
checkPass.LoadPassword(client.server)
|
||||
checkPass.LoadPassword(c.server)
|
||||
// Block the client thread while handling a potentially expensive
|
||||
// password bcrypt operation. Since the server is single-threaded
|
||||
// for commands, we don't want the server to perform the bcrypt,
|
||||
@@ -112,179 +145,182 @@ func (client *Client) readloop() {
|
||||
checkPass.CheckPassword()
|
||||
}
|
||||
|
||||
client.processCommand(command)
|
||||
c.processCommand(command)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) processCommand(cmd Command) {
|
||||
client.server.metrics.Counter("client", "commands").Inc()
|
||||
func (c *Client) processCommand(cmd Command) {
|
||||
cmd.SetClient(c)
|
||||
|
||||
defer func(t time.Time) {
|
||||
v := client.server.metrics.SummaryVec("client", "command_duration_seconds")
|
||||
v.WithLabelValues(cmd.Code().String()).Observe(time.Now().Sub(t).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
cmd.SetClient(client)
|
||||
|
||||
if !client.registered {
|
||||
if !c.registered {
|
||||
regCmd, ok := cmd.(RegServerCommand)
|
||||
if !ok {
|
||||
client.Quit("unexpected command")
|
||||
c.Quit("unexpected command")
|
||||
return
|
||||
}
|
||||
regCmd.HandleRegServer(client.server)
|
||||
regCmd.HandleRegServer(c.server)
|
||||
return
|
||||
}
|
||||
|
||||
srvCmd, ok := cmd.(ServerCommand)
|
||||
if !ok {
|
||||
client.ErrUnknownCommand(cmd.Code())
|
||||
c.ErrUnknownCommand(cmd.Code())
|
||||
return
|
||||
}
|
||||
|
||||
c.server.metrics.Counter("client", "commands").Inc()
|
||||
|
||||
defer func(t time.Time) {
|
||||
v := c.server.metrics.SummaryVec("client", "command_duration_seconds")
|
||||
v.WithLabelValues(cmd.Code().String()).Observe(time.Now().Sub(t).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
switch srvCmd.(type) {
|
||||
case *PingCommand, *PongCommand:
|
||||
client.Touch()
|
||||
c.Touch()
|
||||
|
||||
case *QuitCommand:
|
||||
// no-op
|
||||
|
||||
default:
|
||||
client.Active()
|
||||
client.Touch()
|
||||
c.Active()
|
||||
c.Touch()
|
||||
}
|
||||
|
||||
srvCmd.HandleServer(client.server)
|
||||
srvCmd.HandleServer(c.server)
|
||||
}
|
||||
|
||||
// quit timer goroutine
|
||||
|
||||
func (client *Client) connectionTimeout() {
|
||||
client.processCommand(NewQuitCommand("connection timeout"))
|
||||
func (c *Client) connectionTimeout() {
|
||||
c.processCommand(NewQuitCommand("connection timeout"))
|
||||
}
|
||||
|
||||
//
|
||||
// idle timer goroutine
|
||||
//
|
||||
|
||||
func (client *Client) connectionIdle() {
|
||||
client.server.idle <- client
|
||||
func (c *Client) connectionIdle() {
|
||||
c.server.idle <- c
|
||||
}
|
||||
|
||||
//
|
||||
// server goroutine
|
||||
//
|
||||
|
||||
func (client *Client) Active() {
|
||||
client.atime = time.Now()
|
||||
func (c *Client) Active() {
|
||||
c.atime = time.Now()
|
||||
}
|
||||
|
||||
func (client *Client) Touch() {
|
||||
if client.quitTimer != nil {
|
||||
client.quitTimer.Stop()
|
||||
func (c *Client) Touch() {
|
||||
if c.quitTimer != nil {
|
||||
c.quitTimer.Stop()
|
||||
}
|
||||
|
||||
if client.idleTimer == nil {
|
||||
client.idleTimer = time.AfterFunc(IDLE_TIMEOUT, client.connectionIdle)
|
||||
if c.idleTimer == nil {
|
||||
c.idleTimer = time.AfterFunc(IDLE_TIMEOUT, c.connectionIdle)
|
||||
} else {
|
||||
client.idleTimer.Reset(IDLE_TIMEOUT)
|
||||
c.idleTimer.Reset(IDLE_TIMEOUT)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) Idle() {
|
||||
client.pingTime = time.Now()
|
||||
client.Reply(RplPing(client.server))
|
||||
func (c *Client) Idle() {
|
||||
c.pingTime = time.Now()
|
||||
c.Reply(RplPing(c.server))
|
||||
|
||||
if client.quitTimer == nil {
|
||||
client.quitTimer = time.AfterFunc(QUIT_TIMEOUT, client.connectionTimeout)
|
||||
if c.quitTimer == nil {
|
||||
c.quitTimer = time.AfterFunc(QUIT_TIMEOUT, c.connectionTimeout)
|
||||
} else {
|
||||
client.quitTimer.Reset(QUIT_TIMEOUT)
|
||||
c.quitTimer.Reset(QUIT_TIMEOUT)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) Register() {
|
||||
if client.registered {
|
||||
func (c *Client) Register() {
|
||||
if c.registered {
|
||||
return
|
||||
}
|
||||
client.registered = true
|
||||
client.Touch()
|
||||
c.registered = true
|
||||
c.modes.Set(HostMask)
|
||||
c.Touch()
|
||||
}
|
||||
|
||||
func (client *Client) destroy() {
|
||||
func (c *Client) destroy() {
|
||||
// clean up channels
|
||||
|
||||
client.channels.Range(func(channel *Channel) bool {
|
||||
channel.Quit(client)
|
||||
c.channels.Range(func(channel *Channel) bool {
|
||||
channel.Quit(c)
|
||||
return true
|
||||
})
|
||||
|
||||
// clean up server
|
||||
|
||||
client.server.connections.Dec()
|
||||
client.server.clients.Remove(client)
|
||||
if _, ok := c.socket.conn.(*tls.Conn); ok {
|
||||
c.server.metrics.GaugeVec("server", "clients").WithLabelValues("secure").Dec()
|
||||
} else {
|
||||
c.server.metrics.GaugeVec("server", "clients").WithLabelValues("insecure").Dec()
|
||||
}
|
||||
|
||||
c.server.connections.Dec()
|
||||
c.server.clients.Remove(c)
|
||||
|
||||
// clean up self
|
||||
|
||||
if client.idleTimer != nil {
|
||||
client.idleTimer.Stop()
|
||||
if c.idleTimer != nil {
|
||||
c.idleTimer.Stop()
|
||||
}
|
||||
if client.quitTimer != nil {
|
||||
client.quitTimer.Stop()
|
||||
if c.quitTimer != nil {
|
||||
c.quitTimer.Stop()
|
||||
}
|
||||
|
||||
close(client.replies)
|
||||
close(c.replies)
|
||||
|
||||
client.socket.Close()
|
||||
c.socket.Close()
|
||||
|
||||
log.Debugf("%s: destroyed", client)
|
||||
log.Debugf("%s: destroyed", c)
|
||||
}
|
||||
|
||||
func (client *Client) IdleTime() time.Duration {
|
||||
return time.Since(client.atime)
|
||||
func (c *Client) IdleTime() time.Duration {
|
||||
return time.Since(c.atime)
|
||||
}
|
||||
|
||||
func (client *Client) SignonTime() int64 {
|
||||
return client.ctime.Unix()
|
||||
func (c *Client) SignonTime() int64 {
|
||||
return c.ctime.Unix()
|
||||
}
|
||||
|
||||
func (client *Client) IdleSeconds() uint64 {
|
||||
return uint64(client.IdleTime().Seconds())
|
||||
func (c *Client) IdleSeconds() uint64 {
|
||||
return uint64(c.IdleTime().Seconds())
|
||||
}
|
||||
|
||||
func (client *Client) HasNick() bool {
|
||||
return client.nick != ""
|
||||
func (c *Client) HasNick() bool {
|
||||
return c.nick != ""
|
||||
}
|
||||
|
||||
func (client *Client) HasUsername() bool {
|
||||
return client.username != ""
|
||||
func (c *Client) HasUsername() bool {
|
||||
return c.username != ""
|
||||
}
|
||||
|
||||
func (client *Client) CanSpeak(target *Client) bool {
|
||||
requiresSecure := client.flags[SecureOnly] || target.flags[SecureOnly]
|
||||
isSecure := client.flags[SecureConn] && target.flags[SecureConn]
|
||||
isOperator := client.flags[Operator]
|
||||
func (c *Client) CanSpeak(target *Client) bool {
|
||||
requiresSecure := c.modes.Has(SecureOnly) || target.modes.Has(SecureOnly)
|
||||
isSecure := c.modes.Has(SecureConn) && target.modes.Has(SecureConn)
|
||||
isOperator := c.modes.Has(Operator)
|
||||
|
||||
return !requiresSecure || (requiresSecure && (isOperator || isSecure))
|
||||
}
|
||||
|
||||
// <mode>
|
||||
func (c *Client) ModeString() (str string) {
|
||||
for flag := range c.flags {
|
||||
str += flag.String()
|
||||
}
|
||||
|
||||
if len(str) > 0 {
|
||||
str = "+" + str
|
||||
}
|
||||
return
|
||||
return c.modes.String()
|
||||
}
|
||||
|
||||
func (c *Client) UserHost() Name {
|
||||
func (c *Client) UserHost(cloacked bool) Name {
|
||||
username := "*"
|
||||
if c.HasUsername() {
|
||||
if c.username != "" {
|
||||
username = c.username.String()
|
||||
}
|
||||
return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname))
|
||||
if cloacked {
|
||||
return Name(fmt.Sprintf("%s!%s@%s", c.nick, username, c.hostmask))
|
||||
}
|
||||
return Name(fmt.Sprintf("%s!%s@%s", c.nick, username, c.hostname))
|
||||
}
|
||||
|
||||
func (c *Client) Server() Name {
|
||||
@@ -303,17 +339,17 @@ func (c *Client) Nick() Name {
|
||||
}
|
||||
|
||||
func (c *Client) Id() Name {
|
||||
return c.UserHost()
|
||||
return c.UserHost(true)
|
||||
}
|
||||
|
||||
func (c *Client) String() string {
|
||||
return c.Id().String()
|
||||
}
|
||||
|
||||
func (client *Client) Friends() *ClientSet {
|
||||
func (c *Client) Friends() *ClientSet {
|
||||
friends := NewClientSet()
|
||||
friends.Add(client)
|
||||
client.channels.Range(func(channel *Channel) bool {
|
||||
friends.Add(c)
|
||||
c.channels.Range(func(channel *Channel) bool {
|
||||
channel.members.Range(func(member *Client, _ *ChannelModeSet) bool {
|
||||
friends.Add(member)
|
||||
return true
|
||||
@@ -323,46 +359,48 @@ func (client *Client) Friends() *ClientSet {
|
||||
return friends
|
||||
}
|
||||
|
||||
func (client *Client) SetNickname(nickname Name) {
|
||||
if client.HasNick() {
|
||||
log.Errorf("%s nickname already set!", client)
|
||||
func (c *Client) SetNickname(nickname Name) {
|
||||
if c.nick != "" {
|
||||
log.Errorf("%s nickname already set!", c)
|
||||
return
|
||||
}
|
||||
client.nick = nickname
|
||||
client.server.clients.Add(client)
|
||||
c.nick = nickname
|
||||
c.server.clients.Add(c)
|
||||
}
|
||||
|
||||
func (client *Client) ChangeNickname(nickname Name) {
|
||||
func (c *Client) ChangeNickname(nickname Name) {
|
||||
// Make reply before changing nick to capture original source id.
|
||||
reply := RplNick(client, nickname)
|
||||
client.server.clients.Remove(client)
|
||||
client.server.whoWas.Append(client)
|
||||
client.nick = nickname
|
||||
client.server.clients.Add(client)
|
||||
client.Friends().Range(func(friend *Client) bool {
|
||||
reply := RplNick(c, nickname)
|
||||
c.server.clients.Remove(c)
|
||||
c.server.whoWas.Append(c)
|
||||
c.nick = nickname
|
||||
c.server.clients.Add(c)
|
||||
c.Friends().Range(func(friend *Client) bool {
|
||||
friend.Reply(reply)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (client *Client) Reply(reply string) {
|
||||
client.replies <- reply
|
||||
func (c *Client) Reply(reply string) {
|
||||
if !c.hasQuit.Get() {
|
||||
c.replies <- reply
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) Quit(message Text) {
|
||||
if client.hasQuit {
|
||||
func (c *Client) Quit(message Text) {
|
||||
if c.hasQuit.Get() {
|
||||
return
|
||||
}
|
||||
|
||||
client.hasQuit = true
|
||||
client.Reply(RplError("quit"))
|
||||
client.server.whoWas.Append(client)
|
||||
friends := client.Friends()
|
||||
friends.Remove(client)
|
||||
client.destroy()
|
||||
c.hasQuit.Set(true)
|
||||
c.Reply(RplError("quit"))
|
||||
c.server.whoWas.Append(c)
|
||||
friends := c.Friends()
|
||||
friends.Remove(c)
|
||||
c.destroy()
|
||||
|
||||
if friends.Count() > 0 {
|
||||
reply := RplQuit(client, message)
|
||||
reply := RplQuit(c, message)
|
||||
friends.Range(func(friend *Client) bool {
|
||||
friend.Reply(reply)
|
||||
return true
|
||||
|
@@ -4,9 +4,7 @@ import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
//"sync"
|
||||
|
||||
sync "github.com/sasha-s/go-deadlock"
|
||||
"sync"
|
||||
|
||||
"github.com/DanielOaks/girc-go/ircmatch"
|
||||
)
|
||||
@@ -105,7 +103,7 @@ func (clients *ClientLookupSet) FindAll(userhost Name) *ClientSet {
|
||||
|
||||
var casemappedNickMask string
|
||||
for _, client := range clients.nicks {
|
||||
casemappedNickMask = client.UserHost().String()
|
||||
casemappedNickMask = client.UserHost(false).String()
|
||||
if matcher.Match(casemappedNickMask) {
|
||||
set.Add(client)
|
||||
}
|
||||
@@ -123,7 +121,7 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
|
||||
|
||||
var casemappedNickMask string
|
||||
for _, client := range clients.nicks {
|
||||
casemappedNickMask = client.UserHost().String()
|
||||
casemappedNickMask = client.UserHost(false).String()
|
||||
if matcher.Match(casemappedNickMask) {
|
||||
return client
|
||||
}
|
||||
|
@@ -4,9 +4,7 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
//"sync"
|
||||
|
||||
sync "github.com/sasha-s/go-deadlock"
|
||||
"sync"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
@@ -3,6 +3,7 @@ package irc
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// DefObjectives ...
|
||||
var DefObjectives = map[float64]float64{
|
||||
0.50: 0.05,
|
||||
0.90: 0.01,
|
||||
@@ -17,20 +19,27 @@ var DefObjectives = map[float64]float64{
|
||||
0.99: 0.001,
|
||||
}
|
||||
|
||||
// Metrics ...
|
||||
type Metrics struct {
|
||||
sync.RWMutex
|
||||
|
||||
namespace string
|
||||
metrics map[string]prometheus.Metric
|
||||
guagevecs map[string]*prometheus.GaugeVec
|
||||
sumvecs map[string]*prometheus.SummaryVec
|
||||
}
|
||||
|
||||
// NewMetrics ...
|
||||
func NewMetrics(namespace string) *Metrics {
|
||||
return &Metrics{
|
||||
namespace: namespace,
|
||||
metrics: make(map[string]prometheus.Metric),
|
||||
guagevecs: make(map[string]*prometheus.GaugeVec),
|
||||
sumvecs: make(map[string]*prometheus.SummaryVec),
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter ...
|
||||
func (m *Metrics) NewCounter(subsystem, name, help string) prometheus.Counter {
|
||||
counter := prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
@@ -42,12 +51,15 @@ func (m *Metrics) NewCounter(subsystem, name, help string) prometheus.Counter {
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.metrics[key] = counter
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(counter)
|
||||
|
||||
return counter
|
||||
}
|
||||
|
||||
// NewCounterFunc ...
|
||||
func (m *Metrics) NewCounterFunc(subsystem, name, help string, f func() float64) prometheus.CounterFunc {
|
||||
counter := prometheus.NewCounterFunc(
|
||||
prometheus.CounterOpts{
|
||||
@@ -60,12 +72,15 @@ func (m *Metrics) NewCounterFunc(subsystem, name, help string, f func() float64)
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.metrics[key] = counter
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(counter)
|
||||
|
||||
return counter
|
||||
}
|
||||
|
||||
// NewGauge ...
|
||||
func (m *Metrics) NewGauge(subsystem, name, help string) prometheus.Gauge {
|
||||
guage := prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -77,12 +92,15 @@ func (m *Metrics) NewGauge(subsystem, name, help string) prometheus.Gauge {
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.metrics[key] = guage
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(guage)
|
||||
|
||||
return guage
|
||||
}
|
||||
|
||||
// NewGaugeFunc ...
|
||||
func (m *Metrics) NewGaugeFunc(subsystem, name, help string, f func() float64) prometheus.GaugeFunc {
|
||||
guage := prometheus.NewGaugeFunc(
|
||||
prometheus.GaugeOpts{
|
||||
@@ -95,12 +113,36 @@ func (m *Metrics) NewGaugeFunc(subsystem, name, help string, f func() float64) p
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.metrics[key] = guage
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(guage)
|
||||
|
||||
return guage
|
||||
}
|
||||
|
||||
// NewGaugeVec ...
|
||||
func (m *Metrics) NewGaugeVec(subsystem, name, help string, labels []string) *prometheus.GaugeVec {
|
||||
guagevec := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: m.namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: help,
|
||||
},
|
||||
labels,
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.guagevecs[key] = guagevec
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(guagevec)
|
||||
|
||||
return guagevec
|
||||
}
|
||||
|
||||
// NewSummary ...
|
||||
func (m *Metrics) NewSummary(subsystem, name, help string) prometheus.Summary {
|
||||
summary := prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
@@ -113,12 +155,15 @@ func (m *Metrics) NewSummary(subsystem, name, help string) prometheus.Summary {
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.metrics[key] = summary
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(summary)
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// NewSummaryVec ...
|
||||
func (m *Metrics) NewSummaryVec(subsystem, name, help string, labels []string) *prometheus.SummaryVec {
|
||||
sumvec := prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
@@ -132,36 +177,58 @@ func (m *Metrics) NewSummaryVec(subsystem, name, help string, labels []string) *
|
||||
)
|
||||
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.Lock()
|
||||
m.sumvecs[key] = sumvec
|
||||
m.Unlock()
|
||||
prometheus.MustRegister(sumvec)
|
||||
|
||||
return sumvec
|
||||
}
|
||||
|
||||
// Counter ...
|
||||
func (m *Metrics) Counter(subsystem, name string) prometheus.Counter {
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
return m.metrics[key].(prometheus.Counter)
|
||||
}
|
||||
|
||||
// Gauge ...
|
||||
func (m *Metrics) Gauge(subsystem, name string) prometheus.Gauge {
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.metrics[key].(prometheus.Gauge)
|
||||
}
|
||||
|
||||
// GaugeVec ...
|
||||
func (m *Metrics) GaugeVec(subsystem, name string) *prometheus.GaugeVec {
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.guagevecs[key]
|
||||
}
|
||||
|
||||
// Summary ...
|
||||
func (m *Metrics) Summary(subsystem, name string) prometheus.Summary {
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.metrics[key].(prometheus.Summary)
|
||||
}
|
||||
|
||||
// SummaryVec ...
|
||||
func (m *Metrics) SummaryVec(subsystem, name string) *prometheus.SummaryVec {
|
||||
key := fmt.Sprintf("%s_%s", subsystem, name)
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
return m.sumvecs[key]
|
||||
}
|
||||
|
||||
// Handler ...
|
||||
func (m *Metrics) Handler() http.Handler {
|
||||
return promhttp.Handler()
|
||||
}
|
||||
|
||||
// Run ...
|
||||
func (m *Metrics) Run(addr string) {
|
||||
http.Handle("/", m.Handler())
|
||||
log.Infof("metrics endpoint listening on %s", addr)
|
||||
|
51
irc/metrics_test.go
Normal file
51
irc/metrics_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMetrics(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
m := NewMetrics("test")
|
||||
m.NewCounter("foo", "counter", "help")
|
||||
m.NewCounterFunc("foo", "counter_func", "help", func() float64 { return 1.0 })
|
||||
m.NewGauge("foo", "gauge", "help")
|
||||
m.NewGaugeFunc("foo", "gauge_func", "help", func() float64 { return 1.0 })
|
||||
m.NewGaugeVec("foo", "gauge_vec", "help", []string{"test"})
|
||||
|
||||
m.Counter("foo", "counter").Inc()
|
||||
m.Gauge("foo", "gauge").Add(1)
|
||||
m.GaugeVec("foo", "gauge_vec").WithLabelValues("test").Add(1)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
m.Handler().ServeHTTP(w, r)
|
||||
assert.Equal(w.Code, http.StatusOK)
|
||||
|
||||
assert.Regexp(
|
||||
`
|
||||
# HELP test_foo_counter help
|
||||
# TYPE test_foo_counter counter
|
||||
test_foo_counter 1
|
||||
# HELP test_foo_counter_func help
|
||||
# TYPE test_foo_counter_func counter
|
||||
test_foo_counter_func 1
|
||||
# HELP test_foo_gauge help
|
||||
# TYPE test_foo_gauge gauge
|
||||
test_foo_gauge 1
|
||||
# HELP test_foo_gauge_func help
|
||||
# TYPE test_foo_gauge_func gauge
|
||||
test_foo_gauge_func 1
|
||||
# HELP test_foo_gauge_vec help
|
||||
# TYPE test_foo_gauge_vec gauge
|
||||
test_foo_gauge_vec{test="test"} 1
|
||||
`,
|
||||
w.Body.String(),
|
||||
)
|
||||
}
|
26
irc/modes.go
26
irc/modes.go
@@ -58,11 +58,12 @@ const (
|
||||
Registered UserMode = 'r' // not a real user mode (flag)
|
||||
SecureConn UserMode = 'z'
|
||||
SecureOnly UserMode = 'Z'
|
||||
HostMask UserMode = 'x'
|
||||
)
|
||||
|
||||
var (
|
||||
SupportedUserModes = UserModes{
|
||||
Invisible, Operator,
|
||||
Invisible, Operator, HostMask,
|
||||
}
|
||||
DefaultChannelModes = ChannelModes{
|
||||
NoOutside, OpOnlyTopic,
|
||||
@@ -70,7 +71,6 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
Anonymous ChannelMode = 'a' // flag
|
||||
BanMask ChannelMode = 'b' // arg
|
||||
ChannelCreator ChannelMode = 'O' // flag
|
||||
ChannelOperator ChannelMode = 'o' // arg
|
||||
@@ -82,8 +82,6 @@ const (
|
||||
NoOutside ChannelMode = 'n' // flag
|
||||
OpOnlyTopic ChannelMode = 't' // flag
|
||||
Private ChannelMode = 'p' // flag
|
||||
Quiet ChannelMode = 'q' // flag
|
||||
ReOp ChannelMode = 'r' // flag
|
||||
Secret ChannelMode = 's' // flag, deprecated
|
||||
UserLimit ChannelMode = 'l' // flag arg
|
||||
Voice ChannelMode = 'v' // arg
|
||||
@@ -93,7 +91,7 @@ const (
|
||||
var (
|
||||
SupportedChannelModes = ChannelModes{
|
||||
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
|
||||
OpOnlyTopic, Private, UserLimit, SecureChan,
|
||||
OpOnlyTopic, Private, UserLimit, Secret, SecureChan,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -110,7 +108,7 @@ func (m *ModeCommand) HandleServer(s *Server) {
|
||||
return
|
||||
}
|
||||
|
||||
if client != target && !client.flags[Operator] {
|
||||
if client != target && !client.modes.Has(Operator) {
|
||||
client.ErrUsersDontMatch()
|
||||
return
|
||||
}
|
||||
@@ -119,29 +117,27 @@ func (m *ModeCommand) HandleServer(s *Server) {
|
||||
|
||||
for _, change := range m.changes {
|
||||
switch change.mode {
|
||||
case Invisible, WallOps, SecureOnly:
|
||||
case Invisible, HostMask, WallOps, SecureOnly:
|
||||
switch change.op {
|
||||
case Add:
|
||||
if target.flags[change.mode] {
|
||||
if target.modes.Has(change.mode) {
|
||||
continue
|
||||
}
|
||||
target.flags[change.mode] = true
|
||||
target.modes.Set(change.mode)
|
||||
changes = append(changes, change)
|
||||
|
||||
case Remove:
|
||||
if !target.flags[change.mode] {
|
||||
if !target.modes.Has(change.mode) {
|
||||
continue
|
||||
}
|
||||
delete(target.flags, change.mode)
|
||||
target.modes.Unset(change.mode)
|
||||
changes = append(changes, change)
|
||||
}
|
||||
|
||||
case Operator:
|
||||
if change.op == Remove {
|
||||
if !target.flags[change.mode] {
|
||||
if !target.modes.Has(change.mode) {
|
||||
continue
|
||||
}
|
||||
delete(target.flags, change.mode)
|
||||
target.modes.Unset(change.mode)
|
||||
changes = append(changes, change)
|
||||
}
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ type OperNickCommand struct {
|
||||
func (msg *OperNickCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
|
||||
if !client.flags[Operator] {
|
||||
if !client.modes.Has(Operator) {
|
||||
client.ErrNoPrivileges()
|
||||
return
|
||||
}
|
||||
|
@@ -4,9 +4,8 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
//"sync"
|
||||
"sync"
|
||||
|
||||
sync "github.com/sasha-s/go-deadlock"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -68,7 +67,7 @@ func (store *MemoryPasswordStore) Verify(username, password string) error {
|
||||
hash, ok := store.Get(username)
|
||||
if !ok {
|
||||
log.Debugf("username %s not found", username)
|
||||
return fmt.Errorf("account not found: %S", username)
|
||||
return fmt.Errorf("account not found: %s", username)
|
||||
}
|
||||
|
||||
return store.hasher.Compare(hash, []byte(password))
|
||||
|
22
irc/privacy.go
Normal file
22
irc/privacy.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package irc
|
||||
|
||||
func CanSeeChannel(client *Client, channel *Channel) bool {
|
||||
isPrivate := channel.flags.Has(Private)
|
||||
isSecret := channel.flags.Has(Secret)
|
||||
|
||||
isMember := channel.members.Has(client)
|
||||
isOperator := client.modes.Has(Operator)
|
||||
isRegistered := client.modes.Has(Registered)
|
||||
isSecure := client.modes.Has(SecureConn)
|
||||
|
||||
if !(isSecret || isPrivate) {
|
||||
return true
|
||||
}
|
||||
if isSecret && (isMember || isOperator) {
|
||||
return true
|
||||
}
|
||||
if isPrivate && (isMember || isOperator || (isRegistered && isSecure)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
82
irc/reply.go
82
irc/reply.go
@@ -249,27 +249,27 @@ func (target *Client) RplRehashing() {
|
||||
|
||||
func (target *Client) RplWhois(client *Client) {
|
||||
target.RplWhoisUser(client)
|
||||
if client.flags[Operator] {
|
||||
if client.modes.Has(Operator) {
|
||||
target.RplWhoisOperator(client)
|
||||
}
|
||||
target.RplWhoisIdle(client)
|
||||
target.RplWhoisChannels(client)
|
||||
|
||||
if client.flags[SecureConn] {
|
||||
if client.modes.Has(SecureConn) {
|
||||
target.RplWhoisSecure(client)
|
||||
}
|
||||
target.RplWhoisServer(client)
|
||||
target.RplWhoisLoggedIn(client)
|
||||
target.RplEndOfWhois()
|
||||
target.RplEndOfWhois(client)
|
||||
}
|
||||
|
||||
func (target *Client) RplWhoisUser(client *Client) {
|
||||
var clientHost Name
|
||||
|
||||
if client.flags[SecureConn] {
|
||||
if target.modes.Has(Operator) || !client.modes.Has(HostMask) {
|
||||
clientHost = client.hostname
|
||||
} else {
|
||||
clientHost = NewName("SECURED")
|
||||
clientHost = client.hostmask
|
||||
}
|
||||
|
||||
target.NumericReply(
|
||||
@@ -324,9 +324,12 @@ func (target *Client) RplWhoisServer(client *Client) {
|
||||
)
|
||||
}
|
||||
|
||||
func (target *Client) RplEndOfWhois() {
|
||||
target.NumericReply(RPL_ENDOFWHOIS,
|
||||
":End of WHOIS list")
|
||||
func (target *Client) RplEndOfWhois(client *Client) {
|
||||
target.NumericReply(
|
||||
RPL_ENDOFWHOIS,
|
||||
"%s :End of WHOIS list",
|
||||
client.Nick(),
|
||||
)
|
||||
}
|
||||
|
||||
func (target *Client) RplChannelModeIs(channel *Channel) {
|
||||
@@ -337,15 +340,23 @@ func (target *Client) RplChannelModeIs(channel *Channel) {
|
||||
// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
|
||||
// :<hopcount> <real name>
|
||||
func (target *Client) RplWhoReply(channel *Channel, client *Client) {
|
||||
var clientHost Name
|
||||
|
||||
if target.modes.Has(Operator) || !client.modes.Has(HostMask) {
|
||||
clientHost = client.hostname
|
||||
} else {
|
||||
clientHost = client.hostmask
|
||||
}
|
||||
|
||||
channelName := "*"
|
||||
flags := ""
|
||||
|
||||
if client.flags[Away] {
|
||||
if client.modes.Has(Away) {
|
||||
flags = "G"
|
||||
} else {
|
||||
flags = "H"
|
||||
}
|
||||
if client.flags[Operator] {
|
||||
if client.modes.Has(Operator) {
|
||||
flags += "*"
|
||||
}
|
||||
|
||||
@@ -366,9 +377,18 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
|
||||
}
|
||||
}
|
||||
}
|
||||
target.NumericReply(RPL_WHOREPLY,
|
||||
"%s %s %s %s %s %s :%d %s", channelName, client.username, client.hostname,
|
||||
client.server.name, client.Nick(), flags, client.hops, client.realname)
|
||||
target.NumericReply(
|
||||
RPL_WHOREPLY,
|
||||
"%s %s %s %s %s %s :%d %s",
|
||||
channelName,
|
||||
client.username,
|
||||
clientHost,
|
||||
client.server.name,
|
||||
client.Nick(),
|
||||
flags,
|
||||
client.hops,
|
||||
client.realname,
|
||||
)
|
||||
}
|
||||
|
||||
// <name> :End of WHO list
|
||||
@@ -469,8 +489,13 @@ func (target *Client) RplMOTDEnd() {
|
||||
}
|
||||
|
||||
func (target *Client) RplList(channel *Channel) {
|
||||
target.NumericReply(RPL_LIST,
|
||||
"%s %d :%s", channel, channel.members.Count(), channel.topic)
|
||||
target.NumericReply(
|
||||
RPL_LIST,
|
||||
"%s %d :%s",
|
||||
channel,
|
||||
channel.members.Count(),
|
||||
channel.topic,
|
||||
)
|
||||
}
|
||||
|
||||
func (target *Client) RplListEnd(server *Server) {
|
||||
@@ -484,8 +509,12 @@ func (target *Client) RplNamReply(channel *Channel) {
|
||||
}
|
||||
|
||||
func (target *Client) RplWhoisChannels(client *Client) {
|
||||
target.MultilineReply(client.WhoisChannelsNames(), RPL_WHOISCHANNELS,
|
||||
"%s :%s", client.Nick())
|
||||
target.MultilineReply(
|
||||
client.WhoisChannelsNames(target),
|
||||
RPL_WHOISCHANNELS,
|
||||
"%s :%s",
|
||||
client.Nick(),
|
||||
)
|
||||
}
|
||||
|
||||
func (target *Client) RplVersion() {
|
||||
@@ -550,7 +579,7 @@ func (target *Client) RplLUserChannels() {
|
||||
func (target *Client) RplLUserOp() {
|
||||
nOperators := 0
|
||||
target.server.clients.Range(func(_ Name, client *Client) bool {
|
||||
if client.flags[Operator] {
|
||||
if client.modes.Has(Operator) {
|
||||
nOperators++
|
||||
}
|
||||
return true
|
||||
@@ -579,9 +608,22 @@ func (target *Client) RplLUserMe() {
|
||||
}
|
||||
|
||||
func (target *Client) RplWhoWasUser(whoWas *WhoWas) {
|
||||
target.NumericReply(RPL_WHOWASUSER,
|
||||
var whoWasHost Name
|
||||
|
||||
if target.modes.Has(Operator) {
|
||||
whoWasHost = whoWas.hostname
|
||||
} else {
|
||||
whoWasHost = whoWas.hostmask
|
||||
}
|
||||
|
||||
target.NumericReply(
|
||||
RPL_WHOWASUSER,
|
||||
"%s %s %s * :%s",
|
||||
whoWas.nickname, whoWas.username, whoWas.hostname, whoWas.realname)
|
||||
whoWas.nickname,
|
||||
whoWas.username,
|
||||
whoWasHost,
|
||||
whoWas.realname,
|
||||
)
|
||||
}
|
||||
|
||||
func (target *Client) RplEndOfWhoWas(nickname Name) {
|
||||
|
@@ -2,9 +2,7 @@ package irc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
//"sync"
|
||||
|
||||
sync "github.com/sasha-s/go-deadlock"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SaslState struct {
|
||||
|
@@ -44,13 +44,16 @@ type Server struct {
|
||||
accounts PasswordStore
|
||||
password []byte
|
||||
signals chan os.Signal
|
||||
done chan bool
|
||||
whoWas *WhoWasList
|
||||
ids map[string]*Identity
|
||||
}
|
||||
|
||||
var (
|
||||
SERVER_SIGNALS = []os.Signal{syscall.SIGINT, syscall.SIGHUP,
|
||||
syscall.SIGTERM, syscall.SIGQUIT}
|
||||
SERVER_SIGNALS = []os.Signal{
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
}
|
||||
)
|
||||
|
||||
func NewServer(config *Config) *Server {
|
||||
@@ -70,13 +73,14 @@ func NewServer(config *Config) *Server {
|
||||
operators: config.Operators(),
|
||||
accounts: NewMemoryPasswordStore(config.Accounts(), PasswordStoreOpts{}),
|
||||
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
|
||||
done: make(chan bool),
|
||||
whoWas: NewWhoWasList(100),
|
||||
ids: make(map[string]*Identity),
|
||||
}
|
||||
|
||||
log.Debugf("accounts: %v", config.Accounts())
|
||||
|
||||
// TODO: Make this configurabel?
|
||||
// TODO: Make this configureable?
|
||||
server.ids["global"] = NewIdentity(config.Server.Name, "global")
|
||||
|
||||
if config.Server.Password != "" {
|
||||
@@ -123,15 +127,22 @@ func NewServer(config *Config) *Server {
|
||||
},
|
||||
)
|
||||
|
||||
// server clients gauge
|
||||
// server registered (clients) gauge
|
||||
server.metrics.NewGaugeFunc(
|
||||
"server", "clients",
|
||||
"server", "registered",
|
||||
"Number of registered clients connected",
|
||||
func() float64 {
|
||||
return float64(server.clients.Count())
|
||||
},
|
||||
)
|
||||
|
||||
// server clients gauge (by secure/insecure)
|
||||
server.metrics.NewGaugeVec(
|
||||
"server", "clients",
|
||||
"Number of registered clients connected (by secure/insecure)",
|
||||
[]string{"secure"},
|
||||
)
|
||||
|
||||
// server channels gauge
|
||||
server.metrics.NewGaugeFunc(
|
||||
"server", "channels",
|
||||
@@ -162,7 +173,7 @@ func NewServer(config *Config) *Server {
|
||||
func (server *Server) Wallops(message string) {
|
||||
text := NewText(message)
|
||||
server.clients.Range(func(_ Name, client *Client) bool {
|
||||
if client.flags[WallOps] {
|
||||
if client.modes.Has(WallOps) {
|
||||
server.metrics.Counter("client", "messages").Inc()
|
||||
client.replies <- RplNotice(server, client, text)
|
||||
}
|
||||
@@ -191,13 +202,22 @@ func (server *Server) Shutdown() {
|
||||
server.Global("shutting down...")
|
||||
}
|
||||
|
||||
func (server *Server) Stop() {
|
||||
server.done <- true
|
||||
}
|
||||
|
||||
func (server *Server) Run() {
|
||||
done := false
|
||||
for !done {
|
||||
for {
|
||||
select {
|
||||
case <-server.done:
|
||||
return
|
||||
case <-server.signals:
|
||||
server.Shutdown()
|
||||
done = true
|
||||
// Give at least 1s for clients to see the shutdown
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
server.Stop()
|
||||
}()
|
||||
|
||||
case conn := <-server.newConns:
|
||||
go NewClient(server, conn)
|
||||
@@ -217,6 +237,12 @@ func (s *Server) acceptor(listener net.Listener) {
|
||||
}
|
||||
log.Debugf("%s accept: %s", s, conn.RemoteAddr())
|
||||
|
||||
if _, ok := conn.(*tls.Conn); ok {
|
||||
s.metrics.GaugeVec("server", "clients").WithLabelValues("secure").Inc()
|
||||
} else {
|
||||
s.metrics.GaugeVec("server", "clients").WithLabelValues("insecure").Inc()
|
||||
}
|
||||
|
||||
s.connections.Inc()
|
||||
s.newConns <- conn
|
||||
}
|
||||
@@ -382,7 +408,7 @@ func (msg *RFC2812UserCommand) HandleRegServer(server *Server) {
|
||||
flags := msg.Flags()
|
||||
if len(flags) > 0 {
|
||||
for _, mode := range flags {
|
||||
client.flags[mode] = true
|
||||
client.modes.Set(mode)
|
||||
}
|
||||
client.RplUModeIs(client)
|
||||
}
|
||||
@@ -469,7 +495,7 @@ func (msg *AuthenticateCommand) HandleRegServer(server *Server) {
|
||||
client.RplLoggedIn(authcid)
|
||||
client.RplSaslSuccess()
|
||||
|
||||
client.flags[Registered] = true
|
||||
client.modes.Set(Registered)
|
||||
client.Reply(
|
||||
RplModeChanges(
|
||||
client, client,
|
||||
@@ -598,15 +624,19 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) {
|
||||
}
|
||||
server.metrics.Counter("client", "messages").Inc()
|
||||
target.Reply(RplPrivMsg(client, target, msg.message))
|
||||
if target.flags[Away] {
|
||||
if target.modes.Has(Away) {
|
||||
client.RplAway(target)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) WhoisChannelsNames() []string {
|
||||
func (client *Client) WhoisChannelsNames(target *Client) []string {
|
||||
chstrs := make([]string, client.channels.Count())
|
||||
index := 0
|
||||
client.channels.Range(func(channel *Channel) bool {
|
||||
if !CanSeeChannel(target, channel) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch {
|
||||
case channel.members.Get(client).Has(ChannelOperator):
|
||||
chstrs[index] = "@" + channel.name.String()
|
||||
@@ -643,7 +673,7 @@ func (m *WhoisCommand) HandleServer(server *Server) {
|
||||
|
||||
func whoChannel(client *Client, channel *Channel, friends *ClientSet) {
|
||||
channel.members.Range(func(member *Client, _ *ChannelModeSet) bool {
|
||||
if !client.flags[Invisible] || friends.Has(client) {
|
||||
if !client.modes.Has(Invisible) || friends.Has(client) {
|
||||
client.RplWhoReply(channel, member)
|
||||
}
|
||||
return true
|
||||
@@ -685,8 +715,8 @@ func (msg *OperCommand) HandleServer(server *Server) {
|
||||
return
|
||||
}
|
||||
|
||||
client.flags[Operator] = true
|
||||
client.flags[WallOps] = true
|
||||
client.modes.Set(Operator)
|
||||
client.modes.Set(WallOps)
|
||||
client.RplYoureOper()
|
||||
client.Reply(
|
||||
RplModeChanges(
|
||||
@@ -701,7 +731,7 @@ func (msg *OperCommand) HandleServer(server *Server) {
|
||||
|
||||
func (msg *RehashCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
if !client.flags[Operator] {
|
||||
if !client.modes.Has(Operator) {
|
||||
client.ErrNoPrivileges()
|
||||
return
|
||||
}
|
||||
@@ -726,9 +756,9 @@ func (msg *RehashCommand) HandleServer(server *Server) {
|
||||
func (msg *AwayCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
if len(msg.text) > 0 {
|
||||
client.flags[Away] = true
|
||||
client.modes.Set(Away)
|
||||
} else {
|
||||
delete(client.flags, Away)
|
||||
client.modes.Unset(Away)
|
||||
}
|
||||
client.awayMessage = msg.text
|
||||
}
|
||||
@@ -753,7 +783,7 @@ func (msg *MOTDCommand) HandleServer(server *Server) {
|
||||
func (msg *NoticeCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
|
||||
if msg.target == "*" && client.flags[Operator] {
|
||||
if msg.target == "*" && client.modes.Has(Operator) {
|
||||
server.Global(msg.message.String())
|
||||
return
|
||||
}
|
||||
@@ -813,7 +843,7 @@ func (msg *ListCommand) HandleServer(server *Server) {
|
||||
|
||||
if len(msg.channels) == 0 {
|
||||
server.channels.Range(func(name Name, channel *Channel) bool {
|
||||
if !client.flags[Operator] && channel.flags.Has(Private) {
|
||||
if !CanSeeChannel(client, channel) {
|
||||
return true
|
||||
}
|
||||
client.RplList(channel)
|
||||
@@ -822,7 +852,7 @@ func (msg *ListCommand) HandleServer(server *Server) {
|
||||
} else {
|
||||
for _, chname := range msg.channels {
|
||||
channel := server.channels.Get(chname)
|
||||
if channel == nil || (!client.flags[Operator] && channel.flags.Has(Private)) {
|
||||
if channel == nil || !CanSeeChannel(client, channel) {
|
||||
client.ErrNoSuchChannel(chname)
|
||||
continue
|
||||
}
|
||||
@@ -902,7 +932,7 @@ func (msg *LUsersCommand) HandleServer(server *Server) {
|
||||
|
||||
func (msg *WallopsCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
if !client.flags[Operator] {
|
||||
if !client.modes.Has(Operator) {
|
||||
client.ErrNoPrivileges()
|
||||
return
|
||||
}
|
||||
@@ -912,7 +942,7 @@ func (msg *WallopsCommand) HandleServer(server *Server) {
|
||||
|
||||
func (msg *KillCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
if !client.flags[Operator] {
|
||||
if !client.modes.Has(Operator) {
|
||||
client.ErrNoPrivileges()
|
||||
return
|
||||
}
|
||||
|
65
irc/types.go
65
irc/types.go
@@ -3,9 +3,7 @@ package irc
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
//"sync"
|
||||
|
||||
sync "github.com/sasha-s/go-deadlock"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//
|
||||
@@ -96,6 +94,67 @@ func (c *ChannelNameMap) Remove(channel *Channel) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserModeSet holds a mapping of channel modes
|
||||
type UserModeSet struct {
|
||||
sync.RWMutex
|
||||
modes map[UserMode]bool
|
||||
}
|
||||
|
||||
// NewUserModeSet returns a new UserModeSet
|
||||
func NewUserModeSet() *UserModeSet {
|
||||
return &UserModeSet{modes: make(map[UserMode]bool)}
|
||||
}
|
||||
|
||||
// Set sets mode
|
||||
func (set *UserModeSet) Set(mode UserMode) {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
set.modes[mode] = true
|
||||
}
|
||||
|
||||
// Unset unsets mode
|
||||
func (set *UserModeSet) Unset(mode UserMode) {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
delete(set.modes, mode)
|
||||
}
|
||||
|
||||
// Has returns true if the mode is set
|
||||
func (set *UserModeSet) Has(mode UserMode) bool {
|
||||
set.RLock()
|
||||
defer set.RUnlock()
|
||||
ok, _ := set.modes[mode]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Range ranges of the modes calling f
|
||||
func (set *UserModeSet) Range(f func(mode UserMode) bool) {
|
||||
set.RLock()
|
||||
defer set.RUnlock()
|
||||
for mode := range set.modes {
|
||||
if !f(mode) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representing the channel modes
|
||||
func (set *UserModeSet) String() string {
|
||||
set.RLock()
|
||||
defer set.RUnlock()
|
||||
|
||||
if len(set.modes) == 0 {
|
||||
return ""
|
||||
}
|
||||
strs := make([]string, len(set.modes))
|
||||
index := 0
|
||||
for mode := range set.modes {
|
||||
strs[index] = mode.String()
|
||||
index++
|
||||
}
|
||||
return strings.Join(strs, "")
|
||||
}
|
||||
|
||||
// ChannelModeSet holds a mapping of channel modes
|
||||
type ChannelModeSet struct {
|
||||
sync.RWMutex
|
||||
|
11
irc/utils.go
Normal file
11
irc/utils.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SHA256(data string) string {
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
@@ -1,14 +1,15 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
//PackageName package name
|
||||
// Package package name
|
||||
Package = "eris"
|
||||
|
||||
// Version release version
|
||||
Version = "1.6.0"
|
||||
|
||||
// Build will be overwritten automatically by the build system
|
||||
Build = "-dev"
|
||||
Version = "1.6.4"
|
||||
|
||||
// GitCommit will be overwritten automatically by the build system
|
||||
GitCommit = "HEAD"
|
||||
@@ -16,5 +17,5 @@ var (
|
||||
|
||||
// FullVersion display the full version and build
|
||||
func FullVersion() string {
|
||||
return Package + " v" + Version + Build + " (" + GitCommit + ")"
|
||||
return fmt.Sprintf("%s-%s@%s", Package, Version, GitCommit)
|
||||
}
|
||||
|
@@ -1,9 +1,7 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
//"sync"
|
||||
|
||||
sync "github.com/sasha-s/go-deadlock"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WhoWasList struct {
|
||||
@@ -17,6 +15,7 @@ type WhoWas struct {
|
||||
nickname Name
|
||||
username Name
|
||||
hostname Name
|
||||
hostmask Name
|
||||
realname Text
|
||||
}
|
||||
|
||||
@@ -33,6 +32,7 @@ func (list *WhoWasList) Append(client *Client) {
|
||||
nickname: client.Nick(),
|
||||
username: client.username,
|
||||
hostname: client.hostname,
|
||||
hostmask: client.hostmask,
|
||||
realname: client.realname,
|
||||
}
|
||||
list.end = (list.end + 1) % len(list.buffer)
|
||||
|
5
main.go
5
main.go
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/mmcloughlin/professor"
|
||||
"github.com/prologic/eris/irc"
|
||||
)
|
||||
|
||||
@@ -33,6 +34,10 @@ func main() {
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
if debug {
|
||||
go professor.Launch(":6060")
|
||||
}
|
||||
|
||||
config, err := irc.LoadConfig(configfile)
|
||||
if err != nil {
|
||||
log.Fatal("Config file did not load successfully:", err.Error())
|
||||
|
577
main_test.go
Normal file
577
main_test.go
Normal file
@@ -0,0 +1,577 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/renstrom/shortuuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/thoj/go-ircevent"
|
||||
|
||||
eris "github.com/prologic/eris/irc"
|
||||
)
|
||||
|
||||
const (
|
||||
TIMEOUT = 3 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
server *eris.Server
|
||||
|
||||
debug = flag.Bool("d", false, "enable debug logging")
|
||||
)
|
||||
|
||||
func setupServer() *eris.Server {
|
||||
config := &eris.Config{}
|
||||
|
||||
config.Network.Name = "Test"
|
||||
config.Server.Name = "test"
|
||||
config.Server.Description = "Test"
|
||||
config.Server.Listen = []string{":6667"}
|
||||
|
||||
// SASL
|
||||
config.Account = map[string]*eris.PassConfig{
|
||||
"admin": &eris.PassConfig{"JDJhJDA0JGtUU1JVc1JOUy9DbEh1WEdvYVlMdGVnclp6YnA3NDBOZGY1WUZhdTZtRzVmb1VKdXQ5ckZD"},
|
||||
}
|
||||
|
||||
server := eris.NewServer(config)
|
||||
|
||||
go server.Run()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func randomValidName() string {
|
||||
var name eris.Name
|
||||
for {
|
||||
name = eris.NewName(shortuuid.New())
|
||||
if name.IsNickname() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return name.String()
|
||||
}
|
||||
|
||||
func newClient(start bool) *irc.Connection {
|
||||
name := randomValidName()
|
||||
client := irc.IRC(name, name)
|
||||
client.RealName = fmt.Sprintf("Test Client: %s", name)
|
||||
|
||||
err := client.Connect("localhost:6667")
|
||||
if err != nil {
|
||||
log.Fatalf("error setting up test client: %s", err)
|
||||
}
|
||||
|
||||
if start {
|
||||
go client.Loop()
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
if *debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
} else {
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
server = setupServer()
|
||||
|
||||
result := m.Run()
|
||||
|
||||
server.Stop()
|
||||
|
||||
os.Exit(result)
|
||||
}
|
||||
|
||||
func TestConnection(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client := newClient(false)
|
||||
|
||||
client.AddCallback("001", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
|
||||
defer client.Quit()
|
||||
go client.Loop()
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSASL(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client := newClient(false)
|
||||
client.SASLLogin = "admin"
|
||||
client.SASLPassword = "admin"
|
||||
|
||||
client.AddCallback("001", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
|
||||
defer client.Quit()
|
||||
go client.Loop()
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRplWelcome(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := "Welcome to the .* Internet Relay Network .*!.*@.*"
|
||||
actual := make(chan string)
|
||||
|
||||
client := newClient(false)
|
||||
|
||||
client.AddCallback("001", func(e *irc.Event) {
|
||||
actual <- e.Message()
|
||||
})
|
||||
|
||||
defer client.Quit()
|
||||
go client.Loop()
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Regexp(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser_JOIN(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := newClient(false)
|
||||
|
||||
expected := []string{client.GetNick(), "=", "#join", fmt.Sprintf("@%s", client.GetNick())}
|
||||
actual := make(chan string)
|
||||
|
||||
client.AddCallback("353", func(e *irc.Event) {
|
||||
for i := range e.Arguments {
|
||||
actual <- e.Arguments[i]
|
||||
}
|
||||
})
|
||||
|
||||
defer client.Quit()
|
||||
go client.Loop()
|
||||
|
||||
client.Join("#join")
|
||||
client.SendRaw("NAMES #join")
|
||||
|
||||
for i := range expected {
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected[i], res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_InviteOnly(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
client1.AddCallback("324", func(e *irc.Event) {
|
||||
if strings.Contains(e.Arguments[2], "i") {
|
||||
client2.Join("#inviteonly")
|
||||
} else {
|
||||
client1.Mode("#inviteonly")
|
||||
}
|
||||
})
|
||||
|
||||
client2.AddCallback("473", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
client2.AddCallback("JOIN", func(e *irc.Event) {
|
||||
actual <- false
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
client1.Join("#inviteonly")
|
||||
client1.Mode("#inviteonly", "+i")
|
||||
client1.Mode("#inviteonly")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser_WithHostMask(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
expected := fmt.Sprintf("%x", sha256.Sum256([]byte("localhost")))
|
||||
actual := make(chan string)
|
||||
|
||||
client1.AddCallback("001", func(e *irc.Event) {
|
||||
client1.Mode(client1.GetNick(), "+x")
|
||||
})
|
||||
|
||||
client2.AddCallback("001", func(e *irc.Event) {
|
||||
client2.Whois(client1.GetNick())
|
||||
})
|
||||
|
||||
client2.AddCallback("401", func(e *irc.Event) {
|
||||
client2.Whois(client1.GetNick())
|
||||
})
|
||||
|
||||
client2.AddCallback("311", func(e *irc.Event) {
|
||||
actual <- e.Arguments[3]
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: This test is racey :/
|
||||
func TestUser_WithoutHostMask(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
expected := "localhost"
|
||||
actual := make(chan string)
|
||||
|
||||
client1.AddCallback("001", func(e *irc.Event) {
|
||||
client1.Mode(client1.GetNick(), "-x")
|
||||
})
|
||||
|
||||
client2.AddCallback("001", func(e *irc.Event) {
|
||||
client2.Whois(client1.GetNick())
|
||||
})
|
||||
|
||||
client2.AddCallback("401", func(e *irc.Event) {
|
||||
client2.Whois(client1.GetNick())
|
||||
})
|
||||
|
||||
client2.AddCallback("311", func(e *irc.Event) {
|
||||
actual <- e.Arguments[3]
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestUser_PRIVMSG(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := "Hello World!"
|
||||
actual := make(chan string)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
client1.AddCallback("001", func(e *irc.Event) {
|
||||
client1.Privmsg(client2.GetNick(), expected)
|
||||
|
||||
})
|
||||
client1.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
actual <- e.Message()
|
||||
})
|
||||
|
||||
client2.AddCallback("001", func(e *irc.Event) {
|
||||
client2.Privmsg(client1.GetNick(), expected)
|
||||
})
|
||||
client2.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
actual <- e.Message()
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_PRIVMSG(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := "Hello World!"
|
||||
actual := make(chan string)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
client1.AddCallback("JOIN", func(e *irc.Event) {
|
||||
client1.Privmsg(e.Arguments[0], expected)
|
||||
})
|
||||
client2.AddCallback("JOIN", func(e *irc.Event) {
|
||||
client2.Privmsg(e.Arguments[0], expected)
|
||||
})
|
||||
|
||||
client1.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
actual <- e.Message()
|
||||
})
|
||||
client2.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
actual <- e.Message()
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
client1.Join("#channelprivmsg")
|
||||
client2.Join("#channelprivmsg")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_NoExternal(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client1 := newClient(true)
|
||||
client2 := newClient(true)
|
||||
|
||||
client1.AddCallback("JOIN", func(e *irc.Event) {
|
||||
channel := e.Arguments[0]
|
||||
if channel == "#noexternal" {
|
||||
if e.Nick == client1.GetNick() {
|
||||
client2.Privmsg("#noexternal", "FooBar!")
|
||||
} else {
|
||||
assert.Fail(fmt.Sprintf("unexpected user %s joined %s", e.Nick, channel))
|
||||
}
|
||||
} else {
|
||||
assert.Fail(fmt.Sprintf("unexpected channel %s", channel))
|
||||
}
|
||||
})
|
||||
|
||||
client2.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
if e.Arguments[0] == "#noexternal" {
|
||||
actual <- false
|
||||
}
|
||||
})
|
||||
client2.AddCallback("404", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
client1.Join("#noexternal")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_SetTopic_InvalidChannel(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client1 := newClient(false)
|
||||
|
||||
client1.AddCallback("403", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
go client1.Loop()
|
||||
|
||||
client1.SendRaw("TOPIC #invalidchannel :FooBar")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_SetTopic_NotOnChannel(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
client1.AddCallback("442", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
client2.AddCallback("JOIN", func(e *irc.Event) {
|
||||
client1.SendRaw("TOPIC #notonchannel :FooBar")
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
go client1.Loop()
|
||||
|
||||
client2.Join("#notonchannel")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_BadChannelKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client1 := newClient(false)
|
||||
client2 := newClient(false)
|
||||
|
||||
client1.AddCallback("324", func(e *irc.Event) {
|
||||
if strings.Contains(e.Arguments[2], "k") {
|
||||
client2.Join(e.Arguments[1])
|
||||
} else {
|
||||
client1.Mode("#badchannelkey")
|
||||
}
|
||||
})
|
||||
|
||||
client2.AddCallback("JOIN", func(e *irc.Event) {
|
||||
if e.Nick == client2.GetNick() && e.Arguments[0] == "#badchannelkey" {
|
||||
actual <- false
|
||||
}
|
||||
})
|
||||
client2.AddCallback("475", func(e *irc.Event) {
|
||||
actual <- true
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
client1.Join("#badchannelkey")
|
||||
client1.Mode("#badchannelkey", "+k", "opensesame")
|
||||
client1.Mode("#badchannelkey")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannel_GoodChannelKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
expected := true
|
||||
actual := make(chan bool)
|
||||
|
||||
client1 := newClient(true)
|
||||
client2 := newClient(true)
|
||||
|
||||
client1.AddCallback("324", func(e *irc.Event) {
|
||||
if strings.Contains(e.Arguments[2], "k") {
|
||||
client2.SendRawf("JOIN %s :opensesame", e.Arguments[1])
|
||||
} else {
|
||||
client1.Mode("#goodchannelkey")
|
||||
}
|
||||
})
|
||||
|
||||
client2.AddCallback("JOIN", func(e *irc.Event) {
|
||||
if e.Nick == client2.GetNick() && e.Arguments[0] == "#goodchannelkey" {
|
||||
actual <- true
|
||||
}
|
||||
})
|
||||
client2.AddCallback("475", func(e *irc.Event) {
|
||||
actual <- false
|
||||
})
|
||||
|
||||
defer client1.Quit()
|
||||
defer client2.Quit()
|
||||
go client1.Loop()
|
||||
go client2.Loop()
|
||||
|
||||
client1.Join("#goodchannelkey")
|
||||
client1.Mode("#goodchannelkey", "+k", "opensesame")
|
||||
client1.Mode("#goodchannelkey")
|
||||
|
||||
select {
|
||||
case res := <-actual:
|
||||
assert.Equal(expected, res)
|
||||
case <-time.After(TIMEOUT):
|
||||
assert.Fail("timeout")
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ fi
|
||||
echo -n "Building binaries ... "
|
||||
|
||||
GOOS=linux GOARCH=amd64 go build -o ./bin/eris-Linux-x86_64 .
|
||||
GOOS=linux GOARCH=arm64 go build -o ./bin/eris-Linux-x86_64 .
|
||||
GOOS=linux GOARCH=arm64 go build -o ./bin/eris-Linux-arm_64 .
|
||||
GOOS=darwin GOARCH=amd64 go build -o ./bin/eris-Darwin-x86_64 .
|
||||
GOOS=windows GOARCH=amd64 go build -o ./bin/eris-Windows-x86_64.exe .
|
||||
|
||||
|
1
vendor/github.com/DanielOaks/girc-go
generated
vendored
1
vendor/github.com/DanielOaks/girc-go
generated
vendored
Submodule vendor/github.com/DanielOaks/girc-go deleted from 3a2b80af9b
1
vendor/github.com/beorn7/perks
generated
vendored
1
vendor/github.com/beorn7/perks
generated
vendored
Submodule vendor/github.com/beorn7/perks deleted from 4c0e84591b
1
vendor/github.com/golang/protobuf
generated
vendored
1
vendor/github.com/golang/protobuf
generated
vendored
Submodule vendor/github.com/golang/protobuf deleted from 1e59b77b52
1
vendor/github.com/goshuirc/e-nfa
generated
vendored
1
vendor/github.com/goshuirc/e-nfa
generated
vendored
Submodule vendor/github.com/goshuirc/e-nfa deleted from 7071788e39
1
vendor/github.com/imdario/mergo
generated
vendored
1
vendor/github.com/imdario/mergo
generated
vendored
Submodule vendor/github.com/imdario/mergo deleted from 7fe0c75c13
1
vendor/github.com/matttproud/golang_protobuf_extensions
generated
vendored
1
vendor/github.com/matttproud/golang_protobuf_extensions
generated
vendored
Submodule vendor/github.com/matttproud/golang_protobuf_extensions deleted from c12348ce28
1
vendor/github.com/prometheus/client_golang
generated
vendored
1
vendor/github.com/prometheus/client_golang
generated
vendored
Submodule vendor/github.com/prometheus/client_golang deleted from 5cec1d0429
1
vendor/github.com/prometheus/client_model
generated
vendored
1
vendor/github.com/prometheus/client_model
generated
vendored
Submodule vendor/github.com/prometheus/client_model deleted from 6f38060186
1
vendor/github.com/prometheus/common
generated
vendored
1
vendor/github.com/prometheus/common
generated
vendored
Submodule vendor/github.com/prometheus/common deleted from e3fb1a1acd
1
vendor/github.com/prometheus/procfs
generated
vendored
1
vendor/github.com/prometheus/procfs
generated
vendored
Submodule vendor/github.com/prometheus/procfs deleted from a6e9df898b
1
vendor/github.com/sirupsen/logrus
generated
vendored
1
vendor/github.com/sirupsen/logrus
generated
vendored
Submodule vendor/github.com/sirupsen/logrus deleted from 89742aefa4
1
vendor/golang.org/x/crypto
generated
vendored
1
vendor/golang.org/x/crypto
generated
vendored
Submodule vendor/golang.org/x/crypto deleted from 9f005a07e0
1
vendor/golang.org/x/sys
generated
vendored
1
vendor/golang.org/x/sys
generated
vendored
Submodule vendor/golang.org/x/sys deleted from 0dd5e194bb
1
vendor/golang.org/x/text
generated
vendored
1
vendor/golang.org/x/text
generated
vendored
Submodule vendor/golang.org/x/text deleted from 88f656faf3
1
vendor/gopkg.in/yaml.v2
generated
vendored
1
vendor/gopkg.in/yaml.v2
generated
vendored
Submodule vendor/gopkg.in/yaml.v2 deleted from 287cf08546
Reference in New Issue
Block a user