├── .gitignore ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── app.rb ├── config.rb ├── config.ru ├── logbot.god ├── logbot.rb.example ├── public ├── applications.js └── images │ └── img_dropdown_violet.svg ├── sass ├── _solarized.sass ├── screen.sass └── widget.sass ├── screenshot.png ├── utils └── migrate_db.rb └── views ├── channel.erb └── widget.erb /.gitignore: -------------------------------------------------------------------------------- 1 | .swp 2 | /public/*.css 3 | .sass-cache 4 | .DS_Store 5 | /fire_app_log.txt 6 | *.rdb 7 | /logbot.rb 8 | /tmp 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | from base:latest 2 | run echo "deb http://ppa.launchpad.net/brightbox/ruby-ng/ubuntu precise main" >> /etc/apt/sources.list 3 | run echo "deb http://ppa.launchpad.net/chris-lea/redis-server/ubuntu precise main" >> /etc/apt/sources.list 4 | run apt-get update 5 | run apt-get install --force-yes -y ruby1.9.1 rubygems redis-server 6 | add . / 7 | run gem install bundler 8 | run apt-get install --force-yes -y ruby1.9.1-dev 9 | run bundle install 10 | run compass compile 11 | run cp logbot.rb.example logbot.rb 12 | expose 6379 13 | expose :5000 14 | env LOGBOT_NICK logbot_ 15 | env LOGBOT_SERVER irc.freenode.net 16 | env LOGBOT_CHANNELS #test56 17 | cmd ["sh", "-c", "/usr/bin/redis-server | foreman start"] 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'foreman' 5 | gem 'sinatra' 6 | gem 'async_sinatra' 7 | gem 'eventmachine' 8 | gem 'shotgun' 9 | gem 'compass' 10 | gem 'haml' 11 | gem 'sass' 12 | gem 'redis' 13 | gem 'cinch' 14 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | async_sinatra (1.0.0) 5 | rack (>= 1.4.1) 6 | sinatra (>= 1.3.2) 7 | chunky_png (1.2.7) 8 | cinch (2.0.3) 9 | compass (0.12.2) 10 | chunky_png (~> 1.2) 11 | fssm (>= 0.2.7) 12 | sass (~> 3.1) 13 | eventmachine (1.0.0) 14 | foreman (0.61.0) 15 | thor (>= 0.13.6) 16 | fssm (0.2.9) 17 | haml (3.1.7) 18 | rack (1.4.4) 19 | rack-protection (1.3.2) 20 | rack 21 | rake (10.0.3) 22 | redis (3.0.2) 23 | sass (3.2.5) 24 | shotgun (0.9) 25 | rack (>= 1.0) 26 | sinatra (1.3.3) 27 | rack (~> 1.3, >= 1.3.6) 28 | rack-protection (~> 1.2) 29 | tilt (~> 1.3, >= 1.3.3) 30 | thor (0.16.0) 31 | tilt (1.3.3) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | async_sinatra 38 | cinch 39 | compass 40 | eventmachine 41 | foreman 42 | haml 43 | rake 44 | redis 45 | sass 46 | shotgun 47 | sinatra 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Shao-Chung Chen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: shotgun -o 0.0.0.0 -p 5000 config.ru 2 | logbot: ruby logbot.rb 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logbot 2 | ====== 3 | Logbot is a simple IRC logger with realtime web-based viewer. 4 | 5 | 6 | Screenshot 7 | ---------- 8 | ![Logbot screnshot](https://raw.github.com/Dannvix/Logbot/master/screenshot.png) 9 | 10 | 11 | How to Deploy 12 | ------------- 13 | * Use Docker 14 | 1. Install [Docker](https://www.docker.com/) 15 | 2. Run `docker run -e LOGBOT_NICK=xxxx -e LOGBOT_CHANNELS=#x,#y,#z -e LOGBOT_SERVER=168.95.1.1 dannvix/logbot` 16 | 3. Visit [http://localhost:5000](http://localhost:5000) 17 | 18 | * Manual installation 19 | 1. Ruby (1.9.3+) and Redis server must be installed 20 | 2. Run `bundle install` to install required Ruby gems 21 | 3. Run `compass compile` to compile Sass files 22 | 4. Fire up your `redis-server` 23 | 5. Specify target channels in `logbot.rb` 24 | 6. Run `foreman start` to launch web server (WEBrick) and Logbot agent 25 | 7. Visit [http://localhost:5000](http://localhost:5000). 26 | 27 | 28 | How to Contribute 29 | ----------------- 30 | Just hack it and send me pull requests ;) 31 | 32 | 33 | Resource 34 | -------- 35 | * See [g0v/Logbot](https://github.com/g0v/Logbot) for many bugfixes and enhancements. 36 | 37 | 38 | License 39 | ------- 40 | Licensed under the [MIT license](http://opensource.org/licenses/mit-license.php). 41 | 42 | Copyright (c) 2013 Shao-Chung Chen 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | Encoding.default_internal = "utf-8" 3 | Encoding.default_external = "utf-8" 4 | 5 | require "json" 6 | require "time" 7 | require "date" 8 | require "cgi" 9 | require "sinatra/base" 10 | require "sinatra/async" 11 | require "redis" 12 | require "compass" 13 | require "eventmachine" 14 | 15 | $redis = Redis.new(:thread_safe => true) 16 | 17 | module IRC_Log 18 | class App < Sinatra::Base 19 | configure do 20 | set :protection, :except => :frame_options 21 | end 22 | 23 | get "/" do 24 | redirect "/channel/g0v.tw/today" 25 | end 26 | 27 | get "/channel/:channel" do |channel| 28 | redirect "/channel/#{channel}/today" 29 | end 30 | 31 | get "/channel/:channel/:date" do |channel, date| 32 | case date 33 | when "today" 34 | @date = Time.now.strftime("%F") 35 | when "yesterday" 36 | @date = (Time.now - 86400).strftime("%F") 37 | else 38 | # date in "%Y-%m-%d" format (e.g. 2013-01-01) 39 | @date = date 40 | end 41 | 42 | @channel = channel 43 | 44 | @msgs = $redis.lrange("irclog:channel:##{channel}:#{@date}", 0, -1) 45 | @msgs = @msgs.map {|msg| 46 | msg = JSON.parse(msg) 47 | msg["msg"] = CGI.escapeHTML(msg["msg"]) 48 | if msg["msg"] =~ /^\u0001ACTION (.*)\u0001$/ 49 | msg["msg"].gsub!(/^\u0001ACTION (.*)\u0001$/, "#{msg["nick"]} \\1") 50 | msg["nick"] = "*" 51 | end 52 | msg 53 | } 54 | 55 | erb :channel 56 | end 57 | 58 | get "/widget/:channel" do |channel| 59 | @channel = channel 60 | today = Time.now.strftime("%Y-%m-%d") 61 | @msgs = $redis.lrange("irclog:channel:##{channel}:#{today}", -25, -1) 62 | @msgs = $redis.lrange("irclog:channel:##{channel}:#{today}", -25, -1) 63 | @msgs = @msgs.map {|msg| 64 | ret = JSON.parse(msg) 65 | ret["msg"] = CGI.escape(ret["msg"]) 66 | ret 67 | }.reverse 68 | 69 | erb :widget 70 | end 71 | end 72 | end 73 | 74 | 75 | module Comet 76 | class App < Sinatra::Base 77 | register Sinatra::Async 78 | 79 | get %r{/poll/(.*)/([\d\.]+)/updates.json} do |channel, time| 80 | date = Time.at(time.to_f).strftime("%Y-%m-%d") 81 | msgs = $redis.lrange("irclog:channel:##{channel}:#{date}", -10, -1).map{|msg| 82 | ret = ::JSON.parse(msg) 83 | ret["msg"] = CGI.escapeHTML(ret["msg"]) 84 | ret 85 | } 86 | if (not msgs.empty?) && msgs[-1]["time"] > time 87 | return msgs.select{|msg| msg["time"] > time }.to_json 88 | end 89 | 90 | EventMachine.run do 91 | n, timer = 0, EventMachine::PeriodicTimer.new(0.5) do 92 | msgs = $redis.lrange("irclog:channel:##{channel}:#{date}", -10, -1).map{|msg| 93 | ret = ::JSON.parse(msg) 94 | ret["msg"] = CGI.escapeHTML(ret["msg"]) 95 | ret 96 | } 97 | if (not msgs.empty?) && msgs[-1]["time"] > time || n > 120 98 | timer.cancel 99 | return msgs.select{|msg| msg["time"] > time }.to_json 100 | end 101 | n += 1 102 | end 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | http_path = "/" 2 | css_dir = "public" 3 | sass_dir = "sass" 4 | output_style = :compressed 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # Process.setrlimit(Process::RLIMIT_NOFILE, 4096, 65536) 2 | require File.join(File.dirname(__FILE__), "app") 3 | 4 | run Rack::URLMap.new \ 5 | "/" => IRC_Log::App.new, 6 | "/comet" => Comet::App.new, 7 | "/assets" => Rack::Directory.new("public") 8 | -------------------------------------------------------------------------------- /logbot.god: -------------------------------------------------------------------------------- 1 | God.watch do |w| 2 | w.name = "Logbot agent" 3 | w.start = "ruby /home/rails/logbot/logbot.rb" 4 | w.keepalive 5 | end 6 | -------------------------------------------------------------------------------- /logbot.rb.example: -------------------------------------------------------------------------------- 1 | require "json" 2 | require "cinch" 3 | require "redis" 4 | 5 | channels = (ENV['LOGBOT_CHANNELS'] || '#test56').split /[\s,]+/ 6 | redis = Redis.new(:thread_safe => true) 7 | 8 | channels.each do |chan| 9 | redis.sadd("irclog:channels", "#{chan}") 10 | end 11 | 12 | bot = Cinch::Bot.new do 13 | configure do |conf| 14 | conf.server = (ENV['LOGBOT_SERVER'] || "irc.freenode.net") 15 | conf.nick = (ENV['LOGBOT_NICK'] || "logbot_") 16 | conf.channels = channels 17 | end 18 | 19 | on :message do |msg| 20 | if not msg.channel.nil? 21 | date = msg.time.strftime("%Y-%m-%d") 22 | key = "irclog:channel:#{msg.channel.name}:#{date}" 23 | redis.rpush(key, { 24 | :time => "#{msg.time.strftime("%s.%L")}", 25 | :nick => "#{msg.user.nick}", 26 | :msg => "#{msg.message}" 27 | }.to_json) 28 | end 29 | end 30 | end 31 | bot.start 32 | -------------------------------------------------------------------------------- /public/applications.js: -------------------------------------------------------------------------------- 1 | var strftime = function(date) { 2 | var hour = date.getHours(), 3 | min = date.getMinutes(), 4 | sec = date.getSeconds(); 5 | 6 | if (hour < 10) { hour = "0" + hour; } 7 | if ( min < 10) { min = "0" + min; } 8 | if ( sec < 10) { sec = "0" + sec; } 9 | 10 | return hour + ":" + min + ":" + sec; 11 | }; 12 | 13 | 14 | var lastTimestamp = undefined; 15 | var seenTimestamp = {}; 16 | var pollNewMsg = function(isWidget) { 17 | var isWidget = (isWidget == null) ? false : isWidget; 18 | var time = lastTimestamp || (new Date()).getTime() / 1000.0; 19 | $.ajax({ 20 | url: "/comet/poll/" + channel + "/" + time + "/updates.json", 21 | type: "get", 22 | async: true, 23 | cache: false, 24 | timeout: 60000, 25 | 26 | success: function (data) { 27 | var msgs = JSON.parse(data); 28 | for (var i = 0; i < msgs.length; i++) { 29 | var msg = msgs[i]; 30 | if (seenTimestamp[msg.time]) { continue; } 31 | seenTimestamp[msg.time] = true; 32 | var date = new Date(parseFloat(msg["time"]) * 1000); 33 | var linkedMsg = msg["msg"].replace(/(http[s]*:\/\/[^\s]+)/, '$1'); 34 | var msgElement = $("
  • ").addClass("new-arrival") 35 | .append($("").text(strftime(date))) 36 | .append($("").text(msg["nick"])) 37 | .append($("").html(linkedMsg)); 38 | if (isWidget) { 39 | $(".logs").prepend(msgElement); 40 | } 41 | else { 42 | $(".logs").append(msgElement); 43 | } 44 | } 45 | 46 | // there's new message 47 | if (msgs.length > 0) { 48 | if (isWidget) { 49 | // widget layout 50 | $(document).scrollTop(0); 51 | } 52 | else { 53 | // desktop or mobile layout, there's a switch to turn off auto-scrolling 54 | if (window.can_scroll) { 55 | $(document).scrollTop($(document).height()); 56 | } 57 | } 58 | 59 | // if we're in desktop version 60 | if (typeof Cocoa !== "undefined" && Cocoa !== null) { 61 | Cocoa.requestUserAttention(); 62 | Cocoa.addUnreadCountToBadgeLabel(msgs.length); 63 | } 64 | } 65 | lastTimestamp = (new Date()).getTime() / 1000.0; 66 | try { 67 | lastTimestamp = msgs[msgs.length - 1]["time"]; 68 | } catch (e) {}; 69 | 70 | setTimeout(function(){ 71 | pollNewMsg(isWidget); 72 | }, 3000); 73 | }, 74 | 75 | error: function() { 76 | pollNewMsg(isWidget); 77 | } 78 | }); 79 | } 80 | 81 | var enableDatePicker = function() { 82 | $('#date-picker').on('change', function(event) { 83 | var targetDate = this.value; 84 | location.href = location.href.replace(/[^\/]+$/, targetDate); 85 | }) 86 | } 87 | enableDatePicker(); 88 | 89 | $(".scroll_switch").click(function() { 90 | window.can_scroll = $(".scroll_switch").hasClass("scroll_switch_off"); 91 | $(".scroll_switch").toggleClass("scroll_switch_off"); 92 | }); 93 | 94 | var pageScrollTop = function(position) { 95 | $("html, body").animate({ 96 | scrollTop: position 97 | }, 1000); 98 | }; 99 | -------------------------------------------------------------------------------- /public/images/img_dropdown_violet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sass/_solarized.sass: -------------------------------------------------------------------------------- 1 | $base03: #002b36 2 | $base02: #073642 3 | $base01: #586e75 4 | $base00: #657b83 5 | $base0: #839496 6 | $base1: #93a1a1 7 | $base2: #eee8d5 8 | $base3: #fdf6e3 9 | $yellow: #b58900 10 | $orange: #cb4b16 11 | $red: #dc322f 12 | $magenta: #d33682 13 | $violet: #6c71c4 14 | $blue: #268bd2 15 | $cyan: #2aa198 16 | $green: #859900 -------------------------------------------------------------------------------- /sass/screen.sass: -------------------------------------------------------------------------------- 1 | @import "compass" 2 | @import "compass/reset" 3 | @import "solarized" 4 | 5 | $row-color-odd: rgba(0,0,0,.33) 6 | $row-color-even: rgba(0,0,0,.15) 7 | 8 | @-webkit-keyframes blink 9 | from 10 | background-color: $yellow 11 | to 12 | background-color: transparent 13 | 14 | @keyframes blink 15 | from 16 | background-color: $yellow 17 | to 18 | background-color: transparent 19 | 20 | html * 21 | // @include box-shadow(0 0 0 1px darken(white, 75%)) 22 | color-profile: sRGB 23 | rendering-intent: auto 24 | 25 | body 26 | background: $base02 27 | color: $base00 28 | font-family: "Helvetica", sans-serif 29 | a 30 | color: $blue 31 | text-decoration: none 32 | &:hover 33 | background: $blue 34 | color: $base03 35 | 36 | 37 | .wrapper 38 | width: 90% 39 | margin: 0 auto 40 | 41 | 42 | .header 43 | padding: 30px 0 44 | text-align: center 45 | h1 46 | @include inline-block 47 | font-size: 32pt 48 | font-family: "Ropa Sans", sans-serif 49 | color: $base02 50 | font-weight: bold 51 | background: $green 52 | padding: 5px 20px 53 | text-transform: uppercase 54 | 55 | 56 | .footer 57 | padding: 40px 0 30px 0 58 | font-family: "Ropa Sans", sans-serif 59 | text-transform: uppercase 60 | text-align: center 61 | 62 | 63 | .body 64 | .channel, .date, .scroll_switch 65 | color: $cyan 66 | font: 14pt/20px "Droid Sans Mono", monospace 67 | @include inline-block 68 | @include border-radius(5px) 69 | cursor: pointer 70 | 71 | &:hover 72 | background-color: $base03 73 | .channel 74 | font-weight: bold 75 | padding: 5px 10px 76 | .date 77 | width: 7.2em 78 | height: 30px 79 | background: transparent url('images/img_dropdown_violet.svg') no-repeat 6em 60% 80 | @include background-size(10%) 81 | color: $violet 82 | letter-spacing: -2px 83 | border: none 84 | padding: 0 10px 85 | outline: none 86 | @include appearance(none) 87 | &::after 88 | content: '\25BE' 89 | 90 | .scroll_switch 91 | @include float-right 92 | clear: both 93 | padding: 5px 10px 94 | color: darken($green, 5%) 95 | .scroll_switch_off 96 | color: darken($green, 15%) 97 | text-decoration: line-through 98 | 99 | .logs 100 | list-style-type: none 101 | font-family: "Droid Sans Mono", monospace 102 | margin: 0 10px 103 | li 104 | margin-top: 12px 105 | line-height: 1.33 106 | 107 | &.new-arrival 108 | -webkit-animation: blink 500ms ease-out 109 | animation: blink 500ms ease-out 110 | 111 | .time, .nick 112 | float: left 113 | .time, .nick, .msg 114 | display: block 115 | .time 116 | width: 70px 117 | vertical-align: text-top 118 | letter-spacing: -2px 119 | .nick 120 | width: 110px 121 | color: $yellow 122 | text-align: right 123 | letter-spacing: -1px 124 | margin-left: 5px 125 | .msg 126 | color: $base1 127 | margin-left: 200px 128 | .nick 129 | width: auto 130 | margin-left: 0px 131 | 132 | .wordwrap 133 | -ms-word-break: break-all 134 | word-break: break-all 135 | //Non standard for webkit 136 | word-break: break-word 137 | -webkit-hyphens: auto 138 | -moz-hyphens: auto 139 | hyphens: auto 140 | 141 | 142 | // quick navigation 143 | .quick-nav 144 | position: fixed 145 | top: 45% 146 | right: 3% 147 | @include inline-block 148 | text-align: center 149 | 150 | .nav_page-up, 151 | .nav_page-down 152 | background-color: $base03 153 | color: $base1 154 | font-size: 14pt 155 | padding: 5px 156 | @include border-radius(5px) 157 | &:hover 158 | cursor: pointer 159 | background-color: $base01 160 | color: $base03 161 | 162 | .nav_page-down 163 | margin-top: 15px 164 | 165 | 166 | // mobile layout 167 | @media only screen and (max-width : 568px) 168 | .body .logs li 169 | padding: 10px 170 | &:nth-child(odd) 171 | background: $row-color-odd 172 | &:nth-child(even) 173 | background: $row-color-even 174 | .nick 175 | width: auto 176 | .msg 177 | margin-left: 0 178 | clear: left 179 | 180 | .nav_page-up, 181 | .nav_page-down 182 | padding: 1px 183 | 184 | -------------------------------------------------------------------------------- /sass/widget.sass: -------------------------------------------------------------------------------- 1 | @import "compass" 2 | @import "compass/reset" 3 | @import "solarized" 4 | 5 | html * 6 | // @include box-shadow(0 0 0 1px darken(white, 75%)) 7 | color-profile: sRGB 8 | rendering-intent: auto 9 | 10 | body 11 | background: $base02 12 | color: $base00 13 | font-family: "Helvetica", sans-serif 14 | a 15 | color: $blue 16 | text-decoration: none 17 | &:hover 18 | background: $blue 19 | color: $base03 20 | 21 | .header 22 | text-align: center 23 | h1 24 | font-size: 18pt 25 | font-family: "Ropa Sans", sans-serif 26 | color: $base02 27 | font-weight: bold 28 | background: $green 29 | padding: 10px 0 30 | text-transform: uppercase 31 | .channel 32 | font: bold 16pt/1 "Droid Sans Mono", monospace 33 | letter-spacing: -1px 34 | 35 | 36 | .footer 37 | padding: 10px 0 38 | color: $base02 39 | background: $green 40 | font-family: "Ropa Sans", sans-serif 41 | text-transform: uppercase 42 | text-align: center 43 | a 44 | background: $blue 45 | color: $base03 46 | &:hover 47 | background: transparent 48 | 49 | 50 | .body 51 | .logs 52 | list-style-type: none 53 | font-family: "Droid Sans Mono", monospace 54 | margin: 15px 55 | li 56 | margin-top: 12px 57 | line-height: 1.33 58 | .time, .nick 59 | float: left 60 | .time, .nick, .msg 61 | display: block 62 | .time 63 | width: 70px 64 | vertical-align: text-top 65 | font-size: 10pt 66 | .nick 67 | color: $yellow 68 | text-align: right 69 | font-size: 10pt 70 | .msg 71 | color: $base1 72 | margin-left: 0 73 | clear: left 74 | font-size: 12pt 75 | .nick 76 | width: auto 77 | margin-left: 0px 78 | 79 | .wordwrap 80 | -ms-word-break: break-all 81 | word-break: break-all 82 | word-break: break-word 83 | -webkit-hyphens: auto 84 | -moz-hyphens: auto 85 | hyphens: auto 86 | 87 | .body .logs li 88 | padding: 10px 89 | .body .logs li:nth-child(odd) 90 | background: rgba(0,0,0,.33) 91 | .body .logs li:nth-child(even) 92 | background: rgba(0,0,0,.15) 93 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannvix/Logbot/934bfcf13a41265b97c6c572e186834dcdc827a6/screenshot.png -------------------------------------------------------------------------------- /utils/migrate_db.rb: -------------------------------------------------------------------------------- 1 | require "json" 2 | require "redis" 3 | 4 | require "logger" 5 | log = Logger.new(STDERR) 6 | log.level = Logger::INFO 7 | 8 | redis = Redis.new(:thread_safe => true) 9 | 10 | channels = redis.smembers("irclog:channels") 11 | log.info("channels: #{channels.join(", ")}") 12 | 13 | channels.each do |channel| 14 | log.info("migration channel #{channel}") 15 | channel_key = "irclog:channel:#{channel}" 16 | 17 | messages = redis.lrange(channel_key, 0, -1) 18 | messages.each do |message| 19 | parsed_msg = JSON.parse(message) 20 | datestamp = Time.at(parsed_msg["time"].to_f).strftime("%Y-%m-%d") 21 | redis.rpush("#{channel_key}:#{datestamp}", message) 22 | end 23 | 24 | redis.del(channel_key) 25 | log.info("channel #{channel} migrated and deleted") 26 | end 27 | 28 | log.info("all channels migrated") -------------------------------------------------------------------------------- /views/channel.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logbot | #<%= @channel %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 |
    14 |

    Logbot

    15 |
    16 |
    17 | 18 | 19 |
    20 |
    21 |
    #<%= @channel %>
    22 | 23 | <% given_date = Time.parse(@date) %> 24 | 37 | 38 |
    AUTO⬇
    39 | 40 |
    41 |
      42 | <% @msgs.each do |msg| %> 43 | <% linked_msg = msg["msg"].gsub(/http[s]*:\/\/[^\s]+/, '\0') %> 44 |
    • 45 | <%= Time.at(msg["time"].to_f).strftime("%T") %> 46 | <%= msg["nick"] %> 47 | <%= linked_msg %> 48 |
    • 49 | <% end %> 50 |
    51 |
    52 |
    53 | 56 |
    57 | 58 | 61 | 62 | <% if @date == Date.today.to_s %> 63 | 64 | <% end %> 65 | 66 | 67 | -------------------------------------------------------------------------------- /views/widget.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logbot | #<%= @channel %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 |
    14 |

    Logbot #<%= @channel %>

    15 |
    16 |
    17 |
    18 |
      19 | <% @msgs.each do |msg| %> 20 | <% linked_msg = msg["msg"].gsub(/http[s]*:\/\/[^\s]+/, '\0') %> 21 |
    • 22 | <%= Time.at(msg["time"].to_f).strftime("%T") %> 23 | <%= msg["nick"] %> 24 | <%= linked_msg %> 25 |
    • 26 | <% end %> 27 |
    28 |
    29 |
    30 | 33 |
    34 | 35 | 38 | 39 | 42 | 43 | 44 | --------------------------------------------------------------------------------