From 42e041ce03613c2176f3187ca26a13f1b276d4a5 Mon Sep 17 00:00:00 2001 From: Duarte Figueiredo Date: Sun, 16 Apr 2023 11:24:51 +0100 Subject: [PATCH 1/3] [modules/wakatime] - New module that connects to https://wakatime.com and displays coding duration stats --- bumblebee_status/modules/contrib/wakatime.py | 95 +++++++++++++++++++ screenshots/wakatime.png | Bin 0 -> 1820 bytes tests/modules/contrib/test_wakatime.py | 56 +++++++++++ themes/icons/awesome-fonts.json | 3 + 4 files changed, 154 insertions(+) create mode 100644 bumblebee_status/modules/contrib/wakatime.py create mode 100644 screenshots/wakatime.png create mode 100644 tests/modules/contrib/test_wakatime.py diff --git a/bumblebee_status/modules/contrib/wakatime.py b/bumblebee_status/modules/contrib/wakatime.py new file mode 100644 index 0000000..7e4e33e --- /dev/null +++ b/bumblebee_status/modules/contrib/wakatime.py @@ -0,0 +1,95 @@ +# pylint: disable=C0111,R0903 + +""" +Displays the WakaTime daily/weekly/monthly times: + + * https://wakatime.com/developers#stats + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the Wakatime status query failed, the shown value is `n/a` + +Parameters: + * wakatime.token: Wakatime secret api key, you can get it in https://wakatime.com/settings/account. + * wakatime.range: Range of the output, default is "Today". Can be one of “Today”, “Yesterday”, “Last 7 Days”, “Last 7 Days from Yesterday”, “Last 14 Days”, “Last 30 Days”, “This Week”, “Last Week”, “This Month”, or “Last Month”. + * wakatime.format: Format of the output, default is "digital" + Valid inputs are: + * "decimal" -> 1.37 + * "digital" -> 1:22 + * "seconds" -> 4931.29 + * "text" -> 1 hr 22 mins + * "%H:%M:%S" -> 01:22:31 (or any other valid format) +""" + +import base64 +import shutil +import time +from typing import Final, List + +import requests + +import core.decorators +import core.input +import core.module +import core.widget + +HOST_API: Final[str] = "https://wakatime.com" +SUMMARIES_URL: Final[str] = f"{HOST_API}/api/v1/users/current/summaries" +UTF8: Final[str] = "utf-8" +FORMAT_PARAMETERS: Final[List[str]] = ["decimal", "digital", "seconds", "text"] + + +class Module(core.module.Module): + @core.decorators.every(minutes=5) + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.wakatime)) + + self.background = True + self.__label = "" + + self.__output_format = self.parameter("format", "digital") + self.__range = self.parameter("range", "Today") + + self.__requests = requests.Session() + + token = self.__encode_to_base_64(self.parameter("token", "")) + self.__requests.headers.update({"Authorization": f"Basic {token}"}) + + cmd = "xdg-open" + if not shutil.which(cmd): + cmd = "x-www-browser" + + core.input.register( + self, + button=core.input.LEFT_MOUSE, + cmd=f"{cmd} {HOST_API}/dashboard", + ) + + def wakatime(self, _): + return self.__label + + def update(self): + try: + self.__label = self.__get_waka_time(self.__range) + except Exception: + self.__label = "n/a" + + def __get_waka_time(self, since_date: str) -> str: + response = self.__requests.get(f"{SUMMARIES_URL}?range={since_date}") + + data = response.json() + grand_total = data["cumulative_total"] + + if self.__output_format in FORMAT_PARAMETERS: + return str(grand_total[self.__output_format]) + else: + total_seconds = int(grand_total["seconds"]) + return time.strftime(self.__output_format, time.gmtime(total_seconds)) + + @staticmethod + def __encode_to_base_64(s: str) -> str: + return base64.b64encode(s.encode(UTF8)).decode(UTF8) diff --git a/screenshots/wakatime.png b/screenshots/wakatime.png new file mode 100644 index 0000000000000000000000000000000000000000..82c8c797827652308f6ca4989b90f3d713817063 GIT binary patch literal 1820 zcmV+%2jlpOP)!O6|D$~OQJ#0KB299dD2AOMI=a-1W5=Y#61LYNg~80L4-!gMMNY~ zMNw2qQ9ZKIF6I-ACw6iK-=G7e_VD|P%Fj271=q3T+xWR(AJw^>Dgm#R-~^*cPn!ngm2*X# z)+J}JnOlBveOk&?&c`kI`T6<#ztg^bd%nReu%tm?URG~vkrLd2Ju4A7ZNC>Uh)3WC zgokl|F&lu=Y0OwVol}P{-bidQ`+<)N?nb{rRjOk1~IQ5V7DeAI~4wj)@w2;@Fl~6WqAy z;X8BFaSV8HyP_~}iMD$`-a2;V;JOjR1~k^>9p1l|wqlq?J2IPZl>PR&?p{+l zmY?_f_hRmrLNJOI88iQQS|1YJ74@LN07QdrH3YZd3%6va-nm|efne9(w`&D{tLEtD z&H?_^4O+K5cXPX|EtLdnYD~nzqSY9_%Rsp$4#3yfx2;oi)^_b|l7gENHyZuJ459wy zZuMV3HDoNBfjT;I``0JUMzLP@fwQ)1}G5R{qM@5{^3KzGt)oS z2FfjQAo%$Dw(;~rK}AY%Lk7K%4xyL*`i7!%ygaA0cn$0qPVcI%*ur}oArM@2vyEBF zc#?~EX2RjnK1V!dAcBL!P)9#e@82&J^=n`4+f%TVbJj~5NT7D*XHd(z`kic`+!6;C zi?_Gc+p()k2@Y#DetyZq>F6kCD{0n5(mB)%wl3zqjnESu>{y(z8ldY4-gJI*g&zJBfUaQ=O#`Xvof$iNn9Z51;v{Ep zIY7J=E(I6Xr>DIotKy0_j(SMO(NJ&{B%4U$kO1-xI>?M7fAd27i|>+f*ta`#MrsjY zrr;VXqY@ z*#kFJkVdhd0wm)o5FEO}z8yeUkxODO85a>Uqrejj%jWf|^sdI5d~}k$buz>p!L>3D zpsmI6zm~Shp5gHP*`3)qxg`HFXcIs&)2Bw^u)Jouc=Os=ieL#}L`$^++6l7OLeu`5+*(k8=aXxy;2oCv2RK#HVS_CU7 zxuhpc!$7$`4is6zAyL3jBIzTSy+V7~Uwi=}!?8ozcFkVHCY3BMr%H@r90>x!_2?di z1QTf{`Iqy2L!n;xByl+k}v|F46L;K+-5ZJ~LIVW8X|2a2rV z6biSX1g9_+B{+qtD8VUAMF~z}DoSt)Q}IwJOyz^5992{?t^5sN Date: Sun, 16 Apr 2023 11:29:31 +0100 Subject: [PATCH 2/3] rename mock_summaries_api_response test function --- tests/modules/contrib/test_wakatime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modules/contrib/test_wakatime.py b/tests/modules/contrib/test_wakatime.py index d6091b4..b76165c 100644 --- a/tests/modules/contrib/test_wakatime.py +++ b/tests/modules/contrib/test_wakatime.py @@ -20,7 +20,7 @@ def build_wakatime_module(waka_format=None, waka_range=None): return modules.contrib.wakatime.Module(config=config, theme=None) -def mock_todo_api_response(): +def mock_summaries_api_response(): res = mock.Mock() res.json = lambda: { "cumulative_total": { @@ -39,7 +39,7 @@ class TestWakatimeUnit(TestCase): def test_load_module(self): __import__("modules.contrib.wakatime") - @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + @mock.patch.object(Session, "get", return_value=mock_summaries_api_response()) def test_default_values(self, mock_get): module = build_wakatime_module() module.update() @@ -47,7 +47,7 @@ class TestWakatimeUnit(TestCase): mock_get.assert_called_with('https://wakatime.com/api/v1/users/current/summaries?range=today') - @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + @mock.patch.object(Session, "get", return_value=mock_summaries_api_response()) def test_custom_configs(self, mock_get): module = build_wakatime_module(waka_format="text", waka_range="last 7 days") module.update() From 1b0478edd421e1c6378abe8c829fd24246253881 Mon Sep 17 00:00:00 2001 From: Duarte Figueiredo Date: Sun, 16 Apr 2023 11:38:13 +0100 Subject: [PATCH 3/3] changed icon from normal w to font-awesome clock --- screenshots/wakatime.png | Bin 1820 -> 1729 bytes themes/icons/awesome-fonts.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/screenshots/wakatime.png b/screenshots/wakatime.png index 82c8c797827652308f6ca4989b90f3d713817063..a40a865881e42115a01e8ba9ef982736cd8f91d3 100644 GIT binary patch literal 1729 zcmV;y20rX0ssI23RA7l000JvNklJ+s90m?7-NY&iDJcGv5Sf(3KrBOCbq;91F=g)0kKCkDxgNAfTAd3 z0Yrm}=zfL#ha}(O`Er!KcAVMd!0LNB+~4iWAvCw4u_+6pKqkg)%f@wD_5;qxBjR6{M#>I zz8n!gURqj5jaq(n>IHsMvyoK&*YKWI$tj0eq|(yI!$yqNsgOK=`~+>%ffU)%9a^>N z%)M3}e*;#H9Umb(n#B9SD%~3U2ZWS7dX$xQQCfb`@RC*i`iHSXuU@?x6c(-thYlaZ zd868chm1sv&A>q;WOoA?vufOg2+pU#pq8?uM<`Y?o-JGBOG-)}J}mlqTLQaTXLkSo z12#kVp8eBL|MB$cQ??B;OVb87ZIh6&v!LK^d3kwZ;k^R~Q`@%hB7e&~Zr;4b+oaqj zU?EdjyY9z3cM2XncsOL}s2w}^Fi0Y)bG~u;@?WomMdc+Upz=fb$O(J*?tfHT>e3Gu zr|BiD7!V`lxN;UU+~VS5?J+IwC^m_|9gy7(V9cuL&z~!2Q5oP<+1&ujo7vepsi}vf z7DRLBF)nVM&J0#%WMnZBRP2*!Ga?Trr)t`B=Q25lSFDVi5;1GZ(v?^a4<9Fg%NXnm z4rbPyP{YJd;JDEDA5ydu-=QKmlHOy$;aR8&+z3tlU2 zTtcymNh|UaF5np6?P_ID{KYIN5 z?^G&MYaU&{=tBTvhh$y2v}yCUy7e1N8<$Y5ikLc^_PqJu(QfG1g!bt(XJMz#*qBwL zMo-iv+js1C_Jj3lddVuzPew-aq#jvaN99Ef)@+DSj>>dib~k`YR)JHePGf1l%v9ba zBh)19SKOyFgHkVQ^mFE$0Yp48ufQ12u{CgT-lj$tpGnBU|(R2C33OoWh0(Ema0E&#GO!_hH$*We2Hp z30VV5Z&v>|TbJE6wEiq~e$=A^hUa|@h zm|wi8V0<@j+@$^t__R4f?zE90M_Z-8BgTxKymZ;B<*}>jOG!c-bm;VkOcb}-**TQr z)WBq>MExgE{-#Qh?{No@-c&GP6(d@h@JJQr)TMn);zI8H zfz%9AQqr{U+NG6jlsuW47uea5m3v}hQu_{FL4vPc~BB*Od<1;>zKqotWUm|+!LQRhQ-Y2Qjb`oa1%EwKuOv}m7@u#;m$ z8$E3#$fdE3j|5g(rBJ(WK&Q~|(4RYPX<*}{f^S%5!-g|#R@v}9Y*yLuJ#1Fl@IC$k X*`jvYTDPW%00000NkvXXu0mjf$P`y{ literal 1820 zcmV+%2jlpOP)!O6|D$~OQJ#0KB299dD2AOMI=a-1W5=Y#61LYNg~80L4-!gMMNY~ zMNw2qQ9ZKIF6I-ACw6iK-=G7e_VD|P%Fj271=q3T+xWR(AJw^>Dgm#R-~^*cPn!ngm2*X# z)+J}JnOlBveOk&?&c`kI`T6<#ztg^bd%nReu%tm?URG~vkrLd2Ju4A7ZNC>Uh)3WC zgokl|F&lu=Y0OwVol}P{-bidQ`+<)N?nb{rRjOk1~IQ5V7DeAI~4wj)@w2;@Fl~6WqAy z;X8BFaSV8HyP_~}iMD$`-a2;V;JOjR1~k^>9p1l|wqlq?J2IPZl>PR&?p{+l zmY?_f_hRmrLNJOI88iQQS|1YJ74@LN07QdrH3YZd3%6va-nm|efne9(w`&D{tLEtD z&H?_^4O+K5cXPX|EtLdnYD~nzqSY9_%Rsp$4#3yfx2;oi)^_b|l7gENHyZuJ459wy zZuMV3HDoNBfjT;I``0JUMzLP@fwQ)1}G5R{qM@5{^3KzGt)oS z2FfjQAo%$Dw(;~rK}AY%Lk7K%4xyL*`i7!%ygaA0cn$0qPVcI%*ur}oArM@2vyEBF zc#?~EX2RjnK1V!dAcBL!P)9#e@82&J^=n`4+f%TVbJj~5NT7D*XHd(z`kic`+!6;C zi?_Gc+p()k2@Y#DetyZq>F6kCD{0n5(mB)%wl3zqjnESu>{y(z8ldY4-gJI*g&zJBfUaQ=O#`Xvof$iNn9Z51;v{Ep zIY7J=E(I6Xr>DIotKy0_j(SMO(NJ&{B%4U$kO1-xI>?M7fAd27i|>+f*ta`#MrsjY zrr;VXqY@ z*#kFJkVdhd0wm)o5FEO}z8yeUkxODO85a>Uqrejj%jWf|^sdI5d~}k$buz>p!L>3D zpsmI6zm~Shp5gHP*`3)qxg`HFXcIs&)2Bw^u)Jouc=Os=ieL#}L`$^++6l7OLeu`5+*(k8=aXxy;2oCv2RK#HVS_CU7 zxuhpc!$7$`4is6zAyL3jBIzTSy+V7~Uwi=}!?8ozcFkVHCY3BMr%H@r90>x!_2?di z1QTf{`Iqy2L!n;xByl+k}v|F46L;K+-5ZJ~LIVW8X|2a2rV z6biSX1g9_+B{+qtD8VUAMF~z}DoSt)Q}IwJOyz^5992{?t^5sN