Automatizar la colección de cupones para literatura gratuita

Antecedentes


Litros tiene un sistema de bonos y cupones que aparecen con envidiable regularidad. Para hacer feliz a su esposa y, en general, puede encontrar un libro interesante para sí mismo, comenzó a monitorear el sitio en el que aparecen cupones nuevos y los dejó caer en telegramas. Pero solo unos días después estaba cansado de este negocio y decidí automatizar este proceso para que pudiera estar disponible para todos los que lo deseen.


Implementación


Dado que constantemente publicaba nuevos cupones en telegramas, y en general me gusta esta herramienta, decidí crear otro bot para telegramas, siempre que ya se haya creado un número suficiente de bibliotecas para él. Tome el golang y la biblioteca telegram-bot-api como idioma . También tenemos que elegir un recurso del que extraer información, tenía en mente varios sitios y pensé en escribir un analizador universal en su conjunto, pero en algún momento me volví vago y decidí elegir un recurso. Para almacenar cupones incluso después de reiniciar, decidí usar una base de datos sqlite3 simple. Almacenaremos información sobre cupones en él, así como información sobre usuarios registrados en el bot de telegram, así como información sobre qué cupones ya ha recibido el usuario y cuáles no.


Se ve algo como esto


imagen


Análisis del sitio


El análisis del sitio lo realizará la biblioteca goquery : funciona de la misma manera que jquery.
Uso de la estructura goquery.Document html . , , . . unixtime, . , , . , , js .


Telegram bot


BotFather , telegram . telegram api , http websocket. , telegram-bot-api updater .


type SNBot struct {
    cfg *Config
    bot *tgbotapi.BotAPI
    upd tgbotapi.UpdatesChannel
}

func New(cfg *Config) (*SNBot, error) {
    bot, err := tgbotapi.NewBotAPI(cfg.Token)
    if err != nil {
        return nil, err
    }
    level.Info(cfg.Logger).Log("msg", "Authorized on account", "bot-name", bot.Self.UserName)
    u := tgbotapi.NewUpdate(0)
    u.Timeout = cfg.UpdateTime
    updates, err := bot.GetUpdatesChan(u)
    if err != nil {
        return nil, err
    }
    return &SNBot{
        cfg: cfg,
        bot: bot,
        upd: updates,
    }, nil
}

, , gocron. task gocron storage storage .


Task function
func task(bot *snbot.SNBot, s *storage.Storage, c *collector.Collector, logger kitlog.Logger) {
    c.Collect(collector.ConditionQuery{
        URI: "https://lovikod.ru/knigi/promokody-litres",
    })
    chats, err := s.GetChat()
    if err != nil {
        level.Error(logger).Log("msg", "failed get chats", "err", err)
    }
    for _, id := range chats {
        records, err := s.GetNotUseCoupon(id)
        if err != nil {
            level.Error(logger).Log("msg", "failed get coupons", "err", err)
            return
        }
        var msg string
        for i, rec := range records {
            msg = fmt.Sprintf("%v%v:\t%s \n--->: %s\n : %v\n: %s\n\n", msg, i+1, rec.Link, rec.Code, time.Unix(rec.Date, 0).Format("02.01.2006"), rec.Description)
        }
        if len(msg) != 0 {
            err = bot.Send(id, msg)
            if err != nil {
                level.Error(logger).Log("msg", "failed send message", "err", err)
                continue
            }
            err = s.MarkAsRead(id, records)
            if err != nil {
                level.Error(logger).Log("msg", "failed marked as read", "err", err)
                continue
            }
        }
    }
    level.Info(logger).Log("msg", "send all chats new coupons")
}

- , , , .

func (s *SNBot) Send(chatID int64, msg string) error {
    level.Error(s.cfg.Logger).Log("msg", "try send", "chatID", chatID)
    var numericKeyboard = tgbotapi.NewReplyKeyboard(
        tgbotapi.NewKeyboardButtonRow(
            tgbotapi.NewKeyboardButton("/print5"),
        ),
    )
    m := tgbotapi.NewMessage(chatID, msg)
    m.ReplyMarkup = numericKeyboard
    _, err := s.bot.Send(m)
    if err != nil {
        if err.Error() == errBlockedByUser {
            s.cfg.Storage.UpdChatActivity(chatID, false)
        }
        return err
    }
    return nil
}

  Dockerfile


,   .


# build binary
FROM golang:1.10.3-alpine3.8 AS build
RUN apk add --no-cache linux-headers gcc g++
ARG VERSION=dev
WORKDIR /go/src/github.com/wenkaler/xfreehack
COPY . /go/src/github.com/wenkaler/xfreehack
RUN CGO_ENABLED=1 go build \
    -o /out/xfree \
    -ldflags "-X main.serviceVersion=$VERSION" \
    github.com/wenkaler/xfreehack/cmd

# copy to alpine image
FROM alpine:3.8
WORKDIR /app
RUN mkdir /db
COPY --from=build /out/xfree /app
RUN apk add --no-cache tzdata
RUN apk --no-cache add ca-certificates
ENV TZ Europe/Moscow
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
CMD ["/app/xfree"]

Systemd


. 2.6, upgrad- systemd. ? — 3.


[Unit]
Description=Xfree service
After=network.target
After=network-online.target

[Service]
ExecStart=/urs/local/bin/xfree
Environment="TELEGRAM_TOKEN=$TELEGRAM_TOKEN" "PATH_DB=/db/xfree.db"
TimeoutSec=30
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target


 Ahora mi esposa está feliz de poder recibir libros gratis en litros, pero estaba interesado en resolver este problema. Todavía hay algo que se puede mejorar, agregue un sistema de alerta si MarkAsRead falla (hasta ahora esto no ha sucedido, pero nunca se sabe) , ahora también se da de baja y ya no envía mensajes a las personas que se han dado de baja de él y debe devolverlos al estado activo después de presionar el comando / start nuevamente. Bueno, en general, agregue la capacidad de elegir el tiempo de distribución y la elección de los cupones, porque el sitio no solo tiene cupones de LiteRes. Pero todo esto es necesario, hasta el momento no se han recibido tales solicitudes.


Referencias


  1. Proyecto en sí
  2. Sitio web de cupones
  3. Nombre del bot @xFreeCouponBot

All Articles