Скрипты для Conky на Golang по получению курсов валют и афоризмов

Скрипты для Conky на Golang по получению курсов валют и афоризмов

На работе и дома я пользуюсь исключительно операционной системой Linux. Но какое же рабочее место может быть без виджетов на рабочем столе? Вот я решил установить и настроить у себя Conky. Приложение показалось очень полезным, но чего-то не хватало. Почему бы не добавить туда актуальные курсы валют и не выводить афоризмы? Для этих целей я и решил написать скрипт. А так как речь идёт о консольном приложении, то всё было сделано на GO.

Для начала нам нужно определиться с API, откуда мы будем получать информацию. Долго выбирать не пришлось, нашёл два сервиса:

  1. Курсы валют получаем с сайта www.currencyconverterapi.com. Там потребуется сгенерировать ключ API.
  2. Цитаты и афоризмы получаем с http://rzhunemogu.ru. Там также можно получать и анекдоты. Мне это пригодится в будущем.

Оба моих проекта доступны на github. Можно ознакомиться полностью с исходным кодом и установить у себя:

  1. Скрипт по получению курсов валют: https://github.com/semelyanov86/currency-rates
  2. Скрипт по получению афоризмов: https://github.com/semelyanov86/fortune-receiver

 

Проекты практически одинаковые по функционалу, поэтому в этой статье я разберу только второй, там есть один нюанс, который нужно прояснить.

Всё что будет делать скрипт - это скачивать нужные данные в формате JSON и выводить нужную информацию в консоль.

Первое что нам понадобится - сгенерировать новый проект в вашей IDE.

Далее нам нужно определиться с типами, создать все необходимые интерфейсы, которые бы говорили нам, как работают HTTP-запросы и какие типы данных используются нами.

Поэтому я создаю для этого отдельный файл types.go.

package main

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

type FortuneResult struct {
Content string
}

Как видно, интерфейс GetWebRequest говорит нам, что принимает тип афоризма, а ответ возвращает результат полученных данных или ошибку.

Для чего я создаю интерфейс в первую очередь? Чтобы иметь возможность покрыть тестами, создать функцию, которая будет возвращать тестовые данные. Также мы абстрагируем операции по получению данных и можем быстро поменять источник.

Теперь нужно сделать имплементацию этого интерфейса. Создаю файл fortune.go:

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
}

И конечно же нельзя забыть и о функции main, без неё не запустится наш скрипт.

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)
}

 

Как видите, в этом примере я имплементацию объединил в один файл. Что, может быть, не совсем правильно. Имеет смысл в будущем функцию FetchBytes и структуру LiveGetWebRequest вынести в отдельный файл, под названием http_fortune, которая будет описывать именно имплементацию по http. А если мы в будущем решим грузить афоризмы из файла, то просто сделаем новый файл и поменяем источник только в функции main.

Далее, если вы заметили, в функции getFortune есть один интересный момент. Данные нам приходят в кодировке Windows-1251. Результат в консоли отображается в виде непонятных символов. Нужно всё сконвертировать в UTF-8. Кроме этого, нам нужно поменять символы по переносу строки.

 

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)

 

Теперь когда мы работаем только с интерфейсами, мы можем всё покрыть тестами.

Файл fortune_test выглядит следующим образом:

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)
}
}

 

Чтобы запустить тесты, выполните команду

go test ./

 

Вышеуказанный тест эффективен только при проверке функции json.Unmarshal и наших предположений касательно того, как должен выглядеть корректный ответ по api. Абстрагирование может быть подходящим в нашем примере, но покрытие будет низким.

Поэтому, может иметь смысл сделать низкоуровневые тесты, чтобы убедиться, что сделанный запрос на сервер корректен или что мы отправляем именно get запрос, а не post.

Для этих целей у GO есть набор вспомогательных функций, которые создают тестовые http серверы и клиенты.

 

Имея в наличии go код, теперь надо сгенерировать исполняемый файл. Делаем это командой

go build ./

 

Нам сгенерируется файл fortune-receiver.

Если мы его запустим в консоли, то получим на выходе цитату. Мы также можем передать в качестве аргумента тип и получить не цитату, а анекдот, например:

fortune-receiver -type=1

 

Теперь нужно добавить этот блок в conky.

 

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

 

Как видите из примера, мы просто выводим содержимое файла. Просто показывать тот результат, который нам показывает скрипт, у меня не получилось. Поэтому я выбрал такой обходной вариант.

Постоянно обновлять этот файл не проблема - просто добавим задание в планировщик:

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

 

Итак, теперь у нас есть очень удобный консольный скрипт, который позволяет выводить афоризм или цитату в консоль и в виджеты conky. Также вы можете им пользоваться и в самой консоли. Например, вы можете добавить alias в файле .bashrc:

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

 

Это позволит нам просто ввести команду anekdot в командой строке и на выходе мы можем прочитать случайный анекдот, полученный по API.

Популярное

Самые популярные посты

Как быть максимально продуктивным на удалённой работе?
Business

Как быть максимально продуктивным на удалённой работе?

Я запустил собственный бизнес и намеренно сделал всё возможное, чтобы работать из любой точки мира. Иногда я сижу с своём кабинете с большим 27-дюймовым монитором в своей квартире в г. Чебоксары. Иногда я нахожусь в офисе или в каком-нибудь кафе в другом городе.

Привет! Меня зовут Сергей Емельянов и я трудоголик
Business PHP

Привет! Меня зовут Сергей Емельянов и я трудоголик

Я программист. В душе я предприниматель. Я начал зарабатывать деньги с 11 лет, в суровые 90-е годы, сдавая стеклотару в местный магазин и обменивая её на сладости. Я зарабатывал столько, что хватало на разные вкусняшки.

Акция! Профессиональный разработчик CRM за 2000 руб. в час

Выделю время под ваш проект. Знания технологий Vtiger CRM, SuiteCRM, Laravel, Vue.js, Golang, React.js. Предлагаю варианты сотрудничества, которые помогут вам воспользоваться преимуществами внешнего опыта, оптимизировать затраты и снизить риски. Полная прозрачность всех этапов работы и учёт временных затрат. Оплачивайте только рабочие часы разработки после приемки задачи. Экономьте на платежах по его содержанию разработчика в штате. Возможно заключение договора по ИП. С чего начать, чтобы нанять профессионального разработчика на full-time? Просто заполните форму!

Telegram
@sergeyem
Telephone
+4915211100235