Automatize a coleta de cupons para obter literatura gratuita

fundo


Em litros, existe um sistema de bônus e cupons que aparecem com regularidade invejável. Para fazer sua esposa feliz e, em geral, ele pode encontrar um livro interessante, ele começou a monitorar o site em que novos cupons aparecem e os colocou em telegramas. Poucos dias depois, porém, eu estava cansado desse negócio e decidi automatizar esse processo para que ele estivesse disponível para todos que o desejassem.


Implementação


Como eu constantemente publicava novos cupons em telegramas e, em geral, gosto dessa ferramenta, decidi criar outro bot para telegramas, desde que um número suficiente de bibliotecas já tenha sido criado para ele. Tome golang e a biblioteca telegram-bot-api como idioma . Também precisamos escolher um recurso do qual extrair informações, eu tinha vários sites em mente e pensei em escrever um analisador universal como um todo, mas em algum momento fiquei preguiçoso e decidi escolher um recurso. Para armazenar cupons mesmo após o reinício, decidi usar um banco de dados sqlite3 simples. Armazenaremos informações sobre cupons, bem como informações sobre usuários registrados no bot de telegrama, bem como informações sobre quais cupons o usuário já recebeu e quais ainda não.


Parece algo assim


imagem


Análise de site


A análise do site será feita pela biblioteca goquery - funciona da mesma maneira que o jquery.
Usando a estrutura 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


 Agora, minha esposa está feliz por receber livros gratuitos em litros, mas eu só estava interessado em resolver esse problema. Ainda há algo que pode ser melhorado; adicione um sistema de alerta se o MarkAsRead falhar (até agora, isso não aconteceu, mas você nunca sabe) , ele agora também cancela a inscrição e não envia mais mensagens para as pessoas que cancelaram a inscrição e você precisa retorná-las ao estado ativo depois de pressionar o comando / start novamente. Bem, em geral, adicione a capacidade de escolher o horário da distribuição e a escolha dos cupons, porque o site não possui apenas cupons da LiteRes. Mas tudo isso é necessário, até o momento nenhum pedido foi recebido.


Referências


  1. Projeto em si
  2. Site do cupom
  3. Bot Name @xFreeCouponBot

All Articles