summaryrefslogtreecommitdiff
path: root/github-activity-widget
diff options
context:
space:
mode:
Diffstat (limited to 'github-activity-widget')
-rw-r--r--github-activity-widget/README.md65
-rw-r--r--github-activity-widget/github-activity-widget.lua289
-rw-r--r--github-activity-widget/icons/comment.svg1
-rw-r--r--github-activity-widget/icons/fork.svg1
-rw-r--r--github-activity-widget/icons/github.pngbin0 -> 1571 bytes
-rw-r--r--github-activity-widget/icons/issue.svg1
-rw-r--r--github-activity-widget/icons/pr.svg1
-rw-r--r--github-activity-widget/icons/repo.svg1
-rw-r--r--github-activity-widget/icons/star.svg1
-rw-r--r--github-activity-widget/screenshot.pngbin0 -> 43288 bytes
10 files changed, 360 insertions, 0 deletions
diff --git a/github-activity-widget/README.md b/github-activity-widget/README.md
new file mode 100644
index 0000000..4f69eb5
--- /dev/null
+++ b/github-activity-widget/README.md
@@ -0,0 +1,65 @@
+# GitHub Activity Widget
+
+Widget shows recent activities on GitHub. It is very similar to the GitHub's "All activity" feed on the main page:
+
+<p align="center">
+ <img src="https://github.com/streetturtle/awesome-wm-widgets/raw/master/github-activity-widget/screenshot.png">
+</p>
+
+Mouse click on the item opens repo/issue/pr depending on the type of the activity. Mouse click on user's avatar opens user GitHub profile.
+
+## Customization
+
+It is possible to customize widget by providing a table with all or some of the following config parameters:
+
+| Name | Default | Description |
+|---|---|---|
+| `icon` | github.png from the widget sources | Widget icon displayed on the wibar |
+| `username` | your username | Required parameter |
+| `number_of_events` | 10 | Number of events to display in the list |
+
+## How it works
+
+Everything starts with this timer, which gets recent activities by calling GitHub [Events API](https://developer.github.com/v3/activity/events/) and stores the response under /.cache/awmw/github-activity-widget/activity.json directory:
+
+```lua
+gears.timer {
+ timeout = 600, -- calls every ten minutes
+ call_now = true,
+ autostart = true,
+ callback = function()
+ spawn.easy_async(string.format(UPDATE_EVENTS_CMD, username, CACHE_DIR), function(stdout, stderr)
+ if stderr ~= '' then show_warning(stderr) return end
+ end)
+ end
+}
+```
+
+There are several reasons to store output in a file and then use it as a source to build the widget, instead of calling it everytime the widget is opened:
+ - activity feed does not update that often
+ - events API doesn't provide filtering of fields, so the output is quite large (300 events)
+ - it's much faster to read file from filesystem
+
+ Next important part is **rebuild_widget** function, which is called when mouse button clicks on the widget on the wibar. It receives a json string which contains first n events from the cache file. Those events are processed by `jq` (get first n events, remove unused fields, slightly change the json structure to simplify serialization to lua table). And then it builds a widget, row by row in a loop. To display the text part of the row we already have all neccessary information in the json string which was converted to lua table. But to show an avatar we should download it first. This is done in the following snippet. First it creates a template and then checks if file already exists, and sets it in template, otherwise, downloads it asynchronously and only then sets in:
+
+ ```lua
+local avatar_img = wibox.widget {
+ resize = true,
+ forced_width = 40,
+ forced_height = 40,
+ widget = wibox.widget.imagebox
+}
+
+if gfs.file_readable(path_to_avatar) then
+ avatar_img:set_image(path_to_avatar)
+else
+ -- download it first
+ spawn.easy_async(string.format(
+ DOWNLOAD_AVATAR_CMD,
+ CACHE_DIR,
+ event.actor.id,
+ event.actor.avatar_url),
+ -- and then set
+ function() avatar_img:set_image(path_to_avatar) end)
+end
+ ```
diff --git a/github-activity-widget/github-activity-widget.lua b/github-activity-widget/github-activity-widget.lua
new file mode 100644
index 0000000..b3e29bc
--- /dev/null
+++ b/github-activity-widget/github-activity-widget.lua
@@ -0,0 +1,289 @@
+-------------------------------------------------
+-- GitHub Widget for Awesome Window Manager
+-- Shows the recent activity from GitHub
+-- More details could be found here:
+-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/github-activity-widget
+
+-- @author Pavel Makhov
+-- @copyright 2020 Pavel Makhov
+-------------------------------------------------
+
+local awful = require("awful")
+local wibox = require("wibox")
+local json = require("json")
+local spawn = require("awful.spawn")
+local naughty = require("naughty")
+local gears = require("gears")
+local beautiful = require("beautiful")
+local gfs = require("gears.filesystem")
+
+local HOME_DIR = os.getenv("HOME")
+local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/github-activity-widget'
+local ICONS_DIR = WIDGET_DIR .. '/icons/'
+local CACHE_DIR = HOME_DIR .. '/.cache/awmw/github-activity-widget'
+
+local GET_EVENTS_CMD = [[bash -c "cat %s/activity.json | jq '.[:%d] | [.[] | {type: .type, actor: .actor, repo: .repo, action: .payload.action, issue_url: .payload.issue.html_url, pr_url: .payload.pull_request.html_url, created_at: .created_at}]'"]]
+local DOWNLOAD_AVATAR_CMD = [[bash -c "curl -n --create-dirs -o %s/avatars/%s %s"]]
+local UPDATE_EVENTS_CMD = [[bash -c "curl -s --show-error https://api.github.com/users/%s/received_events > %s/activity.json"]]
+
+--- Utility function to show warning messages
+local function show_warning(message)
+ naughty.notify{
+ preset = naughty.config.presets.critical,
+ title = 'GitHub Activity Widget',
+ text = message}
+end
+
+--- Converts string representation of date (2020-06-02T11:25:27Z) to date
+local function parse_date(date_str)
+ local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)%Z"
+ local y, m, d, h, min, sec, mil = date_str:match(pattern)
+
+ return os.time{year = y, month = m, day = d, hour = h, min = min, sec = sec}
+end
+
+--- Converts seconds to "time ago" represenation, like '1 hour ago'
+local function to_time_ago(seconds)
+ local days = seconds / 86400
+ if days > 1 then
+ days = math.floor(days + 0.5)
+ return days .. (days == 1 and ' day' or ' days') .. ' ago'
+ end
+
+ local hours = (seconds % 86400) / 3600
+ if hours > 1 then
+ hours = math.floor(hours + 0.5)
+ return hours .. (hours == 1 and ' hour' or ' hours') .. ' ago'
+ end
+
+ local minutes = ((seconds % 86400) % 3600) / 60
+ if minutes > 1 then
+ minutes = math.floor(minutes + 0.5)
+ return minutes .. (minutes == 1 and ' minute' or ' minutes') .. ' ago'
+ end
+end
+
+
+local popup = awful.popup{
+ ontop = true,
+ visible = false,
+ shape = gears.shape.rounded_rect,
+ border_width = 1,
+ border_color = beautiful.bg_focus,
+ maximum_width = 350,
+ offset = { y = 5 },
+ widget = {}
+}
+
+local function generate_action_string(event)
+ local action_string = event.type
+ local icon = 'repo.svg'
+ local link = 'http://github.com/' .. event.repo.name
+
+ if (event.type == "PullRequestEvent") then
+ action_string = event.action .. ' a pull request in'
+ link = event.pr_url
+ icon = 'pr.svg'
+ elseif (event.type == "IssuesEvent") then
+ action_string = event.action .. ' an issue in'
+ link = event.issue_url
+ icon = 'issue.svg'
+ elseif (event.type == "IssueCommentEvent") then
+ action_string = event.action == 'created' and 'commented in issue' or event.action .. ' a comment in'
+ link = event.issue_url
+ icon = 'comment.svg'
+ elseif (event.type == "WatchEvent") then
+ action_string = 'starred'
+ link = 'http://github.com/' .. event.repo.name
+ icon = 'star.svg'
+ elseif (event.type == "ForkEvent") then
+ action_string = 'forked'
+ link = 'http://github.com/' .. event.repo.name
+ icon = 'fork.svg'
+ end
+
+ return { action_string = action_string, link = link, icon = icon }
+end
+
+local github_widget = wibox.widget {
+ {
+ {
+ id = 'icon',
+ widget = wibox.widget.imagebox
+ },
+ id = "m",
+ margins = 4,
+ layout = wibox.container.margin
+ },
+ {
+ id = "txt",
+ widget = wibox.widget.textbox
+ },
+ layout = wibox.layout.fixed.horizontal,
+ set_icon = function(self, new_icon)
+ self.m.icon.image = new_icon
+ end,
+ set_text = function(self, new_value)
+ self.txt.text = new_value
+ end
+}
+
+
+local function worker(args)
+
+ if not gfs.dir_readable(CACHE_DIR) then
+ gfs.make_directories(CACHE_DIR)
+ end
+
+ local args = args or {}
+
+ local icon = args.icon or ICONS_DIR .. 'github.png'
+ local username = args.username or show_warning('No username provided')
+ local number_of_events = args.number_of_events or 10
+
+ github_widget:set_icon(icon)
+
+ local rows = {
+ { widget = wibox.widget.textbox },
+ layout = wibox.layout.fixed.vertical,
+ }
+
+ local rebuild_widget = function(widget, stdout, stderr, _, _)
+ if stderr ~= '' then
+ show_warning(stderr)
+ return
+ end
+
+ local current_time = os.time(os.date("!*t"))
+
+ local events = json.decode(stdout)
+
+ for i = 0, #rows do rows[i]=nil end
+ for _, event in ipairs(events) do
+ local path_to_avatar = CACHE_DIR .. '/avatars/' .. event.actor.id
+
+ local avatar_img = wibox.widget {
+ resize = true,
+ forced_width = 40,
+ forced_height = 40,
+ widget = wibox.widget.imagebox
+ }
+
+ if not gfs.file_readable(path_to_avatar) then
+ -- download it first
+ spawn.easy_async(string.format(
+ DOWNLOAD_AVATAR_CMD,
+ CACHE_DIR,
+ event.actor.id,
+ event.actor.avatar_url), function() avatar_img:set_image(path_to_avatar) end)
+ else
+ avatar_img:set_image(path_to_avatar)
+ end
+
+ local action_and_link = generate_action_string(event)
+
+ local avatar = wibox.widget {
+ avatar_img,
+ margins = 8,
+ layout = wibox.container.margin
+ }
+ avatar:buttons(
+ awful.util.table.join(
+ awful.button({}, 1, function()
+ spawn.with_shell('xdg-open http://github.com/' .. event.actor.login)
+ popup.visible = false
+ end)
+ )
+ )
+
+ local repo_info = wibox.widget {
+ {
+ markup = '<b> ' .. event.actor.display_login .. '</b> ' .. action_and_link.action_string .. ' <b>' .. event.repo.name .. '</b>',
+ wrap = 'word',
+ widget = wibox.widget.textbox
+ },
+ {
+ {
+ {
+ image = ICONS_DIR .. action_and_link.icon,
+ resize = true,
+ forced_height = 16,
+ forced_width = 16,
+ widget = wibox.widget.imagebox
+ },
+ valign = 'center',
+ layout = wibox.container.place
+ },
+ {
+ markup = to_time_ago(os.difftime(current_time, parse_date(event.created_at))),
+ widget = wibox.widget.textbox
+ },
+ spacing = 4,
+ layout = wibox.layout.fixed.horizontal,
+ },
+ layout = wibox.layout.align.vertical
+ }
+ repo_info:buttons(
+ awful.util.table.join(
+ awful.button({}, 1, function()
+ spawn.with_shell("xdg-open " .. action_and_link.link)
+ popup.visible = false
+ end)
+ )
+ )
+
+ local row = wibox.widget {
+ {
+ {
+ avatar,
+ repo_info,
+ spacing = 4,
+ layout = wibox.layout.fixed.horizontal
+ },
+ margins = 4,
+ layout = wibox.container.margin
+ },
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ }
+
+ row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end)
+ row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end)
+
+ table.insert(rows, row)
+ end
+
+ popup:setup(rows)
+ end
+
+ github_widget:buttons(
+ awful.util.table.join(
+ awful.button({}, 1, function()
+ if popup.visible then
+ popup.visible = not popup.visible
+ else
+ spawn.easy_async(string.format(GET_EVENTS_CMD, CACHE_DIR, number_of_events), function (stdout, stderr)
+ rebuild_widget(github_widget, stdout, stderr)
+ popup:move_next_to(mouse.current_widget_geometry)
+ end)
+ end
+ end)
+ )
+ )
+
+ -- Calls GitHub event API and stores response in "cache" file
+ gears.timer {
+ timeout = 600,
+ call_now = true,
+ autostart = true,
+ callback = function()
+ spawn.easy_async(string.format(UPDATE_EVENTS_CMD, username, CACHE_DIR), function(stdout, stderr)
+ if stderr ~= '' then show_warning(stderr) return end
+ end)
+ end
+ }
+
+ return github_widget
+end
+
+return setmetatable(github_widget, { __call = function(_, ...) return worker(...) end })
diff --git a/github-activity-widget/icons/comment.svg b/github-activity-widget/icons/comment.svg
new file mode 100644
index 0000000..5cb54bf
--- /dev/null
+++ b/github-activity-widget/icons/comment.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><style type="text/css"><![CDATA[.white{fill: #ffffff;}]]></style><path class="white" fill-rule="evenodd" d="M14 1H2c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2v3.5L7.5 11H14c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1zm0 9H7l-2 2v-2H2V2h12v8z"></path></svg> \ No newline at end of file
diff --git a/github-activity-widget/icons/fork.svg b/github-activity-widget/icons/fork.svg
new file mode 100644
index 0000000..4c722dc
--- /dev/null
+++ b/github-activity-widget/icons/fork.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg height="1024" width="640" xmlns="http://www.w3.org/2000/svg"><style type="text/css"><![CDATA[.white{fill: #ffffff;}]]></style><path class="white" d="M512 192c-70.625 0-128 57.344-128 128 0 47.219 25.875 88.062 64 110.281V448c0 0 0 128-128 128-53.062 0-94.656 11.375-128 28.812V302.28099999999995c38.156-22.219 64-63.062 64-110.281 0-70.656-57.344-128-128-128S0 121.34400000000005 0 192c0 47.219 25.844 88.062 64 110.281V721.75C25.844 743.938 0 784.75 0 832c0 70.625 57.344 128 128 128s128-57.375 128-128c0-33.5-13.188-63.75-34.25-86.625C240.375 722.5 270.656 704 320 704c254 0 256-256 256-256v-17.719c38.125-22.219 64-63.062 64-110.281C640 249.34400000000005 582.625 192 512 192zM128 128c35.406 0 64 28.594 64 64s-28.594 64-64 64-64-28.594-64-64S92.594 128 128 128zM128 896c-35.406 0-64-28.625-64-64 0-35.312 28.594-64 64-64s64 28.688 64 64C192 867.375 163.406 896 128 896zM512 384c-35.375 0-64-28.594-64-64s28.625-64 64-64 64 28.594 64 64S547.375 384 512 384z"/></svg> \ No newline at end of file
diff --git a/github-activity-widget/icons/github.png b/github-activity-widget/icons/github.png
new file mode 100644
index 0000000..628da97
--- /dev/null
+++ b/github-activity-widget/icons/github.png
Binary files differ
diff --git a/github-activity-widget/icons/issue.svg b/github-activity-widget/icons/issue.svg
new file mode 100644
index 0000000..b47543a
--- /dev/null
+++ b/github-activity-widget/icons/issue.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg"><style type="text/css"><![CDATA[.white{fill: #ffffff;}]]></style><path class="white" d="M448 64C200.562 64 0 264.562 0 512c0 247.438 200.562 448 448 448 247.438 0 448-200.562 448-448C896 264.562 695.438 64 448 64zM448 832c-176.781 0-320-143.25-320-320 0-176.781 143.219-320 320-320 176.75 0 320 143.219 320 320C768 688.75 624.75 832 448 832zM384 768h128V640H384V768zM384 576h128V256H384V576z"/></svg> \ No newline at end of file
diff --git a/github-activity-widget/icons/pr.svg b/github-activity-widget/icons/pr.svg
new file mode 100644
index 0000000..412be1a
--- /dev/null
+++ b/github-activity-widget/icons/pr.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg height="1024" width="768" xmlns="http://www.w3.org/2000/svg"><style type="text/css"><![CDATA[.white{fill: #ffffff;}]]></style><path class="white" d="M128 64C57.344 64 0 121.34400000000005 0 192c0 47.219 25.906 88.062 64 110.281V721.75C25.906 743.938 0 784.75 0 832c0 70.625 57.344 128 128 128s128-57.375 128-128c0-47.25-25.844-88.062-64-110.25V302.28099999999995c38.156-22.219 64-63.062 64-110.281C256 121.34400000000005 198.656 64 128 64zM128 896c-35.312 0-64-28.625-64-64 0-35.312 28.688-64 64-64 35.406 0 64 28.688 64 64C192 867.375 163.406 896 128 896zM128 256c-35.312 0-64-28.594-64-64s28.688-64 64-64c35.406 0 64 28.594 64 64S163.406 256 128 256zM704 721.75V320c0-192.5-192-192-192-192h-64V0L256 192l192 192V256c0 0 26.688 0 64 0 56.438 0 64 64 64 64v401.75c-38.125 22.188-64 62.938-64 110.25 0 70.625 57.375 128 128 128s128-57.375 128-128C768 784.75 742.125 743.938 704 721.75zM640 896c-35.312 0-64-28.625-64-64 0-35.312 28.688-64 64-64 35.375 0 64 28.688 64 64C704 867.375 675.375 896 640 896z"/></svg> \ No newline at end of file
diff --git a/github-activity-widget/icons/repo.svg b/github-activity-widget/icons/repo.svg
new file mode 100644
index 0000000..f74a595
--- /dev/null
+++ b/github-activity-widget/icons/repo.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg height="1024" width="768" xmlns="http://www.w3.org/2000/svg"><style type="text/css"><![CDATA[.white{fill: #ffffff;}]]></style><path class="white" d="M320 256h-64v64h64V256zM320 128h-64v64h64V128zM704 0H64C64 0 0 0 0 64v768c0 64 64 64 64 64h128v128l96-96 96 96V896h320c0 0 64-1.125 64-64V64C768 0 704 0 704 0zM704 768c0 61.625-64 64-64 64H384v-64H192v64h-64c-64 0-64-64-64-64v-64h640V768zM704 640H192V64h513L704 640zM320 512h-64v64h64V512zM320 384h-64v64h64V384z"/></svg> \ No newline at end of file
diff --git a/github-activity-widget/icons/star.svg b/github-activity-widget/icons/star.svg
new file mode 100644
index 0000000..7ac51ac
--- /dev/null
+++ b/github-activity-widget/icons/star.svg
@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg"><style type="text/css"><![CDATA[.white{fill: #ffffff;}]]></style><path class="white" d="M896 384l-313.5-40.781L448 64 313.469 343.219 0 384l230.469 208.875L171 895.938l277-148.812 277.062 148.812L665.5 592.875 896 384z"/></svg> \ No newline at end of file
diff --git a/github-activity-widget/screenshot.png b/github-activity-widget/screenshot.png
new file mode 100644
index 0000000..175a755
--- /dev/null
+++ b/github-activity-widget/screenshot.png
Binary files differ