Что делать если нужно быстро создать короткую ссылку? Конечно – использовать сокращатель ссылок. А что если при этом сделать эту ссылку читаемой? Еще и при этом использовать свой личный домен? А лучше было бы делать это без дополнительных серверов. Похоже что ответ есть.


Предыстория

Идея «легкого сокращателя ссылок» пришла ко мне в момент поиска варианта переадресации с использованием домена для одной из комнат в новомодной соцсети Clubhouse. Суть идеи переадресации для комнаты состоял в том, чтобы перезапускать комнату с одинаковым названием, но постоянным онлайном. Решить нужно было проблему постоянной смены адреса комнаты припарковав такую ссылку к под-домену.

Решение напросилось само собой, так как сайт был заранее посажен на Cloudflare. Изначально использовала функция «Page Rules», которая позволяет задать, в том числе, правила переадресации, но вскоре пришла идея сделать эту переадресацию более гибкой и изменяемой без нужды заходить в настройки сервиса. Конечно же, таким решением стал Telegram Bot.

Постановка задачи

Для того чтобы выполнить задуманное нужно решить несколько проблем:

  • Как переадресовывать с определенного под-домена?

  • Куда сохранять ссылки по ключу (сокращение) – значению (адрес переадресации)?

  • С помощью чего создавать такие сокращения?

Как вы догадались, ответы на эти вопросы есть в самом заголовке статьи. Поэтому предлагаю приступить к практической части.

Предусловия

Для более детального описания отмечу основные условия необходимые для реализации нашего проекта:

  • Домен подключенный к Cloudflare;

  • Общие знания JavaScript;

  • Созданный Telegram бот;

  • Документация по Cloudflare Worker'ам и Telegram Bot API.

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

Подготовка

Казалось бы, все предусловия соблюдены, — «Какая еще подготовка?». Предлагаю отметить несколько шагов подготовки к реализации:

1. Создание хранилища – нам поможет Cloudflare KV.

Cloudflare KV — это база данных для Worker'ов по принципу «ключ - значение». Как вы поняли, вторая проблема решилась силами самого Cloudflare.

Последовательность простая: на странице наших Workers переходим во вкладку KV, вводим желаемое имя для хранилища, нажимаем добавить.

Страница Cloudflare KV
Страница Cloudflare KV

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

Внутри нашего хранилища
Внутри нашего хранилища

2. Создаем свой Worker и настраиваем его.

Для этого воспользуемся кнопкой «Create worker», в редакторе сразу сохраним и задеплоим новый Worker («Save and Deploy») и вернемся обратно в меню.

Так выглядит страница нового Worker'а
Так выглядит страница нового Worker'а

Сразу задаем вменяемое имя и перейдя в «Settings» запишем токен нашего Telegram бота, а также привяжем хранилище.

Настройки Worker'а
Настройки Worker'а

3. Привяжем под-домен к скрипту

Для того чтобы обращение по желаемому адресу, в моем случае url.mydomain.com, направляло пользователя в наш будущий «сервис-сокращатель» настроим привязку к под-домену.

Страница Workers для нашего домена
Страница Workers для нашего домена

А именно, нам надо на странице «Workers» для нашего домена добавить свой «Route» на будущий сервис-сокращатель.

Добавление нового перенаправления
Добавление нового перенаправления

Заметьте, что звездочка вконце ссылки обозначает то, что любое значения после нашего домена (path - путь) будет направляться в сокращатель.

Это важный аспект для того чтобы дальше все сработало.

Соответственно, создаем также запись в DNS чтобы все запросы проходили DNS проверку.

Новая запись в DNS по которой будет находиться сокращатель ссылок
Новая запись в DNS по которой будет находиться сокращатель ссылок

Готово! Можем приступать к кодированию.

Реализация

Приступим к непосредственной реализации. Дальнейшие действия будут происходить в редакторе кода, который предоставляет нам Cloudlfare. Мы его уже видели перед тем как инициализировать новый Worker. Вернемся туда воспользовавшись кнопкой «Quick edit» на странице нашего проекта.

Редактор кода
Редактор кода

У нас сервис будет состоять из двух частей:

  • Переадресация

  • Запись нового сокращения

Для реализации переадресации напишем функцию которая будет брать значение из нашей базы данных и в случае нахождения введенного нами пути (URL path) будет создавать редирект. В ином случае будем выдавать ошибку 404.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

/**
 * Respond to the request
 * @param {Request} request
 */
async function handleRequest(request) {
  const requestUrl = new URL(request.url);
  const path = requestUrl.pathname.substring(1); // Удалим символ "/"
  return await redirect(path)
}

/**
 * Make redirect
 * @param {string} shortName
 */
async function redirect(shortName) {
  // Получим значение адреса который запросили по короткой ссылке
  const url = await db.get(shortName);
  if (url) {
    // Переадресовываем по ссылке
    return Response.redirect(url)
  }
  // Не нашли такого сокращения
  return new Response(null, {status: 404})
}

Тут же, в правой половине редактора который позволяет сделать дебаг еще не задеплоенного код, проверяем работу переадресации:

Есть контакт!
Есть контакт!

Теперь приступим к реализации второй части. Тут задача будет более объемной. Для начала мы будем определять что это к нам постучался Telegram через заданный нами URL. Дальше проверим что это боту написали мы, чтобы никто другой не имел доступа к боту пропишем наш Telegram User ID в константу. Следующим шагом достанем из присланного сообщения короткий путь и ссылку куда переадресовывать и запишем ссылку в базу данных. В конце подключим нашего бота через webhook'и.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

const ADMIN = 11111111; // Типо наш Telegram User ID

/**
 * Respond to the request
 * @param {Request} request
 */
async function handleRequest(request) {
  const requestUrl = new URL(request.url);
  const path = requestUrl.pathname.substring(1);
  // Добавили проверку пути на соответствие токену нашего бота
  if (path == BOT_TOKEN) {
    return await bot(await request.json())
  }

  return await redirect(path)
}

/**
 * Make redirect
 * @param {string} shortName
 */
async function redirect(shortName) {
  const url = await db.get(shortName);
  if (url) {
    return Response.redirect(url)
  }
  return new Response(null, {status: 404})
}

/**
 * Create new shorten URL
 * @param {Object} update
 */
async function bot(update) {
  // Пропускаем сообщения от НЕ админов
  if (update.message.from.id != ADMIN) {
    return new Response("OK", {status: 200})
  }
  // Разбиваем сообщения типа "shortname url"
  const [shortName, url] = update.message.text.split(" ");
  // Запоминаем наше новое сокращение
  await db.put(shortName, url);
  const response = {
    // Что мы хотим сделать в ответ на новое сообщение
    "method": "sendMessage",
    // Чем будем отвечать на новую пару сокращение - адрес переадресации
    "text": `Теперь ${url} доступно по короткой ссылке url.mydomain.com/${shortName}`,
    // Кому будем отвечать, тут может быть просто ADMIN (идентично нынешней реализации), 
    // либо update.message.chat.id чтобы у администратора была возможность 
    // использовать бота в групповых чатах
    "chat_id": update.message.from.id
  }

  return new Response(
    JSON.stringify(response), 
    {
      status: 200,
      headers: new Headers({"Content-Type": "application/json"})
    }
  )
}

Тут же, в дебаге, проверяем работу нашего кода:

Выглядит работоспособно
Выглядит работоспособно

Заглянем в нашу базу данных чтобы убедиться что все записалось (тут же можем почистить хранилище от наших тестовых значений):

Работает
Работает

Осталось дело за малым – повесить на нашу страницу Telegram Bot Webhook. У нас для этого все готово, поэтому воспользуемся ссылкой формата:

https://api.telegram.org/bot[BOT_TOKEN]/setWebhook?url=url.domain.com/[BOT_TOKEN]

Ответ Telegram API должен быть:

{"ok":true,"result":true,"description":"Webhook was set"}

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

«Он жив!»

Заключение

В итоге у нас получился короткий и легкий в реализации «сокращатель ссылок» который мы можем модифицировать по нашему усмотрению.

Стоит отметить, что такой подход имеет некоторые ограничения с которыми можно ознакомится на странице Cloudflare Worker'ов. Если коротко, то:

  • записывать в БД мы можем до 1000 значений в день (максимально возможное количество созданных сокращений);

  • считывать из БД до 100 000 раз в день (максимальное количество посещений);

  • сам скрип может быть запущен до 100 000 раз в день (количество сообщений боту и посещений сокращенных ссылок);

  • скрипт должен запускаться не больше 1000 раз в минуту.

Этих ограничений должно хватить для личного использования, поделитесь вашим мнением на этот счет в комментариях.