aboutsummaryrefslogtreecommitdiff
path: root/CHANGELOG.md
blob: eab49ce0575b519279aed44b2b217b9f20f0b8d3 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
# TBD - BitBot v1.20.0

Added:
- `ban-enforce.py` to kick people that match a new ban mask
- MLOCK-like functionality in `channel_op.py`
- Channels can be opted out of inactivity pruning
- Optional to disable youtubeifying `!np` output
- Allow RSS requests to bind to specific source addresses
- `!words` in PM
- `yourls.py` for yourl url shortners
- handle `RPL_VISIBLEHOST`

Changed:
- `ERR_LINKCHANNEL` (470) now removes the initial channel from autojoin
- (IRCv3) `+draft/typing` was ratified
- We're no longer supporting a specific broken charybdis install, for line length calculations
- Much better line truncation
- Handle HTTP redirects ourselves, to avoid redirects on to forbidden hosts

Fixed:
- All bot.conf paths should have ~/{DATA} expanded
- `host-meta` URL for fediverse accounts should be optional - fallback to default webfinger
- Message filter `m/` criterias should operate on formatting-stripped lines
- Quote `!grab`s were wiping the user's category
- Quote `!quotedel`s were looking at the wrong categories
- `!words` was squashing results in to a dict, losing days
- INVEX and EXCEPT lists were looking at the wrong index for masks

# 2020-02-29 - BitBot v1.19.0 ("Command Specs Spark Joy")

Added:
- Commands Specs. expression language for defining format of command args
- `.bitbot/mod-data/` for addition data files for modules
- Concept of core vs additional modules. Core modules can be reloaded but not unloaded
- `channel_access` groups: `low`/`high`/`admin`
- Proof of concept reminders on a cron schedule (`cron_reminders.py`)
- Show how many seconds by which you missed a duck (`ducks.py`)
- Show gitlab wiki events (`git_webhooks`)
- Opt-in prune inactive channels after 2 weeks (`inactive_channels.py`)
- Ability to export decorated functions with @utils.export
- Configrable chance of randomly triggered markovs (`markov.py`)
- Ability to rewrite messages as well as block them (`message_filters.py`)
- Ability to search through recent messages (`messages.py`)
- `rainbow.py` because rainbow text is fun
- Track time/commit of load and events handled per module and show it through `!modinfo` (`modules.py`)
- Support ranges in stepped cron schedules (`cron.py`)
- Format `ACCOUNT` events (`format_activity.py`)
- Track channel list modes like ban, quiet, invex, ban exceptions (`mode_lists.py`)
- Support `WATCH` when available for `nick_regain.py`

Changed:
- IRCv3's `labeled-response` was ratified
- IRCv3's `setname` was ratified
- `channel_log` now logs to `.bitbot/mod-data/channel_log/`
- `channel_log` can now RSA+AES(CBC) encrypt log files
- Word tracking is now done per day. use `migration/v01.19.0-words.py` to migrate old data
- Totally rewrote `badges.py` command interface
- Huge refactor and rework of `channel_op.py` - most commands can take duration and globs now
- Improve `factoids.py` (including nested factoids)
- Split out `src/utils/datetime.py`
- Show github webhook issue/PR titles in more places (`git_webhooks`)
- Show google result descriptions instead of title when available (`google.py`)
- Minimal formatted lines should be relayed (`relay.py`)
- Totally rewrote `todo.py` command interface
- `IRCBuffer` now holds 1024 lines of history (was 64; 1024 might be overkill...)
- Reloaded modules will always be rolled back to previous loaded module if reload fails
- Generate usage from command spec stings when present (`help.py`)
- Much cleaner support for handling server `NOTICE`s (`line_handler`)
- `to_pretty_time` can now do relative time (e.g. including years and months)
- Souped HTTP responses changed from `lxml` parsing to `html5lib` by default
- Bool config options now accept 0 and 1

Fixed:
- Readded lost support for SASL `USERPASS` pseudo algo (`ircv3_sasl`)
- Minor typo in `healthchecks.py`
- Minor typo in `votes.py`
- Crash caused by division by zero in `title.py` difference checking
- `internal.identified` command was being fired for every message with an `@account` tag (`permissions`)

# 2020-01-20 - BitBot v1.18.2

Changed:
- Colourise server address in `server-notice` formatting (`format_activity.py`)

Fixed:
- `user` variable doesn't exist in `INVITE` formatting code (`format_activity.py`)
- `IRCBuffer.Buffer.find()`'s `not_pattern` arg should be optional
- `utils.datetime.iso8601_parse` no longer has a `microseconds` arg (`youtube.py`)

# 2020-01-20 - BitBot v1.18.1

Fixed:
- Formatting variable typo for handling TOPIC (`format_activity.py`)

# 2020-01-20 - BitBot v1.18.0

Added:
- New dependency in `requirements.txt`: `dateutils`
- Show `transferred` github issues by default (`git_webhooks`)
- `hostmask-tracking.py` - keep a history of what hostmasks each nickname has used
- Also watch `NICK` and `QUIT` lines to see when our nickname might be freee (`nick_regain.py`)
- IRCv3 `draft/delete` implementation (`ircv3_editmsg.py`)
- Show account and realname in `JOIN` formattin when available (`format_activity.py`)

Changed:
- Removed `--data-dir`, `--database` and `--log-dir`, these options have been moved to `bot.conf`
- Reworded karma change output
- `!grab` not tries to attach quote to account, not just nickname (`quotes.py`)
- `!wordiest` not defaults to the current channel (`words.py`)
- `!part` now works for `+o` users or users with channel_access `part` (`admin.py`)

Fixed:
- cron would fail to initialise at 59 minutes past the hour (`core_modules/cron.py`)
- Don't send typing notications by default for pattern-based commands
- Regex error when replacement starts with a number (`sed.py`)
- Reimplement lost IRCv3 `account-tag` functionality (`permissions`)
- Show username when fediverse displayname is "" (`fediverse`)
- `++asd++` used to give karma to both `asd++` and `++asd` (`karma.py`)

# 2020-01-08 - BitBot v1.17.2

Fixed:
- Incorrect format for HTTP UserAgent (missing ")")

# 2019-12-13 - BitBot v1.17.1

Fixed:
- Crash caused by switching `coins.py` to using `cron` scheduling without removing timer-related code
- Typo in function call name in `cron.py`

# 2019-12-13 - BitBot v1.17.0

Added:
- Ability to `.save()` `bot.conf` - we now use only this for module whitelist/blacklist
- A cron system (`src/core_modules/cron.py`)
- `healthcheck.py` - ping a URL every 10 minutes (for uptime-tracking services)
- Support `++nickname` style karma (`karma.py`)
- Bot-wide aliases (`!balias` in `aliases.py`)
- Support per-user (PM) `command-method` setting (`commands`)
- `dnsbl` module to loop up given IPv4 and IPv6 addresses in blacklists

Changed:
- "Core" modules (modules needed for base operation of bitbot) moved to `src/core_modules` and made blacklist-immune
- Better parsing error for `!config u birthday` (`birthday.py`)
- Show display name, not username, when available (`fediverse.py`)
- By default, show `locked`/`unlocked` github issue/PR events (`git_webhooks`)
- Switch back to full wolframalpha API but use it better than we used to (`wolframalpha.py`)
- Hostmasks are now precompiled to find users that match them (`permissions`)

Removed:
- `-m`/`-M` args to `bitbotd` - didn't work any more due to databse locking
- `database_backup.py` - this was always a weird hack. Added a note about backups in `README.md`

# 2019-12-01 - BitBot v1.16.1

Changed:
- Response to giving karma now makes sense

Fixed:
- Typo preventing users giving karma

# 2019-12-01 - BitBot v1.16.0

Added:
- Show target in formatted private `NOTICE` events (`format_activity.py`)
- Show first 100 chars of GitHub issue comments (`git_webhooks`)
- Show who opened a GitHub pull request when approperiate (`git_webhooks`)
- Timed user ignores (`ignore.py`)
- Support `"$$"` as `"$"` in command alias argument replacement (`aliases.py`)
- Show explicitly when only a title of an issue/PR was changed (`git_webhooks`)
- Show URLs in `!wikipedia` output (`wikipedia.py`)
- `!raw` and `!perform` now support "human" formats (e.g. `/msg user message`)
- Listing servers with `!servers` (`stats.py`)

Changed:
- Complete refactor of `permissions` module - now with hostmask support
- Big refactor of commands module
- Command aliases moved to their own module. Run `migration/v01.16.00-aliases.py` to migrate old aliases.
- Karma is now stored per-user and you can give yourself karma. Run `migration/v01.16.00-karma` to migrate old karma.
- Unique User-Agent (`utils.http.USERAGENT`)
- Nickname hashed colourisation now matches weechat's default (`utils.irc`)
- `./bitbotd -m permissions -M master-password` moved to `./bitbotctl command master-password`
- `utils.http.Response.data` is now always `bytes` - use `.decode()`/`.json()`/`.soup()`
- `to.py` -> `tell.py`; move `!to` to `!tell` but add `!to` as an alias
- Sed edits are now cumulative (`sed.py`)

# 2019-11-18 - BitBot v1.15.0

Added:
- Optionally colourise nicknames when printed to log (`format_activity.py` and `print_activity.py`)
- Show git branch in `!version` output (`info.py`)
- Stop/start REST API HTTPd when trying to reload all modules (`ModuleManager` and `rest_api.py`)
- Individual channels can opt out of printing to INFO log (`!config c print off`, `print_activity.py`)
- Opt-in shlex argument parsing for command callbacks (`commands`)
- Show when BitBot first saw you speak in `!words` output (`words.py`)

Changed:
- Logging moved to `~/.bitbot/logs/` by default
- Better eval API for `eval_python.py` (now py3 only)
- Better single-line normalisation for ActivityPub Activities (`fediverse`)
- Better error messages shown to user when a fediverse Actor can't be found (`fediverse`)
- Git hashes are now truncated to 7 chars, not 8 (`git_webhooks`)
- Split `utils/__init__.py` out in to more separate files
- Show channel mode status symbols when formatting `NOTICE`s (`format_activity.py`)
- Conbine YouTube API requests in to 1 request, rather than 3 (`youtube.py`)

Fixed:
- Multiple channel keys should be expressed as comma-separated (`channel_keys.py`)
- `!duckstats <nickname>` was meant to show stats for another user (`ducks.py`)
- Fix almost completely broken 908 (`RPL_SASLMECHS`) handling
- Background colour formatting was being lost (`utils.irc`)

Removed:
- `tfl.py` moved to `bitbot-modules`

# 2019-11-04 - BitBot v1.14.0

Added:
- Add Travis config to run `mypy`
- `!cmute`/`!cunmute` to mute/unmute a channel (`+m`) (`admin.py`)
- Support GitLab notes and confidential issues (`git_webhooks`)
- Strip `"'<>()` from words in titles when checking difference between a `<title>` and URL (`title.py`)
- Show when a youtube video was uploaded (`youtube.py`)
- 15 second SASL handshake timeout (`ircv3_sasl`)
- Ability to disable command suggestions (`command_suggestions.py`)
- Format/print/log `CHGHOST` events
- Show when a `!config` option has remained unchanged (`config.py`)
- Show GMT offset in `!time` output (`user_time.py`)

Changed:
- `/me` lines are no longer added to markov chains
- `!seen` last action (`seen-info`) is now per-channel, not per-network (`seen.py`)

Fixed:
- Rectified `src/utils/` circular dependency mess
- Decode fediverse data as utf8
- A bunch of typehinting errors across `src/`
- Switch to `lxml` for fediverse note parsing. `html.parser` was buggy and unpredictable (`fediverse`)

Removed:
- `!timezone`

# 2019-10-25 - BitBot v1.13.0

Added:
- `setup.py`
- `bitbotctl` - daemon control system through unix domain sockets
- Basic GitLab webhook support in `git_webhooks`
- Catch `m.youtube.com` URLs in `auto-youtube`
- Support UTF8 domains by punycode (idna) encoding
- `!` param in `!fedi` to denote "show content-warned content"
- Opt-in URL shortening for Gitea and GitLab
- `?channels=` GET param for git_webhooks to denote what channels should show activity
- `votes-cast-restricted` to restrict voting to voiced-or-above users

Changed:
- `start.py` -> `bitbotd`
- Database and config are now expected in `~/.bitbot/` (unless `--database` and `--config` are specified)
- Database backup files are now in `bot.db.{time}.back` format
- Don't say a users vote was changed when it wasn't (`vote.py`)
- Bot will not die when there's no connected servers
- Command output will be truncated/cut at "word bounaries" (currently only space)
- `!reloadallmodules` and `SIGUSR1` will not try to reload but rollback to currently-loaded on error
- `IRCBot.panic()` now just calls `sys.exit(20)` instead of trying to kill event loop

Fixed:
- Any user was able to add an API KEY (missing `permission` kwarg)
- Be able to `!disconnect` reconnection attempts (regression, `admin.py`)
- Strip only unknown tags from fedi `Note` activities - not the tag content too
- Don't allow users to `!bef`/`!trap` a triggered duck before it has quacked
- Don't set `location` to just a string when we decide a `!weather` arg is not a nickname
- We were not pulling out account ID from `WHOX` (just account name)
- Outdated `tornado` version in `requirements.txt`

Removed:
- `cve.py`
- `EVENTS.md`

# 2019-10-10 - BitBot v1.12.0

Added:
- Basic lock file mechanics (`src/LockFile.py`)
- `external_modules` - an arbitrary directory in which to look for modules
- `channel_blacklist.py` - rewrite `JOIN`s and send instant `PART`s to avoid the bot being in certain channels
- Ability to specify different `channel_op.py` `ban-mask` for users with accounts
- `!invite` in `channel_op.py`
- `check_certificate.py` - warn when a client certificate is close to expiration (or already expired)
- Support `$-` format for alias arg replacement - means "none or all args"
- Support `!config <nickname>`, `!config <#channel>` and `!config *`
- Support dice roll modifiers (e.g. `!roll 2d20+1-2`) in `dice.py`
- Opt-out highlight prevention for `!friends`/`!enemies` in `ducks.py`
- Use a random delay after a duck is triggered before showing the duck (`ducks.py`)
- `!friends`/`!enemies` now defaults to the current channel (`ducks.py`)
- `!action` and `!msg` in `echo.py`
- `git-show-private` channel setting to enable showing private github/gitea webhooks
- `!ghcommit` and support `auto-github` for `@commit`
- `!which` - show what module provides a command
- `!apropos` - show commands that contain a given string
- `!tcpup` - check if a given `host:port` can receive a TCP connection
- `markov.py`
- `mumble.py` - show stats for a given mumble server
- `!grab` as an alias of `!quotegrab`
- `!crate` in `rust.py` - show information on a given crate name
- Support sed `&` syntax
- Support `"<nickname>: s/"` sed syntax (search only for messages by `<nickname>`)
- `!silence` now takes an optional duration parameter
- `!unsilence`
- `!stats` not takes an optional network parameter - to show stats only for a given server
- `!channels` - list the bot's current channels on the current network
- `strip_otr.py` - remove trail whitespace used for automated OTR handshakes
- Support disabling `words.py` word tracking for a while channel
- `utils.deadline_process` - like `@utils.deadline` but uses a subprocess
- Single-line normalisation of fediverse Notes by vaguely parsing HTML
- Single-line normalisation of RSS titles
- Single-line normalisation of tweets

Changed:
- REST API only listens on localhost now, for security reasons
- `!changenickname` -> `!nick`
- `!reconnect` can take an `<alias>` param to tell it which server to reconnect to
- `!disconnect` can now cancel reconnections
- `auto_mode.py` was replaced by `!flags` in `channel_op.py`
- `!mute` masks should also take in to consideration the configured `ban-mask`
- Obscure output of `!config server sasl`
- `channel.get_user_status()` -> `channel.get_user_modes()`
- `!gh`/`!ghissue`/`!ghpull` can now see private repos through API keys
- Allow preventing user-specified nameservers with `!dns`
- Update `ircv3_multiline.py` to `draft/multiline`
- Prevent invalid SASL mechanisms
- `sasl-hard-fail` now defaults to False
- Multi-word karma now needs parentheses, along with a few other tweaks to prevent false positives
- `channel.topic_setter` is now a `Hostmask` object
- Obscure output of `!config server nickserv-password`
- `!quotegrab` can take a number of lines to grab
- `relay-extras` defaults to `False`
- `rest_api` responses now use a `Response` object for complex data and headers
- `!apikey` now has the subcommands `list`, `add`, `remove` and `info`
- `eval_rust.py` -> `rust.py`
- `!silence` now affects command usage errors and command suggestions
- Only do `auto-title` if the `<title>` and URL are significantly similar
- Use `"#<number>"` to denote `!ud` definition index
- Don't use an API for `user_time.py` - use `pytz`
- Support showing `!time` and `!weather` for a location, not just a user
- `Cache.py` is now more of a `key:value` store
- Temporarily cache IRCChannel settings in memory
- `utils.http.request()` now supports a complex request object
- `utils.http.request()` now uses more factors to detect a HTML page's encoding
- `utils.http.is_localhost()` -> `utils.http.host_permitted()` and reject more IP ranges
- `!editserver` should work for currently-disconnected servers
- `INFO` logging should go to a file - stdout should only be `WARN`

Fixed:
- `badges.py` now forces UTC
- `!op`/`!voice` in `channel_op.py` tried to use `send_mode()` without array arguments
- `!cignore` was not working for non-bot-admin users
- `!tw` should be asking for `tweet_mode="extended"`
- Fix `server.send_invite()` arg order
- `masterlogin` should only allow you to bypass `permission` checks, not e.g. `require_access` or `require_mode`

Removed:
- `books.py`
- `botsnack.py`
- `check_urls.py`
- `mixed_unicode.py`
- `!qget` - functionality moved to `!quote`
- `shakespeare.py`
- `slowvoice.py`
- `strax.py`
- `timer.py`

# 2019-08-30 - BitBot v1.11.1

Added:
- `utils.IntRangeSetting`
- `realname` was missing from `!editserver`

Changed:
- Added `"- "` to start of formatted kick lines
- Use `"+0000"` instead of `"Z"` for UTC timezone

Fixed:
- Put a deadline on sed matches to prevent DoS
- Duplicate `def op` in `channel_op.py` (due to copypaste)
- `git-prevent-highlight` was failing to unhighlight organisations

# 2019-08-15 - BitBot v1.11.0

Added:
- `rss.py`
- Show `weather.py` windspeed in MPh too
- `git_webhooks/gitea.py`
- `acronym.py`
- `!editserver` in `admin.py`
- `channel_keys.py` to centrally track/use channel keys
- `!mute` and `!unmute` in `channel_op.py`
- `command_suggestions.py`
- appendable command prefixes
- `@utils.kwarg`
- `fediverse.py`
- gitea webhooks (`git_webhooks/gitea.py`)
- Show available `!hash` algorithms
- per-channel-per-user ignores (`ignore.py`, `!cignore`)
- `ircv3.py` - to show ircv3 support stats
- `isup.py`
- `kick_rejoin.py`
- Handle `ERR_UNAVAILRESOURCE`
- `onionoo.py` (thanks @irl)
- `ops.py` to highlight ops (opt-in)
- Per-channel `perform.py` (`!cperform`)
- `proxy.py`
- Configurable URL shorteners (`shorturl.py`)
- `!unshorten` (`shorturl.py`)
- `slowvoice.py`
- `throttle.py`
- `!timezone` (`user_time.py`)
- Show `!weather` target nickname in command prefix
- Parse youtube playlists (`youtube.py`)
- `utils.http.url_sanitise()`
- `utils.http.request_many()`
- `./start.py --startup-disconnects`
- `./start.py --remove-server <alias>`
- `!remindme` as an alias of `!in` (`in.py`)
- `!source` and `!version` (`info.py`)
- Show TTL for DNS records (`ip_addresses.py`)
- `!addpoint`/`!rmpoint` as more explicit `++`/`--` for karma (`karma.py`)

Changed:
- Move `_check()` call to event loop func
- Split out github webhook functionality to `git_webhooks/github.py`
- Refactored @utils.export settings to be object-oriented
- Warn when channel-only/private-only is not met
- `8ball.py` -> `eightball.py` (can't import things that start with a digit)
- `github.py` -> `git_webhooks`
- revamp `!dns` to take optional nameserver and record typ
- `!quotedel` without quote removes most recent
- Relays moved to relay "groups" that channels can "join" and "leave"
- Rewrote `EventManager` for efficiency and simplicity
- Moved timers/cache/etc from read loop to event loop
- Better and more exhaustive channel move tracking
- Don't silently truncate `ParsedLine` at newline
- `@utils.hook`/`@utils.export` now use a single object that handles parsing
- `!ban`/`!kickban`/`!mute` duration syntax changed (`channel_op.py`)
- Highlight spam protection logic moved to own module (`highlight_spam.py`)
- `IRCBuffer.find()` returns the matched string
- Positive and negative karma throttled seperately (`karma.py`)
- REST API now listens in IPv6 (`rest_api.py`)

Fixed:
- Catch and rethrow not-found definitions in `define.py`
- `ircv3_botignore.py` event priority
- `CAP DEL` crash when `DEL`ing something that was not advertised
- `ParsedLine.format()` didn't prefix `source` with `":"`
- `_write_buffer` locking to avoid race condition
- `Capability().copy().depends_on` was mutable to the original copy

# 2019-06-23 - BitBot v1.10.0

Added:
- Outbound message filtering (`message_filter.py`)
- Mid-callback command permission checks ('event["check_assert"](utils.Check(...))')
- `connected-since` on stats endpoint
- IRCv3: draft/event-playback
- `auto-github-cooldown` to prevent duplicate `auto-github`s in quick succession
- `vote.py`
- IRCv3: `ircv3_botignore.py` to ignore users with `inspircd.org/bot`
- Catch and humanify `!loadmodule` "not found" exception
- cross-channel/network relay (`relay.py`)
- Option to allow anyone to `!startvote`
- IRCv3: CAP dependency system
- IRCv3: labeled-response + echo-message to correlate echos to sends
- `deferred_read.py`

Changed:
- Only strip 2 characters (`++` or `--`) from the end of karma
- Track CHANMODE type B, C and D (not just type D)
- 'x saved a duck' -> 'x befriended a duck'
- IRCv3: CAP REQ streamline for modules
- IRCv3: SASL failure defaults to being "hard" (disconnect/crash)
- `auto-title`, `auto-youtube`, `auto-imgur` etc now work in `/me`
- Move truncation logic from `SentLine` to `ParsedLine`
- Move `!help` logic to it's own file and rework it to be more user friendly
- Get `"city, state, country"` from geocoding in `location.py`, use in `weather.py`
- Convert IRC glob to regex, instead of using fnmatch
- `EventManager` calls can only come from the main thread
- IRCv3: `labeled-response` now depends on `batch`
- `format_activity.py` now only shows highest channel access symbol

Fixed:
- `KeyError` when sts `port` key not present
- lxml wasn't in requirements.txt but it should have been
- Any CRITICAL in read/write thread now kills the main thread too
- `Database.ChannelSettings.find` invalid SQL
- `birthday.py`'s year no longer .lstrip("0")ed
- IRCv3: pay attention to our own msgids (`ircv3_msgid.py`)
- catch and WARN when trying to remove a self-mode we didn't know we had
- `until_read_timeout` -> `until_read_timeout()`
- `PROTOCTL NAMESX` should have been send_raw() not send()
- IRCv3: handle `CAP ACK -<cap>`
- IRCv3: handle `CAP ACK` in response to `CAP REQ` that came from outside `ircv3.py`

Removed:
- `!set`/`!channelset`/`!serverset`/`!botset` (replaced with `!config`)
- `bytes-read-per-second` and `bytes-written-per-second` from stats endpoint
- `upc.py`

# 2019-06-09 - BitBot v1.9.2

Added:
- Show seconds it took to !bef/!trap

Changed:
- IRCv3: `draft/resume-0.4` -> `draft/resume-0.5`

Fixed:
- Fix scenario in which some-but-not-all threads die
- Daemonify tweet thread
- Don't add TAGMSGs to IRCBuffer objects

# 2019-06-08 - BitBot v1.9.1

Fixed:
- Fix ERROR on `CAP NEW` caused by STS typo
- Fix hanging on `CAP NEW` due to duplicate `REQ`
- STATUSMSG stripping should only be STATUSMSG symbols, not all PREFIX symbols

# 2019-06-07 - BitBot v1.9.0

Added:
- IRCv3: Also look at CTCP events for msgids
- Sub-event system within all SentLines
- Show last action in `!seen` (e.g. 'seen 1m ago (<jesopo> hi)')
- WARN when labels are not responded to in a timely fashion
- IRCv3: send `+draft/typing` while processing commands
- Display github `ready_for_review` better
- Parse 221 (RPL_UMODEIS) numerics

Changed:
- `!np` against a known nickname will attempt to resolve to lastfm username
- `PING` and `PONG` now avoid write throttling
- `!bang` -> `!trap`, 'shot' -> 'trapped' for ducks
- Socket reads and socket writes have been moved on to seperate threads
- Use Deques for chat history (more performant!)

Fixed:
- Differentiate between send and received CTCP events
- `IRCSocket._send` will now only return lines that definitely hit the wire
- GitHub `commit_comment` event formatting exception
- Strip xref tags from `!define` output
- `check_purge()` after removing contextual hooks from an EventHook
- IRCv3: Escape message tag values

# 2019-06-03 - BitBot v1.8.0

Added:
- Module dependency system
- Enable TCP keepalives
- IRCv3: `draft/label` tracking on every sent line when CAPs permit
- Enforce Python version 3.6.0 or later
- 'module-whitelist'/'module-blacklist' in `bot.conf`

Changed:
- IRCv3: Use last `server-time` for `RESUME` - not last .recv() time
- IRCv3: `draft/labeled-response` -> `draft/labeled-response-0.2`
- IRCv3: Prune already-seen messages in `chathistory` batches
- Consolidate `PRIVMSG`, `NOTICE` and `TAGMSG` handlers in to one

Fixed
- GitHub highlight prevention - don't detect highlights mid-word
- Pass already-decoded data in to BeautifulSoup
- !enablemodule actually removes module from blacklist setting
- Only enact write throttling when immediate-write-buffer is empty
- Non-throttled lines no longer delay throttled lines

# 2019-05-24 - BitBot v1.7.1

Fixed:
- Fix crash caused by CAP NEW

# 2019-05-23 - BitBot v1.7.0

Added:
- Add !addserver
- Add !masterpassword
- Add auto-tweet setting
- Support triggering commands by regex

Changed:
- Show usage examples for user/channel/server/bot settings
- Strip common command prefixes from PM commands so "!help" works
- Change auto-github to work for github urls too
- IRCv3: draft/resume-0.3 -> draft/resume-0.4
- Remove `ipv4` server attribute - figure it out automatically

Fixed:
- Typos/bugs in BATCH and FAIL
- Fix crash caused by BitBot messaging himself