Scripts for Conky in Golang for getting exchange rates and fortunes

Scripts for Conky in Golang for getting exchange rates and fortunes

At work and at home, I use the Linux operating system exclusively. But what kind of workplace can be without widgets on the desktop? So I decided to install and configure Conky. The application seemed very useful, but something was missing. Why not to add current exchange rates there and display fortunes? For these purposes, I decided to write a script. And since we are talking about a console application, everything was done on GO.

 

To begin with, we need to decide on the API from where we will receive information. I didn’t have to choose for a long time, I found two services:

  1. We get exchange rates from the website www.currencyconverterapi.com. There you will need to generate an API key.
  2. We get quotes and fortunes from http://rzhunemogu.ru. You can also get jokes there. I will need this in the future.

Both of my projects are available on github. You can read the full source code and install for yourself:

  1. Script for getting exchange rates: https://github.com/semelyanov86/currency-rates
  2. Script for receiving fortunes: https://github.com/semelyanov86/fortune-receiver

 

The projects are almost identical in functionality, so in this article I will analyze only the second one, there is one nuance that needs to be clarified.

All that the script will do is download the necessary data in JSON format and display the necessary information in the console.

The first thing we need is to generate a new project in your IDE.

Next, we need to decide on the types, create all the necessary interfaces that would tell us how HTTP requests work and what data types we use.

So I create a separate types.go file for this.

package main

type GetWebRequest interface {
FetchBytes(typeFort int) ([]byte, error)
}

type FortuneResult struct {
Content string
}

As you can see, the GetWebRequest interface tells us that it accepts an fortune type, and the response returns the result of the received data or an error.

Why am I creating an interface in the first place? To be able to cover with tests, create a function that will fake or mock data. We also abstract the data retrieval operations and can quickly change the source.

Now we need to implement this interface. I create fortune.go file:

package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/transform"
"io"
"log"
"net/http"
"strconv"
)

type LiveGetWebRequest struct {
}

func (g LiveGetWebRequest) FetchBytes(typeFort int) ([]byte, error) {
url := "http://rzhunemogu.ru/RandJSON.aspx?CType=" + strconv.Itoa(typeFort)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return []byte{}, err
}
req.Header.Set("User-Agent", "Conky-Fortune-Ext")
res, err := http.DefaultClient.Do(req)
if err != nil {
return []byte{}, err
}
if res.StatusCode != http.StatusOK {
log.Fatalf("Wrong status code: %d", res.StatusCode)
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
return []byte{}, err
}

return body, nil
}

func getFortune(getWebRequest GetWebRequest, typeFort int) (string, error) {
var fortune FortuneResult
body, err := getWebRequest.FetchBytes(typeFort)
if err != nil {
return "", err
}
data := bytes.Replace(body, []byte("\r"), []byte(""), -1)
data = bytes.Replace(data, []byte("\n"), []byte("\\n"), -1)
dec := charmap.Windows1251.NewDecoder()
utf8Bytes, _, err := transform.Bytes(dec, data)
if err != nil {
return "", err
}
err = json.Unmarshal(utf8Bytes, &fortune)
if err != nil {
return "", err
}
return fortune.Content, err
}

And of course do not forget about main function. Without it our script will not work.

func main() {
var key int
flag.IntVar(&key, "type", 4, "Type of fortune")
flag.Parse()
liveClient := LiveGetWebRequest{}
content, err := getFortune(liveClient, key)
if err != nil {
log.Fatal(err)
}

fmt.Println(content)
}

 

As you can see, in this example, I combined the implementation into one file. Which may not be entirely correct. It makes sense in the future to move the FetchBytes function and the LiveGetWebRequest structure into a separate file called http_fortune, which will describe exactly the http implementation. And if in the future we decide to load fortunes from a file, then we will simply make a new implementation and change the source only in the main function.

Further, if you notice, there is one interesting point in the getFortune function. Data comes to us in Windows-1251 encoding. The result in the console is displayed as obscure characters. Everything needs to be converted to UTF-8. In addition, we need to change the characters on line breaks.

 

data := bytes.Replace(body, []byte("\r"), []byte(""), -1)

data = bytes.Replace(data, []byte("\n"), []byte("\\n"), -1)

dec := charmap.Windows1251.NewDecoder()

utf8Bytes, _, err := transform.Bytes(dec, data)

 

Now that we only work with interfaces, we can cover everything with tests.

The fortune_test file looks like this:

package main

import "testing"

type testWebRequest struct {
}

func (t testWebRequest) FetchBytes(typeFort int) ([]byte, error) {
return []byte(`{"content":"this is test"}`), nil
}

func TestGetContent(t *testing.T) {
want := "this is test"
got, err := getFortune(testWebRequest{}, 1)
if err != nil {
t.Fatal(err)
}
if want != got {
t.Errorf("Content want: %s, got: %s", want, got)
}
}

To run the tests, just type command:

go test ./

 

The above test is only effective when testing the json.Unmarshal function and our assumptions about what a valid api response should look like. Abstraction may be appropriate in our example, but the coverage will be low.

Therefore, it may make sense to do low-level tests to make sure that the request made to the server is correct or that we are sending a get request, and not a POST one.

For these purposes, GO has a set of helper functions that create mock http servers and clients.

 

With the go code in place, we now need to generate the executable. Doing it as a with a command

go build ./

 

A fortune-receiver file will be generated for us.

If we run it in the console, we will get a quote at the output. We can also pass a type as an argument and get not a quote, but a joke, for example:

fortune-receiver -type=1

 

Now we can create a conky block:

 

${offset 0}${font Noto Sans:size=10}${execi 500 cat ~/random.log}

 

As you can see from the example, we simply display the contents of the file. I didn't manage to just show the result that the script shows us. So I chose this workaround.

Constantly updating this file is not a problem - just add a task to the scheduler:

*/15 * * * * ~/fortune-receiver > ~/random.log

 

So, now we have a very handy console script that allows you to output an aphorism or a quote to the console and conky widgets. You can also use it in the console itself. For example, you can add alias in .bashrc file:

alias anekdot='/data/sergeyem/forune-receiver/fortune-receiver -type=1'

 

This will allow us to simply enter the command anekdot at the command line, and as output we can read a random joke received from the API.

Popular Posts

My most popular posts

Maximum productivity on remote job
Business

Maximum productivity on remote job

I started my own business and intentionally did my best to work from anywhere in the world. Sometimes I sit with my office with a large 27-inch monitor in my apartment in Cheboksary. Sometimes I’m in the office or in some cafe in another city.

Hello! I am Sergey Emelyanov and I am hardworker
Business PHP

Hello! I am Sergey Emelyanov and I am hardworker

I am a programmer. I am an entrepreneur in my heart. I started making money from the age of 11, in the harsh 90s, handing over glassware to a local store and exchanging it for sweets. I earned so much that was enough for various snacks.

Hire Professional CRM developer for $25 per hour

I will make time for your project. Knowledge of Vtiger CRM, SuiteCRM, Laravel, and Vue.js. I offer cooperation options that will help you take advantage of external experience, optimize costs and reduce risks. Full transparency of all stages of work and accounting for time costs. Pay only development working hours after accepting the task. Accept PayPal and Payoneer payment systems. How to hire professional developer? Just fill in the form

Telegram
@sergeyem
Telephone
+4915211100235