golang

Получение информации с LeetCode о пользователе на Golang

  • понедельник, 10 июня 2024 г. в 00:00:07
https://habr.com/ru/articles/820469/

LeetCode - популярная платформа для подготовки к собеседованиям по программированию, предоставляющая задачи на алгоритмы и структуры данных. Чтобы улучшить свои навыки и изучить свои успехи, пользователи могут хотеть получить информацию о своем профиле на LeetCode, такую как решенные задачи, статистика по времени и другие данные.

В данной статье будет рассмотрено, как можно написать программу на Golang для получения информации о пользователе с помощью API LeetCode. Для разработки будет использоваться библиотека graphql на Golang, чтобы отправить запросы к API LeetCode и получить необходимые данные о пользователе. Для простоты взаимодействия с пользователями будет использован Telegram API. Стоит добавить, что для Телеграм бота не нужно покупать отдельный хостинг, можно все сделать локально, нужно только доступ к интернету.

Почему Golang, а не Python?

Выбор между Golang и Python для разработки ботов зависит от конкретных требований проекта, но есть несколько аспектов, в которых Golang может оказаться более предпочтительным выбором по сравнению с Python:

  1. Производительность: Golang обычно обладает более высокой производительностью и эффективностью исполнения кода по сравнению с Python. Это может быть критично для ботов, которые должны обрабатывать большие объемы данных или выполнять сложные вычисления.

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

  3. Статическая типизация: Golang является языком программирования со статической типизацией, что может помочь обнаружить ошибки на ранних этапах разработки и сделать код более надежным.

  4. Многопоточность: Golang имеет встроенную поддержку горутин, которые облегчают параллельное выполнение операций, что особенно полезно для ботов, работающих с большим количеством одновременных запросов.

  5. Эффективность работы с конкурентностью: Golang имеет механизмы управления конкурентностью, такие как каналы (channels) и слабые блокировки (mutexes), что облегчает создание многозадачных ботов.

Хотя Python также является популярным и мощным языком программирования для разработки ботов благодаря своей легкости и простоте в использовании, Golang может быть предпочтительным выбором, особенно если важны производительность, эффективность и работа с высокой нагрузкой.

Разработка

Перед тем как начать разрабатывать необходимо установить необходимые для работы библиотеки.

go install -v github.com/go-telegram-bot-api/telegram-bot-api/v5
go install -v github.com/machinebox/graphql

Для удобства поддержки и читаемости необходимо организовать код, храня различную функциональность в отдельных файлах. Начнем с того, что есть некая точка входа main.go через которую запускается бот:

main.go
package main

import (
	"log"
)

func check(err error) {
	if err != nil {
		log.Println(err)
	}
}

func main() {
	startBot()
}

После точки входа следует создать файл telegram.go, в котором будет происходить логика работы Телеграм бота, такая как: получение информации от пользователя, отправка ответа пользователю, отправка инструкции использования пользователю:

telegram.go
package main

import (
	"strconv"
	botAPI "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

func isCallbackQuery(update *botAPI.Update) bool {
	return update.CallbackQuery != nil && update.CallbackQuery.Message.Text != ""
}

func isStartMessage(update *botAPI.Update) bool {
	return update.Message != nil && update.Message.Text == "/start"
}

func getKeyboardRow(buttonText string, buttonCode string) []botAPI.InlineKeyboardButton {
	return botAPI.NewInlineKeyboardRow(botAPI.NewInlineKeyboardButtonData(buttonText, buttonCode))
}

func renderingMenu(bot *botAPI.BotAPI, chatId int64) {
	msg := botAPI.NewMessage(chatId, "Select action")
	msg.ReplyMarkup = botAPI.NewInlineKeyboardMarkup(
		getKeyboardRow(left+" "+"Information about bot"+" "+right, "info"),
	)
	bot.Send(msg)
}

func updateProccessing(update *botAPI.Update, bot *botAPI.BotAPI) {
	var message string
	choice := update.CallbackQuery.Data
	if choice == "info" {
		message = "My functionality: \n Get username information from leetcode. You only need to send the username in a message and get the result immediately"
	} 
	msg := botAPI.NewMessage(update.CallbackQuery.From.ID, message)
	bot.Send(msg)
}

func startBot() {
	var message string
	bot, err := botAPI.NewBotAPI(token)
	check(err)
	updateConfig := botAPI.NewUpdate(0)
	updateConfig.Timeout = timeout
	updates := bot.GetUpdatesChan(updateConfig)
	for update := range updates {
		if isCallbackQuery(&update) {
			updateProccessing(&update, bot)
		} else {
			if isStartMessage(&update) {
				message = "Hi everyone! \nIt's a telegram bot. You can get information from leetcode by username. If you need more information than click info."
				renderingMenu(bot, update.Message.Chat.ID)
			} else {
				username := getUsersInfo(update.Message.Text)
				if username.MatchedUser.Username == "" {
					message = cancel+"Information: \nusername:  user not found" 
				} else {
					message = "Information: \nusername: " + username.MatchedUser.Username
					message += "\n" + solved + "solved: " + strconv.Itoa(username.MatchedUser.SubmitStats.AcSubmissionNum[0].Count) + " / " +strconv.Itoa(username.AllQuestionsCount[0].Count)
					message += "\n" + easy + "Easy: " + strconv.Itoa(username.MatchedUser.SubmitStats.AcSubmissionNum[1].Count) + " / " + strconv.Itoa(username.AllQuestionsCount[1].Count)
					message += "\n" + middle + "Middle: " + strconv.Itoa(username.MatchedUser.SubmitStats.AcSubmissionNum[2].Count) + " / " + strconv.Itoa(username.AllQuestionsCount[2].Count)
					message += "\n" + hard + "Hard: " + strconv.Itoa(username.MatchedUser.SubmitStats.AcSubmissionNum[3].Count) + " / " + strconv.Itoa(username.AllQuestionsCount[3].Count)
				}
			}
			msg := botAPI.NewMessage(update.Message.Chat.ID, message)
			msg.ReplyToMessageID = update.Message.MessageID
			bot.Send(msg)
		}
	}
}

Следующая функциональность бота - работа с graphql. Для удобства использования были созданы структуры и определены к ним поля, которые используются в graphql запросах и вынесено в отдельный файл model.go:

model.go
package main

type Submission struct {
	Count      int    `json:"count"`
	Difficulty string `json:"difficulty"`
}

type UserProfileData struct {
	MatchedUser       MatchedUser  `json:"matchedUser"`
	AllQuestionsCount []Submission `json:"allQuestionsCount"`
}

type SubmitStats struct {
	AcSubmissionNum []Submission `json:"acSubmissionNum"`
}

type MatchedUser struct {
	Username    string      `json: username`
	SubmitStats SubmitStats `json:"submitStats"`
}

Для общения с API LeetCode был создан graphql.go. Для простоты использования были созданы функции, которые возвращают graphql запросы. Основная функция осуществляет запрос к API и возвращает структуру, в которой хранится информация о пользователе: имя пользователя, сколько всего решил задач и сколько по каждому уровню сложности было решено:

graphql.go
package main

import (
	"context"
	"github.com/machinebox/graphql"
)

func getQueryUserInfo() string {
	return `query ($username: String!) { matchedUser(username: $username) { 
				username
				submitStats { 
				acSubmissionNum { 
					difficulty 
					count 
					} 
				} 
			}
		}`
}

func getQueryQntyQuestions() string {
	return `{ allQuestionsCount { 
			difficulty 
			count 
			}
		}`
}

func getUsersInfo(username string) UserProfileData {
	var requestUser UserProfileData
	client := graphql.NewClient("https://leetcode.com/graphql")
	query := getQueryQntyQuestions()
	request := graphql.NewRequest(query)
	ctx := context.Background()
	err := client.Run(ctx, request, &requestUser)
	check(err)
	query = getQueryUserInfo()
	request = graphql.NewRequest(query)
	request.Var("username", username)
	err = client.Run(ctx, request, &requestUser)
	if err != nil {
		check(err)
	}
	return requestUser
}

Для хранения константных информаций: токен, таймаут, эмодзи, если нужно, создан constants.go:

constants.go
package main

const (
	token   = "Your Token"
	left    = "\U000025B6"
	right   = "\U000025C0"
	easy    = "\U0001F4D7"
	hard    = "\U0001F4D5"
	middle  = "\U0001F4D9"
	solved	= "\U00002705"
	cancel	= "\U0001F6AB"
	timeout = 5
)

Проверка работоспособности

Предстоит протестировать бот. Для проверки будет взят username автора статьи и несуществующий пользователь(к существующему пользователю допишется цифра). Для начала предстоит проверка на существующем пользователе. Была получена данная информация, да эта информация верна, вот пользователь на LeetCode.

Приветствие бота и получение инструкции
Приветствие бота и получение инструкции
Получение информации о пользователе
Получение информации о пользователе
результат с сайта LeetCode
результат с сайта LeetCode

А теперь проверим на несуществующем. Бот ответил, что такого пользователя нет.

Получение информации, что нет такого пользователя
Получение информации, что нет такого пользователя
Результат, что пользователя нет такого
Результат, что пользователя нет такого

Вывод

Получение информации о пользователе с платформы LeetCode на языке программирования Golang может быть важным и полезным шагом для разработчиков, желающих улучшить свои навыки в алгоритмах и структурах данных. Используя API LeetCode, можно получить доступ к различным данным о пользователе.